From 39de3072bcf9fdeec6d3faeb125924c401242205 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Tue, 15 Apr 2008 12:21:00 +0000 Subject: Merged mochiweb branch back into trunk. git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@648222 13f79535-47bb-0310-9956-ffa450edef68 --- src/couch_inets/ftp.erl | 1597 ----------------------------------------------- 1 file changed, 1597 deletions(-) delete mode 100644 src/couch_inets/ftp.erl (limited to 'src/couch_inets/ftp.erl') diff --git a/src/couch_inets/ftp.erl b/src/couch_inets/ftp.erl deleted file mode 100644 index 92483943..00000000 --- a/src/couch_inets/ftp.erl +++ /dev/null @@ -1,1597 +0,0 @@ -%% ``The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved via the world wide web at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. -%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings -%% AB. All Rights Reserved.'' -%% -%% $Id$ -%% -%% Description: This module implements an ftp client, RFC 959. -%% It also supports ipv6 RFC 2428. - --module(ftp). - --behaviour(gen_server). - -%% API - Client interface --export([cd/2, close/1, delete/2, formaterror/1, - lcd/2, lpwd/1, ls/1, ls/2, - mkdir/2, nlist/1, nlist/2, - open/1, open/2, open/3, force_active/1, - pwd/1, quote/2, - recv/2, recv/3, recv_bin/2, - recv_chunk_start/2, recv_chunk/1, - rename/3, rmdir/2, - send/2, send/3, send_bin/3, - send_chunk_start/2, send_chunk/2, send_chunk_end/1, - type/2, user/3, user/4, account/2, - append/3, append/2, append_bin/3, - append_chunk/2, append_chunk_end/1, append_chunk_start/2]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3]). - -%% supervisor callbacks --export([start_link_sup/1]). - --include("ftp_internal.hrl"). - -%% Constante used in internal state definition --define(CONNECTION_TIMEOUT, 60*1000). --define(DEFAULT_MODE, passive). - -%% Internal Constants --define(FTP_PORT, 21). --define(FILE_BUFSIZE, 4096). - -%% Internal state --record(state, { - csock = undefined, % socket() - Control connection socket - dsock = undefined, % socket() - Data connection socket - verbose = false, % boolean() - ldir = undefined, % string() - Current local directory - type = ftp_server_default, % atom() - binary | ascii - chunk = false, % boolean() - Receiving data chunks - mode = ?DEFAULT_MODE, % passive | active - timeout = ?CONNECTION_TIMEOUT, % integer() - %% Data received so far on the data connection - data = <<>>, % binary() - %% Data received so far on the control connection - %% {BinStream, AccLines}. If a binary sequence - %% ends with ?CR then keep it in the binary to - %% be able to detect if the next received byte is ?LF - %% and hence the end of the response is reached! - ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus} - %% pid() - Client pid (note not the same as "From") - owner = undefined, - client = undefined, % "From" to be used in gen_server:reply/2 - %% Function that activated a connection and maybe some - %% data needed further on. - caller = undefined, % term() - ip_v6_disabled, % boolean() - progress = ignore % ignore | pid() - }). - -%%%========================================================================= -%%% API - CLIENT FUNCTIONS -%%%========================================================================= -%%-------------------------------------------------------------------------- -%% open(Host, , ) -> {ok, Pid} | {error, ehost} -%% Host = string(), -%% Port = integer(), -%% Flags = [Flag], -%% Flag = verbose | debug | trace -%% -%% Description: Start an ftp client and connect to a host. -%%-------------------------------------------------------------------------- -%% The only option was the host in textual form -open({option_list, Options})-> - ensure_started(), - Flags = key_search(flags, Options, []), - {ok, Pid} = ftp_sup:start_child([[[{client, self()}, Flags], []]]), - call(Pid, {open, ip_comm, Options}, pid); - -%% The only option was the tuple form of the ip-number -open(Host) when tuple(Host) -> - open(Host, ?FTP_PORT, []); - -%% Host is the string form of the hostname -open(Host)-> - open(Host, ?FTP_PORT, []). - -open(Host, Port) when integer(Port) -> - open(Host, Port, []); - -open(Host, Flags) when list(Flags) -> - open(Host, ?FTP_PORT, Flags). - -open(Host, Port, Flags) when integer(Port), list(Flags) -> - ensure_started(), - {ok, Pid} = ftp_sup:start_child([[[{client, self()}, Flags], []]]), - Opts = [{host, Host}, {port, Port}| Flags], - call(Pid, {open, ip_comm, Opts}, pid). - -%%-------------------------------------------------------------------------- -%% user(Pid, User, Pass, ) -> ok | {error, euser} | {error, econn} -%% | {error, eacct} -%% Pid = pid(), -%% User = Pass = Acc = string() -%% -%% Description: Login with or without a supplied account name. -%%-------------------------------------------------------------------------- -user(Pid, User, Pass) -> - call(Pid, {user, User, Pass}, atom). - -user(Pid, User, Pass, Acc) -> - call(Pid, {user, User, Pass, Acc}, atom). - -%%-------------------------------------------------------------------------- -%% account(Pid, Acc) -> ok | {error, eacct} -%% Pid = pid() -%% Acc= string() -%% -%% Description: Set a user Account. -%%-------------------------------------------------------------------------- -account(Pid, Acc) -> - call(Pid, {account, Acc}, atom). - -%%-------------------------------------------------------------------------- -%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Get the current working directory at remote server. -%%-------------------------------------------------------------------------- -pwd(Pid) -> - call(Pid, pwd, ctrl). - -%%-------------------------------------------------------------------------- -%% lpwd(Pid) -> {ok, Dir} | {error, elogin} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Get the current working directory at local server. -%%-------------------------------------------------------------------------- -lpwd(Pid) -> - call(Pid, lpwd, string). - -%%-------------------------------------------------------------------------- -%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Change current working directory at remote server. -%%-------------------------------------------------------------------------- -cd(Pid, Dir) -> - call(Pid, {cd, Dir}, atom). - -%%-------------------------------------------------------------------------- -%% lcd(Pid, Dir) -> ok | {error, epath} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Change current working directory for the local client. -%%-------------------------------------------------------------------------- -lcd(Pid, Dir) -> - call(Pid, {lcd, Dir}, string). - -%%-------------------------------------------------------------------------- -%% ls(Pid, ) -> {ok, Listing} | {error, epath} | {error, elogin} | -%% {error, econn} -%% Pid = pid() -%% Dir = string() -%% Listing = string() -%% -%% Description: List the contents of current directory (ls/1) or -%% directory Dir (ls/2) at remote server. -%%-------------------------------------------------------------------------- -ls(Pid) -> - ls(Pid, ""). -ls(Pid, Dir) -> - call(Pid, {dir, long, Dir}, string). - -%%-------------------------------------------------------------------------- -%% nlist(Pid, ) -> {ok, Listing} | {error, epath} | {error, elogin} | -%% {error, econn} -%% Pid = pid() -%% Dir = string() -%% -%% Description: List the contents of current directory (ls/1) or directory -%% Dir (ls/2) at remote server. The returned list is a stream -%% of file names. -%%-------------------------------------------------------------------------- -nlist(Pid) -> - nlist(Pid, ""). -nlist(Pid, Dir) -> - call(Pid, {dir, short, Dir}, string). - -%%-------------------------------------------------------------------------- -%% rename(Pid, CurrFile, NewFile) -> ok | {error, epath} | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% CurrFile = NewFile = string() -%% -%% Description: Rename a file at remote server. -%%-------------------------------------------------------------------------- -rename(Pid, CurrFile, NewFile) -> - call(Pid, {rename, CurrFile, NewFile}, string). - -%%-------------------------------------------------------------------------- -%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} | -%% {error, econn} -%% Pid = pid() -%% File = string() -%% -%% Description: Remove file at remote server. -%%-------------------------------------------------------------------------- -delete(Pid, File) -> - call(Pid, {delete, File}, string). - -%%-------------------------------------------------------------------------- -%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid(), -%% Dir = string() -%% -%% Description: Make directory at remote server. -%%-------------------------------------------------------------------------- -mkdir(Pid, Dir) -> - call(Pid, {mkdir, Dir}, atom). - -%%-------------------------------------------------------------------------- -%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid(), -%% Dir = string() -%% -%% Description: Remove directory at remote server. -%%-------------------------------------------------------------------------- -rmdir(Pid, Dir) -> - call(Pid, {rmdir, Dir}, atom). - -%%-------------------------------------------------------------------------- -%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Type = ascii | binary -%% -%% Description: Set transfer type. -%%-------------------------------------------------------------------------- -type(Pid, Type) -> - call(Pid, {type, Type}, atom). - -%%-------------------------------------------------------------------------- -%% recv(Pid, RemoteFileName ) -> ok | {error, epath} | -%% {error, elogin} | {error, econn} -%% Pid = pid() -%% RemoteFileName = LocalFileName = string() -%% -%% Description: Transfer file from remote server. -%%-------------------------------------------------------------------------- -recv(Pid, RemotFileName) -> - recv(Pid, RemotFileName, RemotFileName). - -recv(Pid, RemotFileName, LocalFileName) -> - call(Pid, {recv, RemotFileName, LocalFileName}, atom). - -%%-------------------------------------------------------------------------- -%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% Bin = binary() -%% -%% Description: Transfer file from remote server into binary. -%%-------------------------------------------------------------------------- -recv_bin(Pid, RemoteFile) -> - call(Pid, {recv_bin, RemoteFile}, bin). - -%%-------------------------------------------------------------------------- -%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start receive of chunks of remote file. -%%-------------------------------------------------------------------------- -recv_chunk_start(Pid, RemoteFile) -> - call(Pid, {recv_chunk_start, RemoteFile}, atom). - -%%-------------------------------------------------------------------------- -%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Transfer file from remote server into binary in chunks -%%-------------------------------------------------------------------------- -recv_chunk(Pid) -> - call(Pid, recv_chunk, atom). - -%%-------------------------------------------------------------------------- -%% send(Pid, LocalFileName ) -> ok | {error, epath} -%% | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% LocalFileName = RemotFileName = string() -%% -%% Description: Transfer file to remote server. -%%-------------------------------------------------------------------------- -send(Pid, LocalFileName) -> - send(Pid, LocalFileName, LocalFileName). - -send(Pid, LocalFileName, RemotFileName) -> - call(Pid, {send, LocalFileName, RemotFileName}, atom). - -%%-------------------------------------------------------------------------- -%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} -%% | {error, enotbinary} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% RemoteFile = string() -%% -%% Description: Transfer a binary to a remote file. -%%-------------------------------------------------------------------------- -send_bin(Pid, Bin, RemoteFile) when binary(Bin) -> - call(Pid, {send_bin, Bin, RemoteFile}, atom); -send_bin(_Pid, _Bin, _RemoteFile) -> - {error, enotbinary}. - -%%-------------------------------------------------------------------------- -%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start transfer of chunks to remote file. -%%-------------------------------------------------------------------------- -send_chunk_start(Pid, RemoteFile) -> - call(Pid, {send_chunk_start, RemoteFile}, atom). - -%%-------------------------------------------------------------------------- -%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | -%% {error, epath} | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start append chunks of data to remote file. -%%-------------------------------------------------------------------------- -append_chunk_start(Pid, RemoteFile) -> - call(Pid, {append_chunk_start, RemoteFile}, atom). - -%%-------------------------------------------------------------------------- -%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} -%% | {error, echunk} | {error, econn} -%% Pid = pid() -%% Bin = binary(). -%% -%% Purpose: Send chunk to remote file. -%%-------------------------------------------------------------------------- -send_chunk(Pid, Bin) when binary(Bin) -> - call(Pid, {transfer_chunk, Bin}, atom); -send_chunk(_Pid, _Bin) -> - {error, enotbinary}. - -%%-------------------------------------------------------------------------- -%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} -%% | {error, echunk} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% -%% Description: Append chunk to remote file. -%%-------------------------------------------------------------------------- -append_chunk(Pid, Bin) when binary(Bin) -> - call(Pid, {transfer_chunk, Bin}, atom); -append_chunk(_Pid, _Bin) -> - {error, enotbinary}. - -%%-------------------------------------------------------------------------- -%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} -%% | {error, econn} -%% Pid = pid() -%% -%% Description: End sending of chunks to remote file. -%%-------------------------------------------------------------------------- -send_chunk_end(Pid) -> - call(Pid, chunk_end, atom). - -%%-------------------------------------------------------------------------- -%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} -%% | {error, econn} -%% Pid = pid() -%% -%% Description: End appending of chunks to remote file. -%%-------------------------------------------------------------------------- -append_chunk_end(Pid) -> - call(Pid, chunk_end, atom). - -%%-------------------------------------------------------------------------- -%% append(Pid, LocalFileName, RemotFileName) -> ok | {error, epath} -%% | {error, elogin} | {error, econn} -%% Pid = pid() -%% LocalFileName = RemotFileName = string() -%% -%% Description: Append the local file to the remote file -%%-------------------------------------------------------------------------- -append(Pid, LocalFileName) -> - append(Pid, LocalFileName, LocalFileName). - -append(Pid, LocalFileName, RemotFileName) -> - call(Pid, {append, LocalFileName, RemotFileName}, atom). - -%%-------------------------------------------------------------------------- -%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} -%% | {error, enotbinary} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% RemoteFile = string() -%% -%% Purpose: Append a binary to a remote file. -%%-------------------------------------------------------------------------- -append_bin(Pid, Bin, RemoteFile) when binary(Bin) -> - call(Pid, {append_bin, Bin, RemoteFile}, atom); -append_bin(_Pid, _Bin, _RemoteFile) -> - {error, enotbinary}. - -%%-------------------------------------------------------------------------- -%% quote(Pid, Cmd) -> ok -%% Pid = pid() -%% Cmd = string() -%% -%% Description: Send arbitrary ftp command. -%%-------------------------------------------------------------------------- -quote(Pid, Cmd) when list(Cmd) -> - call(Pid, {quote, Cmd}, atom). - -%%-------------------------------------------------------------------------- -%% close(Pid) -> ok -%% Pid = pid() -%% -%% Description: End the ftp session. -%%-------------------------------------------------------------------------- -close(Pid) -> - cast(Pid, close), - ok. - -%%-------------------------------------------------------------------------- -%% force_active(Pid) -> ok -%% Pid = pid() -%% -%% Description: Force connection to use active mode. -%%-------------------------------------------------------------------------- -force_active(Pid) -> - error_logger:info_report("This function is deprecated use the mode flag " - "to open/[1,2,3] instead", []), - call(Pid, force_active, atom). - -%%-------------------------------------------------------------------------- -%% formaterror(Tag) -> string() -%% Tag = atom() | {error, atom()} -%% -%% Description: Return diagnostics. -%%-------------------------------------------------------------------------- -formaterror(Tag) -> - ftp_response:error_string(Tag). - -%%%======================================================================== -%%% gen_server callback functions -%%%======================================================================== - -%%------------------------------------------------------------------------- -%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} -%% Description: Initiates the erlang process that manages a ftp connection. -%%------------------------------------------------------------------------- -init([{client, ClientPid}, Flags]) -> - process_flag(trap_exit, true), - erlang:monitor(process, ClientPid), - inet_db:start(), - {ok, LDir} = file:get_cwd(), - State = case is_debug(Flags) or is_trace(Flags) of - true -> - dbg:tracer(), - dbg:p(all, [call]), - case is_debug(Flags) of - true -> - dbg:tp(ftp, [{'_', [], [{return_trace}]}]), - dbg:tp(ftp_response, [{'_', [], - [{return_trace}]}]), - dbg:tp(ftp_progress, [{'_', [], - [{return_trace}]}]); - false -> %trace - dbg:tpl(ftp, [{'_', [], [{return_trace}]}]), - dbg:tpl(ftp_response, [{'_', [], - [{return_trace}]}]), - dbg:tpl(ftp_progress, [{'_', [], - [{return_trace}]}]) - end, - #state{ldir = LDir}; - false -> - case is_verbose(Flags) of - true -> - #state{verbose = true, ldir = LDir}; - false -> - #state{ldir = LDir} - end - end, - process_flag(priority, low), - {ok, State#state{owner = ClientPid, - ip_v6_disabled = is_ipv6_disabled(Flags)}}. - - -%%-------------------------------------------------------------------------- -%% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% Description: Handle incoming requests. -%%------------------------------------------------------------------------- -handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> - {reply, {error, not_connection_owner}, State}; - -handle_call({_, {open, ip_comm, Opts}}, From, State) -> - case key_search(host, Opts, undefined) of - undefined -> - {stop, normal, {error, ehost}, State}; - Host -> - IsPosInt = fun(Int) when is_integer(Int), Int > 0 -> - true; - (_) -> - false - end, - - IsModeAtom = fun(active) -> - true; - (passive) -> - true; - (_) -> - false - end, - - Mode = check_option(IsModeAtom, - key_search(mode, Opts, ?DEFAULT_MODE), - ?DEFAULT_MODE), - Port = check_option(IsPosInt, key_search(port, Opts, ?FTP_PORT), - ?FTP_PORT), - Timeout = check_option(IsPosInt, key_search(timeout, Opts, - ?CONNECTION_TIMEOUT), - ?CONNECTION_TIMEOUT), - ProgressOptions = key_search(progress, Opts, ignore), - - setup_ctrl_connection(Host, Port, Timeout, - State#state{client = From, mode = Mode, - progress = - progress(ProgressOptions)}) - end; - -handle_call({_, force_active}, _, State) -> - {reply, ok, State#state{mode = active}}; - -handle_call({_, {user, User, Password}}, From, State) -> - handle_user(User, Password, "", State#state{client = From}); - -handle_call({_, {user, User, Password, Acc}}, From, State) -> - handle_user(User, Password, Acc, State#state{client = From}); - -handle_call({_, {account, Acc}}, From, State)-> - handle_user_account(Acc, State#state{client = From}); - -handle_call({_, pwd}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("PWD", [])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = pwd}}; - -handle_call({_, lpwd}, From, #state{ldir = LDir} = State) -> - {reply, {ok, LDir}, State#state{client = From}}; - -handle_call({_, {cd, Dir}}, From, #state{chunk = false} - = State) -> - send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = cd}}; - -handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) -> - LDir = filename:absname(Dir, LDir0), - case file:read_file_info(LDir) of %% FIX better check that LDir is a dir. - {ok, _ } -> - {reply, ok, State#state{ldir = LDir}}; - _ -> - {reply, {error, epath}, State} - end; - -handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {dir, Dir, Len}, - client = From}); -handle_call({_, {rename, CurrFile, NewFile}}, From, - #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {rename, NewFile}, client = From}}; - -handle_call({_, {delete, File}}, {_Pid, _} = From, - #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("DELE ~s", [File])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_,{type, Type}}, From, #state{chunk = false} - = State) -> - case Type of - ascii -> - send_ctrl_message(State, mk_cmd("TYPE A", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = type, type = ascii, - client = From}}; - binary -> - send_ctrl_message(State, mk_cmd("TYPE I", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = type, type = binary, - client = From}}; - _ -> - {reply, {error, etype}, State} - end; - -handle_call({_,{recv, RemoteFile, LocalFile}}, From, - #state{chunk = false, ldir = LocalDir} = State) -> - progress_report({remote_file, RemoteFile}, State), - NewLocalFile = filename:absname(LocalFile, LocalDir), - - case file_open(NewLocalFile, write) of - {ok, Fd} -> - setup_data_connection(State#state{client = From, - caller = - {recv_file, - RemoteFile, Fd}}); - {error, _What} -> - {reply, {error, epath}, State} - end; - -handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} = - State) -> - setup_data_connection(State#state{caller = {recv_bin, RemoteFile}, - client = From}); - -handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "RETR", RemoteFile}, - client = From}); - -handle_call({_, recv_chunk}, _, #state{chunk = false} = State) -> - {reply, {error, "ftp:recv_chunk_start/2 not called"}, State}; - -handle_call({_, recv_chunk}, From, #state{chunk = true} = State) -> - activate_data_connection(State), - {noreply, State#state{client = From, caller = recv_chunk}}; - -handle_call({_, {send, LocalFile, RemoteFile}}, From, - #state{chunk = false, ldir = LocalDir} = State) -> - progress_report({local_file, filename:absname(LocalFile, LocalDir)}, - State), - setup_data_connection(State#state{caller = {transfer_file, - {"STOR", - LocalFile, RemoteFile}}, - client = From}); -handle_call({_, {append, LocalFile, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_file, - {"APPE", - LocalFile, RemoteFile}}, - client = From}); -handle_call({_, {send_bin, Bin, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_data, - {"STOR", Bin, RemoteFile}}, - client = From}); -handle_call({_,{append_bin, Bin, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_data, - {"APPE", Bin, RemoteFile}}, - client = From}); -handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "STOR", RemoteFile}, - client = From}); -handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "APPE", RemoteFile}, - client = From}); -handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) -> - send_data_message(State, Bin), - {reply, ok, State}; - -handle_call({_, chunk_end}, From, #state{chunk = true} = State) -> - close_data_connection(State), - activate_ctrl_connection(State), - {noreply, State#state{client = From, dsock = undefined, - caller = end_chunk_transfer, chunk = false}}; - -handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd(Cmd, [])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = quote}}; - -handle_call(_, _, #state{chunk = true} = State) -> - {reply, {error, echunk}, State}; - -%% Catch all - This can only happen if the application programmer writes -%% really bad code that violates the API. -handle_call(Request, _Timeout, State) -> - {stop, {'API_violation_connection_closed', Request}, - {error, {connection_terminated, 'API_violation'}}, State}. - -%%-------------------------------------------------------------------------- -%% handle_cast(Request, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handles cast messages. -%%------------------------------------------------------------------------- -handle_cast({Pid, close}, #state{owner = Pid} = State) -> - send_ctrl_message(State, mk_cmd("QUIT", [])), - close_ctrl_connection(State), - close_data_connection(State), - {stop, normal, State#state{csock = undefined, dsock = undefined}}; - -handle_cast({Pid, close}, State) -> - error_logger:info_report("A none owner process ~p tried to close an " - "ftp connection: ~n", [Pid]), - {noreply, State}; - -%% Catch all - This can oly happen if the application programmer writes -%% really bad code that violates the API. -handle_cast(Msg, State) -> - {stop, {'API_violation_connection_colsed', Msg}, State}. - -%%-------------------------------------------------------------------------- -%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handles tcp messages from the ftp-server. -%% Note: The order of the function clauses is significant. -%%-------------------------------------------------------------------------- - -handle_info(timeout, #state{caller = open} = State) -> - {stop, timeout, State}; - -handle_info(timeout, State) -> - {noreply, State}; - -%%% Data socket messages %%% -handle_info({tcp, Socket, Data}, - #state{dsock = Socket, - caller = {recv_file, Fd}} = State) -> - file_write(binary_to_list(Data), Fd), - progress_report({binary, Data}, State), - activate_data_connection(State), - {noreply, State}; - -handle_info({tcp, Socket, Data}, #state{dsock = Socket, client = From, - caller = recv_chunk} - = State) -> - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, data = <<>>}}; - -handle_info({tcp, Socket, Data}, #state{dsock = Socket} = State) -> - activate_data_connection(State), - {noreply, State#state{data = <<(State#state.data)/binary, - Data/binary>>}}; - -handle_info({tcp_closed, Socket}, #state{dsock = Socket, - caller = {recv_file, Fd}} - = State) -> - file_close(Fd), - 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 = <>}}; - 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(<>, - 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/2 - -handle_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 setup -handle_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 mode -handle_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 directory -handle_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 renaming -handle_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_bin -handle_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_transfer -handle_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_file -handle_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}, - {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}}; -%%-------------------------------------------------------------------------- -%% Default -handle_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, _) when -Status == 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). -- cgit v1.2.3