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
a single place. */
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)
/* CONNECT */
data->info.httpproxycode = http_status;
else {
conn->httpversion =
http_version == HYPER_HTTP_VERSION_1_1 ? 11 :
(http_version == HYPER_HTTP_VERSION_2 ? 20 : 10);
conn->httpversion = (unsigned char)data->req.httpversion;
if(http_version == HYPER_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
* received and parsed.
*/
CURLcode Curl_http_statusline(struct Curl_easy *data,
struct connectdata *conn)
{
struct SingleRequest *k = &data->req;
data->info.httpcode = k->httpcode;
data->info.httpversion = conn->httpversion;
if(!data->state.httpversion ||
data->state.httpversion > conn->httpversion)
switch(k->httpversion) {
case 10:
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 */
data->state.httpversion = conn->httpversion;
data->state.httpversion = (unsigned char)k->httpversion;
/*
* 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. */
}
if(conn->httpversion == 10) {
if(k->httpversion == 10) {
/* Default action for HTTP/1.0 must be to close, unless
we get one of those fancy headers that tell us the
server keeps it open for us! */
infof(data, "HTTP 1.0, assume 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)) {
DEBUGF(infof(data, "HTTP/2 found, allow multiplexing"));
/* HTTP/2 cannot avoid multiplexing since it is a core functionality
of the protocol */
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
}
else if(conn->httpversion >= 11 &&
!conn->bits.close) {
else if(k->httpversion >= 11 && !conn->bits.close) {
/* If HTTP version is >= 1.1 and connection is persistent */
DEBUGF(infof(data,
"HTTP 1.1 or later with persistent connection"));
DEBUGF(infof(data, "HTTP 1.1 or later with persistent connection"));
}
k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200;
@ -3350,109 +3368,32 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data,
}
/*
* Read any HTTP header lines from the server and pass them to the client app.
*/
static CURLcode http_rw_headers(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;
char *hd;
size_t hdlen;
char *end_ptr;
bool leftover_body = FALSE;
/* header line within buffer loop */
*pconsumed = 0;
do {
size_t line_length;
int writetype;
/* data is in network encoding so use 0x0a instead of '\n' */
end_ptr = memchr(buf, 0x0a, blen);
if(!end_ptr) {
/* Not a complete header line within buffer, append the data to
the end of the headerbuff. */
result = Curl_dyn_addn(&data->state.headerb, buf, blen);
if(result)
return result;
*pconsumed += blen;
if(!k->headerline) {
/* check if this looks like a protocol header */
statusline st =
checkprotoprefix(data, conn,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
/* this is not the beginning of a protocol first header line */
k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator");
if(conn->httpversion >= 10) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->set.http09_allowed) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
leftover_body = TRUE;
goto out;
}
}
goto out; /* read more and try again */
}
/* decrease the size of the remaining (supposed) header line */
line_length = (end_ptr - buf) + 1;
result = Curl_dyn_addn(&data->state.headerb, buf, line_length);
if(result)
return result;
blen -= line_length;
buf += line_length;
*pconsumed += line_length;
/****
* We now have a FULL header line in 'headerb'.
*****/
if(!k->headerline) {
/* the first read header */
statusline st = checkprotoprefix(data, conn,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator");
/* this is not the beginning of a protocol first header line */
if(conn->httpversion >= 10) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->set.http09_allowed) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
k->header = FALSE;
leftover_body = TRUE;
goto out;
}
}
/* headers are in network encoding so use 0x0a and 0x0d instead of '\n'
and '\r' */
hd = Curl_dyn_ptr(&data->state.headerb);
hdlen = Curl_dyn_len(&data->state.headerb);
if((0x0a == *hd) || (0x0d == *hd)) {
bool switch_to_h2 = FALSE;
/* Zero-length header line means end of headers! */
if(100 <= k->httpcode && 199 >= k->httpcode) {
(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:
@ -3515,12 +3456,14 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
}
}
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);
}
k->header = FALSE; /* no more header to parse! */
if((k->size == -1) && !k->chunk && !conn->bits.close &&
(conn->httpversion == 11) &&
@ -3574,19 +3517,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
}
#endif
/* now, only output this if the header AND body are requested:
*/
writetype = CLIENTWRITE_HEADER |
((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
result = Curl_client_write(data, writetype, hd, hdlen);
if(result)
return result;
result = Curl_bump_headersize(data, hdlen, FALSE);
if(result)
return result;
/*
* When all the headers have been parsed, see if we should give
* up and return an error.
@ -3607,9 +3537,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
#endif
data->req.deductheadercount =
(100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
/* 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. */
@ -3708,15 +3635,8 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
&& !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 */
}
/* 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
@ -3725,9 +3645,145 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
if(result)
return result;
*pconsumed += blen;
blen = 0;
}
return CURLE_OK;
}
/*
* Read any HTTP header lines from the server and pass them to the client app.
*/
static CURLcode http_rw_headers(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;
char *hd;
size_t hdlen;
char *end_ptr;
bool leftover_body = FALSE;
/* header line within buffer loop */
*pconsumed = 0;
do {
size_t line_length;
int writetype;
/* data is in network encoding so use 0x0a instead of '\n' */
end_ptr = memchr(buf, 0x0a, blen);
if(!end_ptr) {
/* Not a complete header line within buffer, append the data to
the end of the headerbuff. */
result = Curl_dyn_addn(&data->state.headerb, buf, blen);
if(result)
return result;
*pconsumed += blen;
if(!k->headerline) {
/* check if this looks like a protocol header */
statusline st =
checkprotoprefix(data, conn,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
/* this is not the beginning of a protocol first header line */
k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator");
if(conn->httpversion >= 10) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->set.http09_allowed) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
leftover_body = TRUE;
goto out;
}
}
goto out; /* read more and try again */
}
/* decrease the size of the remaining (supposed) header line */
line_length = (end_ptr - buf) + 1;
result = Curl_dyn_addn(&data->state.headerb, buf, line_length);
if(result)
return result;
blen -= line_length;
buf += line_length;
*pconsumed += line_length;
/****
* We now have a FULL header line in 'headerb'.
*****/
if(!k->headerline) {
/* the first read header */
statusline st = checkprotoprefix(data, conn,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator");
/* this is not the beginning of a protocol first header line */
if(conn->httpversion >= 10) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->set.http09_allowed) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
k->header = FALSE;
leftover_body = TRUE;
goto out;
}
}
/* headers are in network encoding so use 0x0a and 0x0d instead of '\n'
and '\r' */
hd = Curl_dyn_ptr(&data->state.headerb);
hdlen = Curl_dyn_len(&data->state.headerb);
if((0x0a == *hd) || (0x0d == *hd)) {
/* Empty header line means end of headers! */
size_t consumed;
/* now, only output this if the header AND body are requested:
*/
Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
writetype = CLIENTWRITE_HEADER |
((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
result = Curl_client_write(data, writetype, hd, hdlen);
if(result)
return result;
result = Curl_bump_headersize(data, hdlen, FALSE);
if(result)
return result;
/* We are done with this line. We reset because response
* processing might switch to HTTP/2 and that might call us
* directly again. */
Curl_dyn_reset(&data->state.headerb);
data->req.deductheadercount =
(100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
/* analyze the response to find out what to do */
result = http_on_response(data, buf, blen, &consumed);
if(result)
return result;
*pconsumed += consumed;
blen -= consumed;
buf += consumed;
if(!k->header || !blen)
goto out; /* exit header line loop */
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
or else we consider this to be the body right away! */
bool fine_statusline = FALSE;
k->httpversion = 0; /* Don't know yet */
if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
/*
* 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
* guarantees on future behaviors since it isn't within the protocol.
*/
int httpversion = 0;
char *p = hd;
while(*p && ISBLANK(*p))
@ -3760,7 +3817,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
p++;
if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
if(ISBLANK(p[2])) {
httpversion = 10 + (p[1] - '0');
k->httpversion = 10 + (p[1] - '0');
p += 3;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
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':
if(!ISBLANK(p[1]))
break;
httpversion = (*p - '0') * 10;
k->httpversion = (*p - '0') * 10;
p += 2;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
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(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(!fine_statusline) {
/* If user has set option HTTP200ALIASES,
compare header line against list of aliases
*/
statusline check =
checkhttpprefix(data,
Curl_dyn_ptr(&data->state.headerb),
Curl_dyn_len(&data->state.headerb));
statusline check = checkhttpprefix(data, hd, hdlen);
if(check == STATUS_DONE) {
fine_statusline = TRUE;
k->httpcode = 200;
conn->httpversion = 10;
k->httpversion = 10;
}
}
}
@ -3860,7 +3883,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
p += 3;
if(ISSPACE(*p)) {
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;
/*
* 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);
if(k->httpcode/100 == 1)
writetype |= CLIENTWRITE_1XX;
result = Curl_client_write(data, writetype, hd, hdlen);
if(result)
return result;

View File

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