diff options
Diffstat (limited to 'lib/ftp/src/ftp.erl')
-rw-r--r-- | lib/ftp/src/ftp.erl | 180 |
1 files changed, 107 insertions, 73 deletions
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl index 60bcc85794..07bc5184e2 100644 --- a/lib/ftp/src/ftp.erl +++ b/lib/ftp/src/ftp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2022. All Rights Reserved. +%% Copyright Ericsson AB 2002-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -1255,7 +1255,7 @@ handle_cast({Pid, close}, State) -> error_logger:info_report(Report), {noreply, State}; -%% Catch all - This can oly happen if the application programmer writes +%% Catch all - This can only happen if the application programmer writes %% really bad code that violates the API. handle_cast(Msg, State) -> {stop, {'API_violation_connection_closed', Msg}, State}. @@ -1549,6 +1549,12 @@ start_link(Opts, GenServerOptions) -> %%% Help functions to handle_call and/or handle_ctrl_result %%-------------------------------------------------------------------------- %% User handling +-spec handle_user(User, Password, Account, State) -> Result when + User :: io:format(), + Password :: io:format(), + Account :: io:format(), + State :: #state{}, + Result :: {noreply, #state{}}. handle_user(User, Password, Acc, State0) -> _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])), State = activate_ctrl_connection(State0), @@ -1568,6 +1574,31 @@ handle_user_account(Acc, State0) -> %%-------------------------------------------------------------------------- %% handle_ctrl_result %%-------------------------------------------------------------------------- +-type ctrl_status_operation() :: efnamena + | elogin + | enofile + | epath + | error + | etnospc + | epnospc + | efnamena + | econn + | perm_neg_compl + | pos_compl + | pos_interm + | pos_interm_acct + | pos_prel + | tls_upgrade + | trans_neg_compl. + +-spec handle_ctrl_result(Operation, State) -> Result when + Operation :: {ctrl_status_operation(), list() | atom()}, + State :: #state{}, + Result :: {noreply, #state{}, integer()} + | {noreply, #state{}} + | {stop, normal | {error, Reason}, #state{}} + | {error, term()}, + Reason :: term(). handle_ctrl_result({pos_compl, _}, #state{csock = {tcp, _Socket}, tls_options = TLSOptions, timeout = Timeout, @@ -1753,76 +1784,27 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) -> ctrl_result_response(error, State0, Error) end; -handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, - Data}, client = From} - = State) -> - case Dir of - "" -> % Current directory - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, - caller = undefined}}; - _ -> - %% <WTF> - %% Dir cannot be assumed to be a dir. It is a string that - %% could be a dir, but could also be a file or even a string - %% containing wildcards (*). - %% - %% %% If there is only one line it might be a directory with one - %% %% file but it might be an error message that the directory - %% %% was not found. So in this case we have to endure a little - %% %% overhead to be able to give a good return value. Alas not - %% %% all ftp implementations behave the same and returning - %% %% an error string is allowed by the FTP RFC. - %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end, - %% binary_to_list(Data)) of - %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) -> - %% send_ctrl_message(State, mk_cmd("PWD", [])), - %% activate_ctrl_connection(State), - %% {noreply, - %% State#state{caller = {handle_dir_data, Dir, Data}}}; - %% _ -> - %% gen_server:reply(From, {ok, Data}), - %% {noreply, State#state{client = undefined, - %% caller = undefined}} - %% end - %% </WTF> - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, - caller = undefined}} - end; +handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, ""=_CurrentDir, + Data}, client = From}= State) -> + gen_server:reply(From, {ok, Data}), + {noreply, State#state{client = undefined, + caller = undefined}}; -handle_ctrl_result({pos_compl, Lines}, - #state{caller = {handle_dir_data, Dir, DirData}} = - State0) -> - OldDir = pwd_result(Lines), - _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])), - State = activate_ctrl_connection(State0), - {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir, - DirData}}}; -handle_ctrl_result({Status, _}, - #state{caller = {handle_dir_data, _, _}} = State) -> - ctrl_result_response(Status, State, {error, epath}); +handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, _Dir, + Data}, client = From}= State) -> + gen_server:reply(From, {ok, Data}), + {noreply, State#state{client = undefined, + caller = undefined}}; + +handle_ctrl_result({pos_compl, _}=Operation, #state{caller = {handle_dir_result, Dir}, + data = Data}= State) -> + handle_ctrl_result(Operation, State#state{caller = {handle_dir_result, Dir, Data}}); handle_ctrl_result(S={_Status, _}, #state{caller = {handle_dir_result, _, _}} = State) -> %% OTP-5731, macosx ctrl_result_response(S, State, {error, epath}); -handle_ctrl_result({pos_compl, _}, - #state{caller = {handle_dir_data_second_phase, OldDir, - DirData}} = State0) -> - _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [OldDir])), - State = activate_ctrl_connection(State0), - {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}}; -handle_ctrl_result({Status, _}, - #state{caller = {handle_dir_data_second_phase, _, _}} - = State) -> - ctrl_result_response(Status, State, {error, epath}); -handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, - client = From} = State) -> - gen_server:reply(From, {ok, DirData}), - {noreply, State#state{client = undefined, caller = undefined}}; - handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> ctrl_result_response(Status, State, {error, Status}); @@ -1959,6 +1941,13 @@ handle_ctrl_result(CtrlMsg, #state{caller = undefined} = State) -> %%-------------------------------------------------------------------------- %% Help functions to handle_ctrl_result %%-------------------------------------------------------------------------- + +-spec ctrl_result_response(Status, State, Error) -> Result when + Status :: ctrl_status_operation() | {ctrl_status_operation(), _}, + State :: #state{}, + Error :: {error, Reason}, + Reason :: term(), + Result :: {noreply, #state{}} | Error. ctrl_result_response(pos_compl, #state{client = From} = State, _) -> gen_server:reply(From, ok), {noreply, State#state{client = undefined, caller = undefined}}; @@ -1990,6 +1979,9 @@ ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> {noreply, State#state{client = undefined, caller = undefined}}. %%-------------------------------------------------------------------------- +-spec handle_caller(State) -> Result when + State :: #state{}, + Result :: {noreply, #state{}}. handle_caller(#state{caller = {dir, Dir, Len}} = State0) -> Cmd = case Len of short -> "NLST"; @@ -2043,6 +2035,13 @@ handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = %% Connect to FTP server at Host (default is TCP port 21) %% in order to establish a control connection. +-spec setup_ctrl_connection(Host, Port, Timeout, State) -> Result when + Host :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number(), + Timeout :: timeout(), + State :: #state{}, + Reason :: timeout | inet:posix(), + Result :: {ok, State, integer()} | {error, Reason}. setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) -> MsTime = erlang:monotonic_time(), case connect(Host, Port, SockOpts, Timeout, State0) of @@ -2060,6 +2059,9 @@ setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = St Error end. +-spec setup_data_connection(State) -> Result when + State :: #state{}, + Result :: {noreply, State}. setup_data_connection(#state{mode = active, caller = Caller, csock = CSock, @@ -2122,6 +2124,14 @@ setup_data_connection(#state{mode = passive, ipfamily = inet, State = activate_ctrl_connection(State0), {noreply, State#state{caller = {setup_data_connection, Caller}}}. +-spec connect(Host, Port, SockOpts, Timeout, State) -> Result when + Host :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number(), + SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()], + Timeout :: timeout(), + State :: #state{}, + Reason :: timeout | inet:posix(), + Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}. connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) -> connect2(Host, Port, IpFam, SockOpts, Timeout); @@ -2155,6 +2165,14 @@ connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) -> end end. +-spec connect2(Host, Port, IpFam, SockOpts, Timeout) -> Result when + Host :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number(), + SockOpts :: [inet:inet_backend() | gen_tcp:connect_option()], + Timeout :: timeout(), + IpFam :: inet:address_family(), + Reason :: timeout | inet:posix(), + Result :: {ok, inet:address_family(), gen_tcp:socket()} | {error, Reason}. connect2(Host, Port, IpFam, SockOpts, Timeout) -> Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts], case gen_tcp:connect(Host, Port, Opts, Timeout) of @@ -2164,6 +2182,9 @@ connect2(Host, Port, IpFam, SockOpts, Timeout) -> Error end. +-spec accept_data_connection_tls_options(State) -> Result when + State :: #state{}, + Result :: [tuple()]. accept_data_connection_tls_options(#state{ csock = {ssl,Socket}, tls_options = TO0, tls_ctrl_session_reuse = true }) -> TO = lists:keydelete(reuse_sessions, 1, TO0), {ok, [{session_id,SSLSessionId},{session_data,SSLSessionData}]} = ssl:connection_information(Socket, [session_id, session_data]), @@ -2171,6 +2192,10 @@ accept_data_connection_tls_options(#state{ csock = {ssl,Socket}, tls_options = T accept_data_connection_tls_options(#state{ tls_options = TO }) -> TO. +-spec accept_data_connection(State) -> Result when + State :: #state{}, + Result :: {ok, #state{}} | {error, Reason}, + Reason :: term(). accept_data_connection(#state{mode = active, dtimeout = DTimeout, tls_options = TLSOptions0, @@ -2208,7 +2233,9 @@ accept_data_connection(#state{mode = passive, accept_data_connection(#state{mode = passive} = State) -> {ok,State}. - +-spec send_ctrl_message(State, Message) -> _ when + State :: #state{}, + Message :: [term() | Message]. send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) -> verbose(lists:flatten(Message),Verbose,send), ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]), @@ -2260,7 +2287,7 @@ activate_connection(API, CloseTag, Socket0) -> case API:setopts(Socket, [{active, once}]) of ok -> ok; - {error, _} -> %% inet can retrun einval instead of closed + {error, _} -> %% inet can return einval instead of closed self() ! {CloseTag, Socket} end. @@ -2311,8 +2338,8 @@ file_close(Fd) -> file_read(Fd) -> case file:read(Fd, ?FILE_BUFSIZE) of - {ok, Bytes} -> - {ok, size(Bytes), Bytes}; + {ok, Bytes} when is_binary(Bytes) -> + {ok, byte_size(Bytes), Bytes}; eof -> {ok, 0, []}; Other -> @@ -2399,8 +2426,8 @@ progress_report(_, #state{progress = ignore}) -> ok; progress_report(stop, #state{progress = ProgressPid}) -> ftp_progress:stop(ProgressPid); -progress_report({binary, Data}, #state{progress = ProgressPid}) -> - ftp_progress:report(ProgressPid, {transfer_size, size(Data)}); +progress_report({binary, Data}, #state{progress = ProgressPid}) when is_binary(Data) -> + ftp_progress:report(ProgressPid, {transfer_size, byte_size(Data)}); progress_report(Report, #state{progress = ProgressPid}) -> ftp_progress:report(ProgressPid, Report). @@ -2481,6 +2508,7 @@ start_options(Options) -> %% progress %% ftp_extension +-spec open_options([tuple()]) -> {ok, [tuple()]} | no_return(). open_options(Options) -> ValidateMode = fun(active) -> true; @@ -2490,8 +2518,7 @@ open_options(Options) -> ValidateHost = fun(Host) when is_list(Host) -> true; - (Host) when is_tuple(Host) andalso - ((size(Host) =:= 4) orelse (size(Host) =:= 8)) -> + (Host) when tuple_size(Host) =:= 4; tuple_size(Host) =:= 8 -> true; (_) -> false @@ -2557,6 +2584,8 @@ open_options(Options) -> {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], validate_options(Options, ValidOptions, []). +%% validates socket options and set defaults +-spec socket_options([tuple()]) -> {ok, tuple()} | no_return(). socket_options(Options) -> CtrlOpts = proplists:get_value(sock_ctrl, Options, []), DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts), @@ -2583,6 +2612,11 @@ valid_socket_option({packet_size,_} ) -> false; valid_socket_option(_) -> true. +-spec validate_options(Options, ValidOptions, Acc) -> Result when + Options :: [tuple()], + ValidOptions :: [tuple()], + Acc :: [tuple()], + Result :: {ok, [tuple()]} | no_return(). validate_options([], [], Acc) -> {ok, lists:reverse(Acc)}; validate_options([], ValidOptions, Acc) -> |