/curl-7.28.0/lib/ftp.c
C | 4565 lines | 3081 code | 608 blank | 876 comment | 747 complexity | cd2ee02b93a9107036548cd61773af15 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2012, 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 http://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 "setup.h"
- #ifndef CURL_DISABLE_FTP
- #ifdef HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #ifdef HAVE_SYS_SOCKET_H
- #include <sys/socket.h>
- #endif
- #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 "socks.h"
- #include "ftp.h"
- #include "fileinfo.h"
- #include "ftplistparser.h"
- #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
- #include "krb4.h"
- #endif
- #include "strtoofft.h"
- #include "strequal.h"
- #include "sslgen.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 "rawstr.h"
- #include "speedcheck.h"
- #include "warnless.h"
- #include "http_proxy.h"
- #include "non-ascii.h"
- #define _MPRINTF_REPLACE /* use our functions only */
- #include <curl/mprintf.h>
- #include "curl_memory.h"
- /* The last #include file should be: */
- #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 */
- static void state(struct connectdata *conn,
- ftpstate newstate);
- 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_post_rest(struct connectdata *conn);
- static CURLcode ftp_state_post_cwd(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, bool *completed);
- static CURLcode ftp_multi_statemach(struct connectdata *conn, bool *done);
- static int ftp_getsock(struct connectdata *conn, curl_socket_t *socks,
- int numsocks);
- static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks,
- int numsocks);
- 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_post_retr_size(struct connectdata *conn,
- curl_off_t filesize);
- static CURLcode ftp_readresp(curl_socket_t sockfd,
- struct pingpong *pp,
- int *ftpcode,
- size_t *size);
- /* easy-to-use macro: */
- #define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \
- return result
- #define PPSENDF(x,y,z) if((result = Curl_pp_sendf(x,y,z)) != CURLE_OK) \
- 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 */
- PORT_FTP, /* defport */
- CURLPROTO_FTP, /* protocol */
- PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD
- | PROTOPT_NOURLQUERY /* 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 */
- PORT_FTPS, /* defport */
- CURLPROTO_FTP | CURLPROTO_FTPS, /* protocol */
- PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION |
- PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY /* flags */
- };
- #endif
- #ifndef CURL_DISABLE_HTTP
- /*
- * HTTP-proxyed FTP protocol handler.
- */
- static const struct Curl_handler Curl_handler_ftp_proxy = {
- "FTP", /* scheme */
- ZERO_NULL, /* setup_connection */
- Curl_http, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- ZERO_NULL, /* proto_getsock */
- ZERO_NULL, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- ZERO_NULL, /* perform_getsock */
- ZERO_NULL, /* disconnect */
- ZERO_NULL, /* readwrite */
- PORT_FTP, /* defport */
- CURLPROTO_HTTP, /* protocol */
- PROTOPT_NONE /* flags */
- };
- #ifdef USE_SSL
- /*
- * HTTP-proxyed FTPS protocol handler.
- */
- static const struct Curl_handler Curl_handler_ftps_proxy = {
- "FTPS", /* scheme */
- ZERO_NULL, /* setup_connection */
- Curl_http, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- ZERO_NULL, /* proto_getsock */
- ZERO_NULL, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- ZERO_NULL, /* perform_getsock */
- ZERO_NULL, /* disconnect */
- ZERO_NULL, /* readwrite */
- PORT_FTPS, /* defport */
- CURLPROTO_HTTP, /* protocol */
- PROTOPT_NONE /* flags */
- };
- #endif
- #endif
- /*
- * 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)
- {
- int i;
- if(ftpc->dirs) {
- for(i=0; i < ftpc->dirdepth; i++) {
- if(ftpc->dirs[i]) {
- free(ftpc->dirs[i]);
- ftpc->dirs[i]=NULL;
- }
- }
- free(ftpc->dirs);
- ftpc->dirs = NULL;
- ftpc->dirdepth = 0;
- }
- if(ftpc->file) {
- free(ftpc->file);
- ftpc->file = NULL;
- }
- }
- /* Returns non-zero if the given string contains CR (\r) or LF (\n),
- which are not allowed within RFC 959 <string>.
- Note: The input string is in the client's encoding which might
- not be ASCII, so escape sequences \r & \n must be used instead
- of hex values 0x0d & 0x0a.
- */
- static bool isBadFtpString(const char *string)
- {
- return ((NULL != strchr(string, '\r')) ||
- (NULL != strchr(string, '\n'))) ? TRUE : FALSE;
- }
- /***********************************************************************
- *
- * 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 SessionHandle *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");
- conn->sock[SECONDARYSOCKET] = s;
- curlx_nonblock(s, TRUE); /* enable non-blocking */
- conn->sock_accepted[SECONDARYSOCKET] = TRUE;
- if(data->set.fsockopt) {
- int error = 0;
- /* activate callback for setting socket options */
- error = data->set.fsockopt(data->set.sockopt_client,
- s,
- CURLSOCKTYPE_ACCEPT);
- if(error) {
- Curl_closesocket(conn, s); /* close the socket and bail out */
- conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
- 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 long ftp_timeleft_accept(struct SessionHandle *data)
- {
- long timeout_ms = DEFAULT_ACCEPT_TIMEOUT;
- long other;
- struct timeval now;
- if(data->set.accepttimeout > 0)
- timeout_ms = data->set.accepttimeout;
- now = Curl_tvnow();
- /* 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_tvdiff(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 SessionHandle *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;
- long 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_FTP_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 SessionHandle *data = conn->data;
- struct FTP *ftp = data->state.proto.ftp;
- CURLcode result = CURLE_OK;
- if(conn->ssl[SECONDARYSOCKET].use) {
- /* 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) {
- *(ftp->bytecountp)=0;
- /* When we know we're uploading a specified file, we can get the file
- size prior to the actual upload. */
- Curl_pgrsSetUploadSize(data, data->set.infilesize);
- /* set the SO_SNDBUF for the secondary socket for those who need it */
- Curl_sndbufset(conn->sock[SECONDARYSOCKET]);
- Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
- SECONDARYSOCKET, ftp->bytecountp);
- }
- else {
- /* FTP download: */
- Curl_setup_transfer(conn, SECONDARYSOCKET,
- conn->proto.ftpc.retr_size_saved, FALSE,
- ftp->bytecountp, -1, NULL); /* no upload here */
- }
- 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
- * - will sit and wait here until the server has connected for easy interface
- * - will check whether data connection is established if so it is accepted
- * for multi interface
- *
- */
- static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected)
- {
- struct SessionHandle *data = conn->data;
- long timeout_ms;
- long interval_ms;
- CURLcode ret = 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);
- for(;;) {
- 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 */
- ret = ReceivedServerConnect(conn, connected);
- if(ret)
- return ret;
- if(*connected) {
- ret = AcceptServerConnect(conn);
- if(ret)
- return ret;
- ret = InitiateTransfer(conn);
- if(ret)
- return ret;
- break; /* connection is accepted, break the loop */
- }
- else {
- if(data->state.used_interface == Curl_if_easy) {
- interval_ms = 1000;
- if(timeout_ms < interval_ms)
- interval_ms = timeout_ms;
- /* sleep for 1 second and then continue */
- Curl_socket_ready(CURL_SOCKET_BAD, CURL_SOCKET_BAD, interval_ms);
- }
- else {
- /* Add timeout to multi handle and break out of the loop */
- if(ret == CURLE_OK && *connected == FALSE) {
- if(data->set.accepttimeout > 0)
- Curl_expire(data, data->set.accepttimeout);
- else
- Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT);
- }
- break; /* connection was not accepted immediately */
- }
- }
- }
- return ret;
- }
- /* 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 int ftp_endofresp(struct pingpong *pp,
- int *code)
- {
- char *line = pp->linestart_resp;
- size_t len = pp->nread_resp;
- if((len > 3) && LASTLINE(line)) {
- *code = curlx_sltosi(strtol(line, NULL, 10));
- return 1;
- }
- return 0;
- }
- 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;
- #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
- struct SessionHandle *data = conn->data;
- char * const buf = data->state.buffer;
- #endif
- CURLcode result = CURLE_OK;
- int code;
- result = Curl_pp_readresp(sockfd, pp, &code, size);
- #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
- /* handle the security-oriented responses 6xx ***/
- /* FIXME: some errorchecking perhaps... ***/
- 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 */
- conn->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. */
- 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];
- long timeout; /* timeout in milliseconds */
- long interval_ms;
- struct SessionHandle *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 */
- timeout = Curl_pp_state_timeout(pp);
- 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 {
- switch (Curl_socket_ready(sockfd, CURL_SOCKET_BAD, 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;
- }
- /* This is the ONLY way to change FTP state! */
- static void state(struct connectdata *conn,
- ftpstate newstate)
- {
- #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
- /* for debug purposes */
- static const char * const 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
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
- if(ftpc->state != newstate)
- infof(conn->data, "FTP %p state change from %s to %s\n",
- ftpc, names[ftpc->state], names[newstate]);
- #endif
- ftpc->state = newstate;
- }
- static CURLcode ftp_state_user(struct connectdata *conn)
- {
- CURLcode result;
- struct FTP *ftp = conn->data->state.proto.ftp;
- /* send USER */
- PPSENDF(&conn->proto.ftpc.pp, "USER %s", ftp->user?ftp->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, "PWD", NULL);
- 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,
- int numsocks)
- {
- return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks);
- }
- /* For the FTP "DO_MORE" phase only */
- static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks,
- int numsocks)
- {
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- if(!numsocks)
- return GETSOCK_BLANK;
- /* 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.
- When waiting for a connect, we will be in FTP_STOP state and then we wait
- for the secondary socket to become writeable. If we're in another state,
- we're still handling commands on the control (primary) connection.
- */
- switch(ftpc->state) {
- case FTP_STOP:
- break;
- default:
- return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks);
- }
- socks[0] = conn->sock[SECONDARYSOCKET];
- if(ftpc->wait_data_conn) {
- socks[1] = conn->sock[FIRSTSOCKET];
- return GETSOCK_READSOCK(0) | GETSOCK_READSOCK(1);
- }
- return GETSOCK_READSOCK(0);
- }
- /* 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_post_cwd(conn);
- else {
- 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) {
- /* 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->count1 = 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->count1 = 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->count1 -1]);
- state(conn, FTP_CWD);
- }
- else {
- /* No CWD necessary */
- result = ftp_state_post_cwd(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 SessionHandle *data=conn->data;
- curl_socket_t portsock= CURL_SOCKET_BAD;
- char myhost[256] = "";
- 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
- char tmp[1024];
- static const char mode[][5] = { "EPRT", "PORT" };
- int 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 *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;
- if((ip_end = strchr(string_ftpport, ']')) != NULL )
- strncpy(addr, ip_start, ip_end - ip_start);
- }
- else
- #endif
- if(*string_ftpport == ':') {
- /* :port */
- ip_end = string_ftpport;
- }
- else if((ip_end = strchr(string_ftpport, ':')) != NULL) {
- /* 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) {
- if((port_start = strchr(ip_end, ':')) != NULL) {
- port_min = curlx_ultous(strtoul(port_start+1, NULL, 10));
- if((port_sep = strchr(port_start, '-')) != NULL) {
- 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 */
- if(!Curl_if2ip(conn->ip_addr->ai_family, addr,
- hbuf, sizeof(hbuf)))
- /* not an interface, use the given string as host name instead */
- host = addr;
- else
- 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(conn, SOCKERRNO) );
- Curl_safefree(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, &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);
- Curl_safefree(addr);
- return CURLE_FTP_PORT_FAILED;
- }
- Curl_safefree(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(conn, error));
- 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(conn, error) );
- sslen = sizeof(ss);
- if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) {
- failf(data, "getsockname() failed: %s",
- Curl_strerror(conn, SOCKERRNO) );
- Curl_closesocket(conn, portsock);
- return CURLE_FTP_PORT_FAILED;
- }
- port = port_min;
- possibly_non_local = FALSE; /* don't try this again */
- continue;
- }
- else if(error != EADDRINUSE && error != EACCES) {
- failf(data, "bind(port=%hu) failed: %s", port,
- Curl_strerror(conn, error) );
- 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(conn, SOCKERRNO) );
- 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(conn, SOCKERRNO));
- 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;
- }
- else if(PORT == fcmd) {
- char *source = myhost;
- char *dest = tmp;
- /* translate x.x.x.x to x,x,x,x */
- while(source && *source) {
- if(*source == '.')
- *dest=',';
- else
- *dest = *source;
- dest++;
- source++;
- }
- *dest = 0;
- snprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff));
- result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], tmp);
- 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;
- /* 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 */
- if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET])
- Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]);
- 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;
- }
- /* 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_post_rest(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->state.proto.ftp;
- struct SessionHandle *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_post_size(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->state.proto.ftp;
- 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_post_rest(conn);
- return result;
- }
- static CURLcode ftp_state_post_type(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->state.proto.ftp;
- 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_post_size(conn);
- return result;
- }
- static CURLcode ftp_state_post_listtype(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct SessionHandle *data = conn->data;
- /* 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 are currently in
- the user's home directory, so 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 *cmd,*lstArg,*slashPos;
- lstArg = NULL;
- if((data->set.ftp_filemethod == FTPFILE_NOCWD) &&
- data->state.path &&
- data->state.path[0] &&
- strchr(data->state.path,'/')) {
- lstArg = strdup(data->state.path);
- if(!lstArg)
- return CURLE_OUT_OF_MEMORY;
- /* Check if path does not end with /, as then we cut off the file part */
- if(lstArg[strlen(lstArg) - 1] != '/') {
- /* chop off the file part if format is dir/dir/file */
- slashPos = strrchr(lstArg,'/');
- if(slashPos)
- *(slashPos+1) = '\0';
- }
- }
- 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: "" );
- if(!cmd) {
- if(lstArg)
- free(lstArg);
- return CURLE_OUT_OF_MEMORY;
- }
- result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd);
- if(lstArg)
- free(lstArg);
- free(cmd);
- if(result != CURLE_OK)
- return result;
- state(conn, FTP_LIST);
- return result;
- }
- static CURLcode ftp_state_post_retrtype(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- /* We've sent the TYPE, now we must send the list of prequote strings */
- result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE);
- return result;
- }
- static CURLcode ftp_state_post_stortype(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- /* We've sent the TYPE, now we must send the list of prequote strings */
- result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE);
- return result;
- }
- static CURLcode ftp_state_post_mdtm(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct FTP *ftp = conn->data->state.proto.ftp;
- struct SessionHandle *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 therefor 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_post_type(conn);
- return result;
- }
- /* This is called after the CWD commands have been done in the beginning of
- the DO phase */
- static CURLcode ftp_state_post_cwd(struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct SessionHandle *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_post_mdtm(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->state.proto.ftp;
- struct SessionHandle *data = conn->data;
- struct ftp_conn *ftpc = &conn->proto.ftpc;
- int seekerr = CURL_SEEKFUNC_OK;
- 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 */
- 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) {
- seekerr = conn->seek_func(conn->seek_client, data->state.resume_from,
- SEEK_SET);
- }
- if(seekerr != CURL_SEEKFUNC_OK) {
- 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) */
- else {
- curl_off_t passed=0;
- do {
- …
Large files files are truncated, but you can click here to view the full file