#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",
#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);
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)
{
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;
/***********************************************************************
*
- * 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 --- */
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;
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",
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) {
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)
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()
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()
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 */
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 */
}
/***********************************************************************
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;
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 */