http: separate response parsing from response action

- move code that triggers on end-of-response into separate function from
  parsing
- simplify some headp/headerlen usage
- add `httpversion` to SingleRequest to indicate the version of the
  current response

Closes #13134
This commit is contained in:
Stefan Eissing 2024-03-11 17:23:15 +01:00 committed by Daniel Stenberg
parent 0c820427f2
commit 0f08b43557
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 340 additions and 318 deletions

View File

@ -274,14 +274,13 @@ static CURLcode status_line(struct Curl_easy *data,
/* We need to set 'httpcodeq' for functions that check the response code in /* We need to set 'httpcodeq' for functions that check the response code in
a single place. */ a single place. */
data->req.httpcode = http_status; data->req.httpcode = http_status;
data->req.httpversion = http_version == HYPER_HTTP_VERSION_1_1? 11 :
(http_version == HYPER_HTTP_VERSION_2 ? 20 : 10);
if(data->state.hconnect) if(data->state.hconnect)
/* CONNECT */ /* CONNECT */
data->info.httpproxycode = http_status; data->info.httpproxycode = http_status;
else { else {
conn->httpversion = conn->httpversion = (unsigned char)data->req.httpversion;
http_version == HYPER_HTTP_VERSION_1_1 ? 11 :
(http_version == HYPER_HTTP_VERSION_2 ? 20 : 10);
if(http_version == HYPER_HTTP_VERSION_1_0) if(http_version == HYPER_HTTP_VERSION_1_0)
data->state.httpwant = CURL_HTTP_VERSION_1_0; data->state.httpwant = CURL_HTTP_VERSION_1_0;

View File

@ -3196,18 +3196,38 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
* Called after the first HTTP response line (the status line) has been * Called after the first HTTP response line (the status line) has been
* received and parsed. * received and parsed.
*/ */
CURLcode Curl_http_statusline(struct Curl_easy *data, CURLcode Curl_http_statusline(struct Curl_easy *data,
struct connectdata *conn) struct connectdata *conn)
{ {
struct SingleRequest *k = &data->req; struct SingleRequest *k = &data->req;
data->info.httpcode = k->httpcode;
data->info.httpversion = conn->httpversion; switch(k->httpversion) {
if(!data->state.httpversion || case 10:
data->state.httpversion > conn->httpversion) case 11:
#ifdef USE_HTTP2
case 20:
#endif
#ifdef ENABLE_QUIC
case 30:
#endif
/* TODO: we should verify that responses do not switch major
* HTTP version of the connection. Now, it seems we might accept
* a HTTP/2 response on a HTTP/1.1 connection, which is wrong. */
conn->httpversion = (unsigned char)k->httpversion;
break;
default:
failf(data, "Unsupported HTTP version (%u.%d) in response",
k->httpversion/10, k->httpversion%10);
return CURLE_UNSUPPORTED_PROTOCOL;
}
data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion;
conn->httpversion = (unsigned char)k->httpversion;
if(!data->state.httpversion || data->state.httpversion > k->httpversion)
/* store the lowest server version we encounter */ /* store the lowest server version we encounter */
data->state.httpversion = conn->httpversion; data->state.httpversion = (unsigned char)k->httpversion;
/* /*
* This code executes as part of processing the header. As a * This code executes as part of processing the header. As a
@ -3224,25 +3244,23 @@ CURLcode Curl_http_statusline(struct Curl_easy *data,
k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ k->ignorebody = TRUE; /* Avoid appending error msg to good data. */
} }
if(conn->httpversion == 10) { if(k->httpversion == 10) {
/* Default action for HTTP/1.0 must be to close, unless /* Default action for HTTP/1.0 must be to close, unless
we get one of those fancy headers that tell us the we get one of those fancy headers that tell us the
server keeps it open for us! */ server keeps it open for us! */
infof(data, "HTTP 1.0, assume close after body"); infof(data, "HTTP 1.0, assume close after body");
connclose(conn, "HTTP/1.0 close after body"); connclose(conn, "HTTP/1.0 close after body");
} }
else if(conn->httpversion == 20 || else if(k->httpversion == 20 ||
(k->upgr101 == UPGR101_H2 && k->httpcode == 101)) { (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) {
DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); DEBUGF(infof(data, "HTTP/2 found, allow multiplexing"));
/* HTTP/2 cannot avoid multiplexing since it is a core functionality /* HTTP/2 cannot avoid multiplexing since it is a core functionality
of the protocol */ of the protocol */
conn->bundle->multiuse = BUNDLE_MULTIPLEX; conn->bundle->multiuse = BUNDLE_MULTIPLEX;
} }
else if(conn->httpversion >= 11 && else if(k->httpversion >= 11 && !conn->bits.close) {
!conn->bits.close) {
/* If HTTP version is >= 1.1 and connection is persistent */ /* If HTTP version is >= 1.1 and connection is persistent */
DEBUGF(infof(data, DEBUGF(infof(data, "HTTP 1.1 or later with persistent connection"));
"HTTP 1.1 or later with persistent connection"));
} }
k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200;
@ -3350,6 +3368,287 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data,
} }
static CURLcode http_on_response(struct Curl_easy *data,
const char *buf, size_t blen,
size_t *pconsumed)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
bool switch_to_h2 = FALSE;
(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 < 100) {
failf(data, "Unsupported response code in HTTP response");
return CURLE_UNSUPPORTED_PROTOCOL;
}
else if(k->httpcode < 200) {
/* "A user agent MAY ignore unexpected 1xx status responses." */
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.
* However, we'll get more headers now so we must get
* back into the header-parsing state!
*/
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
/* if we did wait for this do enable write now! */
Curl_http_exp100_got100(data);
break;
case 101:
if(conn->httpversion == 11) {
/* Switching Protocols only allowed from HTTP/1.1 */
if(k->upgr101 == UPGR101_H2) {
/* Switching to HTTP/2 */
infof(data, "Received 101, Switching to HTTP/2");
k->upgr101 = UPGR101_RECEIVED;
/* we'll get more headers (HTTP/2 response) */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
switch_to_h2 = TRUE;
}
#ifdef USE_WEBSOCKETS
else if(k->upgr101 == UPGR101_WS) {
/* verify the response */
result = Curl_ws_accept(data, buf, blen);
if(result)
return result;
k->header = FALSE; /* no more header to parse! */
*pconsumed += blen; /* ws accept handled the data */
blen = 0;
if(data->set.connect_only)
k->keepon &= ~KEEP_RECV; /* read no more content */
}
#endif
else {
/* Not switching to another protocol */
k->header = FALSE; /* no more header to parse! */
}
}
else {
/* invalid for other HTTP versions */
failf(data, "unexpected 101 response code");
return CURLE_WEIRD_SERVER_REPLY;
}
break;
default:
/* the status code 1xx indicates a provisional response, so
we'll get another set of headers */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
break;
}
}
else {
/* 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");
}
}
if(!k->header) {
result = Curl_http_size(data);
if(result)
return result;
}
/* 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
/*
* When all the headers have been parsed, see if we should give
* up and return an error.
*/
if(http_should_fail(data)) {
failf(data, "The requested URL returned error: %d",
k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
#ifdef USE_WEBSOCKETS
/* All non-101 HTTP status codes are bad when wanting to upgrade to
websockets */
if(data->req.upgr101 == UPGR101_WS) {
failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
#endif
/* 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)
return result;
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.
*
* rewindbeforesend indicates that something has told libcurl to
* continue sending even if it gets discarded
*/
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've 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)
return result;
}
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)
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");
k->keepon |= KEEP_SEND;
}
}
if(!k->header) {
/*
* really end-of-headers.
*
* 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'd
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;
}
if(switch_to_h2) {
/* Having handled the headers, we can do the HTTP/2 switch.
* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
if(result)
return result;
*pconsumed += blen;
}
return CURLE_OK;
}
/* /*
* Read any HTTP header lines from the server and pass them to the client app. * Read any HTTP header lines from the server and pass them to the client app.
*/ */
@ -3449,133 +3748,13 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
hd = Curl_dyn_ptr(&data->state.headerb); hd = Curl_dyn_ptr(&data->state.headerb);
hdlen = Curl_dyn_len(&data->state.headerb); hdlen = Curl_dyn_len(&data->state.headerb);
if((0x0a == *hd) || (0x0d == *hd)) { if((0x0a == *hd) || (0x0d == *hd)) {
bool switch_to_h2 = FALSE; /* Empty header line means end of headers! */
/* Zero-length header line means end of headers! */ size_t consumed;
if(100 <= k->httpcode && 199 >= k->httpcode) {
/* "A user agent MAY ignore unexpected 1xx status responses." */
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.
* However, we'll get more headers now so we must get
* back into the header-parsing state!
*/
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
/* if we did wait for this do enable write now! */
Curl_http_exp100_got100(data);
break;
case 101:
if(conn->httpversion == 11) {
/* Switching Protocols only allowed from HTTP/1.1 */
if(k->upgr101 == UPGR101_H2) {
/* Switching to HTTP/2 */
infof(data, "Received 101, Switching to HTTP/2");
k->upgr101 = UPGR101_RECEIVED;
/* we'll get more headers (HTTP/2 response) */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
switch_to_h2 = TRUE;
}
#ifdef USE_WEBSOCKETS
else if(k->upgr101 == UPGR101_WS) {
/* verify the response */
result = Curl_ws_accept(data, buf, blen);
if(result)
return result;
k->header = FALSE; /* no more header to parse! */
*pconsumed += blen; /* ws accept handled the data */
blen = 0;
if(data->set.connect_only)
k->keepon &= ~KEEP_RECV; /* read no more content */
}
#endif
else {
/* Not switching to another protocol */
k->header = FALSE; /* no more header to parse! */
}
}
else {
/* invalid for other HTTP versions */
failf(data, "unexpected 101 response code");
return CURLE_WEIRD_SERVER_REPLY;
}
break;
default:
/* the status code 1xx indicates a provisional response, so
we'll get another set of headers */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
break;
}
}
else {
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);
}
k->header = FALSE; /* no more header to parse! */
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");
}
}
if(!k->header) {
result = Curl_http_size(data);
if(result)
return result;
}
/* 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
/* now, only output this if the header AND body are requested: /* now, only output this if the header AND body are requested:
*/ */
Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
writetype = CLIENTWRITE_HEADER | writetype = CLIENTWRITE_HEADER |
((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
@ -3586,147 +3765,24 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
result = Curl_bump_headersize(data, hdlen, FALSE); result = Curl_bump_headersize(data, hdlen, FALSE);
if(result) if(result)
return result; return result;
/* We are done with this line. We reset because response
/* * processing might switch to HTTP/2 and that might call us
* When all the headers have been parsed, see if we should give * directly again. */
* up and return an error. Curl_dyn_reset(&data->state.headerb);
*/
if(http_should_fail(data)) {
failf(data, "The requested URL returned error: %d",
k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
#ifdef USE_WEBSOCKETS
/* All non-101 HTTP status codes are bad when wanting to upgrade to
websockets */
if(data->req.upgr101 == UPGR101_WS) {
failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
return CURLE_HTTP_RETURNED_ERROR;
}
#endif
data->req.deductheadercount = data->req.deductheadercount =
(100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
/* Curl_http_auth_act() checks what authentication methods /* analyze the response to find out what to do */
* that are available and decides which one (if any) to result = http_on_response(data, buf, blen, &consumed);
* use. It will set 'newurl' if an auth method was picked. */
result = Curl_http_auth_act(data);
if(result) if(result)
return result; return result;
*pconsumed += consumed;
blen -= consumed;
buf += consumed;
if(k->httpcode >= 300) { if(!k->header || !blen)
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.
*
* rewindbeforesend indicates that something has told libcurl to
* continue sending even if it gets discarded
*/
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've 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)
return result;
}
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)
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");
k->keepon |= KEEP_SEND;
}
}
if(!k->header) {
/*
* really end-of-headers.
*
* 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'd
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;
Curl_debug(data, CURLINFO_HEADER_IN,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
goto out; /* exit header line loop */ goto out; /* exit header line loop */
}
/* We continue reading headers, reset the line-based header */
Curl_dyn_reset(&data->state.headerb);
if(switch_to_h2) {
/* Having handled the headers, we can do the HTTP/2 switch.
* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
if(result)
return result;
*pconsumed += blen;
blen = 0;
}
continue; continue;
} }
@ -3740,6 +3796,8 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
/* This is the first header, it MUST be the error code line /* This is the first header, it MUST be the error code line
or else we consider this to be the body right away! */ or else we consider this to be the body right away! */
bool fine_statusline = FALSE; bool fine_statusline = FALSE;
k->httpversion = 0; /* Don't know yet */
if(conn->handler->protocol & PROTO_FAMILY_HTTP) { if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
/* /*
* https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
@ -3748,7 +3806,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
* says. We allow any three-digit number here, but we cannot make * says. We allow any three-digit number here, but we cannot make
* guarantees on future behaviors since it isn't within the protocol. * guarantees on future behaviors since it isn't within the protocol.
*/ */
int httpversion = 0;
char *p = hd; char *p = hd;
while(*p && ISBLANK(*p)) while(*p && ISBLANK(*p))
@ -3760,7 +3817,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
p++; p++;
if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
if(ISBLANK(p[2])) { if(ISBLANK(p[2])) {
httpversion = 10 + (p[1] - '0'); k->httpversion = 10 + (p[1] - '0');
p += 3; p += 3;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
@ -3780,7 +3837,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
case '3': case '3':
if(!ISBLANK(p[1])) if(!ISBLANK(p[1]))
break; break;
httpversion = (*p - '0') * 10; k->httpversion = (*p - '0') * 10;
p += 2; p += 2;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
@ -3797,49 +3854,15 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
} }
} }
if(fine_statusline) { if(!fine_statusline) {
if(k->httpcode < 100) {
failf(data, "Unsupported response code in HTTP response");
return CURLE_UNSUPPORTED_PROTOCOL;
}
switch(httpversion) {
case 10:
case 11:
#ifdef USE_HTTP2
case 20:
#endif
#ifdef ENABLE_QUIC
case 30:
#endif
conn->httpversion = (unsigned char)httpversion;
break;
default:
failf(data, "Unsupported HTTP version (%u.%d) in response",
httpversion/10, httpversion%10);
return CURLE_UNSUPPORTED_PROTOCOL;
}
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;
}
}
else {
/* If user has set option HTTP200ALIASES, /* If user has set option HTTP200ALIASES,
compare header line against list of aliases compare header line against list of aliases
*/ */
statusline check = statusline check = checkhttpprefix(data, hd, hdlen);
checkhttpprefix(data,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
if(check == STATUS_DONE) { if(check == STATUS_DONE) {
fine_statusline = TRUE; fine_statusline = TRUE;
k->httpcode = 200; k->httpcode = 200;
conn->httpversion = 10; k->httpversion = 10;
} }
} }
} }
@ -3860,7 +3883,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
p += 3; p += 3;
if(ISSPACE(*p)) { if(ISSPACE(*p)) {
fine_statusline = TRUE; fine_statusline = TRUE;
conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */ k->httpversion = 11; /* RTSP acts like HTTP 1.1 */
} }
} }
} }
@ -3892,13 +3915,12 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
return result; return result;
/* /*
* End of header-checks. Write them to the client. * Taken in one (more) header. Write it to the client.
*/ */
if(k->httpcode/100 == 1)
writetype |= CLIENTWRITE_1XX;
Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
if(k->httpcode/100 == 1)
writetype |= CLIENTWRITE_1XX;
result = Curl_client_write(data, writetype, hd, hdlen); result = Curl_client_write(data, writetype, hd, hdlen);
if(result) if(result)
return result; return result;

View File

@ -78,6 +78,7 @@ struct SingleRequest {
first one */ first one */
curl_off_t offset; /* possible resume offset read from the curl_off_t offset; /* possible resume offset read from the
Content-Range: header */ Content-Range: header */
int httpversion; /* Version in response (09, 10, 11, etc.) */
int httpcode; /* error code from the 'HTTP/1.? XXX' or int httpcode; /* error code from the 'HTTP/1.? XXX' or
'RTSP/1.? XXX' line */ 'RTSP/1.? XXX' line */
int keepon; int keepon;