ftp.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 1,598 行 · 第 1/4 页

ERL
1,598
字号
    progress_report({transfer_size, 0}, State),    activate_ctrl_connection(State),    {noreply, State#state{dsock = undefined, data = <<>>}};handle_info({tcp_closed, Socket}, #state{dsock = Socket, client = From,					 caller = recv_chunk} 	    = State) ->    gen_server:reply(From, ok),    {noreply, State#state{dsock = undefined, client = undefined,			  data = <<>>, caller = undefined,			  chunk = false}};handle_info({tcp_closed, Socket}, #state{dsock = Socket, caller = recv_bin, 					 data = Data} = State) ->    activate_ctrl_connection(State),    {noreply, State#state{dsock = undefined, data = <<>>, 			  caller = {recv_bin, Data}}};handle_info({tcp_closed, Socket}, #state{dsock = Socket, data = Data,					 caller = {handle_dir_result, Dir}} 	    = State) ->    activate_ctrl_connection(State),    {noreply, State#state{dsock = undefined, 			  caller = {handle_dir_result, Dir, Data},%			  data = <<?CR,?LF>>}};			  data = <<>>}};	    handle_info({tcp_error, Socket, Reason}, #state{dsock = Socket,						client = From} = State) ->    gen_server:reply(From, {error, Reason}),    close_data_connection(State),    {noreply, State#state{dsock = undefined, client = undefined,			  data = <<>>, caller = undefined, chunk = false}};%%% Ctrl socket messages %%%handle_info({tcp, Socket, Data}, #state{csock = Socket, 					verbose = Verbose,					caller = Caller,					client = From,					ctrl_data = {CtrlData, AccLines, 						     LineStatus}} 	    = State) ->        case ftp_response:parse_lines(<<CtrlData/binary, Data/binary>>, 				  AccLines, LineStatus) of	{ok, Lines, NextMsgData} ->	    verbose(Lines, Verbose, 'receive'),	    CtrlResult = ftp_response:interpret(Lines), 	    case Caller of		quote ->		    gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),		    {noreply, State#state{client = undefined, 					  caller = undefined,					  ctrl_data = {NextMsgData, [], 						       start}}};		_ ->		    handle_ctrl_result(CtrlResult,				       State#state{ctrl_data = 						   {NextMsgData, [], start}})	    end;	{continue, NewCtrlData} ->	    activate_ctrl_connection(State),	    {noreply, State#state{ctrl_data = NewCtrlData}}    end;handle_info({tcp_closed, Socket}, #state{csock = Socket}) ->      %% If the server closes the control channel it is     %% the expected behavior that connection process terminates.    exit(normal); %% User will get error message from terminate/2handle_info({tcp_error, Socket, Reason}, _) ->    error_logger:error_report("tcp_error on socket: ~p  for reason: ~p~n", 			      [Socket, Reason]),    %% If tcp does not work the only option is to terminate,    %% this is the expected behavior under these circumstances.    exit(normal); %% User will get error message from terminate/2%% Monitor messages - if the process owning the ftp connection goes%% down there is no point in continuing.handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->    {stop, normal, State#state{client = undefined}};handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->    {stop, normal, State#state{client = undefined}};    handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->    {stop, normal, State#state{client = undefined}}; handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->    {stop, {stopped, {'EXIT', Process, Reason}},     State#state{client = undefined}};handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->    error_logger:info_report("Progress reporting stopped for reason ~p~n",			     Reason),    {noreply, State#state{progress = ignore}};   %% Catch all - throws away unknown messages (This could happen by "accident"%% so we do not want to crash, but we make a log entry as it is an%% unwanted behaviour.) handle_info(Info, State) ->    error_logger:info_report("ftp : ~p : Unexpected message: ~p\n", 			     [self(), Info]),    {noreply, State}.%%--------------------------------------------------------------------------%% terminate/2 and code_change/3%%--------------------------------------------------------------------------terminate(normal, State) ->    %% If terminate reason =/= normal the progress reporting process will    %% be killed by the exit signal.    progress_report(stop, State),     do_termiante({error, econn}, State);terminate(Reason, State) ->     error_logger:error_report("Ftp connection closed due to: ~p~n", [Reason]),    do_termiante({error, eclosed}, State).do_termiante(ErrorMsg, State) ->    close_data_connection(State),    close_ctrl_connection(State),    case State#state.client of	undefined ->	    ok;	From ->	    gen_server:reply(From, ErrorMsg)    end,    ok. code_change(_, State, _) ->    {ok, State}.%%%=========================================================================%% Start/stop%%%=========================================================================%%--------------------------------------------------------------------------%% start_link_sup([Args, Options]) -> {ok, Pid} | {error, Reason} %%                                    %% Description: Callback function for the ftp supervisor. It is called %%            : when open/[1,3] calls ftp_sup:start_child/1 to start an %%            : instance of the ftp process.%%--------------------------------------------------------------------------start_link_sup([Args, Options]) ->    gen_server:start_link(?MODULE, Args, Options).%%% Stop functionality is handled by close/1%%%========================================================================%%% Internal functions%%%========================================================================%%--------------------------------------------------------------------------%%% Help functions to handle_call and/or handle_ctrl_result%%--------------------------------------------------------------------------%% User handling handle_user(User, Password, Acc, State) ->    send_ctrl_message(State, mk_cmd("USER ~s", [User])),    activate_ctrl_connection(State),    {noreply, State#state{caller = {handle_user, Password, Acc}}}.handle_user_passwd(Password, Acc, State) ->    send_ctrl_message(State, mk_cmd("PASS ~s", [Password])),    activate_ctrl_connection(State),    {noreply, State#state{caller = {handle_user_passwd, Acc}}}.handle_user_account(Acc, State) ->    send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])),    activate_ctrl_connection(State),    {noreply, State#state{caller = handle_user_account}}.%%--------------------------------------------------------------------------%% handle_ctrl_result %%--------------------------------------------------------------------------%%--------------------------------------------------------------------------%% Handling of control connection setuphandle_ctrl_result({pos_compl, _}, #state{caller = open, client = From} 		   = State) ->    gen_server:reply(From,  {ok, self()}),    {noreply, State#state{client = undefined, 			  caller = undefined }};handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->    ctrl_result_response(econn, State, {error, Lines});%%--------------------------------------------------------------------------%% Data connection setup active mode handle_ctrl_result({pos_compl, _Lines}, 		   #state{mode   = active,			  caller = {setup_data_connection, 				    {LSock, Caller}}} = State) ->    handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});handle_ctrl_result({Status, Lines}, 		   #state{mode   = active, 			  caller = {setup_data_connection, {LSock, _}}} 		   = State) ->    close_connection(LSock),    ctrl_result_response(Status, State, {error, Lines});%% Data connection setup passive mode handle_ctrl_result({pos_compl, Lines}, #state{mode = passive,					      ip_v6_disabled = false,					      client=From,					      caller = 					      {setup_data_connection, 					       Caller},					      csock = CSock,					      timeout = Timeout} 		   = State) ->    [_, PortStr | _] =  lists:reverse(string:tokens(Lines, "|")),    {ok, {IP, _}} = inet:peername(CSock),    case connect(IP, list_to_integer(PortStr), Timeout, State) of	{_,{ok, Socket}} ->	       	    handle_caller(State#state{caller = Caller, dsock = Socket});	{_,{error,Reason}} ->	    gen_server:reply(From,{error,Reason}),	    {noreply,State#state{client = undefined, caller = undefined}}    end;handle_ctrl_result({pos_compl, Lines}, 		   #state{mode = passive, ip_v6_disabled = true,			  client=From,			  caller = {setup_data_connection, Caller}, 			  timeout = Timeout} = State) ->        {_, [?LEFT_PAREN | Rest]} = 	lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),    {NewPortAddr, _} =	lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),    [A1, A2, A3, A4, P1, P2] = lists:map(fun(X) -> list_to_integer(X) end,					 string:tokens(NewPortAddr, [$,])),    case connect({A1, A2, A3, A4}, (P1 * 256) + P2, Timeout, State) of	{_,{ok,Socket}} ->	    handle_caller(State#state{caller = Caller, dsock = Socket});	{_,{error,Reason}} ->	    gen_server:reply(From,{error,Reason}),	    {noreply,State#state{client = undefined, caller = undefined}}    end;%% FTP server does not support passive mode try to fallback on active modehandle_ctrl_result(_, #state{mode = passive, caller = {setup_data_connection, 						       Caller}} = State) ->    setup_data_connection(State#state{mode = active, caller = Caller});    %%--------------------------------------------------------------------------%% User handling handle_ctrl_result({pos_interm, _}, #state{caller =					   {handle_user, PassWord, Acc}}		   = State) ->    handle_user_passwd(PassWord, Acc, State);handle_ctrl_result({Status, _}, 		   #state{caller = {handle_user, _, _}} = State) ->    ctrl_result_response(Status, State, {error, euser});%% Accounts handle_ctrl_result({pos_interm_acct, _}, #state{caller = 						{handle_user_passwd, Acc}} = 		   State) when Acc =/= "" ->    handle_user_account(Acc, State);handle_ctrl_result({Status, _},		   #state{caller = {handle_user_passwd, _}} = State) ->    ctrl_result_response(Status, State, {error, euser});%%--------------------------------------------------------------------------%% Print current working directoryhandle_ctrl_result({pos_compl, Lines}, #state{caller = pwd, 					      client = From} = State) ->    Dir = pwd_result(Lines),    gen_server:reply(From, {ok, Dir}),    {noreply, State#state{client = undefined, caller = undefined}};%%--------------------------------------------------------------------------%% Directory listing handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State) ->    NewState = accept_data_connection(State),    activate_data_connection(NewState),    {noreply, NewState#state{caller = {handle_dir_result, Dir}}};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}};	_ ->	    %% If there is only one line it might be a directory with on	    %% 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]; 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    end;handle_ctrl_result({pos_compl, Lines}, 		   #state{caller = {handle_dir_data, Dir, DirData}} = 		   State) ->    OldDir = pwd_result(Lines),        send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])),    activate_ctrl_connection(State),    {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(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}} = State) ->    send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])),    activate_ctrl_connection(State),    {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, epath});handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->     ctrl_result_response(Status, State, {error, epath});%%--------------------------------------------------------------------------%% File renaminghandle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} 		   = State) ->    send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])),    activate_ctrl_connection(State),    {noreply, State#state{caller = rename_second_phase}}; handle_ctrl_result({Status, _}, 		   #state{caller = {rename, _}} = State) ->    ctrl_result_response(Status, State, {error, epath});handle_ctrl_result({Status, _},		   #state{caller = rename_second_phase} = State) ->    ctrl_result_response(Status, State, {error, epath});%%--------------------------------------------------------------------------%% File handling - recv_binhandle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State) ->    NewState = accept_data_connection(State),    activate_data_connection(NewState),    {noreply, NewState};handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},					  client = From} = State) ->    gen_server:reply(From, {ok, Data}),    close_data_connection(State),    {noreply, State#state{client = undefined, caller = undefined}};handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->    close_data_connection(State),    ctrl_result_response(Status, State#state{dsock = undefined}, 			 {error, epath});handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->    close_data_connection(State),    ctrl_result_response(Status, State#state{dsock = undefined}, 			 {error, epath});%%--------------------------------------------------------------------------%% File handling - start_chunk_transferhandle_ctrl_result({pos_prel, _}, #state{client = From,					 caller = start_chunk_transfer}		   = State) ->    NewState = accept_data_connection(State),    gen_server:reply(From, ok),    {noreply, NewState#state{chunk = true, client = undefined,			     caller = undefined}};%%--------------------------------------------------------------------------%% File handling - recv_filehandle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State) ->    NewState = accept_data_connection(State),    activate_data_connection(NewState),    {noreply, NewState};handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->    file_close(Fd),    close_data_connection(State),    ctrl_result_response(Status, State#state{dsock = undefined}, 

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?