mirror of
https://github.com/curl/curl.git
synced 2025-09-13 15:42:39 +03:00
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:
parent
0c820427f2
commit
0f08b43557
|
@ -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;
|
||||||
|
|
||||||
|
|
650
lib/http.c
650
lib/http.c
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user