http: refactor http_on_response into sub functions
No binary (build failed)
—
(log yok)
| @@ -3889,6 +3889,226 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data, | |
| return CURLE_OK; | |
| } | |
| + | /* |
| + | * Handle a 101 Switching Protocols response. Performs the actual protocol |
| + | * upgrade to HTTP/2 or WebSocket based on what was requested. |
| + | */ |
| + | static CURLcode http_on_101_upgrade(struct Curl_easy *data, |
| + | const char *buf, size_t blen, |
| + | size_t *pconsumed, |
| + | bool *conn_changed) |
| + | { |
| + | struct connectdata *conn = data->conn; |
| + | struct SingleRequest *k = &data->req; |
| + | |
| + | #if !defined(USE_NGHTTP2) && defined(CURL_DISABLE_WEBSOCKETS) |
| + | (void)buf; |
| + | (void)blen; |
| + | (void)pconsumed; |
| + | #else |
| + | CURLcode result; |
| + | int upgr101_requested = k->upgr101; |
| + | #endif |
| + | |
| + | if(k->httpversion_sent != 11) { |
| + | /* invalid for other HTTP versions */ |
| + | failf(data, "server sent 101 response while not talking HTTP/1.1"); |
| + | return CURLE_WEIRD_SERVER_REPLY; |
| + | } |
| + | |
| + | /* Whatever the success, upgrade was selected. */ |
| + | k->upgr101 = UPGR101_RECEIVED; |
| + | conn->bits.upgrade_in_progress = FALSE; |
| + | *conn_changed = TRUE; |
| + | |
| + | /* To be fully compliant, we would check the "Upgrade:" response header to |
| + | * mention the protocol we requested. */ |
| + | #ifdef USE_NGHTTP2 |
| + | if(upgr101_requested == UPGR101_H2) { |
| + | /* Switch to HTTP/2, where we will get more responses. blen bytes in buf |
| + | * are already h2 protocol bytes */ |
| + | infof(data, "Received 101, Switching to HTTP/2"); |
| + | result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); |
| + | if(!result) |
| + | *pconsumed += blen; |
| + | return result; |
| + | } |
| + | #endif |
| + | #ifndef CURL_DISABLE_WEBSOCKETS |
| + | if(upgr101_requested == UPGR101_WS) { |
| + | /* Switch to WebSocket, where we now stream ws frames. blen bytes in buf |
| + | * are already ws protocol bytes */ |
| + | infof(data, "Received 101, Switching to WebSocket"); |
| + | result = Curl_ws_accept(data, buf, blen); |
| + | if(!result) |
| + | *pconsumed += blen; /* ws accept handled the data */ |
| + | return result; |
| + | } |
| + | #endif |
| + | /* We silently accept this as the final response. What are we switching to |
| + | * if we did not ask for an Upgrade? Maybe the application provided an |
| + | * `Upgrade: xxx` header? */ |
| + | k->header = FALSE; |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | /* |
| + | * Handle 1xx intermediate HTTP responses. Sets up state for more |
| + | * headers and processes 100-continue and 101 upgrade responses. |
| + | */ |
| + | static CURLcode http_on_1xx_response(struct Curl_easy *data, |
| + | const char *buf, size_t blen, |
| + | size_t *pconsumed, |
| + | bool *conn_changed) |
| + | { |
| + | struct SingleRequest *k = &data->req; |
| + | |
| + | /* "A user agent MAY ignore unexpected 1xx status responses." |
| + | * By default, we expect to get more responses after this one. */ |
| + | k->header = TRUE; |
| + | k->headerline = 0; /* restart the header line counter */ |
| + | |
| + | switch(k->httpcode) { |
| + | case 100: |
| + | /* We have made an HTTP PUT or POST and this is 1.1-lingo that tells us |
| + | * that the server is OK with this and ready to receive the data. */ |
| + | http_exp100_got100(data); |
| + | break; |
| + | case 101: |
| + | return http_on_101_upgrade(data, buf, blen, pconsumed, conn_changed); |
| + | default: |
| + | /* The server may send us other 1xx responses, like informative 103. This |
| + | * has no influence on request processing and we expect to receive a |
| + | * final response eventually. */ |
| + | break; |
| + | } |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | #if defined(USE_NTLM) || defined(USE_SPNEGO) |
| + | /* |
| + | * Check if NTLM or SPNEGO authentication negotiation failed due to |
| + | * connection closure (typically on HTTP/1.0 servers). |
| + | */ |
| + | static void http_check_auth_closure(struct Curl_easy *data, |
| + | struct connectdata *conn) |
| + | { |
| + | /* At this point we have some idea about the fate of the connection. If we |
| + | are closing the connection it may result auth failure. */ |
| + | #ifdef USE_NTLM |
| + | if(conn->bits.close && |
| + | (((data->req.httpcode == 401) && |
| + | (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || |
| + | ((data->req.httpcode == 407) && |
| + | (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { |
| + | infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); |
| + | data->state.authproblem = TRUE; |
| + | } |
| + | #endif |
| + | #ifdef USE_SPNEGO |
| + | if(conn->bits.close && |
| + | (((data->req.httpcode == 401) && |
| + | (conn->http_negotiate_state == GSS_AUTHRECV)) || |
| + | ((data->req.httpcode == 407) && |
| + | (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { |
| + | infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); |
| + | data->state.authproblem = TRUE; |
| + | } |
| + | if((conn->http_negotiate_state == GSS_AUTHDONE) && |
| + | (data->req.httpcode != 401)) { |
| + | conn->http_negotiate_state = GSS_AUTHSUCC; |
| + | } |
| + | if((conn->proxy_negotiate_state == GSS_AUTHDONE) && |
| + | (data->req.httpcode != 407)) { |
| + | conn->proxy_negotiate_state = GSS_AUTHSUCC; |
| + | } |
| + | #endif |
| + | } |
| + | #else |
| + | #define http_check_auth_closure(x,y) /* empty */ |
| + | #endif |
| + | |
| + | /* |
| + | * Handle an error response (>= 300) received while still sending the |
| + | * request body. Deals with 417 Expectation Failed retries, keep-sending |
| + | * on error, and aborting the send. |
| + | */ |
| + | static CURLcode http_handle_send_error(struct Curl_easy *data) |
| + | { |
| + | struct connectdata *conn = data->conn; |
| + | struct SingleRequest *k = &data->req; |
| + | CURLcode result = CURLE_OK; |
| + | |
| + | if(!data->req.authneg && !conn->bits.close && |
| + | !Curl_creader_will_rewind(data)) { |
| + | /* |
| + | * General treatment of errors when about to send data. |
| + | * Including: "417 Expectation Failed", while waiting for |
| + | * 100-continue. |
| + | * |
| + | * The check for close above is done because if something |
| + | * else has already deemed the connection to get closed then |
| + | * something else should have considered the big picture and |
| + | * we avoid this check. |
| + | */ |
| + | |
| + | switch(data->state.httpreq) { |
| + | case HTTPREQ_PUT: |
| + | case HTTPREQ_POST: |
| + | case HTTPREQ_POST_FORM: |
| + | case HTTPREQ_POST_MIME: |
| + | /* We got an error response. If this happened before the |
| + | * whole request body has been sent we stop sending and |
| + | * mark the connection for closure after we have read the |
| + | * entire response. */ |
| + | if(!Curl_req_done_sending(data)) { |
| + | if((k->httpcode == 417) && http_exp100_is_selected(data)) { |
| + | /* 417 Expectation Failed - try again without the |
| + | Expect header */ |
| + | if(!k->writebytecount && http_exp100_is_waiting(data)) { |
| + | infof(data, "Got HTTP failure 417 while waiting for a 100"); |
| + | } |
| + | else { |
| + | infof(data, "Got HTTP failure 417 while sending data"); |
| + | streamclose(conn, "Stop sending data before everything sent"); |
| + | result = http_perhapsrewind(data, conn); |
| + | if(result) |
| + | return result; |
| + | } |
| + | data->state.disableexpect = TRUE; |
| + | Curl_req_abort_sending(data); |
| + | DEBUGASSERT(!data->req.newurl); |
| + | data->req.newurl = Curl_bufref_dup(&data->state.url); |
| + | if(!data->req.newurl) |
| + | return CURLE_OUT_OF_MEMORY; |
| + | } |
| + | else if(data->set.http_keep_sending_on_error) { |
| + | infof(data, "HTTP error before end of send, keep sending"); |
| + | http_exp100_send_anyway(data); |
| + | } |
| + | else { |
| + | infof(data, "HTTP error before end of send, stop sending"); |
| + | streamclose(conn, "Stop sending data before everything sent"); |
| + | result = Curl_req_abort_sending(data); |
| + | if(result) |
| + | return result; |
| + | } |
| + | } |
| + | break; |
| + | |
| + | default: /* default label present to avoid compiler warnings */ |
| + | break; |
| + | } |
| + | } |
| + | |
| + | if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) { |
| + | /* We rewind before next send, continue sending now */ |
| + | infof(data, "Keep sending data to get tossed away"); |
| + | CURL_REQ_SET_SEND(data); |
| + | } |
| + | return result; |
| + | } |
| + | |
| static CURLcode http_on_response(struct Curl_easy *data, | |
| const char *last_hd, size_t last_hd_len, | |
| const char *buf, size_t blen, | |
| @@ -3909,9 +4129,8 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| } | |
| if(k->httpcode < 200 && last_hd) { | |
| - | /* Intermediate responses might trigger processing of more |
| - | * responses, write the last header to the client before |
| - | * proceeding. */ |
| + | /* Intermediate responses might trigger processing of more responses, |
| + | * write the last header to the client before proceeding. */ |
| result = http_write_header(data, last_hd, last_hd_len); | |
| last_hd = NULL; /* handled it */ | |
| if(result) | |
| @@ -3924,82 +4143,15 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| goto out; | |
| } | |
| else if(k->httpcode < 200) { | |
| - | /* "A user agent MAY ignore unexpected 1xx status responses." |
| - | * By default, we expect to get more responses after this one. */ |
| - | k->header = TRUE; |
| - | k->headerline = 0; /* restart the header line counter */ |
| - | |
| - | switch(k->httpcode) { |
| - | case 100: |
| - | /* |
| - | * We have made an HTTP PUT or POST and this is 1.1-lingo |
| - | * that tells us that the server is OK with this and ready |
| - | * to receive the data. |
| - | */ |
| - | http_exp100_got100(data); |
| - | break; |
| - | case 101: { |
| - | int upgr101_requested = k->upgr101; |
| - | |
| - | if(k->httpversion_sent != 11) { |
| - | /* invalid for other HTTP versions */ |
| - | failf(data, "server sent 101 response while not talking HTTP/1.1"); |
| - | result = CURLE_WEIRD_SERVER_REPLY; |
| - | goto out; |
| - | } |
| - | |
| - | /* Whatever the success, upgrade was selected. */ |
| - | k->upgr101 = UPGR101_RECEIVED; |
| - | data->conn->bits.upgrade_in_progress = FALSE; |
| - | conn_changed = TRUE; |
| - | |
| - | /* To be fully conform, we would check the "Upgrade:" response header |
| - | * to mention the protocol we requested. */ |
| - | switch(upgr101_requested) { |
| - | case UPGR101_H2: |
| - | /* Switch to HTTP/2, where we will get more responses. |
| - | * blen bytes in bug are already h2 protocol bytes */ |
| - | infof(data, "Received 101, Switching to HTTP/2"); |
| - | result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); |
| - | if(result) |
| - | goto out; |
| - | *pconsumed += blen; |
| - | break; |
| - | #ifndef CURL_DISABLE_WEBSOCKETS |
| - | case UPGR101_WS: |
| - | /* Switch to WebSocket, where we now stream ws frames. |
| - | * blen bytes in bug are already ws protocol bytes */ |
| - | infof(data, "Received 101, Switching to WebSocket"); |
| - | result = Curl_ws_accept(data, buf, blen); |
| - | if(result) |
| - | goto out; |
| - | *pconsumed += blen; /* ws accept handled the data */ |
| - | break; |
| - | #endif |
| - | default: |
| - | /* We silently accept this as the final response. What are we |
| - | * switching to if we did not ask for an Upgrade? Maybe the |
| - | * application provided an `Upgrade: xxx` header? */ |
| - | k->header = FALSE; |
| - | break; |
| - | } |
| - | /* processed 101 */ |
| - | break; |
| - | } |
| - | default: |
| - | /* The server may send us other 1xx responses, like informative |
| - | * 103. This have no influence on request processing and we expect |
| - | * to receive a final response eventually. */ |
| - | break; |
| - | } |
| + | result = http_on_1xx_response(data, buf, blen, pconsumed, &conn_changed); |
| goto out; | |
| } | |
| /* k->httpcode >= 200, final response */ | |
| k->header = FALSE; | |
| - | if(data->conn->bits.upgrade_in_progress) { |
| - | /* Asked for protocol upgrade, but it was not selected by the server */ |
| - | data->conn->bits.upgrade_in_progress = FALSE; |
| + | if(conn->bits.upgrade_in_progress) { |
| + | /* Asked for protocol upgrade, but it was not selected */ |
| + | conn->bits.upgrade_in_progress = FALSE; |
| conn_changed = TRUE; | |
| } | |
| @@ -4008,48 +4160,17 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| !(conn->scheme->protocol & CURLPROTO_RTSP) && | |
| data->state.httpreq != HTTPREQ_HEAD) { | |
| /* On HTTP 1.1, when connection is not to get closed, but no | |
| - | Content-Length nor Transfer-Encoding chunked have been |
| - | received, according to RFC2616 section 4.4 point 5, we |
| - | assume that the server will close the connection to |
| - | signal the end of the document. */ |
| - | infof(data, "no chunk, no close, no size. Assume close to " |
| - | "signal end"); |
| + | Content-Length nor Transfer-Encoding chunked have been received, |
| + | according to RFC2616 section 4.4 point 5, we assume that the server |
| + | will close the connection to signal the end of the document. */ |
| + | infof(data, "no chunk, no close, no size. Assume close to signal end"); |
| streamclose(conn, "HTTP: No end-of-message indicator"); | |
| } | |
| - | /* At this point we have some idea about the fate of the connection. |
| - | If we are closing the connection it may result auth failure. */ |
| - | #ifdef USE_NTLM |
| - | if(conn->bits.close && |
| - | (((data->req.httpcode == 401) && |
| - | (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || |
| - | ((data->req.httpcode == 407) && |
| - | (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { |
| - | infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); |
| - | data->state.authproblem = TRUE; |
| - | } |
| - | #endif |
| - | #ifdef USE_SPNEGO |
| - | if(conn->bits.close && |
| - | (((data->req.httpcode == 401) && |
| - | (conn->http_negotiate_state == GSS_AUTHRECV)) || |
| - | ((data->req.httpcode == 407) && |
| - | (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { |
| - | infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); |
| - | data->state.authproblem = TRUE; |
| - | } |
| - | if((conn->http_negotiate_state == GSS_AUTHDONE) && |
| - | (data->req.httpcode != 401)) { |
| - | conn->http_negotiate_state = GSS_AUTHSUCC; |
| - | } |
| - | if((conn->proxy_negotiate_state == GSS_AUTHDONE) && |
| - | (data->req.httpcode != 407)) { |
| - | conn->proxy_negotiate_state = GSS_AUTHSUCC; |
| - | } |
| - | #endif |
| + | http_check_auth_closure(data, conn); |
| #ifndef CURL_DISABLE_WEBSOCKETS | |
| - | /* All >=200 HTTP status codes are errors when wanting WebSocket */ |
| + | /* All >=200 HTTP status codes are errors when wanting ws */ |
| if(data->req.upgr101 == UPGR101_WS) { | |
| failf(data, "Refused WebSocket upgrade: %d", k->httpcode); | |
| result = CURLE_HTTP_RETURNED_ERROR; | |
| @@ -4059,88 +4180,23 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| /* Check if this response means the transfer errored. */ | |
| if(http_should_fail(data, data->req.httpcode)) { | |
| - | failf(data, "The requested URL returned error: %d", k->httpcode); |
| + | failf(data, "The requested URL returned error: %d", |
| + | k->httpcode); |
| result = CURLE_HTTP_RETURNED_ERROR; | |
| goto out; | |
| } | |
| - | /* Curl_http_auth_act() checks what authentication methods |
| - | * that are available and decides which one (if any) to |
| - | * use. It will set 'newurl' if an auth method was picked. */ |
| + | /* Curl_http_auth_act() checks what authentication methods that are |
| + | * available and decides which one (if any) to use. It will set 'newurl' if |
| + | * an auth method was picked. */ |
| result = Curl_http_auth_act(data); | |
| if(result) | |
| goto out; | |
| if(k->httpcode >= 300) { | |
| - | if(!data->req.authneg && !conn->bits.close && |
| - | !Curl_creader_will_rewind(data)) { |
| - | /* |
| - | * General treatment of errors when about to send data. Including : |
| - | * "417 Expectation Failed", while waiting for 100-continue. |
| - | * |
| - | * The check for close above is done because of something |
| - | * else has already deemed the connection to get closed then |
| - | * something else should have considered the big picture and we |
| - | * avoid this check. |
| - | * |
| - | */ |
| - | |
| - | switch(data->state.httpreq) { |
| - | case HTTPREQ_PUT: |
| - | case HTTPREQ_POST: |
| - | case HTTPREQ_POST_FORM: |
| - | case HTTPREQ_POST_MIME: |
| - | /* We got an error response. If this happened before the whole |
| - | * request body has been sent we stop sending and mark the |
| - | * connection for closure after we have read the entire response. |
| - | */ |
| - | if(!Curl_req_done_sending(data)) { |
| - | if((k->httpcode == 417) && http_exp100_is_selected(data)) { |
| - | /* 417 Expectation Failed - try again without the Expect |
| - | header */ |
| - | if(!k->writebytecount && http_exp100_is_waiting(data)) { |
| - | infof(data, "Got HTTP failure 417 while waiting for a 100"); |
| - | } |
| - | else { |
| - | infof(data, "Got HTTP failure 417 while sending data"); |
| - | streamclose(conn, "Stop sending data before everything sent"); |
| - | result = http_perhapsrewind(data, conn); |
| - | if(result) |
| - | goto out; |
| - | } |
| - | data->state.disableexpect = TRUE; |
| - | Curl_req_abort_sending(data); |
| - | DEBUGASSERT(!data->req.newurl); |
| - | data->req.newurl = Curl_bufref_dup(&data->state.url); |
| - | if(!data->req.newurl) { |
| - | result = CURLE_OUT_OF_MEMORY; |
| - | goto out; |
| - | } |
| - | } |
| - | else if(data->set.http_keep_sending_on_error) { |
| - | infof(data, "HTTP error before end of send, keep sending"); |
| - | http_exp100_send_anyway(data); |
| - | } |
| - | else { |
| - | infof(data, "HTTP error before end of send, stop sending"); |
| - | streamclose(conn, "Stop sending data before everything sent"); |
| - | result = Curl_req_abort_sending(data); |
| - | if(result) |
| - | goto out; |
| - | } |
| - | } |
| - | break; |
| - | |
| - | default: /* default label present to avoid compiler warnings */ |
| - | break; |
| - | } |
| - | } |
| - | |
| - | if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) { |
| - | /* We rewind before next send, continue sending now */ |
| - | infof(data, "Keep sending data to get tossed away"); |
| - | CURL_REQ_SET_SEND(data); |
| - | } |
| + | result = http_handle_send_error(data); |
| + | if(result) |
| + | goto out; |
| } | |
| /* If we requested a "no body", this is a good time to get | |
| @@ -4149,11 +4205,10 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| if(data->req.no_body) | |
| k->download_done = TRUE; | |
| - | /* If max download size is *zero* (nothing) we already have |
| - | nothing and can safely return ok now! For HTTP/2, we would |
| - | like to call http2_handle_stream_close to properly close a |
| - | stream. In order to do this, we keep reading until we |
| - | close the stream. */ |
| + | /* If max download size is *zero* (nothing) we already have nothing and can |
| + | safely return ok now! For HTTP/2, we would like to call |
| + | http2_handle_stream_close to properly close a stream. In order to do |
| + | this, we keep reading until we close the stream. */ |
| if((k->maxdownload == 0) && (k->httpversion_sent < 20)) | |
| k->download_done = TRUE; | |
| @@ -4161,21 +4216,18 @@ static CURLcode http_on_response(struct Curl_easy *data, | |
| result = http_firstwrite(data); | |
| if(!result) | |
| - | /* This is the last response that we get for the current request. |
| - | * Check on the body size and determine if the response is complete. |
| - | */ |
| + | /* This is the last response that we get for the current request. Check on |
| + | * the body size and determine if the response is complete. */ |
| result = http_size(data); | |
| out: | |
| - | if(last_hd) { |
| + | if(last_hd) |
| /* if not written yet, write it now */ | |
| - | result = Curl_1st_fatal( |
| - | result, http_write_header(data, last_hd, last_hd_len)); |
| - | } |
| - | if(conn_changed) { |
| - | /* poke the multi handle to allow any pending pipewait to retry now */ |
| + | result = Curl_1st_fatal(result, |
| + | http_write_header(data, last_hd, last_hd_len)); |
| + | if(conn_changed) |
| + | /* poke the multi handle to allow pending pipewait to retry */ |
| Curl_multi_connchanged(data->multi); | |
| - | } |
| return result; | |
| } | |
| @@ -70,7 +70,6 @@ extern struct Curl_cftype Curl_cft_nghttp2; | |
| #define Curl_http2_request_upgrade(x, y) CURLE_UNSUPPORTED_PROTOCOL | |
| #define Curl_http2_switch(a) CURLE_UNSUPPORTED_PROTOCOL | |
| - | #define Curl_http2_upgrade(a, b, c, d, e) CURLE_UNSUPPORTED_PROTOCOL |
| #define Curl_h2_http_1_1_error(x) 0 | |
| #endif | |