lib: clarify 'conn->httpversion'

The variable `conn->httpversion` was used for several purposes and it
was unclear at which time the value represents what.

- rename `conn->httpversion` to `conn->httpversion_seen`
  This makes clear that the variable only records the last
  HTTP version seen on the connection - if any. And that it
  no longer is an indication of what version to use.
- Change Alt-Svc handling to no longer modify `conn->httpversion`
  but set `data->state.httpwant` for influencing the HTTP version
  to use on a transfer.
- Add `data->req.httpversion_sent` to have a record of what
  HTTP version was sent in a request
- Add connection filter type CF_TYPE_HTTP
- Add filter query `CF_QUERY_HTTP_VERSION` to ask what HTTP
  filter version is in place
- Lookup filters HTTP version instead of using `conn->httpversion`

Test test_12_05 now switches to HTTP/1.1 correctly and the
expectations have been fixed.

Removed the connection fitler "is_httpN()" checks and using
the version query instead.

Closes #16073
This commit is contained in:
Stefan Eissing 2025-01-22 14:45:30 +01:00 committed by Daniel Stenberg
parent 7e814c8717
commit e83818cae1
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
21 changed files with 206 additions and 203 deletions

View File

@ -494,13 +494,35 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_MULTIPLEX)
return TRUE;
if(cf->cft->flags & CF_TYPE_IP_CONNECT
|| cf->cft->flags & CF_TYPE_SSL)
if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL))
return FALSE;
}
return FALSE;
}
unsigned char Curl_conn_http_version(struct Curl_easy *data)
{
struct Curl_cfilter *cf;
CURLcode result = CURLE_UNKNOWN_OPTION;
unsigned char v = 0;
cf = data->conn ? data->conn->cfilter[FIRSTSOCKET] : NULL;
for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_HTTP) {
int value = 0;
result = cf->cft->query(cf, data, CF_QUERY_HTTP_VERSION, &value, NULL);
if(!result && ((value < 0) || (value > 255)))
result = CURLE_FAILED_INIT;
else
v = (unsigned char)value;
break;
}
if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL))
break;
}
return result ? 0 : v;
}
bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
{
struct Curl_cfilter *cf;

View File

@ -176,6 +176,7 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
#define CF_QUERY_STREAM_ERROR 6 /* error code - */
#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */
#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */
#define CF_QUERY_HTTP_VERSION 9 /* number (10/11/20/30) - */
/**
* Query the cfilter for properties. Filters ignorant of a query will
@ -195,11 +196,13 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
* CF_TYPE_SSL: provide SSL/TLS
* CF_TYPE_MULTIPLEX: provides multiplexing of easy handles
* CF_TYPE_PROXY provides proxying
* CF_TYPE_HTTP implement a version of the HTTP protocol
*/
#define CF_TYPE_IP_CONNECT (1 << 0)
#define CF_TYPE_SSL (1 << 1)
#define CF_TYPE_MULTIPLEX (1 << 2)
#define CF_TYPE_PROXY (1 << 3)
#define CF_TYPE_HTTP (1 << 4)
/* A connection filter type, e.g. specific implementation. */
struct Curl_cftype {
@ -392,6 +395,12 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex);
*/
bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
/**
* Return the HTTP version used on the FIRSTSOCKET connection filters
* or 0 if unknown. Value otherwise is 09, 10, 11, etc.
*/
unsigned char Curl_conn_http_version(struct Curl_easy *data);
/**
* Close the filter chain at `sockindex` for connection `data->conn`.
* Filters remain in place and may be connected again afterwards.

View File

@ -108,9 +108,10 @@ static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn);
static CURLcode http_range(struct Curl_easy *data,
Curl_HttpReq httpreq);
static CURLcode http_req_complete(struct Curl_easy *data,
struct dynbuf *r, Curl_HttpReq httpreq);
struct dynbuf *r, int httpversion,
Curl_HttpReq httpreq);
static CURLcode http_req_set_reader(struct Curl_easy *data,
Curl_HttpReq httpreq,
Curl_HttpReq httpreq, int httpversion,
const char **tep);
static CURLcode http_size(struct Curl_easy *data);
static CURLcode http_statusline(struct Curl_easy *data,
@ -121,8 +122,6 @@ static CURLcode http_useragent(struct Curl_easy *data);
#ifdef HAVE_LIBZ
static CURLcode http_transferencode(struct Curl_easy *data);
#endif
static bool use_http_1_1plus(const struct Curl_easy *data,
const struct connectdata *conn);
/*
@ -533,7 +532,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data)
else
data->info.httpauthpicked = data->state.authhost.picked;
if(data->state.authhost.picked == CURLAUTH_NTLM &&
conn->httpversion > 11) {
(data->req.httpversion_sent > 11)) {
infof(data, "Forcing HTTP/1.1 for NTLM");
connclose(conn, "Force HTTP/1.1 connection");
data->state.httpwant = CURL_HTTP_VERSION_1_1;
@ -1217,45 +1216,55 @@ CURLcode Curl_http_done(struct Curl_easy *data,
return CURLE_OK;
}
/*
* Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons
* to avoid it include:
*
* - if the user specifically requested HTTP 1.0
* - if the server we are connected to only supports 1.0
* - if any server previously contacted to handle this request only supports
* 1.0.
*/
static bool use_http_1_1plus(const struct Curl_easy *data,
const struct connectdata *conn)
/* Determine if we may use HTTP 1.1 for this request. */
static bool http_may_use_1_1(const struct Curl_easy *data)
{
if((data->state.httpversion == 10) || (conn->httpversion == 10))
const struct connectdata *conn = data->conn;
/* We have seen a previous response for *this* transfer with 1.0,
* on another connection or the same one. */
if(data->state.httpversion == 10)
return FALSE;
/* We have seen a previous response on *this* connection with 1.0. */
if(conn->httpversion_seen == 10)
return FALSE;
/* We want 1.0 and have seen no previous response on *this* connection
with a higher version (maybe no response at all yet). */
if((data->state.httpwant == CURL_HTTP_VERSION_1_0) &&
(conn->httpversion <= 10))
(conn->httpversion_seen <= 10))
return FALSE;
/* We want something newer than 1.0 or have no preferences. */
return (data->state.httpwant == CURL_HTTP_VERSION_NONE) ||
(data->state.httpwant >= CURL_HTTP_VERSION_1_1);
}
static const char *get_http_string(const struct Curl_easy *data,
const struct connectdata *conn)
static unsigned char http_request_version(struct Curl_easy *data)
{
if(Curl_conn_is_http3(data, conn, FIRSTSOCKET))
return "3";
if(Curl_conn_is_http2(data, conn, FIRSTSOCKET))
return "2";
if(use_http_1_1plus(data, conn))
return "1.1";
unsigned char httpversion = Curl_conn_http_version(data);
if(!httpversion) {
/* No specific HTTP connection filter installed. */
httpversion = http_may_use_1_1(data) ? 11 : 10;
}
return httpversion;
}
static const char *get_http_string(int httpversion)
{
switch(httpversion) {
case 30:
return "3";
case 20:
return "2";
case 11:
return "1.1";
default:
return "1.0";
}
}
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
bool is_connect,
bool is_connect, int httpversion,
struct dynbuf *req)
{
struct connectdata *conn = data->conn;
char *ptr;
struct curl_slist *h[2];
struct curl_slist *headers;
@ -1268,7 +1277,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
if(is_connect)
proxy = HEADER_CONNECT;
else
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ?
proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ?
HEADER_PROXY : HEADER_SERVER;
switch(proxy) {
@ -1368,7 +1377,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
Connection: */
checkprefix("Connection:", compare))
;
else if((conn->httpversion >= 20) &&
else if((httpversion >= 20) &&
checkprefix("Transfer-Encoding:", compare))
/* HTTP/2 does not support chunked requests */
;
@ -1900,7 +1909,7 @@ static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq)
}
static CURLcode http_req_set_reader(struct Curl_easy *data,
Curl_HttpReq httpreq,
Curl_HttpReq httpreq, int httpversion,
const char **tep)
{
CURLcode result = CURLE_OK;
@ -1920,9 +1929,7 @@ static CURLcode http_req_set_reader(struct Curl_easy *data,
data->req.upload_chunky =
Curl_compareheader(ptr,
STRCONST("Transfer-Encoding:"), STRCONST("chunked"));
if(data->req.upload_chunky &&
use_http_1_1plus(data, data->conn) &&
(data->conn->httpversion >= 20)) {
if(data->req.upload_chunky && (httpversion >= 20)) {
infof(data, "suppressing chunked transfer encoding on connection "
"using HTTP version 2 or higher");
data->req.upload_chunky = FALSE;
@ -1933,10 +1940,10 @@ static CURLcode http_req_set_reader(struct Curl_easy *data,
if(req_clen < 0) {
/* indeterminate request content length */
if(use_http_1_1plus(data, data->conn)) {
if(httpversion > 10) {
/* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not
* need it */
data->req.upload_chunky = (data->conn->httpversion < 20);
data->req.upload_chunky = (httpversion < 20);
}
else {
failf(data, "Chunky upload is not supported by HTTP 1.0");
@ -1955,7 +1962,7 @@ static CURLcode http_req_set_reader(struct Curl_easy *data,
}
static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
bool *announced_exp100)
int httpversion, bool *announced_exp100)
{
CURLcode result;
char *ptr;
@ -1974,9 +1981,7 @@ static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
*announced_exp100 =
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
}
else if(!data->state.disableexpect &&
use_http_1_1plus(data, data->conn) &&
(data->conn->httpversion < 20)) {
else if(!data->state.disableexpect && (httpversion == 11)) {
/* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an
Expect: 100-continue to the headers which actually speeds up post
operations (as there is one packet coming back from the web server) */
@ -1992,7 +1997,8 @@ static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
}
static CURLcode http_req_complete(struct Curl_easy *data,
struct dynbuf *r, Curl_HttpReq httpreq)
struct dynbuf *r, int httpversion,
Curl_HttpReq httpreq)
{
CURLcode result = CURLE_OK;
curl_off_t req_clen;
@ -2052,7 +2058,7 @@ static CURLcode http_req_complete(struct Curl_easy *data,
goto out;
}
}
result = addexpect(data, r, &announced_exp100);
result = addexpect(data, r, httpversion, &announced_exp100);
if(result)
goto out;
break;
@ -2326,6 +2332,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
struct dynbuf req;
char *altused = NULL;
const char *p_accept; /* Accept: string */
unsigned char httpversion;
/* Always consider the DO phase done after this function call, even if there
may be parts of the request that are not yet sent, since we can deal with
@ -2334,29 +2341,29 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
switch(conn->alpn) {
case CURL_HTTP_VERSION_3:
DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET));
DEBUGASSERT(Curl_conn_http_version(data) == 30);
break;
case CURL_HTTP_VERSION_2:
#ifndef CURL_DISABLE_PROXY
if(!Curl_conn_is_http2(data, conn, FIRSTSOCKET) &&
if((Curl_conn_http_version(data) != 20) &&
conn->bits.proxy && !conn->bits.tunnel_proxy
) {
result = Curl_http2_switch(data, conn, FIRSTSOCKET);
result = Curl_http2_switch(data);
if(result)
goto fail;
}
else
#endif
DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
DEBUGASSERT(Curl_conn_http_version(data) == 20);
break;
case CURL_HTTP_VERSION_1_1:
/* continue with HTTP/1.x when explicitly requested */
break;
default:
/* Check if user wants to use HTTP/2 with clear TCP */
if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) {
if(Curl_http2_may_switch(data)) {
DEBUGF(infof(data, "HTTP/2 over clean TCP"));
result = Curl_http2_switch(data, conn, FIRSTSOCKET);
result = Curl_http2_switch(data);
if(result)
goto fail;
}
@ -2420,7 +2427,10 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
goto fail;
#endif
result = http_req_set_reader(data, httpreq, &te);
httpversion = http_request_version(data);
httpstring = get_http_string(httpversion);
result = http_req_set_reader(data, httpreq, httpversion, &te);
if(result)
goto fail;
@ -2431,8 +2441,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(result)
goto fail;
httpstring = get_http_string(data, conn);
/* initialize a dynamic send-buffer */
Curl_dyn_init(&req, DYN_HTTP_REQUEST);
@ -2526,8 +2534,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
goto fail;
}
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) &&
conn->httpversion < 20 &&
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) &&
(data->state.httpwant == CURL_HTTP_VERSION_2)) {
/* append HTTP2 upgrade magic stuff to the HTTP request if it is not done
over SSL */
@ -2546,19 +2553,19 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(!result)
result = Curl_add_timecondition(data, &req);
if(!result)
result = Curl_add_custom_headers(data, FALSE, &req);
result = Curl_add_custom_headers(data, FALSE, httpversion, &req);
if(!result) {
/* req_send takes ownership of the 'req' memory on success */
result = http_req_complete(data, &req, httpreq);
result = http_req_complete(data, &req, httpversion, httpreq);
if(!result)
result = Curl_req_send(data, &req);
result = Curl_req_send(data, &req, httpversion);
}
Curl_dyn_free(&req);
if(result)
goto fail;
if((conn->httpversion >= 20) && data->req.upload_chunky)
if((httpversion >= 20) && data->req.upload_chunky)
/* upload_chunky was set above to set up the request in a chunky fashion,
but is disabled here again to avoid that the chunked encoded version is
actually used when sending the request body over h2 */
@ -2680,8 +2687,8 @@ static CURLcode http_header(struct Curl_easy *data,
)) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL;
if(v) {
/* the ALPN of the current request */
enum alpnid id = (conn->httpversion == 30) ? ALPN_h3 :
(conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
enum alpnid id = (k->httpversion == 30) ? ALPN_h3 :
(k->httpversion == 20) ? ALPN_h2 : ALPN_h1;
return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name,
curlx_uitous((unsigned int)conn->remote_port));
}
@ -2753,7 +2760,7 @@ static CURLcode http_header(struct Curl_easy *data,
streamclose(conn, "Connection: close used");
return CURLE_OK;
}
if((conn->httpversion == 10) &&
if((k->httpversion == 10) &&
HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) {
/*
* An HTTP/1.0 reply with the 'Connection: keep-alive' line
@ -2843,7 +2850,7 @@ static CURLcode http_header(struct Curl_easy *data,
#ifndef CURL_DISABLE_PROXY
v = HD_VAL(hd, hdlen, "Proxy-Connection:");
if(v) {
if((conn->httpversion == 10) && conn->bits.httpproxy &&
if((k->httpversion == 10) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) {
/*
* When an HTTP/1.0 reply comes when using a proxy, the
@ -2854,7 +2861,7 @@ static CURLcode http_header(struct Curl_easy *data,
connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */
infof(data, "HTTP/1.0 proxy connection set to keep alive");
}
else if((conn->httpversion == 11) && conn->bits.httpproxy &&
else if((k->httpversion == 11) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) {
/*
* We get an HTTP/1.1 response from a proxy and it says it will
@ -3043,11 +3050,11 @@ static CURLcode http_statusline(struct Curl_easy *data,
case 30:
#endif
/* no major version switch mid-connection */
if(conn->httpversion &&
(k->httpversion/10 != conn->httpversion/10)) {
if(k->httpversion_sent &&
(k->httpversion/10 != k->httpversion_sent/10)) {
failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)",
conn->httpversion/10, k->httpversion/10);
return CURLE_UNSUPPORTED_PROTOCOL;
k->httpversion_sent/10, k->httpversion/10);
return CURLE_WEIRD_SERVER_REPLY;
}
break;
default:
@ -3058,7 +3065,7 @@ static CURLcode http_statusline(struct Curl_easy *data,
data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion;
conn->httpversion = (unsigned char)k->httpversion;
conn->httpversion_seen = (unsigned char)k->httpversion;
if(!data->state.httpversion || data->state.httpversion > k->httpversion)
/* store the lowest server version we encounter */
@ -3238,7 +3245,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
if(k->upgr101 == UPGR101_RECEIVED) {
/* supposedly upgraded to http2 now */
if(conn->httpversion != 20)
if(data->req.httpversion != 20)
infof(data, "Lying server, not serving HTTP/2");
}
@ -3274,8 +3281,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
break;
case 101:
/* Switching Protocols only allowed from HTTP/1.1 */
if(conn->httpversion != 11) {
if(k->httpversion_sent != 11) {
/* invalid for other HTTP versions */
failf(data, "unexpected 101 response code");
result = CURLE_WEIRD_SERVER_REPLY;
@ -3289,6 +3295,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
/* We expect more response from HTTP/2 later */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
k->httpversion_sent = 20; /* It's a HTTP/2 request now */
/* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
@ -3337,7 +3344,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
}
if((k->size == -1) && !k->chunk && !conn->bits.close &&
(conn->httpversion == 11) &&
(k->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
@ -3486,9 +3493,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
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))
if((0 == k->maxdownload) && (k->httpversion_sent < 20))
k->download_done = TRUE;
/* final response without error, prepare to receive the body */
@ -3573,7 +3578,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data,
p++;
if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
if(ISBLANK(p[2])) {
k->httpversion = 10 + (p[1] - '0');
k->httpversion = (unsigned char)(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 +
@ -3593,7 +3598,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data,
case '3':
if(!ISBLANK(p[1]))
break;
k->httpversion = (*p - '0') * 10;
k->httpversion = (unsigned char)((*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 +
@ -3723,10 +3728,11 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
/* this is not the beginning of a protocol first header line */
/* this is not the beginning of a protocol first header line.
* Cannot be 0.9 if version was detected or connection was reused. */
k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator");
if(conn->httpversion >= 10) {
if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
@ -3761,8 +3767,9 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
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) {
/* this is not the beginning of a protocol first header line.
* Cannot be 0.9 if version was detected or connection was reused. */
if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}

View File

@ -76,7 +76,7 @@ char *Curl_checkProxyheaders(struct Curl_easy *data,
CURLcode Curl_add_timecondition(struct Curl_easy *data, struct dynbuf *req);
CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect,
struct dynbuf *req);
int httpversion, struct dynbuf *req);
CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect,
struct dynhds *hds);

View File

@ -2723,6 +2723,9 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
}
break;
}
case CF_QUERY_HTTP_VERSION:
*pres1 = 20;
return CURLE_OK;
default:
break;
}
@ -2733,7 +2736,7 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_nghttp2 = {
"HTTP/2",
CF_TYPE_MULTIPLEX,
CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
CURL_LOG_LVL_NONE,
cf_h2_destroy,
cf_h2_connect,
@ -2807,35 +2810,12 @@ out:
return result;
}
static bool cf_is_http2(struct Curl_cfilter *cf,
const struct Curl_easy *data)
bool Curl_http2_may_switch(struct Curl_easy *data)
{
(void)data;
for(; cf; cf = cf->next) {
if(cf->cft == &Curl_cft_nghttp2)
return TRUE;
if(cf->cft->flags & CF_TYPE_IP_CONNECT)
return FALSE;
}
return FALSE;
}
bool Curl_conn_is_http2(const struct Curl_easy *data,
const struct connectdata *conn,
int sockindex)
{
return conn ? cf_is_http2(conn->cfilter[sockindex], data) : FALSE;
}
bool Curl_http2_may_switch(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
(void)sockindex;
if(!Curl_conn_is_http2(data, conn, sockindex) &&
if(Curl_conn_http_version(data) < 20 &&
data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) {
/* We do not support HTTP/2 proxies yet. Also it is debatable
whether or not this setting should apply to HTTP/2 proxies. */
infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
@ -2847,21 +2827,19 @@ bool Curl_http2_may_switch(struct Curl_easy *data,
return FALSE;
}
CURLcode Curl_http2_switch(struct Curl_easy *data,
struct connectdata *conn, int sockindex)
CURLcode Curl_http2_switch(struct Curl_easy *data)
{
struct Curl_cfilter *cf;
CURLcode result;
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
DEBUGASSERT(Curl_conn_http_version(data) < 20);
result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE);
result = http2_cfilter_add(&cf, data, data->conn, FIRSTSOCKET, FALSE);
if(result)
return result;
CURL_TRC_CF(data, cf, "switching connection to HTTP/2");
conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
data->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
if(cf->next) {
@ -2876,14 +2854,13 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
struct Curl_cfilter *cf_h2;
CURLcode result;
DEBUGASSERT(!cf_is_http2(cf, data));
DEBUGASSERT(Curl_conn_http_version(data) < 20);
result = http2_cfilter_insert_after(cf, data, FALSE);
if(result)
return result;
cf_h2 = cf->next;
cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
@ -2902,7 +2879,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
struct cf_h2_ctx *ctx;
CURLcode result;
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
DEBUGASSERT(Curl_conn_http_version(data) < 20);
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE);
@ -2935,7 +2912,6 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
" after upgrade: len=%zu", nread);
}
conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
@ -2950,7 +2926,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
CURLE_HTTP2_STREAM error! */
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
{
if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) {
if(Curl_conn_http_version(data) == 20) {
int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
return err == NGHTTP2_HTTP_1_1_REQUIRED;
}

View File

@ -44,15 +44,9 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
/* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */
bool Curl_h2_http_1_1_error(struct Curl_easy *data);
bool Curl_conn_is_http2(const struct Curl_easy *data,
const struct connectdata *conn,
int sockindex);
bool Curl_http2_may_switch(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
bool Curl_http2_may_switch(struct Curl_easy *data);
CURLcode Curl_http2_switch(struct Curl_easy *data,
struct connectdata *conn, int sockindex);
CURLcode Curl_http2_switch(struct Curl_easy *data);
CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data);
@ -69,12 +63,10 @@ extern struct Curl_cftype Curl_cft_nghttp2;
#else /* USE_NGHTTP2 */
#define Curl_cf_is_http2(a,b) FALSE
#define Curl_conn_is_http2(a,b,c) FALSE
#define Curl_http2_may_switch(a,b,c) FALSE
#define Curl_http2_may_switch(a) FALSE
#define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_switch(a,b,c) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_switch(a) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_h2_http_1_1_error(x) 0
#endif

View File

@ -56,7 +56,7 @@ static bool hd_name_eq(const char *n1, size_t n1len,
}
static CURLcode dynhds_add_custom(struct Curl_easy *data,
bool is_connect,
bool is_connect, int httpversion,
struct dynhds *hds)
{
struct connectdata *conn = data->conn;
@ -169,9 +169,9 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data,
Connection: */
hd_name_eq(name, namelen, STRCONST("Connection:")))
;
else if((conn->httpversion >= 20) &&
else if((httpversion >= 20) &&
hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
/* HTTP/2 does not support chunked requests */
/* HTTP/2 and HTTP/3 do not support chunked requests */
;
else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
@ -221,11 +221,18 @@ CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
return CURLE_OK;
}
struct cf_proxy_ctx {
/* the protocol specific sub-filter we install during connect */
struct Curl_cfilter *cf_protocol;
int httpversion; /* HTTP version used to CONNECT */
};
CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
struct Curl_cfilter *cf,
struct Curl_easy *data,
int http_version_major)
{
struct cf_proxy_ctx *ctx = cf->ctx;
const char *hostname = NULL;
char *authority = NULL;
int port;
@ -286,7 +293,7 @@ CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
goto out;
}
result = dynhds_add_custom(data, TRUE, &req->headers);
result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers);
out:
if(result && req) {
@ -298,12 +305,6 @@ out:
return result;
}
struct cf_proxy_ctx {
/* the protocol specific sub-filter we install during connect */
struct Curl_cfilter *cf_protocol;
};
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
@ -325,6 +326,7 @@ connect_sub:
*done = FALSE;
if(!ctx->cf_protocol) {
struct Curl_cfilter *cf_protocol = NULL;
int httpversion = 0;
int alpn = Curl_conn_cf_is_ssl(cf->next) ?
cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
@ -340,6 +342,7 @@ connect_sub:
if(result)
goto out;
cf_protocol = cf->next;
httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
break;
#ifdef USE_NGHTTP2
case CURL_HTTP_VERSION_2:
@ -349,6 +352,7 @@ connect_sub:
if(result)
goto out;
cf_protocol = cf->next;
httpversion = 20;
break;
#endif
default:
@ -358,6 +362,7 @@ connect_sub:
}
ctx->cf_protocol = cf_protocol;
ctx->httpversion = httpversion;
/* after we installed the filter "below" us, we call connect
* on out sub-chain again.
*/

View File

@ -66,7 +66,8 @@ CURLcode Curl_req_soft_reset(struct SingleRequest *req,
req->headerbytecount = 0;
req->allheadercount = 0;
req->deductheadercount = 0;
req->httpversion_sent = 0;
req->httpversion = 0;
result = Curl_client_start(data);
if(result)
return result;
@ -373,7 +374,8 @@ static CURLcode req_send_buffer_add(struct Curl_easy *data,
return CURLE_OK;
}
CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req)
CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req,
unsigned char httpversion)
{
CURLcode result;
const char *buf;
@ -382,6 +384,7 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req)
if(!data || !data->conn)
return CURLE_FAILED_INIT;
data->req.httpversion_sent = httpversion;
buf = Curl_dyn_ptr(req);
blen = Curl_dyn_len(req);
if(!Curl_creader_total_length(data)) {

View File

@ -81,10 +81,11 @@ 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;
unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */
unsigned char httpversion; /* Version in response (09, 10, 11, etc.) */
enum upgrade101 upgr101; /* 101 upgrade state */
/* Client Writer stack, handles transfer- and content-encodings, protocol
@ -199,9 +200,11 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data);
* bytes are really send.
* @param data the transfer making the request
* @param buf the complete header bytes, no body
* @param httpversion version used in request (09, 10, 11, etc.)
* @return CURLE_OK (on blocking with *pnwritten == 0) or error.
*/
CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf);
CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf,
unsigned char httpversion);
/**
* TRUE iff the request has sent all request headers and data.

View File

@ -230,6 +230,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
Curl_RtspReq rtspreq = data->set.rtspreq;
struct RTSP *rtsp = data->req.p.rtsp;
struct dynbuf req_buffer;
unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */
const char *p_request = NULL;
const char *p_session_id = NULL;
@ -499,7 +500,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
goto out;
}
result = Curl_add_custom_headers(data, FALSE, &req_buffer);
result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
if(result)
goto out;
@ -585,7 +586,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE);
/* issue the request */
result = Curl_req_send(data, &req_buffer);
result = Curl_req_send(data, &req_buffer, httpversion);
if(result) {
failf(data, "Failed sending RTSP request");
goto out;

View File

@ -782,7 +782,7 @@ static void xfer_setup(
DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1));
DEBUGASSERT(!shutdown || (sockindex == -1) || (writesockindex == -1));
if(conn->bits.multiplex || conn->httpversion >= 20 || want_send) {
if(Curl_conn_is_multiplex(conn, FIRSTSOCKET) || want_send) {
/* when multiplexing, the read/write sockets need to be the same! */
conn->sockfd = sockindex == -1 ?
((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :

View File

@ -1002,7 +1002,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
if(match->may_multiplex &&
(data->state.httpwant == CURL_HTTP_VERSION_2_0) &&
(needle->handler->protocol & CURLPROTO_HTTP) &&
!conn->httpversion) {
!conn->httpversion_seen) {
if(data->set.pipewait) {
infof(data, "Server upgrade does not support multiplex yet, wait");
match->found = NULL;
@ -1036,17 +1036,18 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
* so we take any existing connection. */
if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
(data->state.httpwant != CURL_HTTP_VERSION_2TLS)) {
if((conn->httpversion >= 20) &&
unsigned char httpversion = Curl_conn_http_version(data);
if((httpversion >= 20) &&
(data->state.httpwant < CURL_HTTP_VERSION_2_0)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h2",
conn->connection_id, conn->httpversion));
conn->connection_id, httpversion));
}
if((conn->httpversion >= 30) &&
if((httpversion >= 30) &&
(data->state.httpwant < CURL_HTTP_VERSION_3)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h3",
conn->connection_id, conn->httpversion));
conn->connection_id, httpversion));
return FALSE;
}
}
@ -3135,14 +3136,14 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
/* protocol version switch */
switch(as->dst.alpnid) {
case ALPN_h1:
conn->httpversion = 11;
data->state.httpwant = CURL_HTTP_VERSION_1_1;
break;
case ALPN_h2:
conn->httpversion = 20;
data->state.httpwant = CURL_HTTP_VERSION_2_0;
break;
case ALPN_h3:
conn->transport = TRNSPRT_QUIC;
conn->httpversion = 30;
data->state.httpwant = CURL_HTTP_VERSION_3;
break;
default: /* should not be possible */
break;

View File

@ -950,7 +950,9 @@ struct connectdata {
#endif
unsigned char transport; /* one of the TRNSPRT_* defines */
unsigned char ip_version; /* copied from the Curl_easy at creation time */
unsigned char httpversion; /* the HTTP version*10 reported by the server */
/* HTTP version last responded with by the server.
* 0 at start, then one of 09, 10, 11, etc. */
unsigned char httpversion_seen;
unsigned char connect_only;
unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */
};

View File

@ -918,7 +918,6 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
if(ctx->handshake_succeeded) {
CURL_TRC_CF(data, cf, "handshake succeeded");
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->httpversion = 30;
cf->connected = TRUE;
cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
@ -1025,6 +1024,9 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at;
return CURLE_OK;
}
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default:
break;
}
@ -1047,7 +1049,7 @@ static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_msh3_destroy,
cf_msh3_connect,

View File

@ -470,7 +470,6 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
ctx->handshake_at = Curl_now();
ctx->tls_handshake_complete = TRUE;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->httpversion = 30;
ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf,
data, &ctx->peer);
@ -2594,6 +2593,9 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at;
return CURLE_OK;
}
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default:
break;
}
@ -2656,7 +2658,7 @@ out:
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_ngtcp2_destroy,
cf_ngtcp2_connect,

View File

@ -570,7 +570,6 @@ static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf,
struct cf_osslq_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->httpversion = 30;
return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
}
@ -2359,6 +2358,9 @@ static CURLcode cf_osslq_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at;
return CURLE_OK;
}
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default:
break;
}
@ -2369,7 +2371,7 @@ static CURLcode cf_osslq_query(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_osslq_destroy,
cf_osslq_connect,

View File

@ -1372,7 +1372,6 @@ static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf,
struct cf_quiche_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->httpversion = 30;
return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
}
@ -1566,6 +1565,9 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at;
return CURLE_OK;
}
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default:
break;
}
@ -1613,7 +1615,7 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_quiche_destroy,
cf_quiche_connect,

View File

@ -689,24 +689,6 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
#endif
}
bool Curl_conn_is_http3(const struct Curl_easy *data,
const struct connectdata *conn,
int sockindex)
{
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
return Curl_conn_is_ngtcp2(data, conn, sockindex);
#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)
return Curl_conn_is_osslq(data, conn, sockindex);
#elif defined(USE_QUICHE)
return Curl_conn_is_quiche(data, conn, sockindex);
#elif defined(USE_MSH3)
return Curl_conn_is_msh3(data, conn, sockindex);
#else
return (conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(conn->httpversion == 30);
#endif
}
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
const struct connectdata *conn)
{

View File

@ -46,16 +46,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
const struct Curl_addrinfo *ai,
int transport);
bool Curl_conn_is_http3(const struct Curl_easy *data,
const struct connectdata *conn,
int sockindex);
extern struct Curl_cftype Curl_cft_http3;
#else /* USE_HTTP3 */
#define Curl_conn_is_http3(a,b,c) FALSE
#endif /* !USE_HTTP3 */
CURLcode Curl_conn_may_http3(struct Curl_easy *data,

View File

@ -68,7 +68,7 @@ Accept: */*
</protocol>
# curl: (1) Version mismatch (from HTTP/1 to HTTP/2)
<errorcode>
1
8
</errorcode>
</verify>
</testcase>

View File

@ -98,6 +98,7 @@ class TestReuse:
for s in r.stats:
assert s['http_version'] == '3', f'{s}'
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
def test_12_04_alt_svc_h3h2(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs()
httpd.reload()
@ -115,11 +116,12 @@ class TestReuse:
'--alt-svc', f'{asfile}',
])
r.check_response(count=count, http_status=200)
# We expect the connection to be reused
# We expect the connection to be reused and use HTTP/2
assert r.total_connects == 1
for s in r.stats:
assert s['http_version'] == '2', f'{s}'
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
def test_12_05_alt_svc_h3h1(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs()
httpd.reload()
@ -137,9 +139,7 @@ class TestReuse:
'--alt-svc', f'{asfile}',
])
r.check_response(count=count, http_status=200)
# We expect the connection to be reused
# We expect the connection to be reused and use HTTP/1.1
assert r.total_connects == 1
# When using http/1.1 from alt-svc, we ALPN-negotiate 'h2,http/1.1' anyway
# which means our server gives us h2
for s in r.stats:
assert s['http_version'] == '2', f'{s}'
assert s['http_version'] == '1.1', f'{s}'