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,109 +3368,32 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data,
} }
/* static CURLcode http_on_response(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,
const char *buf, size_t blen, const char *buf, size_t blen,
size_t *pconsumed) size_t *pconsumed)
{ {
struct connectdata *conn = data->conn; struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK; CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req; 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; 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." */ /* "A user agent MAY ignore unexpected 1xx status responses." */
switch(k->httpcode) { switch(k->httpcode) {
case 100: case 100:
@ -3515,12 +3456,14 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
} }
} }
else { else {
/* k->httpcode >= 200, final response */
k->header = FALSE;
if(k->upgr101 == UPGR101_H2) { if(k->upgr101 == UPGR101_H2) {
/* A requested upgrade was denied, poke the multi handle to possibly /* A requested upgrade was denied, poke the multi handle to possibly
allow a pending pipewait to continue */ allow a pending pipewait to continue */
Curl_multi_connchanged(data->multi); Curl_multi_connchanged(data->multi);
} }
k->header = FALSE; /* no more header to parse! */
if((k->size == -1) && !k->chunk && !conn->bits.close && if((k->size == -1) && !k->chunk && !conn->bits.close &&
(conn->httpversion == 11) && (conn->httpversion == 11) &&
@ -3574,19 +3517,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
} }
#endif #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 * When all the headers have been parsed, see if we should give
* up and return an error. * up and return an error.
@ -3607,9 +3537,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
#endif #endif
data->req.deductheadercount =
(100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
/* Curl_http_auth_act() checks what authentication methods /* Curl_http_auth_act() checks what authentication methods
* that are available and decides which one (if any) to * that are available and decides which one (if any) to
* use. It will set 'newurl' if an auth method was picked. */ * 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_http2(data, conn, FIRSTSOCKET)
&& !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
k->download_done = TRUE; 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) { if(switch_to_h2) {
/* Having handled the headers, we can do the HTTP/2 switch. /* Having handled the headers, we can do the HTTP/2 switch.
* Any remaining `buf` bytes are already HTTP/2 and passed to * 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) if(result)
return result; return result;
*pconsumed += blen; *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; 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;