From: Viktor Szakats Date: Mon, 12 Jan 2026 23:51:21 +0000 (+0100) Subject: lib: reorder protocol functions to avoid forward declarations (ftp) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9570d53b756dc4bc5ba36e1e81b0d7c62f8ccbbc;p=thirdparty%2Fcurl.git lib: reorder protocol functions to avoid forward declarations (ftp) There remains 4 forward declarations. Move protocol hander table to the end of sources, rearrange static functions is reverse dependency order as possible. Closes #20276 --- diff --git a/lib/ftp.c b/lib/ftp.c index 12b8d4a637..0197d6c3de 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -87,7 +87,26 @@ #define ftp_pasv_verbose(a, b, c, d) Curl_nop_stmt #define FTP_CSTATE(c) ((void)(c), "") #else /* CURL_DISABLE_VERBOSE_STRINGS */ - /* for tracing purposes */ +/*************************************************************************** + * + * ftp_pasv_verbose() + * + * This function only outputs some informationals about this second connection + * when we have issued a PASV command before and thus we have connected to a + * possibly new IP address. + * + */ +static void ftp_pasv_verbose(struct Curl_easy *data, + struct Curl_addrinfo *ai, + char *newhost, /* ASCII version */ + int port) +{ + char buf[256]; + Curl_printable_address(ai, buf, sizeof(buf)); + infof(data, "Connecting to %s (%s) port %d", newhost, buf, port); +} + +/* for tracing purposes */ static const char * const ftp_state_names[] = { "STOP", "WAIT220", @@ -166,23 +185,6 @@ static void ftp_state_low(struct Curl_easy *data, #define ftp_state(x, y, z) ftp_state_low(x, y, z, __LINE__) #endif /* DEBUGBUILD */ -static CURLcode ftp_sendquote(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct curl_slist *quote); -static CURLcode ftp_quit(struct Curl_easy *data, struct ftp_conn *ftpc); -static CURLcode ftp_parse_url_path(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp); -static CURLcode ftp_regular_transfer(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - bool *done); -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void ftp_pasv_verbose(struct Curl_easy *data, - struct Curl_addrinfo *ai, - char *newhost, /* ASCII version */ - int port); -#endif static CURLcode ftp_state_mdtm(struct Curl_easy *data, struct ftp_conn *ftpc, struct FTP *ftp); @@ -194,118 +196,8 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, struct ftp_conn *ftpc, struct FTP *ftp, bool ascii, ftpstate newstate); -static int ftp_need_type(struct ftp_conn *ftpc, bool ascii); -static CURLcode ftp_do(struct Curl_easy *data, bool *done); -static CURLcode ftp_done(struct Curl_easy *data, - CURLcode, bool premature); -static CURLcode ftp_connect(struct Curl_easy *data, bool *done); -static CURLcode ftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection); -static CURLcode ftp_do_more(struct Curl_easy *data, int *completed); -static CURLcode ftp_multi_statemach(struct Curl_easy *data, bool *done); -static CURLcode ftp_pollset(struct Curl_easy *data, - struct easy_pollset *ps); -static CURLcode ftp_domore_pollset(struct Curl_easy *data, - struct easy_pollset *ps); -static CURLcode ftp_doing(struct Curl_easy *data, - bool *dophase_done); -static CURLcode ftp_setup_connection(struct Curl_easy *data, - struct connectdata *conn); -static CURLcode init_wc_data(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp); -static CURLcode wc_statemach(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp); -static void wc_data_dtor(void *ptr); -static CURLcode ftp_state_retr(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - curl_off_t filesize); -static CURLcode ftp_readresp(struct Curl_easy *data, - struct ftp_conn *ftpc, - int sockindex, - struct pingpong *pp, - int *ftpcode, - size_t *size); -static CURLcode ftp_dophase_done(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - bool connected); - -/* - * 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_pollset, /* proto_pollset */ - ftp_pollset, /* doing_pollset */ - ftp_domore_pollset, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_FTP, /* defport */ - CURLPROTO_FTP, /* protocol */ - CURLPROTO_FTP, /* family */ - PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | - PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | - PROTOPT_WILDCARD | PROTOPT_SSL_REUSE | - PROTOPT_CONN_REUSE /* 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_pollset, /* proto_pollset */ - ftp_pollset, /* doing_pollset */ - ftp_domore_pollset, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_FTPS, /* defport */ - CURLPROTO_FTPS, /* protocol */ - CURLPROTO_FTP, /* family */ - PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | - PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD | - PROTOPT_CONN_REUSE /* flags */ -}; -#endif - -static void close_secondarysocket(struct Curl_easy *data, - struct ftp_conn *ftpc) -{ - (void)ftpc; - CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_CSTATE(ftpc)); - Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); -} +static CURLcode getftpresponse(struct Curl_easy *data, size_t *nread, + int *ftpcode); static void freedirs(struct ftp_conn *ftpc) { @@ -315,178 +207,158 @@ static void freedirs(struct ftp_conn *ftpc) ftpc->file = NULL; } -#ifdef CURL_PREFER_LF_LINEENDS -/* - * Lineend Conversions - * On ASCII transfers, e.g. directory listings, we might get lines - * ending in '\r\n' and we prefer just '\n'. - * We might also get a lonely '\r' which we convert into a '\n'. - */ -struct ftp_cw_lc_ctx { - struct Curl_cwriter super; - bool newline_pending; -}; - -static CURLcode ftp_cw_lc_write(struct Curl_easy *data, - struct Curl_cwriter *writer, int type, - const char *buf, size_t blen) +static size_t numof_slashes(const char *str) { - static const char nl = '\n'; - struct ftp_cw_lc_ctx *ctx = writer->ctx; - struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - - if(!ftpc) - return CURLE_FAILED_INIT; - - if(!(type & CLIENTWRITE_BODY) || ftpc->transfertype != 'A') - return Curl_cwriter_write(data, writer->next, type, buf, blen); - - /* ASCII mode BODY data, convert lineends */ - while(blen) { - /* do not pass EOS when writing parts */ - int chunk_type = (type & ~CLIENTWRITE_EOS); - const char *cp; - size_t chunk_len; - CURLcode result; - - if(ctx->newline_pending) { - if(buf[0] != '\n') { - /* previous chunk ended in '\r' and we do not see a '\n' in this one, - * need to write a newline. */ - result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1); - if(result) - return result; - } - /* either we just wrote the newline or it is part of the next - * chunk of bytes we write. */ - ctx->newline_pending = FALSE; - } - - cp = memchr(buf, '\r', blen); - if(!cp) - break; - - /* write the bytes before the '\r', excluding the '\r' */ - chunk_len = cp - buf; - if(chunk_len) { - result = Curl_cwriter_write(data, writer->next, chunk_type, - buf, chunk_len); - if(result) - return result; - } - /* skip the '\r', we now have a newline pending */ - buf = cp + 1; - blen = blen - chunk_len - 1; - ctx->newline_pending = TRUE; - } - - /* Any remaining data does not contain a '\r' */ - if(blen) { - DEBUGASSERT(!ctx->newline_pending); - return Curl_cwriter_write(data, writer->next, type, buf, blen); - } - else if(type & CLIENTWRITE_EOS) { - /* EndOfStream, if we have a trailing cr, now is the time to write it */ - if(ctx->newline_pending) { - ctx->newline_pending = FALSE; - return Curl_cwriter_write(data, writer->next, type, &nl, 1); + const char *slashPos; + size_t num = 0; + do { + slashPos = strchr(str, '/'); + if(slashPos) { + ++num; + str = slashPos + 1; } - /* Always pass on the EOS type indicator */ - return Curl_cwriter_write(data, writer->next, type, buf, 0); - } - return CURLE_OK; + } while(slashPos); + return num; } -static const struct Curl_cwtype ftp_cw_lc = { - "ftp-lineconv", - NULL, - Curl_cwriter_def_init, - ftp_cw_lc_write, - Curl_cwriter_def_close, - sizeof(struct ftp_cw_lc_ctx) -}; - -#endif /* CURL_PREFER_LF_LINEENDS */ - -static CURLcode getftpresponse(struct Curl_easy *data, size_t *nread, - int *ftpcode); +#define FTP_MAX_DIR_DEPTH 1000 /*********************************************************************** * - * ftp_check_ctrl_on_data_wait() + * ftp_parse_url_path() + * + * Parse the URL path into separate path components. * */ -static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data, - struct ftp_conn *ftpc) +static CURLcode ftp_parse_url_path(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { - struct connectdata *conn = data->conn; - curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - struct pingpong *pp = &ftpc->pp; - size_t nread; - int ftpcode; - bool response = FALSE; + const char *slashPos = NULL; + const char *fileName = NULL; + CURLcode result = CURLE_OK; + const char *rawPath = NULL; /* url-decoded "raw" path */ + size_t pathLen = 0; - /* First check whether there is a cached response from server */ - if(curlx_dyn_len(&pp->recvbuf)) { - const char *l = curlx_dyn_ptr(&pp->recvbuf); - if(!ISDIGIT(*l) || (*l > '3')) { - /* Data connection could not be established, let's return */ - infof(data, "There is negative response in cache while serv connect"); - (void)getftpresponse(data, &nread, &ftpcode); - return CURLE_FTP_ACCEPT_FAILED; - } - } + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = FALSE; - if(pp->overflow) - /* there is pending control data still in the buffer to read */ - response = TRUE; - else { - int socketstate = SOCKET_READABLE(ctrl_sock, 0); - /* see if the connection request is already here */ - switch(socketstate) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_ACCEPT_FAILED; - default: - if(socketstate & CURL_CSELECT_IN) - response = TRUE; - break; - } + if(ftpc->rawpath) + freedirs(ftpc); + /* url-decode ftp path before further evaluation */ + result = Curl_urldecode(ftp->path, 0, &ftpc->rawpath, &pathLen, REJECT_CTRL); + if(result) { + failf(data, "path contains control characters"); + return result; } + rawPath = ftpc->rawpath; - if(response) { - infof(data, "Ctrl conn has data while waiting for data conn"); - if(pp->overflow > 3) { - const char *r = curlx_dyn_ptr(&pp->recvbuf); - size_t len = curlx_dyn_len(&pp->recvbuf); + switch(data->set.ftp_filemethod) { + case FTPFILE_NOCWD: /* fastest, but less standard-compliant */ - DEBUGASSERT((pp->overflow + pp->nfinal) <= curlx_dyn_len(&pp->recvbuf)); - /* move over the most recently handled response line */ - r += pp->nfinal; - len -= pp->nfinal; + if((pathLen > 0) && (rawPath[pathLen - 1] != '/')) + fileName = rawPath; /* this is a full file path */ + /* + else: ftpc->file is not used anywhere other than for operations on + a file. In other words, never for directory operations. + So we can safely leave filename as NULL here and use it as a + argument in dir/file decisions. + */ + break; - if((len > 3) && LASTLINE(r)) { - curl_off_t status; - if(!curlx_str_number(&r, &status, 999) && (status == 226)) { - /* funny timing situation where we get the final message on the - control connection before traffic on the data connection has been - noticed. Leave the 226 in there and use this as a trigger to read - the data socket. */ - infof(data, "Got 226 before data activity"); - return CURLE_OK; + case FTPFILE_SINGLECWD: + slashPos = strrchr(rawPath, '/'); + if(slashPos) { + /* get path before last slash, except for / */ + size_t dirlen = slashPos - rawPath; + if(dirlen == 0) + dirlen = 1; + + ftpc->dirs = curlx_calloc(1, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + ftpc->dirs[0].start = 0; + ftpc->dirs[0].len = (int)dirlen; + ftpc->dirdepth = 1; /* we consider it to be a single directory */ + fileName = slashPos + 1; /* rest is filename */ + } + else + fileName = rawPath; /* filename only (or empty) */ + break; + + default: /* allow pretty much anything */ + case FTPFILE_MULTICWD: { + /* current position: begin of next path component */ + const char *curPos = rawPath; + + /* number of entries to allocate for the 'dirs' array */ + size_t dirAlloc = numof_slashes(rawPath); + + if(dirAlloc >= FTP_MAX_DIR_DEPTH) + /* suspiciously deep directory hierarchy */ + return CURLE_URL_MALFORMAT; + + if(dirAlloc) { + ftpc->dirs = curlx_calloc(dirAlloc, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + /* parse the URL path into separate path components */ + while(dirAlloc--) { + const char *spos = strchr(curPos, '/'); + size_t clen = spos - curPos; + + /* path starts with a slash: add that as a directory */ + if(!clen && (ftpc->dirdepth == 0)) + ++clen; + + /* we skip empty path components, like "x//y" since the FTP command + CWD requires a parameter and a non-existent parameter a) does not + work on many servers and b) has no effect on the others. */ + if(clen) { + ftpc->dirs[ftpc->dirdepth].start = (int)(curPos - rawPath); + ftpc->dirs[ftpc->dirdepth].len = (int)clen; + ftpc->dirdepth++; } + curPos = spos + 1; } } + fileName = curPos; /* the rest is the filename (or empty) */ + } + break; + } /* switch */ - (void)getftpresponse(data, &nread, &ftpcode); + if(fileName && *fileName) + ftpc->file = fileName; + else + ftpc->file = NULL; /* instead of point to a zero byte, + we make it a NULL pointer */ - infof(data, "FTP code: %03d", ftpcode); + if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { + /* We need a filename when uploading. Return error! */ + failf(data, "Uploading to a URL without a filename"); + return CURLE_URL_MALFORMAT; + } - if(ftpcode / 100 > 3) - return CURLE_FTP_ACCEPT_FAILED; + ftpc->cwddone = FALSE; /* default to not done */ - return CURLE_WEIRD_SERVER_REPLY; + if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) + ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ + else { /* newly created FTP connections are already in entry path */ + const char *oldPath = data->conn->bits.reuse ? ftpc->prevpath : ""; + if(oldPath) { + size_t n = pathLen; + if(data->set.ftp_filemethod == FTPFILE_NOCWD) + n = 0; /* CWD to entry for relative paths */ + else + n -= ftpc->file ? strlen(ftpc->file) : 0; + + if((strlen(oldPath) == n) && rawPath && !strncmp(rawPath, oldPath, n)) { + infof(data, "Request has same path as previous transfer"); + ftpc->cwddone = TRUE; + } + } } return CURLE_OK; @@ -494,95 +366,290 @@ static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data, /*********************************************************************** * - * ftp_initiate_transfer() - * - * After connection from server is accepted this function is called to - * setup transfer parameters and initiate the data transfer. + * ftp_need_type() * + * Returns TRUE if we in the current situation should send TYPE */ -static CURLcode ftp_initiate_transfer(struct Curl_easy *data, - struct ftp_conn *ftpc) +static int ftp_need_type(struct ftp_conn *ftpc, + bool ascii_wanted) { - CURLcode result = CURLE_OK; - bool connected; - - CURL_TRC_FTP(data, "ftp_initiate_transfer()"); - result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); - if(result || !connected) - return result; - - if(data->state.upload) { - /* When we know we are 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_sndbuf_init(data->conn->sock[SECONDARYSOCKET]); - - /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely - * on the server response on the CONTROL connection. */ - Curl_xfer_setup_send(data, SECONDARYSOCKET); - Curl_xfer_set_shutdown(data, TRUE, TRUE); - } - else { - /* FTP download, shutdown, do not ignore errors */ - Curl_xfer_setup_recv(data, SECONDARYSOCKET, data->req.size); - Curl_xfer_set_shutdown(data, TRUE, FALSE); - } - - ftpc->pp.pending_resp = TRUE; /* expect server response */ - ftp_state(data, ftpc, FTP_STOP); - - return CURLE_OK; + return ftpc->transfertype != (ascii_wanted ? 'A' : 'I'); } -static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, - const char *line, size_t len, int *code) +static void close_secondarysocket(struct Curl_easy *data, + struct ftp_conn *ftpc) { - curl_off_t status; - (void)data; - (void)conn; - - if((len > 3) && LASTLINE(line) && !curlx_str_number(&line, &status, 999)) { - *code = (int)status; - return TRUE; - } - - return FALSE; + (void)ftpc; + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_CSTATE(ftpc)); + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } -static CURLcode ftp_readresp(struct Curl_easy *data, - struct ftp_conn *ftpc, - int sockindex, - struct pingpong *pp, - int *ftpcodep, /* return the ftp-code if done */ - size_t *size) /* size of the response */ -{ - int code; - CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); - DEBUGASSERT(ftpcodep); +#ifdef CURL_PREFER_LF_LINEENDS +/* + * Lineend Conversions + * On ASCII transfers, e.g. directory listings, we might get lines + * ending in '\r\n' and we prefer just '\n'. + * We might also get a lonely '\r' which we convert into a '\n'. + */ +struct ftp_cw_lc_ctx { + struct Curl_cwriter super; + bool newline_pending; +}; - /* store the latest code for later retrieval, except during shutdown */ - if(!ftpc->shutdown) - data->info.httpcode = code; +static CURLcode ftp_cw_lc_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + static const char nl = '\n'; + struct ftp_cw_lc_ctx *ctx = writer->ctx; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - *ftpcodep = code; + if(!ftpc) + return CURLE_FAILED_INIT; - if(code == 421) { - /* 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"); - ftp_state(data, ftpc, FTP_STOP); - return CURLE_OPERATION_TIMEDOUT; - } + if(!(type & CLIENTWRITE_BODY) || ftpc->transfertype != 'A') + return Curl_cwriter_write(data, writer->next, type, buf, blen); - return result; -} + /* ASCII mode BODY data, convert lineends */ + while(blen) { + /* do not pass EOS when writing parts */ + int chunk_type = (type & ~CLIENTWRITE_EOS); + const char *cp; + size_t chunk_len; + CURLcode result; + + if(ctx->newline_pending) { + if(buf[0] != '\n') { + /* previous chunk ended in '\r' and we do not see a '\n' in this one, + * need to write a newline. */ + result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1); + if(result) + return result; + } + /* either we just wrote the newline or it is part of the next + * chunk of bytes we write. */ + ctx->newline_pending = FALSE; + } + + cp = memchr(buf, '\r', blen); + if(!cp) + break; + + /* write the bytes before the '\r', excluding the '\r' */ + chunk_len = cp - buf; + if(chunk_len) { + result = Curl_cwriter_write(data, writer->next, chunk_type, + buf, chunk_len); + if(result) + return result; + } + /* skip the '\r', we now have a newline pending */ + buf = cp + 1; + blen = blen - chunk_len - 1; + ctx->newline_pending = TRUE; + } + + /* Any remaining data does not contain a '\r' */ + if(blen) { + DEBUGASSERT(!ctx->newline_pending); + return Curl_cwriter_write(data, writer->next, type, buf, blen); + } + else if(type & CLIENTWRITE_EOS) { + /* EndOfStream, if we have a trailing cr, now is the time to write it */ + if(ctx->newline_pending) { + ctx->newline_pending = FALSE; + return Curl_cwriter_write(data, writer->next, type, &nl, 1); + } + /* Always pass on the EOS type indicator */ + return Curl_cwriter_write(data, writer->next, type, buf, 0); + } + return CURLE_OK; +} + +static const struct Curl_cwtype ftp_cw_lc = { + "ftp-lineconv", + NULL, + Curl_cwriter_def_init, + ftp_cw_lc_write, + Curl_cwriter_def_close, + sizeof(struct ftp_cw_lc_ctx) +}; + +#endif /* CURL_PREFER_LF_LINEENDS */ + +/*********************************************************************** + * + * ftp_check_ctrl_on_data_wait() + * + */ +static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data, + struct ftp_conn *ftpc) +{ + struct connectdata *conn = data->conn; + curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; + struct pingpong *pp = &ftpc->pp; + size_t nread; + int ftpcode; + bool response = FALSE; + + /* First check whether there is a cached response from server */ + if(curlx_dyn_len(&pp->recvbuf)) { + const char *l = curlx_dyn_ptr(&pp->recvbuf); + if(!ISDIGIT(*l) || (*l > '3')) { + /* Data connection could not be established, let's return */ + infof(data, "There is negative response in cache while serv connect"); + (void)getftpresponse(data, &nread, &ftpcode); + return CURLE_FTP_ACCEPT_FAILED; + } + } + + if(pp->overflow) + /* there is pending control data still in the buffer to read */ + response = TRUE; + else { + int socketstate = SOCKET_READABLE(ctrl_sock, 0); + /* see if the connection request is already here */ + switch(socketstate) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + default: + if(socketstate & CURL_CSELECT_IN) + response = TRUE; + break; + } + } + + if(response) { + infof(data, "Ctrl conn has data while waiting for data conn"); + if(pp->overflow > 3) { + const char *r = curlx_dyn_ptr(&pp->recvbuf); + size_t len = curlx_dyn_len(&pp->recvbuf); + + DEBUGASSERT((pp->overflow + pp->nfinal) <= curlx_dyn_len(&pp->recvbuf)); + /* move over the most recently handled response line */ + r += pp->nfinal; + len -= pp->nfinal; + + if((len > 3) && LASTLINE(r)) { + curl_off_t status; + if(!curlx_str_number(&r, &status, 999) && (status == 226)) { + /* funny timing situation where we get the final message on the + control connection before traffic on the data connection has been + noticed. Leave the 226 in there and use this as a trigger to read + the data socket. */ + infof(data, "Got 226 before data activity"); + return CURLE_OK; + } + } + } + + (void)getftpresponse(data, &nread, &ftpcode); + + infof(data, "FTP code: %03d", ftpcode); + + if(ftpcode / 100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_WEIRD_SERVER_REPLY; + } + + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_initiate_transfer() + * + * After connection from server is accepted this function is called to + * setup transfer parameters and initiate the data transfer. + * + */ +static CURLcode ftp_initiate_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc) +{ + CURLcode result = CURLE_OK; + bool connected; + + CURL_TRC_FTP(data, "ftp_initiate_transfer()"); + result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); + if(result || !connected) + return result; + + if(data->state.upload) { + /* When we know we are 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_sndbuf_init(data->conn->sock[SECONDARYSOCKET]); + + /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely + * on the server response on the CONTROL connection. */ + Curl_xfer_setup_send(data, SECONDARYSOCKET); + Curl_xfer_set_shutdown(data, TRUE, TRUE); + } + else { + /* FTP download, shutdown, do not ignore errors */ + Curl_xfer_setup_recv(data, SECONDARYSOCKET, data->req.size); + Curl_xfer_set_shutdown(data, TRUE, FALSE); + } + + ftpc->pp.pending_resp = TRUE; /* expect server response */ + ftp_state(data, ftpc, FTP_STOP); + + return CURLE_OK; +} + +static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, + const char *line, size_t len, int *code) +{ + curl_off_t status; + (void)data; + (void)conn; + + if((len > 3) && LASTLINE(line) && !curlx_str_number(&line, &status, 999)) { + *code = (int)status; + return TRUE; + } + + return FALSE; +} + +static CURLcode ftp_readresp(struct Curl_easy *data, + struct ftp_conn *ftpc, + int sockindex, + struct pingpong *pp, + int *ftpcodep, /* return the ftp-code if done */ + size_t *size) /* size of the response */ +{ + int code; + CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); + DEBUGASSERT(ftpcodep); + + /* store the latest code for later retrieval, except during shutdown */ + if(!ftpc->shutdown) + data->info.httpcode = code; + + *ftpcodep = code; + + if(code == 421) { + /* 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"); + ftp_state(data, ftpc, FTP_STOP); + return CURLE_OPERATION_TIMEDOUT; + } + + return result; +} /* --- parse FTP server responses --- */ @@ -1598,22 +1665,105 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, return result; } -static CURLcode ftp_state_quote(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - bool init, - ftpstate instate) +static CURLcode ftp_state_retr(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + curl_off_t filesize) { CURLcode result = CURLE_OK; - bool quote = FALSE; - struct curl_slist *item; - switch(instate) { - case FTP_QUOTE: - default: - item = data->set.quote; - break; - case FTP_RETR_PREQUOTE: + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_CSTATE(ftpc)); + if(data->set.max_filesize && (filesize > data->set.max_filesize)) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + ftp->downloadsize = filesize; + + if(data->state.resume_from) { + /* We always (attempt to) get the size of downloads, so it is done before + this even when not doing resumes. */ + if(filesize == -1) { + infof(data, "ftp server does not support SIZE"); + /* We could not get the size and therefore we cannot know if there really + is a part of the file left to get, although the server will just + close the connection when we start the connection so it will not cause + us any harm, just not make us exit as nicely. */ + } + else { + /* We got a file size report, so we check that there actually is a + part of the file left to get, or else we go home. */ + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ + if(filesize < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* convert to size to download */ + ftp->downloadsize = -data->state.resume_from; + /* download from where? */ + data->state.resume_from = filesize - ftp->downloadsize; + } + else { + if(filesize < data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* Now store the number of bytes we are expected to download */ + ftp->downloadsize = filesize - data->state.resume_from; + } + } + + if(ftp->downloadsize == 0) { + /* no data to transfer */ + Curl_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + + /* Set ->transfer so that we will not get any error in ftp_done() + * because we did not transfer the any file */ + ftp->transfer = PPTRANSFER_NONE; + ftp_state(data, ftpc, FTP_STOP); + return CURLE_OK; + } + + /* Set resume file transfer offset */ + infof(data, "Instructs server to resume from offset %" FMT_OFF_T, + data->state.resume_from); + + result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, + data->state.resume_from); + if(!result) + ftp_state(data, ftpc, FTP_RETR_REST); + } + else { + /* no resume */ + result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); + if(!result) + ftp_state(data, ftpc, FTP_RETR); + } + + return result; +} + +static CURLcode ftp_state_quote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool init, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + bool quote = FALSE; + struct curl_slist *item; + + switch(instate) { + case FTP_QUOTE: + default: + item = data->set.quote; + break; + case FTP_RETR_PREQUOTE: case FTP_STOR_PREQUOTE: case FTP_LIST_PREQUOTE: item = data->set.prequote; @@ -1978,109 +2128,321 @@ error: return result; } -static CURLcode ftp_state_port_resp(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - int ftpcode) +/* called repeatedly until done from multi.c */ +static CURLcode ftp_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + bool *done) +{ + CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); + + /* Check for the state outside of the Curl_socket_check() return code checks + since at times we are in fact already in this state when this function + gets called. */ + *done = (ftpc->state == FTP_STOP); + + return result; +} + +/* + * ftp_do_more() + * + * This function shall be called when the second FTP (data) connection is + * connected. + * + * 'complete' can return 0 for incomplete, 1 for done and -1 for go back + * (which basically is only for when PASV is being sent to retry a failed + * EPSV). + */ +static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) { struct connectdata *conn = data->conn; - ftpport fcmd = (ftpport)ftpc->count1; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); CURLcode result = CURLE_OK; + bool connected = FALSE; + bool complete = FALSE; + /* the ftp struct is inited in ftp_connect(). If we are connecting to an HTTP + * proxy then the state will not be valid until after that connection is + * complete */ - /* The FTP spec tells a positive response should have code 200. - Be more permissive here to tolerate deviant servers. */ - if(ftpcode / 100 != 2) { - /* the command failed */ + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; - if(EPRT == fcmd) { - infof(data, "disabling EPRT usage"); - conn->bits.ftp_use_eprt = FALSE; - } - fcmd++; + *completep = 0; /* default to stay in the state */ - if(fcmd == DONE) { - failf(data, "Failed to do PORT"); - result = CURLE_FTP_PORT_FAILED; + /* if the second connection has been set up, try to connect it fully + * to the remote host. This may not complete at this time, for several + * reasons: + * - we do EPTR and the server will not connect to our listen socket + * until we send more FTP commands + * - an SSL filter is in place and the server will not start the TLS + * handshake until we send more FTP commands + */ + if(conn->cfilter[SECONDARYSOCKET]) { + bool is_eptr = Curl_conn_is_tcp_listen(data, SECONDARYSOCKET); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); + if(result == CURLE_OUT_OF_MEMORY) + return result; + if(result || (!connected && !is_eptr && + !Curl_conn_is_ip_connected(data, SECONDARYSOCKET))) { + if(result && !is_eptr && (ftpc->count1 == 0)) { + *completep = -1; /* go back to DOING please */ + /* this is a EPSV connect failing, try PASV instead */ + return ftp_epsv_disable(data, ftpc, conn); + } + return result; } - else - /* try next */ - result = ftp_state_use_port(data, ftpc, fcmd); - } - else { - infof(data, "Connect data stream actively"); - ftp_state(data, ftpc, FTP_STOP); /* end of DO phase */ - result = ftp_dophase_done(data, ftpc, ftp, FALSE); } - return result; -} + if(ftpc->state) { + /* already in a state so skip the initial commands. + They are only done to kickstart the do_more state */ + result = ftp_statemach(data, ftpc, &complete); -static int twodigit(const char *p) -{ - return (p[0] - '0') * 10 + (p[1] - '0'); -} + *completep = (int)complete; -static bool ftp_213_date(const char *p, int *year, int *month, int *day, - int *hour, int *minute, int *second) -{ - size_t len = strlen(p); - if(len < 14) - return FALSE; - *year = twodigit(&p[0]) * 100 + twodigit(&p[2]); - *month = twodigit(&p[4]); - *day = twodigit(&p[6]); - *hour = twodigit(&p[8]); - *minute = twodigit(&p[10]); - *second = twodigit(&p[12]); + /* if we got an error or if we do not wait for a data connection return + immediately */ + if(result || !ftpc->wait_data_conn) + return result; - if((*month > 12) || (*day > 31) || (*hour > 23) || (*minute > 59) || - (*second > 60)) - return FALSE; - return TRUE; -} + /* if we reach the end of the FTP state machine here, *complete will be + TRUE but so is ftpc->wait_data_conn, which says we need to wait for the + data connection and therefore we are not actually complete */ + *completep = 0; + } -static CURLcode client_write_header(struct Curl_easy *data, - char *buf, size_t blen) -{ - /* Some replies from an FTP server are written to the client - * as CLIENTWRITE_HEADER, formatted as if they came from a - * HTTP conversation. - * In all protocols, CLIENTWRITE_HEADER data is only passed to - * the body write callback when data->set.include_header is set - * via CURLOPT_HEADER. - * For historic reasons, FTP never played this game and expects - * all its headers to do that always. Set that flag during the - * call to Curl_client_write() so it does the right thing. - * - * Notice that we cannot enable this flag for FTP in general, - * as an FTP transfer might involve an HTTP proxy connection and - * headers from CONNECT should not automatically be part of the - * output. */ - CURLcode result; - bool save = data->set.include_header; - data->set.include_header = TRUE; - result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, blen); - data->set.include_header = save; - return result; -} + if(ftp->transfer <= PPTRANSFER_INFO) { + /* a transfer is about to take place, or if not a filename was given so we + will do a SIZE on it later and then we need the right TYPE first */ -static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - int ftpcode) -{ - CURLcode result = CURLE_OK; + if(ftpc->wait_data_conn) { + bool serv_conned; - switch(ftpcode) { - case 213: { - /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the - last .sss part is optional and means fractions of a second */ - int year, month, day, hour, minute, second; - struct pingpong *pp = &ftpc->pp; - char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4; - bool showtime = FALSE; - if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { - /* we have a time, reformat it */ + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &serv_conned); + if(result) + return result; /* Failed to accept data connection */ + + if(serv_conned) { + /* It looks data connection is established */ + ftpc->wait_data_conn = FALSE; + result = ftp_initiate_transfer(data, ftpc); + + if(result) + return result; + + *completep = 1; /* this state is now complete when the server has + connected back to us */ + } + else { + result = ftp_check_ctrl_on_data_wait(data, ftpc); + if(result) + return result; + } + } + else if(data->state.upload) { + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, + FTP_STOR_TYPE); + if(result) + return result; + + result = ftp_statemach(data, ftpc, &complete); + /* ftp_nb_type() might have skipped sending `TYPE A|I` when not + * deemed necessary and directly sent `STORE name`. If this was + * then complete, but we are still waiting on the data connection, + * the transfer has not been initiated yet. */ + *completep = (int)(ftpc->wait_data_conn ? 0 : complete); + } + else { + /* download */ + ftp->downloadsize = -1; /* unknown as of yet */ + + result = Curl_range(data); + + if(result == CURLE_OK && data->req.maxdownload >= 0) { + /* Do not check for successful transfer */ + ftpc->dont_check = TRUE; + } + + if(result) + ; + else if((data->state.list_only || !ftpc->file) && + !(data->set.prequote)) { + /* The specified path ends with a slash, and therefore we think this + is a directory that is requested, use LIST. But before that we + need to set ASCII transfer mode. */ + + /* But only if a body transfer was requested. */ + if(ftp->transfer == PPTRANSFER_BODY) { + result = ftp_nb_type(data, ftpc, ftp, TRUE, FTP_LIST_TYPE); + if(result) + return result; + } + /* otherwise just fall through */ + } + else { + if(data->set.prequote && !ftpc->file) { + result = ftp_nb_type(data, ftpc, ftp, TRUE, + FTP_RETR_LIST_TYPE); + } + else { + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, + FTP_RETR_TYPE); + } + if(result) + return result; + } + + result = ftp_statemach(data, ftpc, &complete); + *completep = (int)complete; + } + return result; + } + + /* no data to transfer */ + Curl_xfer_setup_nop(data); + + if(!ftpc->wait_data_conn) { + /* no waiting for the data connection so this is now complete */ + *completep = 1; + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_CSTATE(ftpc), + (int)result); + } + + return result; +} + +/* call this when the DO phase has completed */ +static CURLcode ftp_dophase_done(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool connected) +{ + if(connected) { + int completed; + CURLcode result = ftp_do_more(data, &completed); + + if(result) { + close_secondarysocket(data, ftpc); + return result; + } + } + + if(ftp->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_xfer_setup_nop(data); + else if(!connected) + /* since we did not connect now, we want do_more to get called */ + data->conn->bits.do_more = TRUE; + + ftpc->ctl_valid = TRUE; /* seems good */ + + return CURLE_OK; +} + +static CURLcode ftp_state_port_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + int ftpcode) +{ + struct connectdata *conn = data->conn; + ftpport fcmd = (ftpport)ftpc->count1; + CURLcode result = CURLE_OK; + + /* The FTP spec tells a positive response should have code 200. + Be more permissive here to tolerate deviant servers. */ + if(ftpcode / 100 != 2) { + /* the command failed */ + + if(EPRT == fcmd) { + infof(data, "disabling EPRT usage"); + conn->bits.ftp_use_eprt = FALSE; + } + fcmd++; + + if(fcmd == DONE) { + failf(data, "Failed to do PORT"); + result = CURLE_FTP_PORT_FAILED; + } + else + /* try next */ + result = ftp_state_use_port(data, ftpc, fcmd); + } + else { + infof(data, "Connect data stream actively"); + ftp_state(data, ftpc, FTP_STOP); /* end of DO phase */ + result = ftp_dophase_done(data, ftpc, ftp, FALSE); + } + + return result; +} + +static int twodigit(const char *p) +{ + return (p[0] - '0') * 10 + (p[1] - '0'); +} + +static bool ftp_213_date(const char *p, int *year, int *month, int *day, + int *hour, int *minute, int *second) +{ + size_t len = strlen(p); + if(len < 14) + return FALSE; + *year = twodigit(&p[0]) * 100 + twodigit(&p[2]); + *month = twodigit(&p[4]); + *day = twodigit(&p[6]); + *hour = twodigit(&p[8]); + *minute = twodigit(&p[10]); + *second = twodigit(&p[12]); + + if((*month > 12) || (*day > 31) || (*hour > 23) || (*minute > 59) || + (*second > 60)) + return FALSE; + return TRUE; +} + +static CURLcode client_write_header(struct Curl_easy *data, + char *buf, size_t blen) +{ + /* Some replies from an FTP server are written to the client + * as CLIENTWRITE_HEADER, formatted as if they came from a + * HTTP conversation. + * In all protocols, CLIENTWRITE_HEADER data is only passed to + * the body write callback when data->set.include_header is set + * via CURLOPT_HEADER. + * For historic reasons, FTP never played this game and expects + * all its headers to do that always. Set that flag during the + * call to Curl_client_write() so it does the right thing. + * + * Notice that we cannot enable this flag for FTP in general, + * as an FTP transfer might involve an HTTP proxy connection and + * headers from CONNECT should not automatically be part of the + * output. */ + CURLcode result; + bool save = data->set.include_header; + data->set.include_header = TRUE; + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, blen); + data->set.include_header = save; + return result; +} + +static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + int ftpcode) +{ + CURLcode result = CURLE_OK; + + switch(ftpcode) { + case 213: { + /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the + last .sss part is optional and means fractions of a second */ + int year, month, day, hour, minute, second; + struct pingpong *pp = &ftpc->pp; + char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4; + bool showtime = FALSE; + if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { + /* we have a time, reformat it */ char timebuf[24]; curl_msnprintf(timebuf, sizeof(timebuf), "%04d%02d%02d %02d:%02d:%02d GMT", @@ -2211,99 +2573,16 @@ static CURLcode ftp_state_type_resp(struct Curl_easy *data, return result; } -static CURLcode ftp_state_retr(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - curl_off_t filesize) +static CURLcode ftp_state_size_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + int ftpcode, + ftpstate instate) { CURLcode result = CURLE_OK; - - CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_CSTATE(ftpc)); - if(data->set.max_filesize && (filesize > data->set.max_filesize)) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - ftp->downloadsize = filesize; - - if(data->state.resume_from) { - /* We always (attempt to) get the size of downloads, so it is done before - this even when not doing resumes. */ - if(filesize == -1) { - infof(data, "ftp server does not support SIZE"); - /* We could not get the size and therefore we cannot know if there really - is a part of the file left to get, although the server will just - close the connection when we start the connection so it will not cause - us any harm, just not make us exit as nicely. */ - } - else { - /* We got a file size report, so we check that there actually is a - part of the file left to get, or else we go home. */ - if(data->state.resume_from < 0) { - /* We are supposed to download the last abs(from) bytes */ - if(filesize < -data->state.resume_from) { - failf(data, "Offset (%" FMT_OFF_T - ") was beyond file size (%" FMT_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* convert to size to download */ - ftp->downloadsize = -data->state.resume_from; - /* download from where? */ - data->state.resume_from = filesize - ftp->downloadsize; - } - else { - if(filesize < data->state.resume_from) { - failf(data, "Offset (%" FMT_OFF_T - ") was beyond file size (%" FMT_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* Now store the number of bytes we are expected to download */ - ftp->downloadsize = filesize - data->state.resume_from; - } - } - - if(ftp->downloadsize == 0) { - /* no data to transfer */ - Curl_xfer_setup_nop(data); - infof(data, "File already completely downloaded"); - - /* Set ->transfer so that we will not get any error in ftp_done() - * because we did not transfer the any file */ - ftp->transfer = PPTRANSFER_NONE; - ftp_state(data, ftpc, FTP_STOP); - return CURLE_OK; - } - - /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" FMT_OFF_T, - data->state.resume_from); - - result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, - data->state.resume_from); - if(!result) - ftp_state(data, ftpc, FTP_RETR_REST); - } - else { - /* no resume */ - result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); - if(!result) - ftp_state(data, ftpc, FTP_RETR); - } - - return result; -} - -static CURLcode ftp_state_size_resp(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - curl_off_t filesize = -1; - char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); - size_t len = ftpc->pp.nfinal; + curl_off_t filesize = -1; + char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); + size_t len = ftpc->pp.nfinal; /* get the size from the ascii string: */ if(ftpcode == 213) { @@ -3108,21 +3387,6 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, return result; } -/* called repeatedly until done from multi.c */ -static CURLcode ftp_statemach(struct Curl_easy *data, - struct ftp_conn *ftpc, - bool *done) -{ - CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); - - /* Check for the state outside of the Curl_socket_check() return code checks - since at times we are in fact already in this state when this function - gets called. */ - *done = (ftpc->state == FTP_STOP); - - return result; -} - /* called repeatedly until done from multi.c */ static CURLcode ftp_multi_statemach(struct Curl_easy *data, bool *done) @@ -3189,6 +3453,61 @@ static CURLcode ftp_connect(struct Curl_easy *data, return result; } +/*********************************************************************** + * + * ftp_sendquote() + * + * Where a 'quote' means a list of custom commands to send to the server. + * The quote list is passed as an argument. + * + * BLOCKING + */ +static CURLcode ftp_sendquote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct curl_slist *quote) +{ + struct curl_slist *item; + struct pingpong *pp = &ftpc->pp; + + item = quote; + while(item) { + if(item->data) { + size_t nread; + char *cmd = item->data; + bool acceptfail = FALSE; + CURLcode result; + int ftpcode = 0; + + /* if a command starts with an asterisk, which a legal FTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + acceptfail = TRUE; + } + + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); + if(!result) { + pp->response = *Curl_pgrs_now(data); /* timeout relative now */ + result = getftpresponse(data, &nread, &ftpcode); + } + if(result) + return result; + + if(!acceptfail && (ftpcode >= 400)) { + failf(data, "QUOT string not accepted: %s", cmd); + return CURLE_QUOTE_ERROR; + } + } + + item = item->next; + } + + return CURLE_OK; +} + /*********************************************************************** * * ftp_done() @@ -3397,73 +3716,6 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, return result; } -/*********************************************************************** - * - * ftp_sendquote() - * - * Where a 'quote' means a list of custom commands to send to the server. - * The quote list is passed as an argument. - * - * BLOCKING - */ -static CURLcode ftp_sendquote(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct curl_slist *quote) -{ - struct curl_slist *item; - struct pingpong *pp = &ftpc->pp; - - item = quote; - while(item) { - if(item->data) { - size_t nread; - char *cmd = item->data; - bool acceptfail = FALSE; - CURLcode result; - int ftpcode = 0; - - /* if a command starts with an asterisk, which a legal FTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server responds. */ - - if(cmd[0] == '*') { - cmd++; - acceptfail = TRUE; - } - - result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); - if(!result) { - pp->response = *Curl_pgrs_now(data); /* timeout relative now */ - result = getftpresponse(data, &nread, &ftpcode); - } - if(result) - return result; - - if(!acceptfail && (ftpcode >= 400)) { - failf(data, "QUOT string not accepted: %s", cmd); - return CURLE_QUOTE_ERROR; - } - } - - item = item->next; - } - - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_need_type() - * - * Returns TRUE if we in the current situation should send TYPE - */ -static int ftp_need_type(struct ftp_conn *ftpc, - bool ascii_wanted) -{ - return ftpc->transfertype != (ascii_wanted ? 'A' : 'I'); -} - /*********************************************************************** * * ftp_nb_type() @@ -3495,220 +3747,29 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, return result; } -/*************************************************************************** - * - * ftp_pasv_verbose() - * - * This function only outputs some informationals about this second connection - * when we have issued a PASV command before and thus we have connected to a - * possibly new IP address. - * - */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void -ftp_pasv_verbose(struct Curl_easy *data, - struct Curl_addrinfo *ai, - char *newhost, /* ASCII version */ - int port) -{ - char buf[256]; - Curl_printable_address(ai, buf, sizeof(buf)); - infof(data, "Connecting to %s (%s) port %d", newhost, buf, port); -} -#endif - -/* - * ftp_do_more() +/*********************************************************************** * - * This function shall be called when the second FTP (data) connection is - * connected. + * ftp_perform() * - * 'complete' can return 0 for incomplete, 1 for done and -1 for go back - * (which basically is only for when PASV is being sent to retry a failed - * EPSV). + * This is the actual DO function for FTP. Get a file/directory according to + * the options previously setup. */ -static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) +static +CURLcode ftp_perform(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool *connected, /* connect status after PASV / PORT */ + bool *dophase_done) { - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + /* this is FTP and no proxy */ CURLcode result = CURLE_OK; - bool connected = FALSE; - bool complete = FALSE; - /* the ftp struct is inited in ftp_connect(). If we are connecting to an HTTP - * proxy then the state will not be valid until after that connection is - * complete */ - if(!ftpc || !ftp) - return CURLE_FAILED_INIT; + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_CSTATE(ftpc)); - *completep = 0; /* default to stay in the state */ - - /* if the second connection has been set up, try to connect it fully - * to the remote host. This may not complete at this time, for several - * reasons: - * - we do EPTR and the server will not connect to our listen socket - * until we send more FTP commands - * - an SSL filter is in place and the server will not start the TLS - * handshake until we send more FTP commands - */ - if(conn->cfilter[SECONDARYSOCKET]) { - bool is_eptr = Curl_conn_is_tcp_listen(data, SECONDARYSOCKET); - result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); - if(result == CURLE_OUT_OF_MEMORY) - return result; - if(result || (!connected && !is_eptr && - !Curl_conn_is_ip_connected(data, SECONDARYSOCKET))) { - if(result && !is_eptr && (ftpc->count1 == 0)) { - *completep = -1; /* go back to DOING please */ - /* this is a EPSV connect failing, try PASV instead */ - return ftp_epsv_disable(data, ftpc, conn); - } - return result; - } - } - - if(ftpc->state) { - /* already in a state so skip the initial commands. - They are only done to kickstart the do_more state */ - result = ftp_statemach(data, ftpc, &complete); - - *completep = (int)complete; - - /* if we got an error or if we do not wait for a data connection return - immediately */ - if(result || !ftpc->wait_data_conn) - return result; - - /* if we reach the end of the FTP state machine here, *complete will be - TRUE but so is ftpc->wait_data_conn, which says we need to wait for the - data connection and therefore we are not actually complete */ - *completep = 0; - } - - if(ftp->transfer <= PPTRANSFER_INFO) { - /* a transfer is about to take place, or if not a filename was given so we - will do a SIZE on it later and then we need the right TYPE first */ - - if(ftpc->wait_data_conn) { - bool serv_conned; - - result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &serv_conned); - if(result) - return result; /* Failed to accept data connection */ - - if(serv_conned) { - /* It looks data connection is established */ - ftpc->wait_data_conn = FALSE; - result = ftp_initiate_transfer(data, ftpc); - - if(result) - return result; - - *completep = 1; /* this state is now complete when the server has - connected back to us */ - } - else { - result = ftp_check_ctrl_on_data_wait(data, ftpc); - if(result) - return result; - } - } - else if(data->state.upload) { - result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, - FTP_STOR_TYPE); - if(result) - return result; - - result = ftp_statemach(data, ftpc, &complete); - /* ftp_nb_type() might have skipped sending `TYPE A|I` when not - * deemed necessary and directly sent `STORE name`. If this was - * then complete, but we are still waiting on the data connection, - * the transfer has not been initiated yet. */ - *completep = (int)(ftpc->wait_data_conn ? 0 : complete); - } - else { - /* download */ - ftp->downloadsize = -1; /* unknown as of yet */ - - result = Curl_range(data); - - if(result == CURLE_OK && data->req.maxdownload >= 0) { - /* Do not check for successful transfer */ - ftpc->dont_check = TRUE; - } - - if(result) - ; - else if((data->state.list_only || !ftpc->file) && - !(data->set.prequote)) { - /* The specified path ends with a slash, and therefore we think this - is a directory that is requested, use LIST. But before that we - need to set ASCII transfer mode. */ - - /* But only if a body transfer was requested. */ - if(ftp->transfer == PPTRANSFER_BODY) { - result = ftp_nb_type(data, ftpc, ftp, TRUE, FTP_LIST_TYPE); - if(result) - return result; - } - /* otherwise just fall through */ - } - else { - if(data->set.prequote && !ftpc->file) { - result = ftp_nb_type(data, ftpc, ftp, TRUE, - FTP_RETR_LIST_TYPE); - } - else { - result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, - FTP_RETR_TYPE); - } - if(result) - return result; - } - - result = ftp_statemach(data, ftpc, &complete); - *completep = (int)complete; - } - return result; - } - - /* no data to transfer */ - Curl_xfer_setup_nop(data); - - if(!ftpc->wait_data_conn) { - /* no waiting for the data connection so this is now complete */ - *completep = 1; - CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_CSTATE(ftpc), - (int)result); - } - - return result; -} - -/*********************************************************************** - * - * ftp_perform() - * - * This is the actual DO function for FTP. Get a file/directory according to - * the options previously setup. - */ -static -CURLcode ftp_perform(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - bool *connected, /* connect status after PASV / PORT */ - bool *dophase_done) -{ - /* this is FTP and no proxy */ - CURLcode result = CURLE_OK; - - CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_CSTATE(ftpc)); - - if(data->req.no_body) { - /* requested no body means no transfer... */ - ftp->transfer = PPTRANSFER_INFO; - } + if(data->req.no_body) { + /* requested no body means no transfer... */ + ftp->transfer = PPTRANSFER_INFO; + } *dophase_done = FALSE; /* not done yet */ @@ -3931,373 +3992,41 @@ static CURLcode wc_statemach(struct Curl_easy *data, return CURLE_OK; } return result; - } - - case CURLWC_SKIP: { - if(data->set.chunk_end) { - Curl_set_in_callback(data, TRUE); - data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, FALSE); - } - Curl_node_remove(Curl_llist_head(&wildcard->filelist)); - wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? - CURLWC_CLEAN : CURLWC_DOWNLOADING; - continue; - } - - case CURLWC_CLEAN: { - struct ftp_wc *ftpwc = wildcard->ftpwc; - result = CURLE_OK; - if(ftpwc) - result = Curl_ftp_parselist_geterror(ftpwc->parser); - - wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; - return result; - } - - case CURLWC_DONE: - case CURLWC_ERROR: - case CURLWC_CLEAR: - if(wildcard->dtor) { - wildcard->dtor(wildcard->ftpwc); - wildcard->ftpwc = NULL; - } - return result; - } - } - /* UNREACHABLE */ -} - -/*********************************************************************** - * - * ftp_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (ftp_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode ftp_do(struct Curl_easy *data, bool *done) -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); - - *done = FALSE; /* default to false */ - if(!ftpc || !ftp) - return CURLE_FAILED_INIT; - ftpc->wait_data_conn = FALSE; /* default to no such wait */ - -#ifdef CURL_PREFER_LF_LINEENDS - { - /* FTP data may need conversion. */ - struct Curl_cwriter *ftp_lc_writer; - - result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc, - CURL_CW_CONTENT_DECODE); - if(result) - return result; - - result = Curl_cwriter_add(data, ftp_lc_writer); - if(result) { - Curl_cwriter_free(data, ftp_lc_writer); - return result; - } - } -#endif /* CURL_PREFER_LF_LINEENDS */ - - if(data->state.wildcardmatch) { - result = wc_statemach(data, ftpc, ftp); - if(data->wildcard->state == CURLWC_SKIP || - data->wildcard->state == CURLWC_DONE) { - /* do not call ftp_regular_transfer */ - return CURLE_OK; - } - if(result) /* error, loop or skipping the file */ - return result; - } - else { /* no wildcard FSM needed */ - result = ftp_parse_url_path(data, ftpc, ftp); - if(result) - return result; - } - - result = ftp_regular_transfer(data, ftpc, ftp, done); - - return result; -} - -/*********************************************************************** - * - * ftp_quit() - * - * This should be called before calling sclose() on an ftp control connection - * (not data connections). We should then wait for the response from the - * server before returning. The calling code should then try to close the - * connection. - * - */ -static CURLcode ftp_quit(struct Curl_easy *data, - struct ftp_conn *ftpc) -{ - CURLcode result = CURLE_OK; - - if(ftpc->ctl_valid) { - CURL_TRC_FTP(data, "sending QUIT to close session"); - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "QUIT"); - if(result) { - failf(data, "Failure sending QUIT command: %s", - curl_easy_strerror(result)); - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(data->conn, "QUIT command failed"); /* mark for closure */ - ftp_state(data, ftpc, FTP_STOP); - return result; - } - - ftp_state(data, ftpc, FTP_QUIT); - - result = ftp_block_statemach(data, ftpc); - } - - return result; -} - -/*********************************************************************** - * - * ftp_disconnect() - * - * Disconnect from an FTP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode ftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); - - if(!ftpc) - return CURLE_FAILED_INIT; - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. - - ftp_quit() will check the state of ftp->ctl_valid. If it is ok it - will try to send the QUIT command, otherwise it will just return. - */ - ftpc->shutdown = TRUE; - if(dead_connection || Curl_pp_needs_flush(data, &ftpc->pp)) - ftpc->ctl_valid = FALSE; - - /* The FTP session may or may not have been allocated/setup at this point! */ - (void)ftp_quit(data, ftpc); /* ignore errors on the QUIT */ - return CURLE_OK; -} - -static size_t numof_slashes(const char *str) -{ - const char *slashPos; - size_t num = 0; - do { - slashPos = strchr(str, '/'); - if(slashPos) { - ++num; - str = slashPos + 1; - } - } while(slashPos); - return num; -} - -#define FTP_MAX_DIR_DEPTH 1000 - -/*********************************************************************** - * - * ftp_parse_url_path() - * - * Parse the URL path into separate path components. - * - */ -static CURLcode ftp_parse_url_path(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp) -{ - const char *slashPos = NULL; - const char *fileName = NULL; - CURLcode result = CURLE_OK; - const char *rawPath = NULL; /* url-decoded "raw" path */ - size_t pathLen = 0; - - ftpc->ctl_valid = FALSE; - ftpc->cwdfail = FALSE; - - if(ftpc->rawpath) - freedirs(ftpc); - /* url-decode ftp path before further evaluation */ - result = Curl_urldecode(ftp->path, 0, &ftpc->rawpath, &pathLen, REJECT_CTRL); - if(result) { - failf(data, "path contains control characters"); - return result; - } - rawPath = ftpc->rawpath; - - switch(data->set.ftp_filemethod) { - case FTPFILE_NOCWD: /* fastest, but less standard-compliant */ - - if((pathLen > 0) && (rawPath[pathLen - 1] != '/')) - fileName = rawPath; /* this is a full file path */ - /* - else: ftpc->file is not used anywhere other than for operations on - a file. In other words, never for directory operations. - So we can safely leave filename as NULL here and use it as a - argument in dir/file decisions. - */ - break; - - case FTPFILE_SINGLECWD: - slashPos = strrchr(rawPath, '/'); - if(slashPos) { - /* get path before last slash, except for / */ - size_t dirlen = slashPos - rawPath; - if(dirlen == 0) - dirlen = 1; - - ftpc->dirs = curlx_calloc(1, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; - - ftpc->dirs[0].start = 0; - ftpc->dirs[0].len = (int)dirlen; - ftpc->dirdepth = 1; /* we consider it to be a single directory */ - fileName = slashPos + 1; /* rest is filename */ - } - else - fileName = rawPath; /* filename only (or empty) */ - break; - - default: /* allow pretty much anything */ - case FTPFILE_MULTICWD: { - /* current position: begin of next path component */ - const char *curPos = rawPath; - - /* number of entries to allocate for the 'dirs' array */ - size_t dirAlloc = numof_slashes(rawPath); - - if(dirAlloc >= FTP_MAX_DIR_DEPTH) - /* suspiciously deep directory hierarchy */ - return CURLE_URL_MALFORMAT; - - if(dirAlloc) { - ftpc->dirs = curlx_calloc(dirAlloc, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; - - /* parse the URL path into separate path components */ - while(dirAlloc--) { - const char *spos = strchr(curPos, '/'); - size_t clen = spos - curPos; - - /* path starts with a slash: add that as a directory */ - if(!clen && (ftpc->dirdepth == 0)) - ++clen; - - /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) does not - work on many servers and b) has no effect on the others. */ - if(clen) { - ftpc->dirs[ftpc->dirdepth].start = (int)(curPos - rawPath); - ftpc->dirs[ftpc->dirdepth].len = (int)clen; - ftpc->dirdepth++; - } - curPos = spos + 1; - } - } - fileName = curPos; /* the rest is the filename (or empty) */ - } - break; - } /* switch */ - - if(fileName && *fileName) - ftpc->file = fileName; - else - ftpc->file = NULL; /* instead of point to a zero byte, - we make it a NULL pointer */ - - if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { - /* We need a filename when uploading. Return error! */ - failf(data, "Uploading to a URL without a filename"); - return CURLE_URL_MALFORMAT; - } - - ftpc->cwddone = FALSE; /* default to not done */ - - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) - ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ - else { /* newly created FTP connections are already in entry path */ - const char *oldPath = data->conn->bits.reuse ? ftpc->prevpath : ""; - if(oldPath) { - size_t n = pathLen; - if(data->set.ftp_filemethod == FTPFILE_NOCWD) - n = 0; /* CWD to entry for relative paths */ - else - n -= ftpc->file ? strlen(ftpc->file) : 0; - - if((strlen(oldPath) == n) && rawPath && !strncmp(rawPath, oldPath, n)) { - infof(data, "Request has same path as previous transfer"); - ftpc->cwddone = TRUE; - } - } - } - - return CURLE_OK; -} - -/* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct Curl_easy *data, - struct ftp_conn *ftpc, - struct FTP *ftp, - bool connected) -{ - if(connected) { - int completed; - CURLcode result = ftp_do_more(data, &completed); - - if(result) { - close_secondarysocket(data, ftpc); - return result; - } - } - - if(ftp->transfer != PPTRANSFER_BODY) - /* no data to transfer */ - Curl_xfer_setup_nop(data); - else if(!connected) - /* since we did not connect now, we want do_more to get called */ - data->conn->bits.do_more = TRUE; - - ftpc->ctl_valid = TRUE; /* seems good */ - - return CURLE_OK; -} + } -/* called from multi.c while DOing */ -static CURLcode ftp_doing(struct Curl_easy *data, - bool *dophase_done) -{ - struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); - CURLcode result; + case CURLWC_SKIP: { + if(data->set.chunk_end) { + Curl_set_in_callback(data, TRUE); + data->set.chunk_end(data->set.wildcardptr); + Curl_set_in_callback(data, FALSE); + } + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); + wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? + CURLWC_CLEAN : CURLWC_DOWNLOADING; + continue; + } - if(!ftpc || !ftp) - return CURLE_FAILED_INIT; - result = ftp_statemach(data, ftpc, dophase_done); + case CURLWC_CLEAN: { + struct ftp_wc *ftpwc = wildcard->ftpwc; + result = CURLE_OK; + if(ftpwc) + result = Curl_ftp_parselist_geterror(ftpwc->parser); - if(result) - CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_CSTATE(ftpc)); - else if(*dophase_done) { - result = ftp_dophase_done(data, ftpc, ftp, FALSE /* not connected */); + wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; + return result; + } - CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_CSTATE(ftpc)); + case CURLWC_DONE: + case CURLWC_ERROR: + case CURLWC_CLEAR: + if(wildcard->dtor) { + wildcard->dtor(wildcard->ftpwc); + wildcard->ftpwc = NULL; + } + return result; + } } - return result; + /* UNREACHABLE */ } /*********************************************************************** @@ -4346,6 +4075,153 @@ static CURLcode ftp_regular_transfer(struct Curl_easy *data, return result; } +/*********************************************************************** + * + * ftp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (ftp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode ftp_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + + *done = FALSE; /* default to false */ + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + ftpc->wait_data_conn = FALSE; /* default to no such wait */ + +#ifdef CURL_PREFER_LF_LINEENDS + { + /* FTP data may need conversion. */ + struct Curl_cwriter *ftp_lc_writer; + + result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ftp_lc_writer); + if(result) { + Curl_cwriter_free(data, ftp_lc_writer); + return result; + } + } +#endif /* CURL_PREFER_LF_LINEENDS */ + + if(data->state.wildcardmatch) { + result = wc_statemach(data, ftpc, ftp); + if(data->wildcard->state == CURLWC_SKIP || + data->wildcard->state == CURLWC_DONE) { + /* do not call ftp_regular_transfer */ + return CURLE_OK; + } + if(result) /* error, loop or skipping the file */ + return result; + } + else { /* no wildcard FSM needed */ + result = ftp_parse_url_path(data, ftpc, ftp); + if(result) + return result; + } + + result = ftp_regular_transfer(data, ftpc, ftp, done); + + return result; +} + +/*********************************************************************** + * + * ftp_quit() + * + * This should be called before calling sclose() on an ftp control connection + * (not data connections). We should then wait for the response from the + * server before returning. The calling code should then try to close the + * connection. + * + */ +static CURLcode ftp_quit(struct Curl_easy *data, + struct ftp_conn *ftpc) +{ + CURLcode result = CURLE_OK; + + if(ftpc->ctl_valid) { + CURL_TRC_FTP(data, "sending QUIT to close session"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "QUIT"); + if(result) { + failf(data, "Failure sending QUIT command: %s", + curl_easy_strerror(result)); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + connclose(data->conn, "QUIT command failed"); /* mark for closure */ + ftp_state(data, ftpc, FTP_STOP); + return result; + } + + ftp_state(data, ftpc, FTP_QUIT); + + result = ftp_block_statemach(data, ftpc); + } + + return result; +} + +/*********************************************************************** + * + * ftp_disconnect() + * + * Disconnect from an FTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode ftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + + if(!ftpc) + return CURLE_FAILED_INIT; + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. + + ftp_quit() will check the state of ftp->ctl_valid. If it is ok it + will try to send the QUIT command, otherwise it will just return. + */ + ftpc->shutdown = TRUE; + if(dead_connection || Curl_pp_needs_flush(data, &ftpc->pp)) + ftpc->ctl_valid = FALSE; + + /* The FTP session may or may not have been allocated/setup at this point! */ + (void)ftp_quit(data, ftpc); /* ignore errors on the QUIT */ + return CURLE_OK; +} + +/* called from multi.c while DOing */ +static CURLcode ftp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + CURLcode result; + + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + result = ftp_statemach(data, ftpc, dophase_done); + + if(result) + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_CSTATE(ftpc)); + else if(*dophase_done) { + result = ftp_dophase_done(data, ftpc, ftp, FALSE /* not connected */); + + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_CSTATE(ftpc)); + } + return result; +} + static void ftp_easy_dtor(void *key, size_t klen, void *entry) { struct FTP *ftp = entry; @@ -4464,4 +4340,67 @@ bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn) return TRUE; } +/* + * 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_pollset, /* proto_pollset */ + ftp_pollset, /* doing_pollset */ + ftp_domore_pollset, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_FTP, /* defport */ + CURLPROTO_FTP, /* protocol */ + CURLPROTO_FTP, /* family */ + PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | + PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | + PROTOPT_WILDCARD | PROTOPT_SSL_REUSE | + PROTOPT_CONN_REUSE /* 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_pollset, /* proto_pollset */ + ftp_pollset, /* doing_pollset */ + ftp_domore_pollset, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_FTPS, /* defport */ + CURLPROTO_FTPS, /* protocol */ + CURLPROTO_FTP, /* family */ + PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | + PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD | + PROTOPT_CONN_REUSE /* flags */ +}; +#endif + #endif /* CURL_DISABLE_FTP */