On Sun, 18 Dec 2011, Gokhan Sengun wrote:
I have implemented required functionality to make "Establishing data
connections in FTP non-blocking for multi interface".
Great! Many thanks for your work on this. In general it looks very good and it
ran all the tests fine in my end as well.
I only have some comments and two questions.
- I converted your patch to be a patch on top of my previous change so
that it would be easier to review separately. See the attach file.
- I made both new error codes re-use obsoleted numbers. I updated the code
and test cases accordingly.
- I removed one of the new options, there's no need to have two for the same
thing. Previous options that have one for full seconds and one for
milliseconds were added before we had any millisecond options at all.
- I fixed test case 1119 (the symbols check) to run fine.
Now for my questions:
- Line 2359 in lib/ftp.c now has a comment that says:
/* TODO: gseng - do it once for connected */
Can you explain that for us? Do what and why is it a TODO and not done now?
- You only did a very minor change to lib/multi.c but I don't like it:
case CURLM_STATE_DO_DONE:
+
+ if(easy->easy_conn->bits.wait_data_conn == TRUE) {
+ multistate(easy, CURLM_STATE_DO_MORE);
+ result = CURLM_OK;
+ break;
+ }
+
Why would you switch to the CURLM_STATE_DO_DONE state in the first place if
the conditions say it should actually be in the CURLM_STATE_DO_MORE state?
I don't like this. The condition should be added to the code that changed
the state in the first place I think, unless you have a proper motivation
for this peculiarity.
--
/ daniel.haxx.seFrom 52165972d56bd92ae949e286aff15735ba888172 Mon Sep 17 00:00:00 2001
From: Gokhan Sengun <[email protected]>
Date: Mon, 19 Dec 2011 14:35:20 +0100
Subject: [PATCH] FTP: perform active connections non-blocking
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.
---
docs/libcurl/symbols-in-versions | 3 +
include/curl/curl.h | 12 +-
lib/connect.c | 28 +++
lib/connect.h | 5 +
lib/ftp.c | 364 ++++++++++++++++++++++++++++----------
lib/ftp.h | 3 +
lib/multi.c | 7 +
lib/progress.c | 4 +
lib/progress.h | 1 +
lib/strerror.c | 8 +-
lib/url.c | 9 +-
lib/urldata.h | 4 +
tests/data/DISABLED | 4 +-
tests/data/test1206 | 9 +-
tests/data/test1207 | 9 +-
tests/data/test1208 | 9 +-
tests/data/test591 | 2 +-
tests/data/test592 | 2 +-
tests/data/test593 | 2 +-
tests/libtest/lib591.c | 3 +-
20 files changed, 361 insertions(+), 127 deletions(-)
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index ec902fd..73d50a2 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -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
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 8f82348..085cf35 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -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;
diff --git a/lib/connect.c b/lib/connect.c
index bcd5384..cc83580 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -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.
diff --git a/lib/connect.h b/lib/connect.h
index f84361f..4e905bd 100644
--- a/lib/connect.h
+++ b/lib/connect.h
@@ -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
diff --git a/lib/ftp.c b/lib/ftp.c
index b64ef62..aa1257a 100644
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -108,6 +108,8 @@
#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); /* TODO: gseng - do it once for connected */
+ 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;
diff --git a/lib/ftp.h b/lib/ftp.h
index b5b3ada..4c1296f 100644
--- 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
diff --git a/lib/multi.c b/lib/multi.c
index e507343..e408ab1 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -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 */
diff --git a/lib/progress.c b/lib/progress.c
index 1514e1e..bfc18cc 100644
--- a/lib/progress.c
+++ b/lib/progress.c
@@ -167,6 +167,10 @@ void Curl_pgrsTime(struct SessionHandle *data, timerid timer)
data->progress.t_startsingle = Curl_tvnow();
break;
+ case TIMER_STARTACCEPT:
+ data->progress.t_acceptdata = Curl_tvnow();
+ break;
+
case TIMER_NAMELOOKUP:
data->progress.t_nslookup =
Curl_tvdiff_secs(Curl_tvnow(), data->progress.t_startsingle);
diff --git a/lib/progress.h b/lib/progress.h
index 95944f0..f5cc540 100644
--- a/lib/progress.h
+++ b/lib/progress.h
@@ -34,6 +34,7 @@ typedef enum {
TIMER_STARTTRANSFER,
TIMER_POSTRANSFER,
TIMER_STARTSINGLE,
+ TIMER_STARTACCEPT,
TIMER_REDIRECT,
TIMER_LAST /* must be last */
} timerid;
diff --git a/lib/strerror.c b/lib/strerror.c
index fcb617c..4aa1257 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -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:
diff --git a/lib/url.c b/lib/url.c
index b0ec7c4..c77f3ae 100644
--- 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);
diff --git a/lib/urldata.h b/lib/urldata.h
index 53df18c..f7c35e3 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -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 */
diff --git a/tests/data/DISABLED b/tests/data/DISABLED
index 88336f1..5a0f2bf 100644
--- a/tests/data/DISABLED
+++ b/tests/data/DISABLED
@@ -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
diff --git a/tests/data/test1206 b/tests/data/test1206
index ba578a1..3f853d1 100644
--- a/tests/data/test1206
+++ b/tests/data/test1206
@@ -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
PASS [email protected]
@@ -50,9 +44,10 @@ EPRT |1|
TYPE I
SIZE 1206
RETR 1206
+QUIT
</protocol>
<errorcode>
-28
+10
</errorcode>
</verify>
</testcase>
diff --git a/tests/data/test1207 b/tests/data/test1207
index 6ca7131..283e46d 100644
--- a/tests/data/test1207
+++ b/tests/data/test1207
@@ -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
PASS [email protected]
@@ -50,9 +44,10 @@ EPRT |1|
TYPE I
SIZE 1207
RETR 1207
+QUIT
</protocol>
<errorcode>
-28
+10
</errorcode>
</verify>
</testcase>
diff --git a/tests/data/test1208 b/tests/data/test1208
index 725d18a..a0d428b 100644
--- a/tests/data/test1208
+++ b/tests/data/test1208
@@ -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
PASS [email protected]
@@ -50,9 +44,10 @@ EPRT |1|
TYPE I
SIZE 1208
RETR 1208
+QUIT
</protocol>
<errorcode>
-28
+12
</errorcode>
</verify>
</testcase>
diff --git a/tests/data/test591 b/tests/data/test591
index 0d4bac7..e04ae5b 100644
--- a/tests/data/test591
+++ b/tests/data/test591
@@ -64,7 +64,7 @@ STOR 591
QUIT
</protocol>
<errorcode>
-28
+10
</errorcode>
<upload>
</upload>
diff --git a/tests/data/test592 b/tests/data/test592
index 4af04e3..487290d 100644
--- a/tests/data/test592
+++ b/tests/data/test592
@@ -64,7 +64,7 @@ STOR 592
QUIT
</protocol>
<errorcode>
-28
+10
</errorcode>
<upload>
</upload>
diff --git a/tests/data/test593 b/tests/data/test593
index 811bf93..c3b1f91 100644
--- a/tests/data/test593
+++ b/tests/data/test593
@@ -64,7 +64,7 @@ STOR 593
QUIT
</protocol>
<errorcode>
-28
+12
</errorcode>
<upload>
</upload>
diff --git a/tests/libtest/lib591.c b/tests/libtest/lib591.c
index 101f2db..8a55e2c 100644
--- a/tests/libtest/lib591.c
+++ b/tests/libtest/lib591.c
@@ -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);
--
1.7.7.3
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html