ftp.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 1,598 行 · 第 1/4 页
ERL
1,598 行
{error, epath});%%--------------------------------------------------------------------------%% File handling - transfer_*handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} = State) -> NewState = accept_data_connection(State), send_file(Fd, NewState); handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} = State) -> NewState = accept_data_connection(State), send_data_message(NewState, Bin), close_data_connection(NewState), activate_ctrl_connection(NewState), {noreply, NewState#state{caller = transfer_data_second_phase, dsock = undefined}};%%--------------------------------------------------------------------------%% Defaulthandle_ctrl_result({Status, Lines}, #state{client = From} = State) when From =/= undefined -> ctrl_result_response(Status, State, {error, Lines}).%%--------------------------------------------------------------------------%% Help functions to handle_ctrl_result%%--------------------------------------------------------------------------ctrl_result_response(pos_compl, #state{client = From} = State, _) -> gen_server:reply(From, ok), {noreply, State#state{client = undefined, caller = undefined}};ctrl_result_response(Status, #state{client = From} = State, _) whenStatus == etnospc; Status == epnospc; Status == efnamena; Status == econn ->%Status == etnospc; Status == epnospc; Status == econn -> gen_server:reply(From, {error, Status}),%% {stop, normal, {error, Status}, State#state{client = undefined}}; {stop, normal, State#state{client = undefined}};ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> gen_server:reply(From, ErrorMsg), {noreply, State#state{client = undefined, caller = undefined}}.%%--------------------------------------------------------------------------handle_caller(#state{caller = {dir, Dir, Len}} = State) -> Cmd = case Len of short -> "NLST"; long -> "LIST" end, case Dir of "" -> send_ctrl_message(State, mk_cmd(Cmd, "")); _ -> send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir])) end, activate_ctrl_connection(State), {noreply, State#state{caller = {dir, Dir}}}; handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) -> send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), activate_ctrl_connection(State), {noreply, State#state{caller = recv_bin}};handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} = State) -> send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), activate_ctrl_connection(State), {noreply, State#state{caller = start_chunk_transfer}};handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) -> send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), activate_ctrl_connection(State), {noreply, State#state{caller = {recv_file, Fd}}};handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}}, ldir = LocalDir, client = From} = State) -> case file_open(filename:absname(LocalFile, LocalDir), read) of {ok, Fd} -> send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), activate_ctrl_connection(State), {noreply, State#state{caller = {transfer_file, Fd}}}; {error, _} -> gen_server:reply(From, {error, epath}), {noreply, State#state{client = undefined, caller = undefined, dsock = undefined}} end;handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = State) -> send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), activate_ctrl_connection(State), {noreply, State#state{caller = {transfer_data, Bin}}}.%% ----------- FTP SERVER COMMUNICATION ------------------------- %% Connect to FTP server at Host (default is TCP port 21) %% in order to establish a control connection.setup_ctrl_connection(Host, Port, Timeout, State)-> MsTime = millisec_time(), case connect(Host, Port, Timeout, State) of {Ipv, {ok, CSock}} -> NewState = case Ipv of ipv4 -> State#state{csock = CSock, ip_v6_disabled = true}; ipv6 -> State#state{csock = CSock} end, activate_ctrl_connection(NewState), case Timeout - (millisec_time() - MsTime) of Timeout2 when (Timeout2 >= 0) -> {noreply, NewState#state{caller = open}, Timeout2}; _ -> %% Oups: Simulate timeout self() ! timeout, {noreply, NewState#state{caller = open}} end; {_,{error, _}} -> gen_server:reply(State#state.client, {error, ehost}), {stop, normal, State#state{client = undefined}} end.setup_data_connection(#state{mode = active, caller = Caller, csock = CSock} = State) -> IntToString = fun(Element) -> integer_to_list(Element) end, case (catch inet:sockname(CSock)) of {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, inet6, binary, {packet, 0}]), {ok, Port} = inet:port(LSock), Cmd = mk_cmd("EPRT |2|~s:~s:~s:~s:~s:~s:~s:~s|~s|", lists:map(IntToString, tuple_to_list(IP) ++ [Port])), send_ctrl_message(State, Cmd), activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, {LSock, Caller}}}}; {ok, {{_,_,_,_} = IP, _}} -> {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, binary, {packet, 0}]), {ok, Port} = inet:port(LSock), {IP1, IP2, IP3, IP4} = IP, {Port1, Port2} = {Port div 256, Port rem 256}, send_ctrl_message(State, mk_cmd("PORT ~w,~w,~w,~w,~w,~w", [IP1, IP2, IP3, IP4, Port1, Port2])), activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, {LSock, Caller}}}} end;setup_data_connection(#state{mode = passive, ip_v6_disabled = false, caller = Caller} = State) -> send_ctrl_message(State, mk_cmd("EPSV", [])), activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, Caller}}};setup_data_connection(#state{mode = passive, ip_v6_disabled = true, caller = Caller} = State) -> send_ctrl_message(State, mk_cmd("PASV", [])), activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, Caller}}}.connect(Host = {_,_,_,_}, Port, TimeOut, _) -> {ipv4, gen_tcp:connect(Host, Port,[binary, {packet, 0}, {active, false}] , TimeOut)};connect(Host = {_,_,_,_,_,_,_,_}, Port, TimeOut, #state{ip_v6_disabled = false}) -> {ipv6, gen_tcp:connect(Host, Port, [binary, {packet, 0}, {active, false}, inet6], TimeOut)}; connect(Host, Port, TimeOut, #state{ip_v6_disabled = false}) -> {Opts, NewHost, Ipv} = case (inet:getaddr(Host, inet6)) of %% If an ipv4-mapped ipv6 address is returned %% use ipv4 directly as some ftp-servers does not %% handle "ip4-ipv6-compatiblity" mode well! {ok, IP = {0, 0, 0, 0, 0, 16#ffff, _, _}} -> case inet:getaddr(Host, inet) of {ok,NewIP} -> {[binary, {packet, 0}, {active, false}], NewIP, ipv4}; _Error -> {[binary, {packet, 0}, {active, false}, inet6], IP,ipv6} end; {ok, IP} -> {[binary, {packet, 0}, {active, false}, inet6], IP, ipv6}; {error, _} -> {[binary, {packet, 0}, {active, false}], Host, ipv4} end, {Ipv, gen_tcp:connect(NewHost, Port, Opts, TimeOut)};connect(Host, Port, TimeOut, #state{ip_v6_disabled = true}) -> Opts = [binary, {packet, 0}, {active, false}], {ipv4, gen_tcp:connect(Host, Port, Opts, TimeOut)}.accept_data_connection(#state{mode = active, dsock = {lsock, LSock}} = State) -> {ok, Socket} = gen_tcp:accept(LSock), gen_tcp:close(LSock), State#state{dsock = Socket};accept_data_connection(#state{mode = passive} = State) -> State.send_ctrl_message(#state{csock = Socket,verbose=Verbose}, Message) ->% io:format("Sending: ~p~n",[Message]), verbose(lists:flatten(Message),Verbose,send), send_message(Socket, Message).send_data_message(#state{dsock = Socket}, Message) -> send_message(Socket, Message).send_message(Socket, Message) -> case gen_tcp:send(Socket, Message) of ok -> ok; {error, Reason} -> error_logger:error_report("gen_tcp:send/2 failed for " "reason ~p~n", [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 end.activate_ctrl_connection(#state{csock = Socket, ctrl_data = {<<>>, _, _}}) -> activate_connection(Socket);activate_ctrl_connection(#state{csock = Socket}) -> %% We have already received at least part of the next control message, %% that has been saved in ctrl_data, process this first. self() ! {tcp, Socket, <<>>}.activate_data_connection(#state{dsock = Socket}) -> activate_connection(Socket).activate_connection(Socket) -> inet:setopts(Socket, [{active, once}]).close_ctrl_connection(#state{csock = undefined}) -> ok;close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket).close_data_connection(#state{dsock = undefined}) -> ok;close_data_connection(#state{dsock = {lsock, Socket}}) -> close_connection(Socket);close_data_connection(#state{dsock = Socket}) -> close_connection(Socket).close_connection(Socket) -> gen_tcp:close(Socket).%% ------------ FILE HANDELING ---------------------------------------- send_file(Fd, State) -> case file_read(Fd) of {ok, N, Bin} when N > 0-> send_data_message(State, Bin), progress_report({binary, Bin}, State), send_file(Fd, State); {ok, _, _} -> file_close(Fd), close_data_connection(State), progress_report({transfer_size, 0}, State), activate_ctrl_connection(State), {noreply, State#state{caller = transfer_file_second_phase, dsock = undefined}}; {error, Reason} -> gen_server:reply(State#state.client, {error, Reason}), {stop, normal, State#state{client = undefined}} end.file_open(File, Option) -> file:open(File, [raw, binary, Option]).file_close(Fd) -> file:close(Fd).file_read(Fd) -> case file:read(Fd, ?FILE_BUFSIZE) of {ok, Bytes} -> {ok, size(Bytes), Bytes}; eof -> {ok, 0, []}; Other -> Other end.file_write(Bytes, Fd) -> file:write(Fd, Bytes).%% -------------- MISC ---------------------------------------------- call(GenServer, Msg, Format) -> call(GenServer, Msg, Format, infinity).call(GenServer, Msg, Format, Timeout) -> Result = (catch gen_server:call(GenServer, {self(), Msg}, Timeout)), case Result of {ok, Bin} when binary(Bin), Format == string -> {ok, binary_to_list(Bin)}; {'EXIT', _} -> {error, eclosed}; Result -> Result end.cast(GenServer, Msg) -> gen_server:cast(GenServer, {self(), Msg}).mk_cmd(Fmt, Args) -> [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.pwd_result(Lines) -> {_, [?DOUBLE_QUOTE | Rest]} = lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines), {Dir, _} = lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest), Dir.is_verbose(Params) -> check_param(verbose, Params).is_debug(Flags) -> check_param(debug, Flags).is_trace(Flags) -> check_param(trace, Flags).is_ipv6_disabled(Flags) -> check_param(ip_v6_disabled, Flags).check_param(Param, Params) -> lists:member(Param, Params).key_search(Key, List, Default)-> case lists:keysearch(Key, 1, List) of {value, {_,Val}} -> Val; false -> Default end.check_option(Pred, Value, Default) -> case Pred(Value) of true -> Value; false -> Default end.verbose(Lines, true, Direction) -> DirStr = case Direction of send -> "Sending: "; _ -> "Receiving: " end, Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR), erlang:display(DirStr++Str);verbose(_, false,_) -> ok.ensure_started() -> %% Start of the inets application should really be handled by the %% application using inets. case application:start(inets) of {error,{already_started,inets}} -> ok; {error,{{already_started, _}, % Started as an included application {inets_app,start, _}}} -> ok; ok -> error_logger:info_report("The inets application was not started." " Has now been started as a temporary" " application.") end.progress(Options) -> ftp_progress:start_link(Options).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(Report, #state{progress = ProgressPid}) -> ftp_progress:report(ProgressPid, Report).millisec_time() -> {A,B,C} = erlang:now(), A*1000000000+B*1000+(C div 1000).
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?