in libs/curl/lib/http.c [3379:3665]
static CURLcode http_on_response(struct Curl_easy *data,
const char *last_hd, size_t last_hd_len,
const char *buf, size_t blen,
size_t *pconsumed)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
(void)buf; /* not used without HTTP2 enabled */
*pconsumed = 0;
if(k->upgr101 == UPGR101_RECEIVED) {
/* supposedly upgraded to http2 now */
if(conn->httpversion != 20)
infof(data, "Lying server, not serving HTTP/2");
}
if(conn->httpversion < 20) {
conn->bundle->multiuse = BUNDLE_NO_MULTIUSE;
}
if(k->httpcode < 200 && last_hd) {
/* 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)
goto out;
}
if(k->httpcode < 100) {
failf(data, "Unsupported response code in HTTP response");
result = CURLE_UNSUPPORTED_PROTOCOL;
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.
*/
Curl_http_exp100_got100(data);
break;
case 101:
/* Switching Protocols only allowed from HTTP/1.1 */
if(conn->httpversion != 11) {
/* invalid for other HTTP versions */
failf(data, "unexpected 101 response code");
result = CURLE_WEIRD_SERVER_REPLY;
goto out;
}
if(k->upgr101 == UPGR101_H2) {
/* Switching to HTTP/2, where we will get more responses */
infof(data, "Received 101, Switching to HTTP/2");
k->upgr101 = UPGR101_RECEIVED;
/* We expect more response from HTTP/2 later */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
/* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
if(result)
goto out;
*pconsumed += blen;
}
#ifdef USE_WEBSOCKETS
else if(k->upgr101 == UPGR101_WS) {
/* verify the response. Any passed `buf` bytes are already in
* WebSockets format and taken in by the protocol handler. */
result = Curl_ws_accept(data, buf, blen);
if(result)
goto out;
*pconsumed += blen; /* ws accept handled the data */
k->header = FALSE; /* we will not get more responses */
if(data->set.connect_only)
k->keepon &= ~KEEP_RECV; /* read no more content */
}
#endif
else {
/* We silently accept this as the final response.
* TODO: this looks, uhm, wrong. 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;
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;
}
goto out;
}
/* k->httpcode >= 200, final response */
k->header = FALSE;
if(k->upgr101 == UPGR101_H2) {
/* A requested upgrade was denied, poke the multi handle to possibly
allow a pending pipewait to continue */
Curl_multi_connchanged(data->multi);
}
if((k->size == -1) && !k->chunk && !conn->bits.close &&
(conn->httpversion == 11) &&
!(conn->handler->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");
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. */
#if defined(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
#if defined(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
#ifdef USE_WEBSOCKETS
/* All >=200 HTTP status codes are errors when wanting websockets */
if(data->req.upgr101 == UPGR101_WS) {
failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
result = CURLE_HTTP_RETURNED_ERROR;
goto out;
}
#endif
/* 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);
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. */
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 simply because of something
* else has already deemed the connection to get closed then
* something else should've 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) && Curl_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;
DEBUGASSERT(!data->req.newurl);
data->req.newurl = strdup(data->state.url);
Curl_req_abort_sending(data);
}
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");
k->keepon |= KEEP_SEND;
}
}
/* This is the last response that we will got for the current request.
* Check on the body size and determine if the response is complete.
*/
result = Curl_http_size(data);
if(result)
goto out;
/* If we requested a "no body", this is a good time to get
* out and return home.
*/
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! But 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(0 == k->maxdownload
&& !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
&& !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
k->download_done = TRUE;
/* final response without error, prepare to receive the body */
result = Curl_http_firstwrite(data);
out:
if(last_hd) {
/* if not written yet, write it now */
CURLcode r2 = http_write_header(data, last_hd, last_hd_len);
if(!result)
result = r2;
}
return result;
}