diff options
Diffstat (limited to 'libs/libcurl/src/multi.c')
-rw-r--r-- | libs/libcurl/src/multi.c | 1414 |
1 files changed, 914 insertions, 500 deletions
diff --git a/libs/libcurl/src/multi.c b/libs/libcurl/src/multi.c index 7aed3f5fc9..d89657e2f3 100644 --- a/libs/libcurl/src/multi.c +++ b/libs/libcurl/src/multi.c @@ -50,6 +50,7 @@ #include "http2.h"
#include "socketpair.h"
#include "socks.h"
+#include "urlapi-int.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@@ -79,7 +80,7 @@ * are not NULL, but no longer have the MAGIC touch. This gives
* us early warning on things only discovered by valgrind otherwise. */
#define GOOD_MULTI_HANDLE(x) \
- (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \
+ (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \
(DEBUGASSERT(!(x)), FALSE))
#else
#define GOOD_MULTI_HANDLE(x) \
@@ -98,10 +99,10 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, long *timeout_ms);
static void process_pending_handles(struct Curl_multi *multi);
static void multi_xfer_bufs_free(struct Curl_multi *multi);
-static void Curl_expire_ex(struct Curl_easy *data, const struct curltime *nowp,
- timediff_t milli, expire_id id);
+static void expire_ex(struct Curl_easy *data, const struct curltime *nowp,
+ timediff_t milli, expire_id id);
-#ifdef DEBUGBUILD
+#if defined( DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
static const char * const multi_statename[]={
"INIT",
"PENDING",
@@ -240,12 +241,13 @@ static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, }
#define TRHASH_SIZE 13
+
+/* the given key here is a struct Curl_easy pointer */
static size_t trhash(void *key, size_t key_length, size_t slots_num)
{
- size_t keyval = (size_t)*(struct Curl_easy **)key;
- (void) key_length;
-
- return (keyval % slots_num);
+ unsigned char bytes = ((unsigned char *)key)[key_length - 1] ^
+ ((unsigned char *)key)[0];
+ return (bytes % slots_num);
}
static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len)
@@ -449,7 +451,7 @@ error: return NULL;
}
-struct Curl_multi *curl_multi_init(void)
+CURLM *curl_multi_init(void)
{
return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE,
CURL_CONNECTION_HASH_SIZE,
@@ -463,17 +465,18 @@ static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data) infof(data, "!!! WARNING !!!");
infof(data, "This is a debug build of libcurl, "
"do not use in production.");
- multi->warned = true;
+ multi->warned = TRUE;
}
}
#else
#define multi_warn_debug(x,y) Curl_nop_stmt
#endif
-CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
- struct Curl_easy *data)
+CURLMcode curl_multi_add_handle(CURLM *m, CURL *d)
{
CURLMcode rc;
+ struct Curl_multi *multi = m;
+ struct Curl_easy *data = d;
/* First, make some basic checks that the CURLM handle is a good handle */
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
@@ -755,6 +758,8 @@ static CURLcode multi_done(struct Curl_easy *data, mdctx.premature = premature;
Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx);
+ /* flush the netrc cache */
+ Curl_netrc_cleanup(&data->state.netrc);
return result;
}
@@ -768,10 +773,10 @@ static void close_connect_only(struct connectdata *conn, connclose(conn, "Removing connect-only easy handle");
}
-CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
- struct Curl_easy *data)
+CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d)
{
- struct Curl_easy *easy = data;
+ struct Curl_multi *multi = m;
+ struct Curl_easy *data = d;
bool premature;
struct Curl_llist_node *e;
CURLMcode rc;
@@ -796,7 +801,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- premature = (data->mstate < MSTATE_COMPLETED) ? TRUE : FALSE;
+ premature = (data->mstate < MSTATE_COMPLETED);
/* If the 'state' is not INIT or COMPLETED, we might need to do something
nice to put the easy_handle in a good known state when this returns. */
@@ -846,7 +851,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* This ignores the return code even in case of problems because there is
nothing more to do about that, here */
- (void)singlesocket(multi, easy); /* to let the application know what sockets
+ (void)singlesocket(multi, data); /* to let the application know what sockets
that vanish with this handle */
/* Remove the association between the connection and the handle */
@@ -886,7 +891,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) {
struct Curl_message *msg = Curl_node_elem(e);
- if(msg->extmsg.easy_handle == easy) {
+ if(msg->extmsg.easy_handle == data) {
Curl_node_remove(e);
/* there can only be one from this specific handle */
break;
@@ -1125,14 +1130,19 @@ static void multi_getsock(struct Curl_easy *data, }
if(expect_sockets && !ps->num &&
- !(data->req.keepon & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) &&
+ !Curl_llist_count(&data->state.timeoutlist) &&
+ !Curl_cwriter_is_paused(data) && !Curl_creader_is_paused(data) &&
Curl_conn_is_ip_connected(data, FIRSTSOCKET)) {
- infof(data, "WARNING: no socket in pollset, transfer may stall!");
+ /* We expected sockets for POLL monitoring, but none are set.
+ * We are not waiting on any timer.
+ * None of the READ/WRITE directions are paused.
+ * We are connected to the server on IP level, at least. */
+ infof(data, "WARNING: no socket in pollset or timer, transfer may stall!");
DEBUGASSERT(0);
}
}
-CURLMcode curl_multi_fdset(struct Curl_multi *multi,
+CURLMcode curl_multi_fdset(CURLM *m,
fd_set *read_fd_set, fd_set *write_fd_set,
fd_set *exc_fd_set, int *max_fd)
{
@@ -1141,6 +1151,7 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, and then we must make sure that is done. */
int this_max_fd = -1;
struct Curl_llist_node *e;
+ struct Curl_multi *multi = m;
(void)exc_fd_set; /* not used */
if(!GOOD_MULTI_HANDLE(multi))
@@ -1173,7 +1184,7 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, return CURLM_OK;
}
-CURLMcode curl_multi_waitfds(struct Curl_multi *multi,
+CURLMcode curl_multi_waitfds(CURLM *m,
struct curl_waitfd *ufds,
unsigned int size,
unsigned int *fd_count)
@@ -1181,6 +1192,7 @@ CURLMcode curl_multi_waitfds(struct Curl_multi *multi, struct curl_waitfds cwfds;
CURLMcode result = CURLM_OK;
struct Curl_llist_node *e;
+ struct Curl_multi *multi = m;
if(!ufds)
return CURLM_BAD_FUNCTION_ARGUMENT;
@@ -1478,7 +1490,7 @@ out: return result;
}
-CURLMcode curl_multi_wait(struct Curl_multi *multi,
+CURLMcode curl_multi_wait(CURLM *multi,
struct curl_waitfd extra_fds[],
unsigned int extra_nfds,
int timeout_ms,
@@ -1488,7 +1500,7 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi, FALSE);
}
-CURLMcode curl_multi_poll(struct Curl_multi *multi,
+CURLMcode curl_multi_poll(CURLM *multi,
struct curl_waitfd extra_fds[],
unsigned int extra_nfds,
int timeout_ms,
@@ -1498,11 +1510,12 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi, TRUE);
}
-CURLMcode curl_multi_wakeup(struct Curl_multi *multi)
+CURLMcode curl_multi_wakeup(CURLM *m)
{
/* this function is usually called from another thread,
it has to be careful only to access parts of the
Curl_multi struct that are constant */
+ struct Curl_multi *multi = m;
#if defined(ENABLE_WAKEUP) && !defined(USE_WINSOCK)
#ifdef USE_EVENTFD
@@ -1657,9 +1670,9 @@ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) static bool multi_handle_timeout(struct Curl_easy *data,
struct curltime *now,
bool *stream_error,
- CURLcode *result,
- bool connect_timeout)
+ CURLcode *result)
{
+ bool connect_timeout = data->mstate < MSTATE_DO;
timediff_t timeout_ms = Curl_timeleft(data, now, connect_timeout);
if(timeout_ms < 0) {
/* Handle timed out */
@@ -1811,19 +1824,781 @@ static void multi_posttransfer(struct Curl_easy *data) #endif
}
+/*
+ * multi_follow() handles the URL redirect magic. Pass in the 'newurl' string
+ * as given by the remote server and set up the new URL to request.
+ *
+ * This function DOES NOT FREE the given url.
+ */
+static CURLcode multi_follow(struct Curl_easy *data,
+ char *newurl, /* the Location: string */
+ followtype type) /* see transfer.h */
+{
+#ifdef CURL_DISABLE_HTTP
+ (void)data;
+ (void)newurl;
+ (void)type;
+ /* Location: following will not happen when HTTP is disabled */
+ return CURLE_TOO_MANY_REDIRECTS;
+#else
+
+ /* Location: redirect */
+ bool disallowport = FALSE;
+ bool reachedmax = FALSE;
+ CURLUcode uc;
+
+ DEBUGASSERT(type != FOLLOW_NONE);
+
+ if(type != FOLLOW_FAKE)
+ data->state.requests++; /* count all real follows */
+ if(type == FOLLOW_REDIR) {
+ if((data->set.maxredirs != -1) &&
+ (data->state.followlocation >= data->set.maxredirs)) {
+ reachedmax = TRUE;
+ type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
+ to URL */
+ }
+ else {
+ data->state.followlocation++; /* count redirect-followings, including
+ auth reloads */
+
+ if(data->set.http_auto_referer) {
+ CURLU *u;
+ char *referer = NULL;
+
+ /* We are asked to automatically set the previous URL as the referer
+ when we get the next URL. We pick the ->url field, which may or may
+ not be 100% correct */
+
+ if(data->state.referer_alloc) {
+ Curl_safefree(data->state.referer);
+ data->state.referer_alloc = FALSE;
+ }
+
+ /* Make a copy of the URL without credentials and fragment */
+ u = curl_url();
+ if(!u)
+ return CURLE_OUT_OF_MEMORY;
+
+ uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0);
+ if(!uc)
+ uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
+ if(!uc)
+ uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
+ if(!uc)
+ uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
+ if(!uc)
+ uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
+
+ curl_url_cleanup(u);
+
+ if(uc || !referer)
+ return CURLE_OUT_OF_MEMORY;
+
+ data->state.referer = referer;
+ data->state.referer_alloc = TRUE; /* yes, free this later */
+ }
+ }
+ }
+
+ if((type != FOLLOW_RETRY) &&
+ (data->req.httpcode != 401) && (data->req.httpcode != 407) &&
+ Curl_is_absolute_url(newurl, NULL, 0, FALSE)) {
+ /* If this is not redirect due to a 401 or 407 response and an absolute
+ URL: do not allow a custom port number */
+ disallowport = TRUE;
+ }
+
+ DEBUGASSERT(data->state.uh);
+ uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int)
+ ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
+ ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) |
+ CURLU_ALLOW_SPACE |
+ (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)));
+ if(uc) {
+ if(type != FOLLOW_FAKE) {
+ failf(data, "The redirect target URL could not be parsed: %s",
+ curl_url_strerror(uc));
+ return Curl_uc_to_curlcode(uc);
+ }
+
+ /* the URL could not be parsed for some reason, but since this is FAKE
+ mode, just duplicate the field as-is */
+ newurl = strdup(newurl);
+ if(!newurl)
+ return CURLE_OUT_OF_MEMORY;
+ }
+ else {
+ uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0);
+ if(uc)
+ return Curl_uc_to_curlcode(uc);
+
+ /* Clear auth if this redirects to a different port number or protocol,
+ unless permitted */
+ if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) {
+ char *portnum;
+ int port;
+ bool clear = FALSE;
+
+ if(data->set.use_port && data->state.allow_port)
+ /* a custom port is used */
+ port = (int)data->set.use_port;
+ else {
+ uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum,
+ CURLU_DEFAULT_PORT);
+ if(uc) {
+ free(newurl);
+ return Curl_uc_to_curlcode(uc);
+ }
+ port = atoi(portnum);
+ free(portnum);
+ }
+ if(port != data->info.conn_remote_port) {
+ infof(data, "Clear auth, redirects to port from %u to %u",
+ data->info.conn_remote_port, port);
+ clear = TRUE;
+ }
+ else {
+ char *scheme;
+ const struct Curl_handler *p;
+ uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0);
+ if(uc) {
+ free(newurl);
+ return Curl_uc_to_curlcode(uc);
+ }
+
+ p = Curl_get_scheme_handler(scheme);
+ if(p && (p->protocol != data->info.conn_protocol)) {
+ infof(data, "Clear auth, redirects scheme from %s to %s",
+ data->info.conn_scheme, scheme);
+ clear = TRUE;
+ }
+ free(scheme);
+ }
+ if(clear) {
+ Curl_safefree(data->state.aptr.user);
+ Curl_safefree(data->state.aptr.passwd);
+ }
+ }
+ }
+
+ if(type == FOLLOW_FAKE) {
+ /* we are only figuring out the new URL if we would have followed locations
+ but now we are done so we can get out! */
+ data->info.wouldredirect = newurl;
+
+ if(reachedmax) {
+ failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs);
+ return CURLE_TOO_MANY_REDIRECTS;
+ }
+ return CURLE_OK;
+ }
+
+ if(disallowport)
+ data->state.allow_port = FALSE;
+
+ if(data->state.url_alloc)
+ Curl_safefree(data->state.url);
+
+ data->state.url = newurl;
+ data->state.url_alloc = TRUE;
+ Curl_req_soft_reset(&data->req, data);
+ infof(data, "Issue another request to this URL: '%s'", data->state.url);
+
+ /*
+ * We get here when the HTTP code is 300-399 (and 401). We need to perform
+ * differently based on exactly what return code there was.
+ *
+ * News from 7.10.6: we can also get here on a 401 or 407, in case we act on
+ * an HTTP (proxy-) authentication scheme other than Basic.
+ */
+ switch(data->info.httpcode) {
+ /* 401 - Act on a WWW-Authenticate, we keep on moving and do the
+ Authorization: XXXX header in the HTTP request code snippet */
+ /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
+ Proxy-Authorization: XXXX header in the HTTP request code snippet */
+ /* 300 - Multiple Choices */
+ /* 306 - Not used */
+ /* 307 - Temporary Redirect */
+ default: /* for all above (and the unknown ones) */
+ /* Some codes are explicitly mentioned since I have checked RFC2616 and
+ * they seem to be OK to POST to.
+ */
+ break;
+ case 301: /* Moved Permanently */
+ /* (quote from RFC7231, section 6.4.2)
+ *
+ * Note: For historical reasons, a user agent MAY change the request
+ * method from POST to GET for the subsequent request. If this
+ * behavior is undesired, the 307 (Temporary Redirect) status code
+ * can be used instead.
+ *
+ * ----
+ *
+ * Many webservers expect this, so these servers often answers to a POST
+ * request with an error page. To be sure that libcurl gets the page that
+ * most user agents would get, libcurl has to force GET.
+ *
+ * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
+ * can be overridden with CURLOPT_POSTREDIR.
+ */
+ if((data->state.httpreq == HTTPREQ_POST
+ || data->state.httpreq == HTTPREQ_POST_FORM
+ || data->state.httpreq == HTTPREQ_POST_MIME)
+ && !(data->set.keep_post & CURL_REDIR_POST_301)) {
+ infof(data, "Switch from POST to GET");
+ data->state.httpreq = HTTPREQ_GET;
+ Curl_creader_set_rewind(data, FALSE);
+ }
+ break;
+ case 302: /* Found */
+ /* (quote from RFC7231, section 6.4.3)
+ *
+ * Note: For historical reasons, a user agent MAY change the request
+ * method from POST to GET for the subsequent request. If this
+ * behavior is undesired, the 307 (Temporary Redirect) status code
+ * can be used instead.
+ *
+ * ----
+ *
+ * Many webservers expect this, so these servers often answers to a POST
+ * request with an error page. To be sure that libcurl gets the page that
+ * most user agents would get, libcurl has to force GET.
+ *
+ * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
+ * can be overridden with CURLOPT_POSTREDIR.
+ */
+ if((data->state.httpreq == HTTPREQ_POST
+ || data->state.httpreq == HTTPREQ_POST_FORM
+ || data->state.httpreq == HTTPREQ_POST_MIME)
+ && !(data->set.keep_post & CURL_REDIR_POST_302)) {
+ infof(data, "Switch from POST to GET");
+ data->state.httpreq = HTTPREQ_GET;
+ Curl_creader_set_rewind(data, FALSE);
+ }
+ break;
+
+ case 303: /* See Other */
+ /* 'See Other' location is not the resource but a substitute for the
+ * resource. In this case we switch the method to GET/HEAD, unless the
+ * method is POST and the user specified to keep it as POST.
+ * https://github.com/curl/curl/issues/5237#issuecomment-614641049
+ */
+ if(data->state.httpreq != HTTPREQ_GET &&
+ ((data->state.httpreq != HTTPREQ_POST &&
+ data->state.httpreq != HTTPREQ_POST_FORM &&
+ data->state.httpreq != HTTPREQ_POST_MIME) ||
+ !(data->set.keep_post & CURL_REDIR_POST_303))) {
+ data->state.httpreq = HTTPREQ_GET;
+ infof(data, "Switch to %s",
+ data->req.no_body ? "HEAD" : "GET");
+ }
+ break;
+ case 304: /* Not Modified */
+ /* 304 means we did a conditional request and it was "Not modified".
+ * We should not get any Location: header in this response!
+ */
+ break;
+ case 305: /* Use Proxy */
+ /* (quote from RFC2616, section 10.3.6):
+ * "The requested resource MUST be accessed through the proxy given
+ * by the Location field. The Location field gives the URI of the
+ * proxy. The recipient is expected to repeat this single request
+ * via the proxy. 305 responses MUST only be generated by origin
+ * servers."
+ */
+ break;
+ }
+ Curl_pgrsTime(data, TIMER_REDIRECT);
+ Curl_pgrsResetTransferSizes(data);
+
+ return CURLE_OK;
+#endif /* CURL_DISABLE_HTTP */
+}
+
+static CURLMcode state_performing(struct Curl_easy *data,
+ struct curltime *nowp,
+ bool *stream_errorp,
+ CURLcode *resultp)
+{
+ char *newurl = NULL;
+ bool retry = FALSE;
+ timediff_t recv_timeout_ms = 0;
+ timediff_t send_timeout_ms = 0;
+ CURLMcode rc = CURLM_OK;
+ CURLcode result = *resultp = CURLE_OK;
+ *stream_errorp = FALSE;
+
+ /* check if over send speed */
+ if(data->set.max_send_speed)
+ send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul,
+ data->set.max_send_speed,
+ *nowp);
+
+ /* check if over recv speed */
+ if(data->set.max_recv_speed)
+ recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl,
+ data->set.max_recv_speed,
+ *nowp);
+
+ if(send_timeout_ms || recv_timeout_ms) {
+ Curl_ratelimit(data, *nowp);
+ multistate(data, MSTATE_RATELIMITING);
+ if(send_timeout_ms >= recv_timeout_ms)
+ Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
+ else
+ Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
+ return CURLM_OK;
+ }
+
+ /* read/write data if it is ready to do so */
+ result = Curl_sendrecv(data, nowp);
+
+ if(data->req.done || (result == CURLE_RECV_ERROR)) {
+ /* If CURLE_RECV_ERROR happens early enough, we assume it was a race
+ * condition and the server closed the reused connection exactly when we
+ * wanted to use it, so figure out if that is indeed the case.
+ */
+ CURLcode ret = Curl_retry_request(data, &newurl);
+ if(!ret)
+ retry = !!newurl;
+ else if(!result)
+ result = ret;
+
+ if(retry) {
+ /* if we are to retry, set the result to OK and consider the
+ request as done */
+ result = CURLE_OK;
+ data->req.done = TRUE;
+ }
+ }
+ else if((CURLE_HTTP2_STREAM == result) &&
+ Curl_h2_http_1_1_error(data)) {
+ CURLcode ret = Curl_retry_request(data, &newurl);
+
+ if(!ret) {
+ infof(data, "Downgrades to HTTP/1.1");
+ streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1");
+ data->state.httpwant = CURL_HTTP_VERSION_1_1;
+ /* clear the error message bit too as we ignore the one we got */
+ data->state.errorbuf = FALSE;
+ if(!newurl)
+ /* typically for HTTP_1_1_REQUIRED error on first flight */
+ newurl = strdup(data->state.url);
+ /* if we are to retry, set the result to OK and consider the request
+ as done */
+ retry = TRUE;
+ result = CURLE_OK;
+ data->req.done = TRUE;
+ }
+ else
+ result = ret;
+ }
+
+ if(result) {
+ /*
+ * The transfer phase returned error, we mark the connection to get closed
+ * to prevent being reused. This is because we cannot possibly know if the
+ * connection is in a good shape or not now. Unless it is a protocol which
+ * uses two "channels" like FTP, as then the error happened in the data
+ * connection.
+ */
+
+ if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
+ result != CURLE_HTTP2_STREAM)
+ streamclose(data->conn, "Transfer returned error");
+
+ multi_posttransfer(data);
+ multi_done(data, result, TRUE);
+ }
+ else if(data->req.done && !Curl_cwriter_is_paused(data)) {
+
+ /* call this even if the readwrite function returned error */
+ multi_posttransfer(data);
+
+ /* When we follow redirects or is set to retry the connection, we must to
+ go back to the CONNECT state */
+ if(data->req.newurl || retry) {
+ followtype follow = FOLLOW_NONE;
+ if(!retry) {
+ /* if the URL is a follow-location and not just a retried request then
+ figure out the URL here */
+ free(newurl);
+ newurl = data->req.newurl;
+ data->req.newurl = NULL;
+ follow = FOLLOW_REDIR;
+ }
+ else
+ follow = FOLLOW_RETRY;
+ (void)multi_done(data, CURLE_OK, FALSE);
+ /* multi_done() might return CURLE_GOT_NOTHING */
+ result = multi_follow(data, newurl, follow);
+ if(!result) {
+ multistate(data, MSTATE_SETUP);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ }
+ else {
+ /* after the transfer is done, go DONE */
+
+ /* but first check to see if we got a location info even though we are
+ not following redirects */
+ if(data->req.location) {
+ free(newurl);
+ newurl = data->req.location;
+ data->req.location = NULL;
+ result = multi_follow(data, newurl, FOLLOW_FAKE);
+ if(result) {
+ *stream_errorp = TRUE;
+ result = multi_done(data, result, TRUE);
+ }
+ }
+
+ if(!result) {
+ multistate(data, MSTATE_DONE);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ }
+ }
+ else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) {
+ /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer does
+ not get stuck on this transfer at the expense of other concurrent
+ transfers */
+ Curl_expire(data, 0, EXPIRE_RUN_NOW);
+ }
+ free(newurl);
+ *resultp = result;
+ return rc;
+}
+
+static CURLMcode state_do(struct Curl_easy *data,
+ bool *stream_errorp,
+ CURLcode *resultp)
+{
+ CURLMcode rc = CURLM_OK;
+ CURLcode result = CURLE_OK;
+ if(data->set.fprereq) {
+ int prereq_rc;
+
+ /* call the prerequest callback function */
+ Curl_set_in_callback(data, TRUE);
+ prereq_rc = data->set.fprereq(data->set.prereq_userp,
+ data->info.primary.remote_ip,
+ data->info.primary.local_ip,
+ data->info.primary.remote_port,
+ data->info.primary.local_port);
+ Curl_set_in_callback(data, FALSE);
+ if(prereq_rc != CURL_PREREQFUNC_OK) {
+ failf(data, "operation aborted by pre-request callback");
+ /* failure in pre-request callback - do not do any other processing */
+ result = CURLE_ABORTED_BY_CALLBACK;
+ multi_posttransfer(data);
+ multi_done(data, result, FALSE);
+ *stream_errorp = TRUE;
+ goto end;
+ }
+ }
+
+ if(data->set.connect_only == 1) {
+ /* keep connection open for application to use the socket */
+ connkeep(data->conn, "CONNECT_ONLY");
+ multistate(data, MSTATE_DONE);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ else {
+ bool dophase_done = FALSE;
+ /* Perform the protocol's DO action */
+ result = multi_do(data, &dophase_done);
+
+ /* When multi_do() returns failure, data->conn might be NULL! */
+
+ if(!result) {
+ if(!dophase_done) {
+#ifndef CURL_DISABLE_FTP
+ /* some steps needed for wildcard matching */
+ if(data->state.wildcardmatch) {
+ struct WildcardData *wc = data->wildcard;
+ if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
+ /* skip some states if it is important */
+ multi_done(data, CURLE_OK, FALSE);
+
+ /* if there is no connection left, skip the DONE state */
+ multistate(data, data->conn ?
+ MSTATE_DONE : MSTATE_COMPLETED);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ goto end;
+ }
+ }
+#endif
+ /* DO was not completed in one function call, we must continue
+ DOING... */
+ multistate(data, MSTATE_DOING);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+
+ /* after DO, go DO_DONE... or DO_MORE */
+ else if(data->conn->bits.do_more) {
+ /* we are supposed to do more, but we need to sit down, relax and wait
+ a little while first */
+ multistate(data, MSTATE_DOING_MORE);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ else {
+ /* we are done with the DO, now DID */
+ multistate(data, MSTATE_DID);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ }
+ else if((CURLE_SEND_ERROR == result) &&
+ data->conn->bits.reuse) {
+ /*
+ * In this situation, a connection that we were trying to use may have
+ * unexpectedly died. If possible, send the connection back to the
+ * CONNECT phase so we can try again.
+ */
+ char *newurl = NULL;
+ followtype follow = FOLLOW_NONE;
+ CURLcode drc;
+
+ drc = Curl_retry_request(data, &newurl);
+ if(drc) {
+ /* a failure here pretty much implies an out of memory */
+ result = drc;
+ *stream_errorp = TRUE;
+ }
+
+ multi_posttransfer(data);
+ drc = multi_done(data, result, FALSE);
+
+ /* When set to retry the connection, we must go back to the CONNECT
+ * state */
+ if(newurl) {
+ if(!drc || (drc == CURLE_SEND_ERROR)) {
+ follow = FOLLOW_RETRY;
+ drc = multi_follow(data, newurl, follow);
+ if(!drc) {
+ multistate(data, MSTATE_SETUP);
+ rc = CURLM_CALL_MULTI_PERFORM;
+ result = CURLE_OK;
+ }
+ else {
+ /* Follow failed */
+ result = drc;
+ }
+ }
+ else {
+ /* done did not return OK or SEND_ERROR */
+ result = drc;
+ }
+ }
+ else {
+ /* Have error handler disconnect conn if we cannot retry */
+ *stream_errorp = TRUE;
+ }
+ free(newurl);
+ }
+ else {
+ /* failure detected */
+ multi_posttransfer(data);
+ if(data->conn)
+ multi_done(data, result, FALSE);
+ *stream_errorp = TRUE;
+ }
+ }
+end:
+ *resultp = result;
+ return rc;
+}
+
+static CURLMcode state_ratelimiting(struct Curl_easy *data,
+ struct curltime *nowp,
+ CURLcode *resultp)
+{
+ CURLcode result = CURLE_OK;
+ CURLMcode rc = CURLM_OK;
+ DEBUGASSERT(data->conn);
+ /* if both rates are within spec, resume transfer */
+ if(Curl_pgrsUpdate(data))
+ result = CURLE_ABORTED_BY_CALLBACK;
+ else
+ result = Curl_speedcheck(data, *nowp);
+
+ if(result) {
+ if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
+ result != CURLE_HTTP2_STREAM)
+ streamclose(data->conn, "Transfer returned error");
+
+ multi_posttransfer(data);
+ multi_done(data, result, TRUE);
+ }
+ else {
+ timediff_t recv_timeout_ms = 0;
+ timediff_t send_timeout_ms = 0;
+ if(data->set.max_send_speed)
+ send_timeout_ms =
+ Curl_pgrsLimitWaitTime(&data->progress.ul,
+ data->set.max_send_speed,
+ *nowp);
+
+ if(data->set.max_recv_speed)
+ recv_timeout_ms =
+ Curl_pgrsLimitWaitTime(&data->progress.dl,
+ data->set.max_recv_speed,
+ *nowp);
+
+ if(!send_timeout_ms && !recv_timeout_ms) {
+ multistate(data, MSTATE_PERFORMING);
+ Curl_ratelimit(data, *nowp);
+ /* start performing again right away */
+ rc = CURLM_CALL_MULTI_PERFORM;
+ }
+ else if(send_timeout_ms >= recv_timeout_ms)
+ Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
+ else
+ Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
+ }
+ *resultp = result;
+ return rc;
+}
+
+static CURLMcode state_resolving(struct Curl_multi *multi,
+ struct Curl_easy *data,
+ bool *stream_errorp,
+ CURLcode *resultp)
+{
+ struct Curl_dns_entry *dns = NULL;
+ struct connectdata *conn = data->conn;
+ const char *hostname;
+ CURLcode result = CURLE_OK;
+ CURLMcode rc = CURLM_OK;
+
+ DEBUGASSERT(conn);
+#ifndef CURL_DISABLE_PROXY
+ if(conn->bits.httpproxy)
+ hostname = conn->http_proxy.host.name;
+ else
+#endif
+ if(conn->bits.conn_to_host)
+ hostname = conn->conn_to_host.name;
+ else
+ hostname = conn->host.name;
+
+ /* check if we have the name resolved by now */
+ dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port);
+
+ if(dns) {
+#ifdef CURLRES_ASYNCH
+ data->state.async.dns = dns;
+ data->state.async.done = TRUE;
+#endif
+ result = CURLE_OK;
+ infof(data, "Hostname '%s' was found in DNS cache", hostname);
+ }
+
+ if(!dns)
+ result = Curl_resolv_check(data, &dns);
+
+ /* Update sockets here, because the socket(s) may have been closed and the
+ application thus needs to be told, even if it is likely that the same
+ socket(s) will again be used further down. If the name has not yet been
+ resolved, it is likely that new sockets have been opened in an attempt to
+ contact another resolver. */
+ rc = singlesocket(multi, data);
+ if(rc)
+ return rc;
+
+ if(dns) {
+ bool connected;
+ /* Perform the next step in the connection phase, and then move on to the
+ WAITCONNECT state */
+ result = Curl_once_resolved(data, &connected);
+
+ if(result)
+ /* if Curl_once_resolved() returns failure, the connection struct is
+ already freed and gone */
+ data->conn = NULL; /* no more connection */
+ else {
+ /* call again please so that we get the next socket setup */
+ rc = CURLM_CALL_MULTI_PERFORM;
+ if(connected)
+ multistate(data, MSTATE_PROTOCONNECT);
+ else {
+ multistate(data, MSTATE_CONNECTING);
+ }
+ }
+ }
+
+ if(result)
+ /* failure detected */
+ *stream_errorp = TRUE;
+
+ *resultp = result;
+ return rc;
+}
+
+static CURLMcode state_connect(struct Curl_multi *multi,
+ struct Curl_easy *data,
+ struct curltime *nowp,
+ CURLcode *resultp)
+{
+ /* Connect. We want to get a connection identifier filled in. This state can
+ be entered from SETUP and from PENDING. */
+ bool connected;
+ bool async;
+ CURLMcode rc = CURLM_OK;
+ CURLcode result = Curl_connect(data, &async, &connected);
+ if(CURLE_NO_CONNECTION_AVAILABLE == result) {
+ /* There was no connection available. We will go to the pending state and
+ wait for an available connection. */
+ multistate(data, MSTATE_PENDING);
+ /* unlink from process list */
+ Curl_node_remove(&data->multi_queue);
+ /* add handle to pending list */
+ Curl_llist_append(&multi->pending, data, &data->multi_queue);
+ *resultp = CURLE_OK;
+ return rc;
+ }
+ else
+ process_pending_handles(data->multi);
+
+ if(!result) {
+ *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE);
+ if(async)
+ /* We are now waiting for an asynchronous name lookup */
+ multistate(data, MSTATE_RESOLVING);
+ else {
+ /* after the connect has been sent off, go WAITCONNECT unless the
+ protocol connect is already done and we can go directly to WAITDO or
+ DO! */
+ rc = CURLM_CALL_MULTI_PERFORM;
+
+ if(connected) {
+ if(!data->conn->bits.reuse &&
+ Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
+ /* new connection, can multiplex, wake pending handles */
+ process_pending_handles(data->multi);
+ }
+ multistate(data, MSTATE_PROTOCONNECT);
+ }
+ else {
+ multistate(data, MSTATE_CONNECTING);
+ }
+ }
+ }
+ *resultp = result;
+ return rc;
+}
+
static CURLMcode multi_runsingle(struct Curl_multi *multi,
struct curltime *nowp,
struct Curl_easy *data)
{
struct Curl_message *msg = NULL;
bool connected;
- bool async;
bool protocol_connected = FALSE;
bool dophase_done = FALSE;
CURLMcode rc;
CURLcode result = CURLE_OK;
- timediff_t recv_timeout_ms;
- timediff_t send_timeout_ms;
int control;
if(!GOOD_EASY_HANDLE(data))
@@ -1862,14 +2637,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Wait for the connect state as only then is the start time stored, but
we must not check already completed handles */
if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED) &&
- multi_handle_timeout(data, nowp, &stream_error, &result, FALSE))
+ multi_handle_timeout(data, nowp, &stream_error, &result))
/* Skip the statemachine and go directly to error handling section. */
goto statemachine_end;
switch(data->mstate) {
case MSTATE_INIT:
- /* Transitional state. init this transfer. A handle never comes
- back to this state. */
+ /* Transitional state. init this transfer. A handle never comes back to
+ this state. */
result = Curl_pretransfer(data);
if(result)
break;
@@ -1895,119 +2670,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, FALLTHROUGH();
case MSTATE_CONNECT:
- /* Connect. We want to get a connection identifier filled in. This state
- can be entered from SETUP and from PENDING. */
- result = Curl_connect(data, &async, &connected);
- if(CURLE_NO_CONNECTION_AVAILABLE == result) {
- /* There was no connection available. We will go to the pending
- state and wait for an available connection. */
- multistate(data, MSTATE_PENDING);
- /* unlink from process list */
- Curl_node_remove(&data->multi_queue);
- /* add handle to pending list */
- Curl_llist_append(&multi->pending, data, &data->multi_queue);
- result = CURLE_OK;
- break;
- }
- else
- process_pending_handles(data->multi);
-
- if(!result) {
- *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE);
- if(async)
- /* We are now waiting for an asynchronous name lookup */
- multistate(data, MSTATE_RESOLVING);
- else {
- /* after the connect has been sent off, go WAITCONNECT unless the
- protocol connect is already done and we can go directly to
- WAITDO or DO! */
- rc = CURLM_CALL_MULTI_PERFORM;
-
- if(connected) {
- if(!data->conn->bits.reuse &&
- Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
- /* new connection, can multiplex, wake pending handles */
- process_pending_handles(data->multi);
- }
- multistate(data, MSTATE_PROTOCONNECT);
- }
- else {
- multistate(data, MSTATE_CONNECTING);
- }
- }
- }
+ rc = state_connect(multi, data, nowp, &result);
break;
case MSTATE_RESOLVING:
/* awaiting an asynch name resolve to complete */
- {
- struct Curl_dns_entry *dns = NULL;
- struct connectdata *conn = data->conn;
- const char *hostname;
-
- DEBUGASSERT(conn);
-#ifndef CURL_DISABLE_PROXY
- if(conn->bits.httpproxy)
- hostname = conn->http_proxy.host.name;
- else
-#endif
- if(conn->bits.conn_to_host)
- hostname = conn->conn_to_host.name;
- else
- hostname = conn->host.name;
-
- /* check if we have the name resolved by now */
- dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port);
-
- if(dns) {
-#ifdef CURLRES_ASYNCH
- data->state.async.dns = dns;
- data->state.async.done = TRUE;
-#endif
- result = CURLE_OK;
- infof(data, "Hostname '%s' was found in DNS cache", hostname);
- }
-
- if(!dns)
- result = Curl_resolv_check(data, &dns);
-
- /* Update sockets here, because the socket(s) may have been
- closed and the application thus needs to be told, even if it
- is likely that the same socket(s) will again be used further
- down. If the name has not yet been resolved, it is likely
- that new sockets have been opened in an attempt to contact
- another resolver. */
- rc = singlesocket(multi, data);
- if(rc)
- return rc;
-
- if(dns) {
- /* Perform the next step in the connection phase, and then move on
- to the WAITCONNECT state */
- result = Curl_once_resolved(data, &connected);
-
- if(result)
- /* if Curl_once_resolved() returns failure, the connection struct
- is already freed and gone */
- data->conn = NULL; /* no more connection */
- else {
- /* call again please so that we get the next socket setup */
- rc = CURLM_CALL_MULTI_PERFORM;
- if(connected)
- multistate(data, MSTATE_PROTOCONNECT);
- else {
- multistate(data, MSTATE_CONNECTING);
- }
- }
- }
-
- if(result) {
- /* failure detected */
- stream_error = TRUE;
- break;
- }
- }
- break;
+ rc = state_resolving(multi, data, &stream_error, &result);
+ break;
#ifndef CURL_DISABLE_HTTP
case MSTATE_TUNNELING:
@@ -2048,9 +2717,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case MSTATE_PROTOCONNECT:
if(!result && data->conn->bits.reuse) {
- /* ftp seems to hang when protoconnect on reused connection
- * since we handle PROTOCONNECT in general inside the filers, it
- * seems wrong to restart this on a reused connection. */
+ /* ftp seems to hang when protoconnect on reused connection since we
+ * handle PROTOCONNECT in general inside the filers, it seems wrong to
+ * restart this on a reused connection.
+ */
multistate(data, MSTATE_DO);
rc = CURLM_CALL_MULTI_PERFORM;
break;
@@ -2092,135 +2762,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break;
case MSTATE_DO:
- if(data->set.fprereq) {
- int prereq_rc;
-
- /* call the prerequest callback function */
- Curl_set_in_callback(data, true);
- prereq_rc = data->set.fprereq(data->set.prereq_userp,
- data->info.primary.remote_ip,
- data->info.primary.local_ip,
- data->info.primary.remote_port,
- data->info.primary.local_port);
- Curl_set_in_callback(data, false);
- if(prereq_rc != CURL_PREREQFUNC_OK) {
- failf(data, "operation aborted by pre-request callback");
- /* failure in pre-request callback - do not do any other
- processing */
- result = CURLE_ABORTED_BY_CALLBACK;
- multi_posttransfer(data);
- multi_done(data, result, FALSE);
- stream_error = TRUE;
- break;
- }
- }
-
- if(data->set.connect_only == 1) {
- /* keep connection open for application to use the socket */
- connkeep(data->conn, "CONNECT_ONLY");
- multistate(data, MSTATE_DONE);
- result = CURLE_OK;
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- else {
- /* Perform the protocol's DO action */
- result = multi_do(data, &dophase_done);
-
- /* When multi_do() returns failure, data->conn might be NULL! */
-
- if(!result) {
- if(!dophase_done) {
-#ifndef CURL_DISABLE_FTP
- /* some steps needed for wildcard matching */
- if(data->state.wildcardmatch) {
- struct WildcardData *wc = data->wildcard;
- if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
- /* skip some states if it is important */
- multi_done(data, CURLE_OK, FALSE);
-
- /* if there is no connection left, skip the DONE state */
- multistate(data, data->conn ?
- MSTATE_DONE : MSTATE_COMPLETED);
- rc = CURLM_CALL_MULTI_PERFORM;
- break;
- }
- }
-#endif
- /* DO was not completed in one function call, we must continue
- DOING... */
- multistate(data, MSTATE_DOING);
- rc = CURLM_CALL_MULTI_PERFORM;
- }
-
- /* after DO, go DO_DONE... or DO_MORE */
- else if(data->conn->bits.do_more) {
- /* we are supposed to do more, but we need to sit down, relax
- and wait a little while first */
- multistate(data, MSTATE_DOING_MORE);
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- else {
- /* we are done with the DO, now DID */
- multistate(data, MSTATE_DID);
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- }
- else if((CURLE_SEND_ERROR == result) &&
- data->conn->bits.reuse) {
- /*
- * In this situation, a connection that we were trying to use
- * may have unexpectedly died. If possible, send the connection
- * back to the CONNECT phase so we can try again.
- */
- char *newurl = NULL;
- followtype follow = FOLLOW_NONE;
- CURLcode drc;
-
- drc = Curl_retry_request(data, &newurl);
- if(drc) {
- /* a failure here pretty much implies an out of memory */
- result = drc;
- stream_error = TRUE;
- }
-
- multi_posttransfer(data);
- drc = multi_done(data, result, FALSE);
-
- /* When set to retry the connection, we must go back to the CONNECT
- * state */
- if(newurl) {
- if(!drc || (drc == CURLE_SEND_ERROR)) {
- follow = FOLLOW_RETRY;
- drc = Curl_follow(data, newurl, follow);
- if(!drc) {
- multistate(data, MSTATE_SETUP);
- rc = CURLM_CALL_MULTI_PERFORM;
- result = CURLE_OK;
- }
- else {
- /* Follow failed */
- result = drc;
- }
- }
- else {
- /* done did not return OK or SEND_ERROR */
- result = drc;
- }
- }
- else {
- /* Have error handler disconnect conn if we cannot retry */
- stream_error = TRUE;
- }
- free(newurl);
- }
- else {
- /* failure detected */
- multi_posttransfer(data);
- if(data->conn)
- multi_done(data, result, FALSE);
- stream_error = TRUE;
- }
- }
+ rc = state_do(data, &stream_error, &result);
break;
case MSTATE_DOING:
@@ -2230,7 +2772,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!result) {
if(dophase_done) {
/* after DO, go DO_DONE or DO_MORE */
- multistate(data, data->conn->bits.do_more?
+ multistate(data, data->conn->bits.do_more ?
MSTATE_DOING_MORE : MSTATE_DID);
rc = CURLM_CALL_MULTI_PERFORM;
} /* dophase_done */
@@ -2254,7 +2796,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(control) {
/* if positive, advance to DO_DONE
if negative, go back to DOING */
- multistate(data, control == 1?
+ multistate(data, control == 1 ?
MSTATE_DID : MSTATE_DOING);
rc = CURLM_CALL_MULTI_PERFORM;
}
@@ -2293,195 +2835,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break;
case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */
- DEBUGASSERT(data->conn);
- /* if both rates are within spec, resume transfer */
- if(Curl_pgrsUpdate(data))
- result = CURLE_ABORTED_BY_CALLBACK;
- else
- result = Curl_speedcheck(data, *nowp);
-
- if(result) {
- if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
- result != CURLE_HTTP2_STREAM)
- streamclose(data->conn, "Transfer returned error");
-
- multi_posttransfer(data);
- multi_done(data, result, TRUE);
- }
- else {
- send_timeout_ms = 0;
- if(data->set.max_send_speed)
- send_timeout_ms =
- Curl_pgrsLimitWaitTime(&data->progress.ul,
- data->set.max_send_speed,
- *nowp);
-
- recv_timeout_ms = 0;
- if(data->set.max_recv_speed)
- recv_timeout_ms =
- Curl_pgrsLimitWaitTime(&data->progress.dl,
- data->set.max_recv_speed,
- *nowp);
-
- if(!send_timeout_ms && !recv_timeout_ms) {
- multistate(data, MSTATE_PERFORMING);
- Curl_ratelimit(data, *nowp);
- /* start performing again right away */
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- else if(send_timeout_ms >= recv_timeout_ms)
- Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
- else
- Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
- }
+ rc = state_ratelimiting(data, nowp, &result);
break;
case MSTATE_PERFORMING:
- {
- char *newurl = NULL;
- bool retry = FALSE;
- /* check if over send speed */
- send_timeout_ms = 0;
- if(data->set.max_send_speed)
- send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul,
- data->set.max_send_speed,
- *nowp);
-
- /* check if over recv speed */
- recv_timeout_ms = 0;
- if(data->set.max_recv_speed)
- recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl,
- data->set.max_recv_speed,
- *nowp);
-
- if(send_timeout_ms || recv_timeout_ms) {
- Curl_ratelimit(data, *nowp);
- multistate(data, MSTATE_RATELIMITING);
- if(send_timeout_ms >= recv_timeout_ms)
- Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
- else
- Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
- break;
- }
-
- /* read/write data if it is ready to do so */
- result = Curl_sendrecv(data, nowp);
-
- if(data->req.done || (result == CURLE_RECV_ERROR)) {
- /* If CURLE_RECV_ERROR happens early enough, we assume it was a race
- * condition and the server closed the reused connection exactly when
- * we wanted to use it, so figure out if that is indeed the case.
- */
- CURLcode ret = Curl_retry_request(data, &newurl);
- if(!ret)
- retry = (newurl)?TRUE:FALSE;
- else if(!result)
- result = ret;
-
- if(retry) {
- /* if we are to retry, set the result to OK and consider the
- request as done */
- result = CURLE_OK;
- data->req.done = TRUE;
- }
- }
- else if((CURLE_HTTP2_STREAM == result) &&
- Curl_h2_http_1_1_error(data)) {
- CURLcode ret = Curl_retry_request(data, &newurl);
-
- if(!ret) {
- infof(data, "Downgrades to HTTP/1.1");
- streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1");
- data->state.httpwant = CURL_HTTP_VERSION_1_1;
- /* clear the error message bit too as we ignore the one we got */
- data->state.errorbuf = FALSE;
- if(!newurl)
- /* typically for HTTP_1_1_REQUIRED error on first flight */
- newurl = strdup(data->state.url);
- /* if we are to retry, set the result to OK and consider the request
- as done */
- retry = TRUE;
- result = CURLE_OK;
- data->req.done = TRUE;
- }
- else
- result = ret;
- }
-
- if(result) {
- /*
- * The transfer phase returned error, we mark the connection to get
- * closed to prevent being reused. This is because we cannot possibly
- * know if the connection is in a good shape or not now. Unless it is
- * a protocol which uses two "channels" like FTP, as then the error
- * happened in the data connection.
- */
-
- if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
- result != CURLE_HTTP2_STREAM)
- streamclose(data->conn, "Transfer returned error");
-
- multi_posttransfer(data);
- multi_done(data, result, TRUE);
- }
- else if(data->req.done && !Curl_cwriter_is_paused(data)) {
-
- /* call this even if the readwrite function returned error */
- multi_posttransfer(data);
-
- /* When we follow redirects or is set to retry the connection, we must
- to go back to the CONNECT state */
- if(data->req.newurl || retry) {
- followtype follow = FOLLOW_NONE;
- if(!retry) {
- /* if the URL is a follow-location and not just a retried request
- then figure out the URL here */
- free(newurl);
- newurl = data->req.newurl;
- data->req.newurl = NULL;
- follow = FOLLOW_REDIR;
- }
- else
- follow = FOLLOW_RETRY;
- (void)multi_done(data, CURLE_OK, FALSE);
- /* multi_done() might return CURLE_GOT_NOTHING */
- result = Curl_follow(data, newurl, follow);
- if(!result) {
- multistate(data, MSTATE_SETUP);
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- }
- else {
- /* after the transfer is done, go DONE */
-
- /* but first check to see if we got a location info even though we
- are not following redirects */
- if(data->req.location) {
- free(newurl);
- newurl = data->req.location;
- data->req.location = NULL;
- result = Curl_follow(data, newurl, FOLLOW_FAKE);
- if(result) {
- stream_error = TRUE;
- result = multi_done(data, result, TRUE);
- }
- }
-
- if(!result) {
- multistate(data, MSTATE_DONE);
- rc = CURLM_CALL_MULTI_PERFORM;
- }
- }
- }
- else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) {
- /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer
- will not get stuck on this transfer at the expense of other
- concurrent transfers */
- Curl_expire(data, 0, EXPIRE_RUN_NOW);
- }
- free(newurl);
+ rc = state_performing(data, nowp, &stream_error, &result);
break;
- }
case MSTATE_DONE:
/* this state is highly transient, so run another loop after this */
@@ -2529,14 +2888,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->mstate >= MSTATE_CONNECT &&
data->mstate < MSTATE_DO &&
rc != CURLM_CALL_MULTI_PERFORM &&
- !multi_ischanged(multi, false)) {
+ !multi_ischanged(multi, FALSE)) {
/* We now handle stream timeouts if and only if this will be the last
* loop iteration. We only check this on the last iteration to ensure
* that if we know we have additional work to do immediately
* (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before
* declaring the connection timed out as we may almost have a completed
* connection. */
- multi_handle_timeout(data, nowp, &stream_error, &result, FALSE);
+ multi_handle_timeout(data, nowp, &stream_error, &result);
}
statemachine_end:
@@ -2584,8 +2943,8 @@ statemachine_end: streamclose(data->conn, "Aborted by callback");
/* if not yet in DONE state, go there, otherwise COMPLETED */
- multistate(data, (data->mstate < MSTATE_DONE)?
- MSTATE_DONE: MSTATE_COMPLETED);
+ multistate(data, (data->mstate < MSTATE_DONE) ?
+ MSTATE_DONE : MSTATE_COMPLETED);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
@@ -2621,13 +2980,14 @@ statemachine_end: }
-CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
+CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
{
CURLMcode returncode = CURLM_OK;
struct Curl_tree *t = NULL;
struct curltime now = Curl_now();
struct Curl_llist_node *e;
struct Curl_llist_node *n = NULL;
+ struct Curl_multi *multi = m;
SIGPIPE_VARIABLE(pipe_st);
if(!GOOD_MULTI_HANDLE(multi))
@@ -2679,8 +3039,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) if(data->mstate == MSTATE_PENDING) {
bool stream_unused;
CURLcode result_unused;
- if(multi_handle_timeout(data, &now, &stream_unused, &result_unused,
- FALSE)) {
+ if(multi_handle_timeout(data, &now, &stream_unused, &result_unused)) {
infof(data, "PENDING handle timeout");
move_pending_to_connect(multi, data);
}
@@ -2714,16 +3073,15 @@ static void unlink_all_msgsent_handles(struct Curl_multi *multi) }
}
-CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
+CURLMcode curl_multi_cleanup(CURLM *m)
{
+ struct Curl_multi *multi = m;
if(GOOD_MULTI_HANDLE(multi)) {
struct Curl_llist_node *e;
struct Curl_llist_node *n;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- multi->magic = 0; /* not good anymore */
-
/* move the pending and msgsent entries back to process
so that there is just one list to iterate over */
unlink_all_msgsent_handles(multi);
@@ -2757,6 +3115,8 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) Curl_cpool_destroy(&multi->cpool);
+ multi->magic = 0; /* not good anymore */
+
sockhash_destroy(&multi->sockhash);
Curl_hash_destroy(&multi->proto_hash);
Curl_hash_destroy(&multi->hostcache);
@@ -2791,9 +3151,10 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) * beyond. The current design is fully O(1).
*/
-CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue)
+CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue)
{
struct Curl_message *msg;
+ struct Curl_multi *multi = m;
*msgs_in_queue = 0; /* default to none */
@@ -2866,7 +3227,7 @@ CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi, if(entry) {
/* check if new for this transfer */
unsigned int j;
- for(j = 0; j< last_ps->num; j++) {
+ for(j = 0; j < last_ps->num; j++) {
if(s == last_ps->sockets[j]) {
last_action = last_ps->actions[j];
break;
@@ -3220,7 +3581,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, else {
/* Expire with out current now, so we will get it below when
* asking the splaytree for expired transfers. */
- Curl_expire_ex(data, &mrc.now, 0, EXPIRE_RUN_NOW);
+ expire_ex(data, &mrc.now, 0, EXPIRE_RUN_NOW);
}
}
}
@@ -3256,12 +3617,13 @@ out: }
#undef curl_multi_setopt
-CURLMcode curl_multi_setopt(struct Curl_multi *multi,
+CURLMcode curl_multi_setopt(CURLM *m,
CURLMoption option, ...)
{
CURLMcode res = CURLM_OK;
va_list param;
unsigned long uarg;
+ struct Curl_multi *multi = m;
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
@@ -3337,24 +3699,26 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, /* we define curl_multi_socket() in the public multi.h header */
#undef curl_multi_socket
-CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s,
- int *running_handles)
+CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles)
{
+ struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, FALSE, s, 0, running_handles);
}
-CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s,
+CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s,
int ev_bitmask, int *running_handles)
{
+ struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, FALSE, s, ev_bitmask, running_handles);
}
-CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles)
+CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles)
{
+ struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles);
@@ -3379,7 +3743,7 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, multi->timetree = Curl_splay(tv_zero, multi->timetree);
/* this will not return NULL from a non-emtpy tree, but some compilers
* are not convinced of that. Analyzers are hard. */
- *expire_time = multi->timetree? multi->timetree->key : tv_zero;
+ *expire_time = multi->timetree ? multi->timetree->key : tv_zero;
/* 'multi->timetree' will be non-NULL here but the compilers sometimes
yell at us if we assume so */
@@ -3404,10 +3768,11 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, return CURLM_OK;
}
-CURLMcode curl_multi_timeout(struct Curl_multi *multi,
+CURLMcode curl_multi_timeout(CURLM *m,
long *timeout_ms)
{
struct curltime expire_time;
+ struct Curl_multi *multi = m;
/* First, make some basic checks that the CURLM handle is a good handle */
if(!GOOD_MULTI_HANDLE(multi))
@@ -3556,20 +3921,9 @@ multi_addtimeout(struct Curl_easy *data, return CURLM_OK;
}
-/*
- * Curl_expire()
- *
- * given a number of milliseconds from now to use to set the 'act before
- * this'-time for the transfer, to be extracted by curl_multi_timeout()
- *
- * The timeout will be added to a queue of timeouts if it defines a moment in
- * time that is later than the current head of queue.
- *
- * Expire replaces a former timeout using the same id if already set.
- */
-static void Curl_expire_ex(struct Curl_easy *data,
- const struct curltime *nowp,
- timediff_t milli, expire_id id)
+static void expire_ex(struct Curl_easy *data,
+ const struct curltime *nowp,
+ timediff_t milli, expire_id id)
{
struct Curl_multi *multi = data->multi;
struct curltime *curr_expire = &data->state.expiretime;
@@ -3627,10 +3981,21 @@ static void Curl_expire_ex(struct Curl_easy *data, &data->state.timenode);
}
+/*
+ * Curl_expire()
+ *
+ * given a number of milliseconds from now to use to set the 'act before
+ * this'-time for the transfer, to be extracted by curl_multi_timeout()
+ *
+ * The timeout will be added to a queue of timeouts if it defines a moment in
+ * time that is later than the current head of queue.
+ *
+ * Expire replaces a former timeout using the same id if already set.
+ */
void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
{
struct curltime now = Curl_now();
- Curl_expire_ex(data, &now, milli, id);
+ expire_ex(data, &now, milli, id);
}
/*
@@ -3684,10 +4049,11 @@ bool Curl_expire_clear(struct Curl_easy *data) return FALSE;
}
-CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s,
+CURLMcode curl_multi_assign(CURLM *m, curl_socket_t s,
void *hashp)
{
struct Curl_sh_entry *there = NULL;
+ struct Curl_multi *multi = m;
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
@@ -3758,10 +4124,10 @@ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) return multi->max_concurrent_streams;
}
-struct Curl_easy **curl_multi_get_handles(struct Curl_multi *multi)
+CURL **curl_multi_get_handles(CURLM *m)
{
- struct Curl_easy **a = malloc(sizeof(struct Curl_easy *) *
- (multi->num_easy + 1));
+ struct Curl_multi *multi = m;
+ CURL **a = malloc(sizeof(struct Curl_easy *) * (multi->num_easy + 1));
if(a) {
unsigned int i = 0;
struct Curl_llist_node *e;
@@ -3882,6 +4248,51 @@ void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) data->multi->xfer_ulbuf_borrowed = FALSE;
}
+CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data,
+ size_t blen, char **pbuf)
+{
+ DEBUGASSERT(data);
+ DEBUGASSERT(data->multi);
+ *pbuf = NULL;
+ if(!data->multi) {
+ failf(data, "transfer has no multi handle");
+ return CURLE_FAILED_INIT;
+ }
+ if(data->multi->xfer_sockbuf_borrowed) {
+ failf(data, "attempt to borrow xfer_sockbuf when already borrowed");
+ return CURLE_AGAIN;
+ }
+
+ if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) {
+ /* not large enough, get a new one */
+ free(data->multi->xfer_sockbuf);
+ data->multi->xfer_sockbuf = NULL;
+ data->multi->xfer_sockbuf_len = 0;
+ }
+
+ if(!data->multi->xfer_sockbuf) {
+ data->multi->xfer_sockbuf = malloc(blen);
+ if(!data->multi->xfer_sockbuf) {
+ failf(data, "could not allocate xfer_sockbuf of %zu bytes", blen);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ data->multi->xfer_sockbuf_len = blen;
+ }
+
+ data->multi->xfer_sockbuf_borrowed = TRUE;
+ *pbuf = data->multi->xfer_sockbuf;
+ return CURLE_OK;
+}
+
+void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf)
+{
+ (void)buf;
+ DEBUGASSERT(data);
+ DEBUGASSERT(data->multi);
+ DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf);
+ data->multi->xfer_sockbuf_borrowed = FALSE;
+}
+
static void multi_xfer_bufs_free(struct Curl_multi *multi)
{
DEBUGASSERT(multi);
@@ -3891,6 +4302,9 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi) Curl_safefree(multi->xfer_ulbuf);
multi->xfer_ulbuf_len = 0;
multi->xfer_ulbuf_borrowed = FALSE;
+ Curl_safefree(multi->xfer_sockbuf);
+ multi->xfer_sockbuf_len = 0;
+ multi->xfer_sockbuf_borrowed = FALSE;
}
struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi,
|