#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) ? 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 = 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);
+ result = CURLE_OK;
+ 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))
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;
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:
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;
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 = 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_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:
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 = 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_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 */