lib: reorder protocol functions to avoid forward declarations (ftp)
No binary (build failed)
—
(log yok)
| @@ -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 */ | |