]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: reorder protocol functions to avoid forward declarations (ftp)
authorViktor Szakats <commit@vsz.me>
Mon, 12 Jan 2026 23:51:21 +0000 (00:51 +0100)
committerViktor Szakats <commit@vsz.me>
Tue, 13 Jan 2026 00:31:27 +0000 (01:31 +0100)
There remains 4 forward declarations.

Move protocol hander table to the end of sources, rearrange static
functions is reverse dependency order as possible.

Closes #20276

lib/ftp.c

index 12b8d4a6379e9ee8812ada0d37834ef68bcab52b..0197d6c3de542ff20faa54c95aec28cefc2d01ef 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
 #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 */