FTP: perform active connections non-blocking
authorGokhan Sengun <gokhansengun@gmail.com>
Mon, 19 Dec 2011 13:35:20 +0000 (14:35 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 20 Dec 2011 19:30:02 +0000 (20:30 +0100)
1- Two new error codes are introduced.

CURLE_FTP_ACCEPT_FAILED to be set whenever ACCEPTing fails because of
FTP server connected.

CURLE_FTP_ACCEPT_TIMEOUT to be set whenever ACCEPTing timeouts.

Neither of these errors are considered fatal and control connection
remains OK because it could just be a firewall blocking server to
connect to the client.

2- One new setopt option was introduced.

CURLOPT_ACCEPTTIMEOUT_MS

It sets the maximum amount of time FTP client is going to wait for a
server to connect. Internal default accept timeout is 60 seconds.

20 files changed:
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/connect.c
lib/connect.h
lib/ftp.c
lib/ftp.h
lib/multi.c
lib/progress.c
lib/progress.h
lib/strerror.c
lib/url.c
lib/urldata.h
tests/data/DISABLED
tests/data/test1206
tests/data/test1207
tests/data/test1208
tests/data/test591
tests/data/test592
tests/data/test593
tests/libtest/lib591.c

index ec902fd..73d50a2 100644 (file)
@@ -45,6 +45,8 @@ CURLE_COULDNT_RESOLVE_PROXY     7.1
 CURLE_FAILED_INIT               7.1
 CURLE_FILESIZE_EXCEEDED         7.10.8
 CURLE_FILE_COULDNT_READ_FILE    7.1
+CURLE_FTP_ACCEPT_FAILED         7.24.0
+CURLE_FTP_ACCEPT_TIMEOUT        7.24.0
 CURLE_FTP_ACCESS_DENIED         7.1
 CURLE_FTP_BAD_DOWNLOAD_RESUME   7.1           7.1
 CURLE_FTP_BAD_FILE_LIST         7.21.0
@@ -286,6 +288,7 @@ CURLOPTTYPE_FUNCTIONPOINT       7.1
 CURLOPTTYPE_LONG                7.1
 CURLOPTTYPE_OBJECTPOINT         7.1
 CURLOPTTYPE_OFF_T               7.11.0
+CURLOPT_ACCEPTTIMEOUT_MS        7.24.0
 CURLOPT_ACCEPT_ENCODING         7.21.6
 CURLOPT_ADDRESS_SCOPE           7.19.0
 CURLOPT_APPEND                  7.17.0
index 1e908a3..f53d6d4 100644 (file)
@@ -411,9 +411,12 @@ typedef enum {
   CURLE_REMOTE_ACCESS_DENIED,    /* 9 a service was denied by the server
                                     due to lack of access - when login fails
                                     this is not returned. */
-  CURLE_OBSOLETE10,              /* 10 - NOT USED */
+  CURLE_FTP_ACCEPT_FAILED,       /* 10 - [was obsoleted in April 2006 for
+                                    7.15.4, reused in Dec 2011 for 7.24.0]*/
   CURLE_FTP_WEIRD_PASS_REPLY,    /* 11 */
-  CURLE_OBSOLETE12,              /* 12 - NOT USED */
+  CURLE_FTP_ACCEPT_TIMEOUT,      /* 12 - timeout occurred accepting server
+                                    [was obsoleted in August 2007 for 7.17.0,
+                                    reused in Dec 2011 for 7.24.0]*/
   CURLE_FTP_WEIRD_PASV_REPLY,    /* 13 */
   CURLE_FTP_WEIRD_227_FORMAT,    /* 14 */
   CURLE_FTP_CANT_GET_HOST,       /* 15 */
@@ -511,7 +514,6 @@ typedef enum {
   CURLE_RTSP_SESSION_ERROR,      /* 86 - mismatch of RTSP Session Ids */
   CURLE_FTP_BAD_FILE_LIST,       /* 87 - unable to parse FTP file list */
   CURLE_CHUNK_FAILED,            /* 88 - chunk callback reported error */
-
   CURL_LAST /* never use! */
 } CURLcode;
 
@@ -1489,6 +1491,10 @@ typedef enum {
   /* Set the name servers to use for DNS resolution */
   CINIT(DNS_SERVERS, OBJECTPOINT, 211),
 
+  /* Time-out accept operations (currently for FTP only) after this amount
+     of miliseconds. */
+  CINIT(ACCEPTTIMEOUT_MS, LONG, 212),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
index bcd5384..cc83580 100644 (file)
@@ -99,6 +99,34 @@ singleipconnect(struct connectdata *conn,
                 bool *connected);
 
 /*
+ * Curl_timeleft_accept() returns the amount of milliseconds left allowed for
+ * waiting server to connect. If the value is negative, the timeout time has
+ * already elapsed.
+ *
+ * The start time is stored in progress.t_acceptdata - as set with
+ * Curl_pgrsTime(..., TIMER_STARTACCEPT);
+ *
+ */
+long Curl_timeleft_accept(struct SessionHandle *data)
+{
+  long timeout_ms = DEFAULT_ACCEPT_TIMEOUT;
+  struct timeval now;
+
+  if(data->set.accepttimeout > 0)
+    timeout_ms = data->set.accepttimeout;
+
+  now = Curl_tvnow();
+
+  /* subtract elapsed time */
+  timeout_ms -= Curl_tvdiff(now, data->progress.t_acceptdata);
+  if(!timeout_ms)
+    /* avoid returning 0 as that means no timeout! */
+    return -1;
+
+  return timeout_ms;
+}
+
+/*
  * Curl_timeleft() returns the amount of milliseconds left allowed for the
  * transfer/connection. If the value is negative, the timeout time has already
  * elapsed.
index f84361f..4e905bd 100644 (file)
@@ -43,7 +43,12 @@ long Curl_timeleft(struct SessionHandle *data,
                    struct timeval *nowp,
                    bool duringconnect);
 
+/* function that returns how much time there's left to wait for incoming
+   server connect */
+long Curl_timeleft_accept(struct SessionHandle *data);
+
 #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
+#define DEFAULT_ACCEPT_TIMEOUT   60000 /* milliseconds == one minute */
 
 /*
  * Used to extract socket and connectdata struct for the most recent
index b64ef62..3271907 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
 #endif
 
 /* Local API functions */
+static void state(struct connectdata *conn,
+                  ftpstate newstate);
 static CURLcode ftp_sendquote(struct connectdata *conn,
                               struct curl_slist *quote);
 static CURLcode ftp_quit(struct connectdata *conn);
@@ -150,6 +152,11 @@ static void wc_data_dtor(void *ptr);
 static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
                                          curl_off_t filesize);
 
+static CURLcode ftp_readresp(curl_socket_t sockfd,
+                             struct pingpong *pp,
+                             int *ftpcode,
+                             size_t *size);
+
 /* easy-to-use macro: */
 #define FTPSENDF(x,y,z)    if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \
                               return result
@@ -310,19 +317,16 @@ static bool isBadFtpString(const char *string)
 
 /***********************************************************************
  *
- * AllowServerConnect()
+ * AcceptServerConnect()
  *
- * When we've issue the PORT command, we have told the server to connect
- * to us. This function will sit and wait here until the server has
- * connected.
+ * After connection request is received from the server this function is
+ * called to accept the connection and close the listening socket
  *
  */
-static CURLcode AllowServerConnect(struct connectdata *conn)
+static CURLcode AcceptServerConnect(struct connectdata *conn)
 {
   struct SessionHandle *data = conn->data;
   curl_socket_t sock = conn->sock[SECONDARYSOCKET];
-  long timeout_ms;
-  long interval_ms;
   curl_socket_t s = CURL_SOCKET_BAD;
 #ifdef ENABLE_IPV6
   struct Curl_sockaddr_storage add;
@@ -331,48 +335,220 @@ static CURLcode AllowServerConnect(struct connectdata *conn)
 #endif
   curl_socklen_t size = (curl_socklen_t) sizeof(add);
 
-  for(;;) {
-    timeout_ms = Curl_timeleft(data, NULL, TRUE);
+  if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) {
+    size = sizeof(add);
+
+    s=accept(sock, (struct sockaddr *) &add, &size);
+  }
+  Curl_closesocket(conn, sock); /* close the first socket */
+
+  if(CURL_SOCKET_BAD == s) {
+    failf(data, "Error accept()ing server connect");
+    return CURLE_FTP_PORT_FAILED;
+  }
+  infof(data, "Connection accepted from server\n");
+
+  conn->sock[SECONDARYSOCKET] = s;
+  curlx_nonblock(s, TRUE); /* enable non-blocking */
+  conn->sock_accepted[SECONDARYSOCKET] = TRUE;
+  return CURLE_OK;
+
+}
+
+/***********************************************************************
+ *
+ * ReceivedServerConnect()
+ *
+ * After allowing server to connect to us from data port, this function
+ * checks both data connection for connection establishment and ctrl
+ * connection for a negative response regarding a failure in connecting
+ *
+ */
+static CURLcode ReceivedServerConnect(struct connectdata* conn, bool* received)
+{
+  struct SessionHandle *data = conn->data;
+  curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET];
+  curl_socket_t data_sock = conn->sock[SECONDARYSOCKET];
+  struct ftp_conn *ftpc = &conn->proto.ftpc;
+  struct pingpong *pp = &ftpc->pp;
+  int result;
+  long timeout_ms;
+  ssize_t nread;
+  int ftpcode;
+
+  *received = FALSE;
+
+  timeout_ms = Curl_timeleft_accept(data);
+  infof(data, "Checking for server connect\n");
+  if(timeout_ms < 0) {
+    /* if a timeout was already reached, bail out */
+    failf(data, "Accept timeout occurred while waiting server connect");
+    return CURLE_FTP_ACCEPT_TIMEOUT;
+  }
+
+  /* First check whether there is a cached response from server */
+  if(pp->cache_size && pp->cache && pp->cache[0] > '3') {
+    /* Data connection could not be established, let's return */
+    infof(data, "There is negative response in cache while serv connect");
+    Curl_GetFTPResponse(&nread, conn, &ftpcode);
+    return CURLE_FTP_ACCEPT_FAILED;
+  }
+
+  result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0);
+
+  /* see if the connection request is already here */
+  switch (result) {
+  case -1: /* error */
+    /* let's die here */
+    failf(data, "Error while waiting for server connect");
+    return CURLE_FTP_ACCEPT_FAILED;
+  case 0:  /* Server connect is not received yet */
+    break; /* loop */
+  default:
+
+    if(result & CURL_CSELECT_IN2) {
+      infof(data, "Ready to accept data connection from server\n");
+      *received = TRUE;
+    }
+    else if(result & CURL_CSELECT_IN) {
+      infof(data, "Ctrl conn has data while waiting for data conn\n");
+      Curl_GetFTPResponse(&nread, conn, &ftpcode);
+
+      if(ftpcode/100 > 3)
+        return CURLE_FTP_ACCEPT_FAILED;
+
+      return CURLE_FTP_WEIRD_SERVER_REPLY;
+    }
+
+    break;
+  } /* switch() */
+
+  return CURLE_OK;
+}
+
+
+/***********************************************************************
+ *
+ * InitiateTransfer()
+ *
+ * After connection from server is accepted this function is called to
+ * setup transfer parameters and initiate the data transfer.
+ *
+ */
+static CURLcode InitiateTransfer(struct connectdata *conn)
+{
+  struct SessionHandle *data = conn->data;
+  struct FTP *ftp = data->state.proto.ftp;
+  CURLcode result = CURLE_OK;
+
+  if(conn->ssl[SECONDARYSOCKET].use) {
+    /* since we only have a plaintext TCP connection here, we must now
+     * do the TLS stuff */
+    infof(data, "Doing the SSL/TLS handshake on the data stream\n");
+    result = Curl_ssl_connect(conn, SECONDARYSOCKET);
+    if(result)
+      return result;
+  }
+
+  if(conn->proto.ftpc.state_saved == FTP_STOR) {
+    *(ftp->bytecountp)=0;
+
+    /* When we know we're uploading a specified file, we can get the file
+       size prior to the actual upload. */
+
+    Curl_pgrsSetUploadSize(data, data->set.infilesize);
+
+    /* set the SO_SNDBUF for the secondary socket for those who need it */
+    Curl_sndbufset(conn->sock[SECONDARYSOCKET]);
+
+    Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
+                        SECONDARYSOCKET, ftp->bytecountp);
+  }
+  else {
+    /* FTP download: */
+    Curl_setup_transfer(conn, SECONDARYSOCKET,
+        conn->proto.ftpc.retr_size_saved, FALSE,
+        ftp->bytecountp, -1, NULL); /* no upload here */
+  }
+
+  conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */
+  state(conn, FTP_STOP);
+
+  return CURLE_OK;
+}
+
+/***********************************************************************
+ *
+ * AllowServerConnect()
+ *
+ * When we've issue the PORT command, we have told the server to connect
+ * to us. This function
+ *   - will sit and wait here until the server has connected for easy interface
+ *   - will check whether data connection is established if so it is accepted
+ *   for multi interface
+ *
+ */
+static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected)
+{
+  struct SessionHandle *data = conn->data;
+  long timeout_ms;
+  long interval_ms;
+  CURLcode ret = CURLE_OK;
+
+  *connected = FALSE;
+  infof(data, "Preparing for accepting server on data port\n");
+
+  /* Save the time we start accepting server connect */
+  Curl_pgrsTime(data, TIMER_STARTACCEPT);
 
+  for(;;) {
+    timeout_ms = Curl_timeleft_accept(data);
     if(timeout_ms < 0) {
       /* if a timeout was already reached, bail out */
-      failf(data, "Timeout while waiting for server connect");
-      return CURLE_OPERATION_TIMEDOUT;
+      failf(data, "Accept timeout occurred while waiting server connect");
+      return CURLE_FTP_ACCEPT_TIMEOUT;
     }
 
-    interval_ms = 1000;  /* use 1 second timeout intervals */
-    if(timeout_ms < interval_ms)
-      interval_ms = timeout_ms;
+    /* see if the connection request is already here */
+    ret = ReceivedServerConnect(conn, connected);
+    if(ret)
+      return ret;
 
-    switch (Curl_socket_ready(sock, CURL_SOCKET_BAD, interval_ms)) {
-    case -1: /* error */
-      /* let's die here */
-      failf(data, "Error while waiting for server connect");
-      return CURLE_FTP_PORT_FAILED;
-    case 0:  /* timeout */
-      break; /* loop */
-    default:
-      /* we have received data here */
-      if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) {
-        size = sizeof(add);
+    if(*connected) {
+      ret = AcceptServerConnect(conn);
+      if(ret)
+        return ret;
 
-        s=accept(sock, (struct sockaddr *) &add, &size);
-      }
-      Curl_closesocket(conn, sock); /* close the first socket */
+      ret = InitiateTransfer(conn);
+      if(ret)
+        return ret;
 
-      if(CURL_SOCKET_BAD == s) {
-        failf(data, "Error accept()ing server connect");
-        return CURLE_FTP_PORT_FAILED;
+      break; /* connection is accepted, break the loop */
+    }
+    else {
+      if(data->state.used_interface == Curl_if_easy) {
+        interval_ms = 1000;
+        if(timeout_ms < interval_ms)
+          interval_ms = timeout_ms;
+
+        /* sleep for 1 second and then continue */
+        Curl_socket_ready(CURL_SOCKET_BAD, CURL_SOCKET_BAD, interval_ms);
       }
-      infof(data, "Connection accepted from server\n");
+      else {
+        /* Add timeout to multi handle and break out of the loop */
+        if(ret == CURLE_OK && *connected == FALSE) {
+          if(data->set.accepttimeout > 0)
+            Curl_expire(data, data->set.accepttimeout);
+          else
+            Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT);
+        }
 
-      conn->sock[SECONDARYSOCKET] = s;
-      curlx_nonblock(s, TRUE); /* enable non-blocking */
-      conn->sock_accepted[SECONDARYSOCKET] = TRUE;
-      return CURLE_OK;
-    } /* switch() */
+        break; /* connection was not accepted immediately */
+      }
+    }
   }
-  /* never reaches this point */
+
+  return ret;
 }
 
 /* macro to check for a three-digit ftp status code at the start of the
@@ -668,6 +844,10 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks,
   }
 
   socks[0] = conn->sock[SECONDARYSOCKET];
+  if(conn->bits.wait_data_conn) {
+    socks[1] = conn->sock[FIRSTSOCKET];
+    return GETSOCK_READSOCK(0) | GETSOCK_READSOCK(1);
+  }
 
   return GETSOCK_READSOCK(0);
 }
@@ -2153,11 +2333,10 @@ static CURLcode ftp_state_rest_resp(struct connectdata *conn,
 }
 
 static CURLcode ftp_state_stor_resp(struct connectdata *conn,
-                                    int ftpcode)
+                                    int ftpcode, ftpstate instate)
 {
   CURLcode result = CURLE_OK;
   struct SessionHandle *data = conn->data;
-  struct FTP *ftp = data->state.proto.ftp;
 
   if(ftpcode>=400) {
     failf(data, "Failed FTP upload: %0d", ftpcode);
@@ -2165,41 +2344,26 @@ static CURLcode ftp_state_stor_resp(struct connectdata *conn,
     return CURLE_UPLOAD_FAILED;
   }
 
+  conn->proto.ftpc.state_saved = instate;
+
+  /* PORT means we are now awaiting the server to connect to us. */
   if(data->set.ftp_use_port) {
-    /* BLOCKING */
-    /* PORT means we are now awaiting the server to connect to us. */
-    result = AllowServerConnect(conn);
-    if(result)
-      return result;
-  }
+    bool connected;
 
-  if(conn->ssl[SECONDARYSOCKET].use) {
-    /* since we only have a plaintext TCP connection here, we must now
-       do the TLS stuff */
-    infof(data, "Doing the SSL/TLS handshake on the data stream\n");
-    /* BLOCKING */
-    result = Curl_ssl_connect(conn, SECONDARYSOCKET);
+    result = AllowServerConnect(conn, &connected);
     if(result)
       return result;
-  }
-
-  *(ftp->bytecountp)=0;
-
-  /* When we know we're uploading a specified file, we can get the file
-     size prior to the actual upload. */
 
-  Curl_pgrsSetUploadSize(data, data->set.infilesize);
-
-  /* set the SO_SNDBUF for the secondary socket for those who need it */
-  Curl_sndbufset(conn->sock[SECONDARYSOCKET]);
-
-  Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */
-                      SECONDARYSOCKET, ftp->bytecountp);
-  state(conn, FTP_STOP);
-
-  conn->proto.ftpc.pp.pending_resp = TRUE; /* expect a server response */
+    if(!connected) {
+      infof(data, "Data conn was not available immediately\n");
+      state(conn, FTP_STOP);
+      conn->bits.wait_data_conn = TRUE;
+    }
 
-  return result;
+    return CURLE_OK;
+  }
+  else
+    return InitiateTransfer(conn);
 }
 
 /* for LIST and RETR responses */
@@ -2280,22 +2444,6 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn,
     else if(ftp->downloadsize > -1)
       size = ftp->downloadsize;
 
-    if(data->set.ftp_use_port) {
-      /* BLOCKING */
-      result = AllowServerConnect(conn);
-      if(result)
-        return result;
-    }
-
-    if(conn->ssl[SECONDARYSOCKET].use) {
-      /* since we only have a plaintext TCP connection here, we must now
-         do the TLS stuff */
-      infof(data, "Doing the SSL/TLS handshake on the data stream\n");
-      result = Curl_ssl_connect(conn, SECONDARYSOCKET);
-      if(result)
-        return result;
-    }
-
     if(size > data->req.maxdownload && data->req.maxdownload > 0)
       size = data->req.size = data->req.maxdownload;
     else if((instate != FTP_LIST) && (data->set.prefer_ascii))
@@ -2307,11 +2455,24 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn,
       infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size);
 
     /* FTP download: */
-    Curl_setup_transfer(conn, SECONDARYSOCKET, size, FALSE,
-                        ftp->bytecountp, -1, NULL); /* no upload here */
+    conn->proto.ftpc.state_saved = instate;
+    conn->proto.ftpc.retr_size_saved = size;
+
+    if(data->set.ftp_use_port) {
+      bool connected;
+
+      result = AllowServerConnect(conn, &connected);
+      if(result)
+        return result;
 
-    conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */
-    state(conn, FTP_STOP);
+      if(!connected) {
+        infof(data, "Data conn was not available immediately\n");
+        state(conn, FTP_STOP);
+        conn->bits.wait_data_conn = TRUE;
+      }
+    }
+    else
+      return InitiateTransfer(conn);
   }
   else {
     if((instate == FTP_LIST) && (ftpcode == 450)) {
@@ -2459,7 +2620,6 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
   if(pp->sendleft)
     return Curl_pp_flushsend(pp);
 
-  /* we read a piece of response */
   result = ftp_readresp(sock, pp, &ftpcode, &nread);
   if(result)
     return result;
@@ -2865,7 +3025,7 @@ static CURLcode ftp_statemach_act(struct connectdata *conn)
       break;
 
     case FTP_STOR:
-      result = ftp_state_stor_resp(conn, ftpcode);
+      result = ftp_state_stor_resp(conn, ftpcode, ftpc->state);
       break;
 
     case FTP_QUIT:
@@ -3081,6 +3241,8 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
   case CURLE_BAD_DOWNLOAD_RESUME:
   case CURLE_FTP_WEIRD_PASV_REPLY:
   case CURLE_FTP_PORT_FAILED:
+  case CURLE_FTP_ACCEPT_FAILED:
+  case CURLE_FTP_ACCEPT_TIMEOUT:
   case CURLE_FTP_COULDNT_SET_TYPE:
   case CURLE_FTP_COULDNT_RETR_FILE:
   case CURLE_UPLOAD_FAILED:
@@ -3474,7 +3636,24 @@ static CURLcode ftp_nextconnect(struct connectdata *conn)
     /* a transfer is about to take place, or if not a file name was given
        so we'll do a SIZE on it later and then we need the right TYPE first */
 
-    if(data->set.upload) {
+    if(conn->bits.wait_data_conn == TRUE) {
+      bool serv_conned;
+
+      result = ReceivedServerConnect(conn, &serv_conned);
+      if(result)
+        return result; /* Failed to accept data connection */
+
+      if(serv_conned) {
+        /* It looks data connection is established */
+        result = AcceptServerConnect(conn);
+        conn->bits.wait_data_conn = FALSE;
+        if(result == CURLE_OK)
+          result = InitiateTransfer(conn);
+      }
+
+      return result;
+    }
+    else if(data->set.upload) {
       result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE);
       if(result)
         return result;
@@ -3513,7 +3692,6 @@ static CURLcode ftp_nextconnect(struct connectdata *conn)
        too! */
     Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
 
-  /* end of transfer */
   DEBUGF(infof(data, "DO-MORE phase ends with %d\n", (int)result));
 
   return result;
index b5b3ada..4c1296f 100644 (file)
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -146,6 +146,9 @@ struct ftp_conn {
   int count2; /* general purpose counter for the state machine */
   int count3; /* general purpose counter for the state machine */
   ftpstate state; /* always use ftp.c:state() to change state! */
+  ftpstate state_saved; /* transfer type saved to be reloaded after
+                           data connection is established */
+  curl_off_t retr_size_saved; /* Size of retrieved file saved */
   char * server_os;     /* The target server operating system. */
   curl_off_t known_filesize; /* file size is different from -1, if wildcard
                                 LIST parsing was done and wc_statemach set
index e507343..e408ab1 100644 (file)
@@ -1388,6 +1388,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
       break;
 
     case CURLM_STATE_DO_DONE:
+
+      if(easy->easy_conn->bits.wait_data_conn == TRUE) {
+        multistate(easy, CURLM_STATE_DO_MORE);
+        result = CURLM_OK;
+        break;
+      }
+
       /* Move ourselves from the send to recv pipeline */
       moveHandleFromSendToRecvPipeline(data, easy->easy_conn);
       /* Check if we can move pending requests to send pipe */
index 6abe72e..1eeb780 100644 (file)
@@ -169,6 +169,10 @@ void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
     data->progress.t_startsingle = now;
     break;
 
+  case TIMER_STARTACCEPT:
+    data->progress.t_acceptdata = Curl_tvnow();
+    break;
+
   case TIMER_NAMELOOKUP:
     data->progress.t_nslookup =
       Curl_tvdiff_secs(now, data->progress.t_startsingle);
index 95944f0..f5cc540 100644 (file)
@@ -34,6 +34,7 @@ typedef enum {
   TIMER_STARTTRANSFER,
   TIMER_POSTRANSFER,
   TIMER_STARTSINGLE,
+  TIMER_STARTACCEPT,
   TIMER_REDIRECT,
   TIMER_LAST /* must be last */
 } timerid;
index fcb617c..4aa1257 100644 (file)
@@ -81,6 +81,12 @@ curl_easy_strerror(CURLcode error)
   case CURLE_REMOTE_ACCESS_DENIED:
     return "Access denied to remote resource";
 
+  case CURLE_FTP_ACCEPT_FAILED:
+    return "FTP: The server failed to connect to data port";
+
+  case CURLE_FTP_ACCEPT_TIMEOUT:
+    return "FTP: Accepting server connect has timed out";
+
   case CURLE_FTP_PRET_FAILED:
     return "FTP: The server did not accept the PRET command.";
 
@@ -284,8 +290,6 @@ curl_easy_strerror(CURLcode error)
     return "Chunk callback failed";
 
     /* error codes not used by current libcurl */
-  case CURLE_OBSOLETE10:
-  case CURLE_OBSOLETE12:
   case CURLE_OBSOLETE16:
   case CURLE_OBSOLETE20:
   case CURLE_OBSOLETE24:
index a4aadb2..b952e92 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -1677,6 +1677,13 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
     data->set.connecttimeout = va_arg(param, long);
     break;
 
+  case CURLOPT_ACCEPTTIMEOUT_MS:
+    /*
+     * The maximum time you allow curl to wait for server connect
+     */
+    data->set.accepttimeout = va_arg(param, long);
+    break;
+
   case CURLOPT_USERPWD:
     /*
      * user:password to use in the operation
@@ -5457,7 +5464,7 @@ CURLcode Curl_do_more(struct connectdata *conn)
   if(conn->handler->do_more)
     result = conn->handler->do_more(conn);
 
-  if(result == CURLE_OK)
+  if(result == CURLE_OK && conn->bits.wait_data_conn == FALSE)
     /* do_complete must be called after the protocol-specific DO function */
     do_complete(conn);
 
index 53df18c..f7c35e3 100644 (file)
@@ -412,6 +412,8 @@ struct ConnectBits {
   bool do_more; /* this is set TRUE if the ->curl_do_more() function is
                    supposed to be called, after ->curl_do() */
 
+  bool wait_data_conn; /* this is set TRUE if data connection is waited */
+
   bool tcpconnect[2]; /* the TCP layer (or similar) is connected, this is set
                          the first time on the first connect function call */
   bool protoconnstart;/* the protocol layer has STARTED its operation after
@@ -1038,6 +1040,7 @@ struct Progress {
 
   struct timeval start;
   struct timeval t_startsingle;
+  struct timeval t_acceptdata;
 #define CURR_TIME (5+1) /* 6 entries for 5 seconds */
 
   curl_off_t speeder[ CURR_TIME ];
@@ -1407,6 +1410,7 @@ struct UserDefined {
   void *ioctl_client;   /* pointer to pass to the ioctl callback */
   long timeout;         /* in milliseconds, 0 means no timeout */
   long connecttimeout;  /* in milliseconds, 0 means no timeout */
+  long accepttimeout;   /* in milliseconds, 0 means no timeout */
   long server_response_timeout; /* in milliseconds, 0 means no timeout */
   long tftp_blksize ; /* in bytes, 0 means use default */
   curl_off_t infilesize;      /* size of file to upload, -1 means unknown */
index 88336f1..5a0f2bf 100644 (file)
@@ -2,8 +2,6 @@
 # test cases are run by runtests.pl. Just add the plain test case numbers, one
 # per line.
 # Lines starting with '#' letters are treated as comments.
-591
-592
-593
 594
+1209
 1211
index ba578a1..3f853d1 100644 (file)
@@ -36,12 +36,6 @@ FTP PORT and 425 on download
 <strippart>
 s/^EPRT \|1\|(.*)/EPRT \|1\|/
 </strippart>
-
-# The protocol part does not include QUIT simply because the error is
-# CURLE_OPERATION_TIMEDOUT (28) which is a generic timeout error without
-# specificly saying for which connection it concerns, and for timeouts libcurl
-# marks the control channel as "invalid". As this test case times out for the
-# data connection it could still use the control channel.
 <protocol>
 USER anonymous\r
 PASS ftp@example.com\r
@@ -50,9 +44,10 @@ EPRT |1|
 TYPE I\r
 SIZE 1206\r
 RETR 1206\r
+QUIT\r
 </protocol>
 <errorcode>
-28
+10
 </errorcode>
 </verify>
 </testcase>
index 6ca7131..283e46d 100644 (file)
@@ -36,12 +36,6 @@ FTP PORT and 421 on download
 <strippart>
 s/^EPRT \|1\|(.*)/EPRT \|1\|/
 </strippart>
-
-# The protocol part does not include QUIT simply because the error is
-# CURLE_OPERATION_TIMEDOUT (28) which is a generic timeout error without
-# specificly saying for which connection it concerns, and for timeouts libcurl
-# marks the control channel as "invalid". As this test case times out for the
-# data connection it could still use the control channel.
 <protocol>
 USER anonymous\r
 PASS ftp@example.com\r
@@ -50,9 +44,10 @@ EPRT |1|
 TYPE I\r
 SIZE 1207\r
 RETR 1207\r
+QUIT\r
 </protocol>
 <errorcode>
-28
+10
 </errorcode>
 </verify>
 </testcase>
index 725d18a..a0d428b 100644 (file)
@@ -36,12 +36,6 @@ FTP PORT download, no data conn and no transient negative reply
 <strippart>
 s/^EPRT \|1\|(.*)/EPRT \|1\|/
 </strippart>
-
-# The protocol part does not include QUIT simply because the error is
-# CURLE_OPERATION_TIMEDOUT (28) which is a generic timeout error without
-# specificly saying for which connection it concerns, and for timeouts libcurl
-# marks the control channel as "invalid". As this test case times out for the
-# data connection it could still use the control channel.
 <protocol>
 USER anonymous\r
 PASS ftp@example.com\r
@@ -50,9 +44,10 @@ EPRT |1|
 TYPE I\r
 SIZE 1208\r
 RETR 1208\r
+QUIT\r
 </protocol>
 <errorcode>
-28
+12
 </errorcode>
 </verify>
 </testcase>
index 0d4bac7..e04ae5b 100644 (file)
@@ -64,7 +64,7 @@ STOR 591
 QUIT\r
 </protocol>
 <errorcode>
-28
+10
 </errorcode>
 <upload>
 </upload>
index 4af04e3..487290d 100644 (file)
@@ -64,7 +64,7 @@ STOR 592
 QUIT\r
 </protocol>
 <errorcode>
-28
+10
 </errorcode>
 <upload>
 </upload>
index 811bf93..c3b1f91 100644 (file)
@@ -64,7 +64,7 @@ STOR 593
 QUIT\r
 </protocol>
 <errorcode>
-28
+12
 </errorcode>
 <upload>
 </upload>
index 101f2db..8a55e2c 100644 (file)
@@ -77,7 +77,8 @@ int test(char *URL)
   easy_setopt(easy, CURLOPT_FTPPORT, "-");
 
   /* server connection timeout */
-  easy_setopt(easy, CURLOPT_CONNECTTIMEOUT, strtol(libtest_arg2, NULL, 10));
+  easy_setopt(easy, CURLOPT_ACCEPTTIMEOUT_MS,
+              strtol(libtest_arg2, NULL, 10)*1000);
 
   multi_init(multi);