From 5d2ecfef56e49a8e4bfad25a582ff1597987f717 Mon Sep 17 00:00:00 2001 From: dartraiden Date: Wed, 6 Nov 2024 20:55:13 +0300 Subject: libcurl: update to 8.11.0 --- libs/libcurl/src/multi.c | 1414 ++++++++++++++++++++++++++++++---------------- 1 file changed, 914 insertions(+), 500 deletions(-) (limited to 'libs/libcurl/src/multi.c') 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, -- cgit v1.2.3