/lib/ftp.c
C | 4367 lines | 3409 code | 395 blank | 563 comment | 465 complexity | fec878c8bbbc9e0371dd7a71a897472d MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.haxx.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ***************************************************************************/
- #include "curl_setup.h"
- #ifndef CURL_DISABLE_FTP
- #ifdef HAVE_NETINET_IN_H
- #include <netinet/in.h>
- #endif
- #ifdef HAVE_ARPA_INET_H
- #include <arpa/inet.h>
- #endif
- #ifdef HAVE_UTSNAME_H
- #include <sys/utsname.h>
- #endif
- #ifdef HAVE_NETDB_H
- #include <netdb.h>
- #endif
- #ifdef __VMS
- #include <in.h>
- #include <inet.h>
- #endif
- #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
- #undef in_addr_t
- #define in_addr_t unsigned long
- #endif
- #include <curl/curl.h>
- #include "urldata.h"
- #include "sendf.h"
- #include "if2ip.h"
- #include "hostip.h"
- #include "progress.h"
- #include "transfer.h"
- #include "escape.h"
- #include "http.h" /* for HTTP proxy tunnel stuff */
- #include "ftp.h"
- #include "fileinfo.h"
- #include "ftplistparser.h"
- #include "curl_range.h"
- #include "curl_sec.h"
- #include "strtoofft.h"
- #include "strcase.h"
- #include "vtls/vtls.h"
- #include "connect.h"
- #include "strerror.h"
- #include "inet_ntop.h"
- #include "inet_pton.h"
- #include "select.h"
- #include "parsedate.h" /* for the week day and month names */
- #include "sockaddr.h" /* required for Curl_sockaddr_storage */
- #include "multiif.h"
- #include "url.h"
- #include "strcase.h"
- #include "speedcheck.h"
- #include "warnless.h"
- #include "http_proxy.h"
- #include "non-ascii.h"
- #include "socks.h"
- /* The last 3 #include files should be in this order */
- #include "curl_printf.h"
- #include "curl_memory.h"
- #include "memdebug.h"
- #ifndef NI_MAXHOST
- #define NI_MAXHOST 1025
- #endif
- #ifndef INET_ADDRSTRLEN
- #define INET_ADDRSTRLEN 16
- #endif
- #ifdef CURL_DISABLE_VERBOSE_STRINGS
- #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt
- #endif
- /* Local API functions */
- #ifndef DEBUGBUILD
- static void _state(struct connectdata *conn,
- ftpstate newstate);
- #define state(x,y) _state(x,y)
- #else
- static void _state(struct connectdata *conn,
- ftpstate newstate,
- int lineno);
- #define state(x,y) _state(x,y,__LINE__)
- #endif
- static CURLcode ftp_sendquote(struct connectdata *conn,
- struct curl_slist *quote);
- static CURLcode ftp_quit(struct connectdata *conn);
- static CURLcode ftp_parse_url_path(struct connectdata *conn);
- static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done);
- #ifndef CURL_DISABLE_VERBOSE_STRINGS
- static void ftp_pasv_verbose(struct connectdata *conn,
- Curl_addrinfo *ai,
- char *newhost, /* ascii version */
- int port);
- #endif
- static CURLcode ftp_state_prepare_transfer(struct connectdata *conn);
- static CURLcode ftp_state_mdtm(struct connectdata *conn);
- static CURLcode ftp_state_quote(struct connectdata *conn,
- bool init, ftpstate instate);
- static CURLcode ftp_nb_type(struct connectdata *conn,
- bool ascii, ftpstate newstate);
- static int ftp_need_type(struct connectdata *conn,
- bool ascii);
- static CURLcode ftp_do(struct connectdata *conn, bool *done);
- static CURLcode ftp_done(struct connectdata *conn,
- CURLcode, bool premature);
- static CURLcode ftp_connect(struct connectdata *conn, bool *done);
- static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection);
- static CURLcode ftp_do_more(struct connectdata *conn, int *completed);
- static CURLcode ftp_multi_statemach(struct connectdata *conn, bool *done);
- static int ftp_getsock(struct connectdata *conn, curl_socket_t *socks);
- static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks);
- static CURLcode ftp_doing(struct connectdata *conn,
- bool *dophase_done);
- static CURLcode ftp_setup_connection(struct connectdata * conn);
- static CURLcode init_wc_data(struct connectdata *conn);
- static CURLcode wc_statemach(struct connectdata *conn);
- static void wc_data_dtor(void *ptr);
- static CURLcode ftp_state_retr(struct connectdata *conn, curl_off_t filesize);
- static CURLcode ftp_readresp(curl_socket_t sockfd,
- struct pingpong *pp,
- int *ftpcode,
- size_t *size);
- static CURLcode ftp_dophase_done(struct connectdata *conn,
- bool connected);
- /* easy-to-use macro: */
- #define PPSENDF(x,y,z) result = Curl_pp_sendf(x,y,z); \
- if(result) \
- return result
- /*
- * FTP protocol handler.
- */
- const struct Curl_handler Curl_handler_ftp = {
- "FTP", /* scheme */
- ftp_setup_connection, /* setup_connection */
- ftp_do, /* do_it */
- ftp_done, /* done */
- ftp_do_more, /* do_more */
- ftp_connect, /* connect_it */
- ftp_multi_statemach, /* connecting */
- ftp_doing, /* doing */
- ftp_getsock, /* proto_getsock */
- ftp_getsock, /* doing_getsock */
- ftp_domore_getsock, /* domore_getsock */
- ZERO_NULL, /* perform_getsock */
- ftp_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- ZERO_NULL, /* connection_check */
- PORT_FTP, /* defport */
- CURLPROTO_FTP, /* protocol */
- PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD |
- PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP |
- PROTOPT_WILDCARD /* flags */
- };
- #ifdef USE_SSL
- /*
- * FTPS protocol handler.
- */
- const struct Curl_handler Curl_handler_ftps = {
- "FTPS", /* scheme */
- ftp_setup_connection, /* setup_connection */
- ftp_do, /* do_it */
- ftp_done, /* done */
- ftp_do_more, /* do_more */
- ftp_connect, /* connect_it */
- ftp_multi_statemach, /* connecting */
- ftp_doing, /* doing */
- ftp_getsock, /* proto_getsock */
- ftp_getsock, /* doing_getsock */
- ftp_domore_getsock, /* domore_getsock */
- ZERO_NULL, /* perform_getsock */
- ftp_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- ZERO_NULL, /* connection_check */
- PORT_FTPS, /* defport */
- CURLPROTO_FTPS, /* protocol */
- PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION |
- PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD /* flags */
- };
- #endif
- static void close_secondarysocket(struct connectdata *conn)
- {
- if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) {
- Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]);
- conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
- }
- conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE;
- }
- /*
- * NOTE: back in the old days, we added code in the FTP code that made NOBODY
- * requests on files respond with headers passed to the client/stdout that
- * looked like HTTP ones.
- *
- * This approach is not very elegant, it causes confusion and is error-prone.
- * It is subject for removal at the next (or at least a future) soname bump.
- * Until then you can test the effects of the removal by undefining the
- * following define named CURL_FTP_HTTPSTYLE_HEAD.
- */
- #define CURL_FTP_HTTPSTYLE_HEAD 1
- static void freedirs(struct ftp_conn *ftpc)
- {
- if(ftpc->dirs) {
- int i;
- for(i = 0; i < ftpc->dirdepth; i++) {
- free(ftpc->dirs[i]);
- ftpc->dirs[i] = NULL;
- }
- free(ftpc->dirs);
- ftpc->dirs = NULL;
- ftpc->dirdepth = 0;
- }
- Curl_safefree(ftpc->file);
- /* no longer of any use */
- Curl_safefree(ftpc->newhost);
- }
- /***********************************************************************
- *
- * AcceptServerConnect()
- *
- * After connection request is received from the server this function is
- * called to accept the connection and close the listening socket
- *
- */
- static CURLcode AcceptServerConnect(struct connectdata *conn)
- {
- struct Curl_easy *data = conn->data;
- curl_socket_t sock = conn->sock[SECONDARYSOCKET];
- curl_socket_t s = CURL_SOCKET_BAD;
- #ifdef ENABLE_IPV6
- struct Curl_sockaddr_storage add;
- #else
- struct sockaddr_in add;
- #endif
- curl_socklen_t size = (curl_socklen_t) sizeof(add);
- if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) {
- size = sizeof(add);
- s = accept(sock, (struct sockaddr *) &add, &size);
- }
- Curl_closesocket(conn, sock); /* close the first socket */
- if(CURL_SOCKET_BAD == s) {
- failf(data, "Error accept()ing server connect");
- return CURLE_FTP_PORT_FAILED;
- }
- infof(data, "Connection accepted from server\n");
- /* when this happens within the DO state it is important that we mark us as
- not needing DO_MORE anymore */
- conn->bits.do_more = FALSE;
- conn->sock[SECONDARYSOCKET] = s;
- (void)curlx_nonblock(s, TRUE); /* enable non-blocking */
- conn->sock_accepted = TRUE;
- if(data->set.fsockopt) {
- int error = 0;
- /* activate callback for setting socket options */
- Curl_set_in_callback(data, true);
- error = data->set.fsockopt(data->set.sockopt_client,
- s,
- CURLSOCKTYPE_ACCEPT);
- Curl_set_in_callback(data, false);
- if(error) {
- close_secondarysocket(conn);
- return CURLE_ABORTED_BY_CALLBACK;
- }
- }
- return CURLE_OK;
- }
- /*
- * ftp_timeleft_accept() returns the amount of milliseconds left allowed for
- * waiting server to connect. If the value is negative, the timeout time has
- * already elapsed.
- *
- * The start time is stored in progress.t_acceptdata - as set with
- * Curl_pgrsTime(..., TIMER_STARTACCEPT);
- *
- */
- static timediff_t ftp_timeleft_accept(struct Curl_easy *data)
- {
- timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT;
- timediff_t other;
- struct curltime now;
- if(data->set.accepttimeout > 0)
- timeout_ms = data->set.accepttimeout;
- now = Curl_now();
- /* check if the generic timeout possibly is set shorter */
- other = Curl_timeleft(data, &now, FALSE);
- if(other && (other < timeout_ms))
- /* note that this also works fine for when other happens to be negative
- due to it already having elapsed */
- timeout_ms = other;
- else {
- /* subtract elapsed time */
- timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata);
- if(!timeout_ms)
- /* avoid returning 0 as that means no timeout! */
- return -1;
- }
- return timeout_ms;
- }
- /***********************************************************************
- *
- * ReceivedServerConnect()
- *
- * After allowing server to connect to us from data port, this function
- * checks both data connection for connection establishment and ctrl
- * connection for a negative response regarding a failure in connecting
- *
- */
- static CURLcode ReceivedServerConnect(struct connectdata *conn, bool *received)
- {
- struct Curl_easy *data = conn->data;
- curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET];
- curl_socket_t data_sock = conn->sock[SECONDARYSOCKET];
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- struct pingpong *pp = &ftpc->pp;
- int result;
- timediff_t timeout_ms;
- ssize_t nread;
- int ftpcode;
- *received = FALSE;
- timeout_ms = ftp_timeleft_accept(data);
- infof(data, "Checking for server connect\n");
- if(timeout_ms < 0) {
- /* if a timeout was already reached, bail out */
- failf(data, "Accept timeout occurred while waiting server connect");
- return CURLE_FTP_ACCEPT_TIMEOUT;
- }
- /* First check whether there is a cached response from server */
- if(pp->cache_size && pp->cache && pp->cache[0] > '3') {
- /* Data connection could not be established, let's return */
- infof(data, "There is negative response in cache while serv connect\n");
- Curl_GetFTPResponse(&nread, conn, &ftpcode);
- return CURLE_FTP_ACCEPT_FAILED;
- }
- result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0);
- /* see if the connection request is already here */
- switch(result) {
- case -1: /* error */
- /* let's die here */
- failf(data, "Error while waiting for server connect");
- return CURLE_FTP_ACCEPT_FAILED;
- case 0: /* Server connect is not received yet */
- break; /* loop */
- default:
- if(result & CURL_CSELECT_IN2) {
- infof(data, "Ready to accept data connection from server\n");
- *received = TRUE;
- }
- else if(result & CURL_CSELECT_IN) {
- infof(data, "Ctrl conn has data while waiting for data conn\n");
- Curl_GetFTPResponse(&nread, conn, &ftpcode);
- if(ftpcode/100 > 3)
- return CURLE_FTP_ACCEPT_FAILED;
- return CURLE_WEIRD_SERVER_REPLY;
- }
- break;
- } /* switch() */
- return CURLE_OK;
- }
- /***********************************************************************
- *
- * InitiateTransfer()
- *
- * After connection from server is accepted this function is called to
- * setup transfer parameters and initiate the data transfer.
- *
- */
- static CURLcode InitiateTransfer(struct connectdata *conn)
- {
- struct Curl_easy *data = conn->data;
- CURLcode result = CURLE_OK;
- if(conn->bits.ftp_use_data_ssl) {
- /* since we only have a plaintext TCP connection here, we must now
- * do the TLS stuff */
- infof(data, "Doing the SSL/TLS handshake on the data stream\n");
- result = Curl_ssl_connect(conn, SECONDARYSOCKET);
- if(result)
- return result;
- }
- if(conn->proto.ftpc.state_saved == FTP_STOR) {
- /* When we know we're uploading a specified file, we can get the file
- size prior to the actual upload. */
- Curl_pgrsSetUploadSize(data, data->state.infilesize);
- /* set the SO_SNDBUF for the secondary socket for those who need it */
- Curl_sndbufset(conn->sock[SECONDARYSOCKET]);
- Curl_setup_transfer(data, -1, -1, FALSE, SECONDARYSOCKET);
- }
- else {
- /* FTP download: */
- Curl_setup_transfer(data, SECONDARYSOCKET,
- conn->proto.ftpc.retr_size_saved, FALSE, -1);
- }
- conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */
- state(conn, FTP_STOP);
- return CURLE_OK;
- }
- /***********************************************************************
- *
- * AllowServerConnect()
- *
- * When we've issue the PORT command, we have told the server to connect to
- * us. This function checks whether data connection is established if so it is
- * accepted.
- *
- */
- static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected)
- {
- struct Curl_easy *data = conn->data;
- timediff_t timeout_ms;
- CURLcode result = CURLE_OK;
- *connected = FALSE;
- infof(data, "Preparing for accepting server on data port\n");
- /* Save the time we start accepting server connect */
- Curl_pgrsTime(data, TIMER_STARTACCEPT);
- timeout_ms = ftp_timeleft_accept(data);
- if(timeout_ms < 0) {
- /* if a timeout was already reached, bail out */
- failf(data, "Accept timeout occurred while waiting server connect");
- return CURLE_FTP_ACCEPT_TIMEOUT;
- }
- /* see if the connection request is already here */
- result = ReceivedServerConnect(conn, connected);
- if(result)
- return result;
- if(*connected) {
- result = AcceptServerConnect(conn);
- if(result)
- return result;
- result = InitiateTransfer(conn);
- if(result)
- return result;
- }
- else {
- /* Add timeout to multi handle and break out of the loop */
- if(*connected == FALSE) {
- Curl_expire(data, data->set.accepttimeout > 0 ?
- data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, 0);
- }
- }
- return result;
- }
- /* macro to check for a three-digit ftp status code at the start of the
- given string */
- #define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \
- ISDIGIT(line[2]))
- /* macro to check for the last line in an FTP server response */
- #define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3]))
- static bool ftp_endofresp(struct connectdata *conn, char *line, size_t len,
- int *code)
- {
- (void)conn;
- if((len > 3) && LASTLINE(line)) {
- *code = curlx_sltosi(strtol(line, NULL, 10));
- return TRUE;
- }
- return FALSE;
- }
- static CURLcode ftp_readresp(curl_socket_t sockfd,
- struct pingpong *pp,
- int *ftpcode, /* return the ftp-code if done */
- size_t *size) /* size of the response */
- {
- struct connectdata *conn = pp->conn;
- struct Curl_easy *data = conn->data;
- #ifdef HAVE_GSSAPI
- char * const buf = data->state.buffer;
- #endif
- int code;
- CURLcode result = Curl_pp_readresp(sockfd, pp, &code, size);
- #if defined(HAVE_GSSAPI)
- /* handle the security-oriented responses 6xx ***/
- switch(code) {
- case 631:
- code = Curl_sec_read_msg(conn, buf, PROT_SAFE);
- break;
- case 632:
- code = Curl_sec_read_msg(conn, buf, PROT_PRIVATE);
- break;
- case 633:
- code = Curl_sec_read_msg(conn, buf, PROT_CONFIDENTIAL);
- break;
- default:
- /* normal ftp stuff we pass through! */
- break;
- }
- #endif
- /* store the latest code for later retrieval */
- data->info.httpcode = code;
- if(ftpcode)
- *ftpcode = code;
- if(421 == code) {
- /* 421 means "Service not available, closing control connection." and FTP
- * servers use it to signal that idle session timeout has been exceeded.
- * If we ignored the response, it could end up hanging in some cases.
- *
- * This response code can come at any point so having it treated
- * generically is a good idea.
- */
- infof(data, "We got a 421 - timeout!\n");
- state(conn, FTP_STOP);
- return CURLE_OPERATION_TIMEDOUT;
- }
- return result;
- }
- /* --- parse FTP server responses --- */
- /*
- * Curl_GetFTPResponse() is a BLOCKING function to read the full response
- * from a server after a command.
- *
- */
- CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
- struct connectdata *conn,
- int *ftpcode) /* return the ftp-code */
- {
- /*
- * We cannot read just one byte per read() and then go back to select() as
- * the OpenSSL read() doesn't grok that properly.
- *
- * Alas, read as much as possible, split up into lines, use the ending
- * line in a response or continue reading. */
- curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
- struct Curl_easy *data = conn->data;
- CURLcode result = CURLE_OK;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- struct pingpong *pp = &ftpc->pp;
- size_t nread;
- int cache_skip = 0;
- int value_to_be_ignored = 0;
- if(ftpcode)
- *ftpcode = 0; /* 0 for errors */
- else
- /* make the pointer point to something for the rest of this function */
- ftpcode = &value_to_be_ignored;
- *nreadp = 0;
- while(!*ftpcode && !result) {
- /* check and reset timeout value every lap */
- time_t timeout = Curl_pp_state_timeout(pp, FALSE);
- time_t interval_ms;
- if(timeout <= 0) {
- failf(data, "FTP response timeout");
- return CURLE_OPERATION_TIMEDOUT; /* already too little time */
- }
- interval_ms = 1000; /* use 1 second timeout intervals */
- if(timeout < interval_ms)
- interval_ms = timeout;
- /*
- * Since this function is blocking, we need to wait here for input on the
- * connection and only then we call the response reading function. We do
- * timeout at least every second to make the timeout check run.
- *
- * A caution here is that the ftp_readresp() function has a cache that may
- * contain pieces of a response from the previous invoke and we need to
- * make sure we don't just wait for input while there is unhandled data in
- * that cache. But also, if the cache is there, we call ftp_readresp() and
- * the cache wasn't good enough to continue we must not just busy-loop
- * around this function.
- *
- */
- if(pp->cache && (cache_skip < 2)) {
- /*
- * There's a cache left since before. We then skipping the wait for
- * socket action, unless this is the same cache like the previous round
- * as then the cache was deemed not enough to act on and we then need to
- * wait for more data anyway.
- */
- }
- else if(!Curl_conn_data_pending(conn, FIRSTSOCKET)) {
- switch(SOCKET_READABLE(sockfd, interval_ms)) {
- case -1: /* select() error, stop reading */
- failf(data, "FTP response aborted due to select/poll error: %d",
- SOCKERRNO);
- return CURLE_RECV_ERROR;
- case 0: /* timeout */
- if(Curl_pgrsUpdate(conn))
- return CURLE_ABORTED_BY_CALLBACK;
- continue; /* just continue in our loop for the timeout duration */
- default: /* for clarity */
- break;
- }
- }
- result = ftp_readresp(sockfd, pp, ftpcode, &nread);
- if(result)
- break;
- if(!nread && pp->cache)
- /* bump cache skip counter as on repeated skips we must wait for more
- data */
- cache_skip++;
- else
- /* when we got data or there is no cache left, we reset the cache skip
- counter */
- cache_skip = 0;
- *nreadp += nread;
- } /* while there's buffer left and loop is requested */
- pp->pending_resp = FALSE;
- return result;
- }
- #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
- /* for debug purposes */
- static const char * const ftp_state_names[]={
- "STOP",
- "WAIT220",
- "AUTH",
- "USER",
- "PASS",
- "ACCT",
- "PBSZ",
- "PROT",
- "CCC",
- "PWD",
- "SYST",
- "NAMEFMT",
- "QUOTE",
- "RETR_PREQUOTE",
- "STOR_PREQUOTE",
- "POSTQUOTE",
- "CWD",
- "MKD",
- "MDTM",
- "TYPE",
- "LIST_TYPE",
- "RETR_TYPE",
- "STOR_TYPE",
- "SIZE",
- "RETR_SIZE",
- "STOR_SIZE",
- "REST",
- "RETR_REST",
- "PORT",
- "PRET",
- "PASV",
- "LIST",
- "RETR",
- "STOR",
- "QUIT"
- };
- #endif
- /* This is the ONLY way to change FTP state! */
- static void _state(struct connectdata *conn,
- ftpstate newstate
- #ifdef DEBUGBUILD
- , int lineno
- #endif
- )
- {
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- #if defined(DEBUGBUILD)
- #if defined(CURL_DISABLE_VERBOSE_STRINGS)
- (void) lineno;
- #else
- if(ftpc->state != newstate)
- infof(conn->data, "FTP %p (line %d) state change from %s to %s\n",
- (void *)ftpc, lineno, ftp_state_names[ftpc->state],
- ftp_state_names[newstate]);
- #endif
- #endif
- ftpc->state = newstate;
- }
- static CURLcode ftp_state_user(struct connectdata *conn)
- {
- CURLcode result;
- /* send USER */
- PPSENDF(&conn->proto.ftpc.pp, "USER %s", conn->user?conn->user:"");
- state(conn, FTP_USER);
- conn->data->state.ftp_trying_alternative = FALSE;
- return CURLE_OK;
- }
- static CURLcode ftp_state_pwd(struct connectdata *conn)
- {
- CURLcode result;
- /* send PWD to discover our entry point */
- PPSENDF(&conn->proto.ftpc.pp, "%s", "PWD");
- state(conn, FTP_PWD);
- return CURLE_OK;
- }
- /* For the FTP "protocol connect" and "doing" phases only */
- static int ftp_getsock(struct connectdata *conn,
- curl_socket_t *socks)
- {
- return Curl_pp_getsock(&conn->proto.ftpc.pp, socks);
- }
- /* For the FTP "DO_MORE" phase only */
- static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks)
- {
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- /* When in DO_MORE state, we could be either waiting for us to connect to a
- * remote site, or we could wait for that site to connect to us. Or just
- * handle ordinary commands.
- */
- if(SOCKS_STATE(conn->cnnct.state))
- return Curl_SOCKS_getsock(conn, socks, SECONDARYSOCKET);
- if(FTP_STOP == ftpc->state) {
- int bits = GETSOCK_READSOCK(0);
- /* if stopped and still in this state, then we're also waiting for a
- connect on the secondary connection */
- socks[0] = conn->sock[FIRSTSOCKET];
- if(!conn->data->set.ftp_use_port) {
- int s;
- int i;
- /* PORT is used to tell the server to connect to us, and during that we
- don't do happy eyeballs, but we do if we connect to the server */
- for(s = 1, i = 0; i<2; i++) {
- if(conn->tempsock[i] != CURL_SOCKET_BAD) {
- socks[s] = conn->tempsock[i];
- bits |= GETSOCK_WRITESOCK(s++);
- }
- }
- }
- else {
- socks[1] = conn->sock[SECONDARYSOCKET];
- bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1);
- }
- return bits;
- }
- return Curl_pp_getsock(&conn->proto.ftpc.pp, socks);
- }
- /* This is called after the FTP_QUOTE state is passed.
- ftp_state_cwd() sends the range of CWD commands to the server to change to
- the correct directory. It may also need to send MKD commands to create
- missing ones, if that option is enabled.
- */
- static CURLcode ftp_state_cwd(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- if(ftpc->cwddone)
- /* already done and fine */
- result = ftp_state_mdtm(conn);
- else {
- /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */
- DEBUGASSERT((conn->data->set.ftp_filemethod != FTPFILE_NOCWD) ||
- !(ftpc->dirdepth && ftpc->dirs[0][0] == '/'));
- ftpc->count2 = 0; /* count2 counts failed CWDs */
- /* count3 is set to allow a MKD to fail once. In the case when first CWD
- fails and then MKD fails (due to another session raced it to create the
- dir) this then allows for a second try to CWD to it */
- ftpc->count3 = (conn->data->set.ftp_create_missing_dirs == 2)?1:0;
- if(conn->bits.reuse && ftpc->entrypath &&
- /* no need to go to entrypath when we have an absolute path */
- !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) {
- /* This is a re-used connection. Since we change directory to where the
- transfer is taking place, we must first get back to the original dir
- where we ended up after login: */
- ftpc->cwdcount = 0; /* we count this as the first path, then we add one
- for all upcoming ones in the ftp->dirs[] array */
- PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->entrypath);
- state(conn, FTP_CWD);
- }
- else {
- if(ftpc->dirdepth) {
- ftpc->cwdcount = 1;
- /* issue the first CWD, the rest is sent when the CWD responses are
- received... */
- PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->dirs[ftpc->cwdcount -1]);
- state(conn, FTP_CWD);
- }
- else {
- /* No CWD necessary */
- result = ftp_state_mdtm(conn);
- }
- }
- }
- return result;
- }
- typedef enum {
- EPRT,
- PORT,
- DONE
- } ftpport;
- static CURLcode ftp_state_use_port(struct connectdata *conn,
- ftpport fcmd) /* start with this */
- {
- CURLcode result = CURLE_OK;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- struct Curl_easy *data = conn->data;
- curl_socket_t portsock = CURL_SOCKET_BAD;
- char myhost[MAX_IPADR_LEN + 1] = "";
- struct Curl_sockaddr_storage ss;
- Curl_addrinfo *res, *ai;
- curl_socklen_t sslen;
- char hbuf[NI_MAXHOST];
- struct sockaddr *sa = (struct sockaddr *)&ss;
- struct sockaddr_in * const sa4 = (void *)sa;
- #ifdef ENABLE_IPV6
- struct sockaddr_in6 * const sa6 = (void *)sa;
- #endif
- static const char mode[][5] = { "EPRT", "PORT" };
- enum resolve_t rc;
- int error;
- char *host = NULL;
- char *string_ftpport = data->set.str[STRING_FTPPORT];
- struct Curl_dns_entry *h = NULL;
- unsigned short port_min = 0;
- unsigned short port_max = 0;
- unsigned short port;
- bool possibly_non_local = TRUE;
- char buffer[STRERROR_LEN];
- char *addr = NULL;
- /* Step 1, figure out what is requested,
- * accepted format :
- * (ipv4|ipv6|domain|interface)?(:port(-range)?)?
- */
- if(data->set.str[STRING_FTPPORT] &&
- (strlen(data->set.str[STRING_FTPPORT]) > 1)) {
- #ifdef ENABLE_IPV6
- size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ?
- INET6_ADDRSTRLEN : strlen(string_ftpport);
- #else
- size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ?
- INET_ADDRSTRLEN : strlen(string_ftpport);
- #endif
- char *ip_start = string_ftpport;
- char *ip_end = NULL;
- char *port_start = NULL;
- char *port_sep = NULL;
- addr = calloc(addrlen + 1, 1);
- if(!addr)
- return CURLE_OUT_OF_MEMORY;
- #ifdef ENABLE_IPV6
- if(*string_ftpport == '[') {
- /* [ipv6]:port(-range) */
- ip_start = string_ftpport + 1;
- ip_end = strchr(string_ftpport, ']');
- if(ip_end)
- strncpy(addr, ip_start, ip_end - ip_start);
- }
- else
- #endif
- if(*string_ftpport == ':') {
- /* :port */
- ip_end = string_ftpport;
- }
- else {
- ip_end = strchr(string_ftpport, ':');
- if(ip_end) {
- /* either ipv6 or (ipv4|domain|interface):port(-range) */
- #ifdef ENABLE_IPV6
- if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) {
- /* ipv6 */
- port_min = port_max = 0;
- strcpy(addr, string_ftpport);
- ip_end = NULL; /* this got no port ! */
- }
- else
- #endif
- /* (ipv4|domain|interface):port(-range) */
- strncpy(addr, string_ftpport, ip_end - ip_start);
- }
- else
- /* ipv4|interface */
- strcpy(addr, string_ftpport);
- }
- /* parse the port */
- if(ip_end != NULL) {
- port_start = strchr(ip_end, ':');
- if(port_start) {
- port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10));
- port_sep = strchr(port_start, '-');
- if(port_sep) {
- port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10));
- }
- else
- port_max = port_min;
- }
- }
- /* correct errors like:
- * :1234-1230
- * :-4711, in this case port_min is (unsigned)-1,
- * therefore port_min > port_max for all cases
- * but port_max = (unsigned)-1
- */
- if(port_min > port_max)
- port_min = port_max = 0;
- if(*addr != '\0') {
- /* attempt to get the address of the given interface name */
- switch(Curl_if2ip(conn->ip_addr->ai_family,
- Curl_ipv6_scope(conn->ip_addr->ai_addr),
- conn->scope_id, addr, hbuf, sizeof(hbuf))) {
- case IF2IP_NOT_FOUND:
- /* not an interface, use the given string as host name instead */
- host = addr;
- break;
- case IF2IP_AF_NOT_SUPPORTED:
- return CURLE_FTP_PORT_FAILED;
- case IF2IP_FOUND:
- host = hbuf; /* use the hbuf for host name */
- }
- }
- else
- /* there was only a port(-range) given, default the host */
- host = NULL;
- } /* data->set.ftpport */
- if(!host) {
- /* not an interface and not a host name, get default by extracting
- the IP from the control connection */
- sslen = sizeof(ss);
- if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) {
- failf(data, "getsockname() failed: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- free(addr);
- return CURLE_FTP_PORT_FAILED;
- }
- switch(sa->sa_family) {
- #ifdef ENABLE_IPV6
- case AF_INET6:
- Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf));
- break;
- #endif
- default:
- Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf));
- break;
- }
- host = hbuf; /* use this host name */
- possibly_non_local = FALSE; /* we know it is local now */
- }
- /* resolv ip/host to ip */
- rc = Curl_resolv(conn, host, 0, FALSE, &h);
- if(rc == CURLRESOLV_PENDING)
- (void)Curl_resolver_wait_resolv(conn, &h);
- if(h) {
- res = h->addr;
- /* when we return from this function, we can forget about this entry
- to we can unlock it now already */
- Curl_resolv_unlock(data, h);
- } /* (h) */
- else
- res = NULL; /* failure! */
- if(res == NULL) {
- failf(data, "failed to resolve the address provided to PORT: %s", host);
- free(addr);
- return CURLE_FTP_PORT_FAILED;
- }
- free(addr);
- host = NULL;
- /* step 2, create a socket for the requested address */
- portsock = CURL_SOCKET_BAD;
- error = 0;
- for(ai = res; ai; ai = ai->ai_next) {
- result = Curl_socket(conn, ai, NULL, &portsock);
- if(result) {
- error = SOCKERRNO;
- continue;
- }
- break;
- }
- if(!ai) {
- failf(data, "socket failure: %s",
- Curl_strerror(error, buffer, sizeof(buffer)));
- return CURLE_FTP_PORT_FAILED;
- }
- /* step 3, bind to a suitable local address */
- memcpy(sa, ai->ai_addr, ai->ai_addrlen);
- sslen = ai->ai_addrlen;
- for(port = port_min; port <= port_max;) {
- if(sa->sa_family == AF_INET)
- sa4->sin_port = htons(port);
- #ifdef ENABLE_IPV6
- else
- sa6->sin6_port = htons(port);
- #endif
- /* Try binding the given address. */
- if(bind(portsock, sa, sslen) ) {
- /* It failed. */
- error = SOCKERRNO;
- if(possibly_non_local && (error == EADDRNOTAVAIL)) {
- /* The requested bind address is not local. Use the address used for
- * the control connection instead and restart the port loop
- */
- infof(data, "bind(port=%hu) on non-local address failed: %s\n", port,
- Curl_strerror(error, buffer, sizeof(buffer)));
- sslen = sizeof(ss);
- if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) {
- failf(data, "getsockname() failed: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- port = port_min;
- possibly_non_local = FALSE; /* don't try this again */
- continue;
- }
- if(error != EADDRINUSE && error != EACCES) {
- failf(data, "bind(port=%hu) failed: %s", port,
- Curl_strerror(error, buffer, sizeof(buffer)));
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- }
- else
- break;
- port++;
- }
- /* maybe all ports were in use already*/
- if(port > port_max) {
- failf(data, "bind() failed, we ran out of ports!");
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- /* get the name again after the bind() so that we can extract the
- port number it uses now */
- sslen = sizeof(ss);
- if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) {
- failf(data, "getsockname() failed: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- /* step 4, listen on the socket */
- if(listen(portsock, 1)) {
- failf(data, "socket failure: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- /* step 5, send the proper FTP command */
- /* get a plain printable version of the numerical address to work with
- below */
- Curl_printable_address(ai, myhost, sizeof(myhost));
- #ifdef ENABLE_IPV6
- if(!conn->bits.ftp_use_eprt && conn->bits.ipv6)
- /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the
- request and enable EPRT again! */
- conn->bits.ftp_use_eprt = TRUE;
- #endif
- for(; fcmd != DONE; fcmd++) {
- if(!conn->bits.ftp_use_eprt && (EPRT == fcmd))
- /* if disabled, goto next */
- continue;
- if((PORT == fcmd) && sa->sa_family != AF_INET)
- /* PORT is IPv4 only */
- continue;
- switch(sa->sa_family) {
- case AF_INET:
- port = ntohs(sa4->sin_port);
- break;
- #ifdef ENABLE_IPV6
- case AF_INET6:
- port = ntohs(sa6->sin6_port);
- break;
- #endif
- default:
- continue; /* might as well skip this */
- }
- if(EPRT == fcmd) {
- /*
- * Two fine examples from RFC2428;
- *
- * EPRT |1|132.235.1.2|6275|
- *
- * EPRT |2|1080::8:800:200C:417A|5282|
- */
- result = Curl_pp_sendf(&ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd],
- sa->sa_family == AF_INET?1:2,
- myhost, port);
- if(result) {
- failf(data, "Failure sending EPRT command: %s",
- curl_easy_strerror(result));
- Curl_closesocket(conn, portsock);
- /* don't retry using PORT */
- ftpc->count1 = PORT;
- /* bail out */
- state(conn, FTP_STOP);
- return result;
- }
- break;
- }
- if(PORT == fcmd) {
- /* large enough for [IP address],[num],[num] */
- char target[sizeof(myhost) + 20];
- char *source = myhost;
- char *dest = target;
- /* translate x.x.x.x to x,x,x,x */
- while(source && *source) {
- if(*source == '.')
- *dest = ',';
- else
- *dest = *source;
- dest++;
- source++;
- }
- *dest = 0;
- msnprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff));
- result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], target);
- if(result) {
- failf(data, "Failure sending PORT command: %s",
- curl_easy_strerror(result));
- Curl_closesocket(conn, portsock);
- /* bail out */
- state(conn, FTP_STOP);
- return result;
- }
- break;
- }
- }
- /* store which command was sent */
- ftpc->count1 = fcmd;
- close_secondarysocket(conn);
- /* we set the secondary socket variable to this for now, it is only so that
- the cleanup function will close it in case we fail before the true
- secondary stuff is made */
- conn->sock[SECONDARYSOCKET] = portsock;
- /* this tcpconnect assignment below is a hackish work-around to make the
- multi interface with active FTP work - as it will not wait for a
- (passive) connect in Curl_is_connected().
- The *proper* fix is to make sure that the active connection from the
- server is done in a non-blocking way. Currently, it is still BLOCKING.
- */
- conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE;
- state(conn, FTP_PORT);
- return result;
- }
- static CURLcode ftp_state_use_pasv(struct connectdata *conn)
- {
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- CURLcode result = CURLE_OK;
- /*
- Here's the excecutive summary on what to do:
- PASV is RFC959, expect:
- 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
- LPSV is RFC1639, expect:
- 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2)
- EPSV is RFC2428, expect:
- 229 Entering Extended Passive Mode (|||port|)
- */
- static const char mode[][5] = { "EPSV", "PASV" };
- int modeoff;
- #ifdef PF_INET6
- if(!conn->bits.ftp_use_epsv && conn->bits.ipv6)
- /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the
- request and enable EPSV again! */
- conn->bits.ftp_use_epsv = TRUE;
- #endif
- modeoff = conn->bits.ftp_use_epsv?0:1;
- PPSENDF(&ftpc->pp, "%s", mode[modeoff]);
- ftpc->count1 = modeoff;
- state(conn, FTP_PASV);
- infof(conn->data, "Connect data stream passively\n");
- return result;
- }
- /*
- * ftp_state_prepare_transfer() starts PORT, PASV or PRET etc.
- *
- * REST is the last command in the chain of commands when a "head"-like
- * request is made. Thus, if an actual transfer is to be made this is where we
- * take off for real.
- */
- static CURLcode ftp_state_prepare_transfer(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->req.protop;
- struct Curl_easy *data = conn->data;
- if(ftp->transfer != FTPTRANSFER_BODY) {
- /* doesn't transfer any data */
- /* still possibly do PRE QUOTE jobs */
- state(conn, FTP_RETR_PREQUOTE);
- result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
- }
- else if(data->set.ftp_use_port) {
- /* We have chosen to use the PORT (or similar) command */
- result = ftp_state_use_port(conn, EPRT);
- }
- else {
- /* We have chosen (this is default) to use the PASV (or similar) command */
- if(data->set.ftp_use_pret) {
- /* The user has requested that we send a PRET command
- to prepare the server for the upcoming PASV */
- if(!conn->proto.ftpc.file) {
- PPSENDF(&conn->proto.ftpc.pp, "PRET %s",
- data->set.str[STRING_CUSTOMREQUEST]?
- data->set.str[STRING_CUSTOMREQUEST]:
- (data->set.ftp_list_only?"NLST":"LIST"));
- }
- else if(data->set.upload) {
- PPSENDF(&conn->proto.ftpc.pp, "PRET STOR %s", conn->proto.ftpc.file);
- }
- else {
- PPSENDF(&conn->proto.ftpc.pp, "PRET RETR %s", conn->proto.ftpc.file);
- }
- state(conn, FTP_PRET);
- }
- else {
- result = ftp_state_use_pasv(conn);
- }
- }
- return result;
- }
- static CURLcode ftp_state_rest(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->req.protop;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- if((ftp->transfer != FTPTRANSFER_BODY) && ftpc->file) {
- /* if a "head"-like request is being made (on a file) */
- /* Determine if server can respond to REST command and therefore
- whether it supports range */
- PPSENDF(&conn->proto.ftpc.pp, "REST %d", 0);
- state(conn, FTP_REST);
- }
- else
- result = ftp_state_prepare_transfer(conn);
- return result;
- }
- static CURLcode ftp_state_size(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->req.protop;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- if((ftp->transfer == FTPTRANSFER_INFO) && ftpc->file) {
- /* if a "head"-like request is being made (on a file) */
- /* we know ftpc->file is a valid pointer to a file name */
- PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
- state(conn, FTP_SIZE);
- }
- else
- result = ftp_state_rest(conn);
- return result;
- }
- static CURLcode ftp_state_list(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct Curl_easy *data = conn->data;
- struct FTP *ftp = data->req.protop;
- /* If this output is to be machine-parsed, the NLST command might be better
- to use, since the LIST command output is not specified or standard in any
- way. It has turned out that the NLST list output is not the same on all
- servers either... */
- /*
- if FTPFILE_NOCWD was specified, we should add the path
- as argument for the LIST / NLST / or custom command.
- Whether the server will support this, is uncertain.
- The other ftp_filemethods will CWD into dir/dir/ first and
- then just do LIST (in that case: nothing to do here)
- */
- char *lstArg = NULL;
- char *cmd;
- if((data->set.ftp_filemethod == FTPFILE_NOCWD) && ftp->path) {
- /* url-decode before evaluation: e.g. paths starting/ending with %2f */
- const char *slashPos = NULL;
- char *rawPath = NULL;
- result = Curl_urldecode(data, ftp->path, 0, &rawPath, NULL, TRUE);
- if(result)
- return result;
- slashPos = strrchr(rawPath, '/');
- if(slashPos) {
- /* chop off the file part if format is dir/file otherwise remove
- the trailing slash for dir/dir/ except for absolute path / */
- size_t n = slashPos - rawPath;
- if(n == 0)
- ++n;
- lstArg = rawPath;
- lstArg[n] = '\0';
- }
- else
- free(rawPath);
- }
- cmd = aprintf("%s%s%s",
- data->set.str[STRING_CUSTOMREQUEST]?
- data->set.str[STRING_CUSTOMREQUEST]:
- (data->set.ftp_list_only?"NLST":"LIST"),
- lstArg? " ": "",
- lstArg? lstArg: "");
- free(lstArg);
- if(!cmd)
- return CURLE_OUT_OF_MEMORY;
- result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd);
- free(cmd);
- if(result)
- return result;
- state(conn, FTP_LIST);
- return result;
- }
- static CURLcode ftp_state_retr_prequote(struct connectdata *conn)
- {
- /* We've sent the TYPE, now we must send the list of prequote strings */
- return ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
- }
- static CURLcode ftp_state_stor_prequote(struct connectdata *conn)
- {
- /* We've sent the TYPE, now we must send the list of prequote strings */
- return ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE);
- }
- static CURLcode ftp_state_type(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->req.protop;
- struct Curl_easy *data = conn->data;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- /* If we have selected NOBODY and HEADER, it means that we only want file
- information. Which in FTP can't be much more than the file size and
- date. */
- if(data->set.opt_no_body && ftpc->file &&
- ftp_need_type(conn, data->set.prefer_ascii)) {
- /* The SIZE command is _not_ RFC 959 specified, and therefore many servers
- may not support it! It is however the only way we have to get a file's
- size! */
- ftp->transfer = FTPTRANSFER_INFO;
- /* this means no actual transfer will be made */
- /* Some servers return different sizes for different modes, and thus we
- must set the proper type before we check the size */
- result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_TYPE);
- if(result)
- return result;
- }
- else
- result = ftp_state_size(conn);
- return result;
- }
- /* This is called after the CWD commands have been done in the beginning of
- the DO phase */
- static CURLcode ftp_state_mdtm(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct Curl_easy *data = conn->data;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- /* Requested time of file or time-depended transfer? */
- if((data->set.get_filetime || data->set.timecondition) && ftpc->file) {
- /* we have requested to get the modified-time of the file, this is a white
- spot as the MDTM is not mentioned in RFC959 */
- PPSENDF(&ftpc->pp, "MDTM %s", ftpc->file);
- state(conn, FTP_MDTM);
- }
- else
- result = ftp_state_type(conn);
- return result;
- }
- /* This is called after the TYPE and possible quote commands have been sent */
- static CURLcode ftp_state_ul_setup(struct connectdata *conn,
- bool sizechecked)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->req.protop;
- struct Curl_easy *data = conn->data;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- if((data->state.resume_from && !sizechecked) ||
- ((data->state.resume_from > 0) && sizechecked)) {
- /* we're about to continue the uploading of a file */
- /* 1. get already existing file's size. We use the SIZE command for this
- which may not exist in the server! The SIZE command is not in
- RFC959. */
- /* 2. This used to set REST. But since we can do append, we
- don't another ftp command. We just skip the source file
- offset and then we APPEND the rest on the file instead */
- /* 3. pass file-size number of bytes in the source file */
- /* 4. lower the infilesize counter */
- /* => transfer as usual */
- int seekerr = CURL_SEEKFUNC_OK;
- if(data->state.resume_from < 0) {
- /* Got no given size to start from, figure it out */
- PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
- state(conn, FTP_STOR_SIZE);
- return result;
- }
- /* enable append */
- data->set.ftp_append = TRUE;
- /* Let's read off the proper amount of bytes from the input. */
- if(conn->seek_func) {
- Curl_set_in_callback(data, true);
- seekerr = conn->seek_func(conn->seek_client, data->state.resume_from,
- SEEK_SET);
- Curl_set_in_callback(data, false);
- }
- if(seekerr != CURL_SEEKFUNC_OK) {
- curl_off_t passed = 0;
- if(seekerr != CURL_SEEKFUNC_CANTSEEK) {
- failf(data, "Could not seek stream");
- return CURLE_FTP_COULDNT_USE_REST;
- }
- /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */
- do {
- size_t readthisamountnow =
- (data->state.resume_from - passed > data->set.buffer_size) ?
- (size_t)data->set.buffer_size :
- curlx_sotouz(data->state.resume_from - passed);
- size_t actuallyread =
- data->state.fread_func(data->state.buffer, 1, readthisamountnow,
- data->state.in);
- passed += actuallyread;
- if((actuallyread == 0) || (actuallyread > readthisamountnow)) {
- /* this checks for greater-than only to make sure that the
- CURL_READFUNC_ABORT return code still aborts */
- failf(data, "Failed to read data");
- return CURLE_FTP_COULDNT_USE_REST;
- }
- } while(passed < data->state.resume_from);
- }
- /* now, decrease the size of the read */
- if(data->state.infilesize>0) {
- data->state.infilesize -= data->state.resume_from;
- if(data->state.infilesize <= 0) {
- infof(data, "File already completely uploaded\n");
- /* no data to transfer */
- Curl_setup_transfer(data, -1, -1, FALSE, -1);
- /* Set ->transfer so that we won't get any error in
- * ftp_done() because we didn't transfer anything! */
- ftp->transfer = FTPTRANSFER_NONE;
- state(conn, FTP_STOP);
- return CURLE_OK;
- }
- }
- /* we've passed, proceed as normal */
- } /* resume_from */
- PPSENDF(&ftpc->pp, data->set.ftp_append?"APPE %s":"STOR %s",
- ftpc->file);
- state(conn, FTP_STOR);
- return result;
- }
- static CURLcode ftp_state_quote(struct connectdata *conn,
- bool init,
- ftpstate instate)
- {
- CURLcode result = CURLE_OK;
- struct Curl_easy *data = conn->data;
- struct FTP *ftp = data->req.protop…
Large files files are truncated, but you can click here to view the full file