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) { for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_MULTIPLEX) if(cf->cft->flags & CF_TYPE_MULTIPLEX)
return TRUE; return TRUE;
if(cf->cft->flags & CF_TYPE_IP_CONNECT if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL))
|| cf->cft->flags & CF_TYPE_SSL)
return FALSE; return FALSE;
} }
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) bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
{ {
struct Curl_cfilter *cf; 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_STREAM_ERROR 6 /* error code - */
#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */ #define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */
#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */ #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 * 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_SSL: provide SSL/TLS
* CF_TYPE_MULTIPLEX: provides multiplexing of easy handles * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles
* CF_TYPE_PROXY provides proxying * 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_IP_CONNECT (1 << 0)
#define CF_TYPE_SSL (1 << 1) #define CF_TYPE_SSL (1 << 1)
#define CF_TYPE_MULTIPLEX (1 << 2) #define CF_TYPE_MULTIPLEX (1 << 2)
#define CF_TYPE_PROXY (1 << 3) #define CF_TYPE_PROXY (1 << 3)
#define CF_TYPE_HTTP (1 << 4)
/* A connection filter type, e.g. specific implementation. */ /* A connection filter type, e.g. specific implementation. */
struct Curl_cftype { 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); 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`. * Close the filter chain at `sockindex` for connection `data->conn`.
* Filters remain in place and may be connected again afterwards. * 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, static CURLcode http_range(struct Curl_easy *data,
Curl_HttpReq httpreq); Curl_HttpReq httpreq);
static CURLcode http_req_complete(struct Curl_easy *data, 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, static CURLcode http_req_set_reader(struct Curl_easy *data,
Curl_HttpReq httpreq, Curl_HttpReq httpreq, int httpversion,
const char **tep); const char **tep);
static CURLcode http_size(struct Curl_easy *data); static CURLcode http_size(struct Curl_easy *data);
static CURLcode http_statusline(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 #ifdef HAVE_LIBZ
static CURLcode http_transferencode(struct Curl_easy *data); static CURLcode http_transferencode(struct Curl_easy *data);
#endif #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 else
data->info.httpauthpicked = data->state.authhost.picked; data->info.httpauthpicked = data->state.authhost.picked;
if(data->state.authhost.picked == CURLAUTH_NTLM && if(data->state.authhost.picked == CURLAUTH_NTLM &&
conn->httpversion > 11) { (data->req.httpversion_sent > 11)) {
infof(data, "Forcing HTTP/1.1 for NTLM"); infof(data, "Forcing HTTP/1.1 for NTLM");
connclose(conn, "Force HTTP/1.1 connection"); connclose(conn, "Force HTTP/1.1 connection");
data->state.httpwant = CURL_HTTP_VERSION_1_1; data->state.httpwant = CURL_HTTP_VERSION_1_1;
@ -1217,45 +1216,55 @@ CURLcode Curl_http_done(struct Curl_easy *data,
return CURLE_OK; return CURLE_OK;
} }
/* /* Determine if we may use HTTP 1.1 for this request. */
* Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons static bool http_may_use_1_1(const struct Curl_easy *data)
* 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)
{ {
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; 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) && if((data->state.httpwant == CURL_HTTP_VERSION_1_0) &&
(conn->httpversion <= 10)) (conn->httpversion_seen <= 10))
return FALSE; return FALSE;
/* We want something newer than 1.0 or have no preferences. */
return (data->state.httpwant == CURL_HTTP_VERSION_NONE) || return (data->state.httpwant == CURL_HTTP_VERSION_NONE) ||
(data->state.httpwant >= CURL_HTTP_VERSION_1_1); (data->state.httpwant >= CURL_HTTP_VERSION_1_1);
} }
static const char *get_http_string(const struct Curl_easy *data, static unsigned char http_request_version(struct Curl_easy *data)
const struct connectdata *conn)
{ {
if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)) unsigned char httpversion = Curl_conn_http_version(data);
return "3"; if(!httpversion) {
if(Curl_conn_is_http2(data, conn, FIRSTSOCKET)) /* No specific HTTP connection filter installed. */
return "2"; httpversion = http_may_use_1_1(data) ? 11 : 10;
if(use_http_1_1plus(data, conn)) }
return "1.1"; 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"; return "1.0";
} }
}
CURLcode Curl_add_custom_headers(struct Curl_easy *data, CURLcode Curl_add_custom_headers(struct Curl_easy *data,
bool is_connect, bool is_connect, int httpversion,
struct dynbuf *req) struct dynbuf *req)
{ {
struct connectdata *conn = data->conn;
char *ptr; char *ptr;
struct curl_slist *h[2]; struct curl_slist *h[2];
struct curl_slist *headers; struct curl_slist *headers;
@ -1268,7 +1277,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
if(is_connect) if(is_connect)
proxy = HEADER_CONNECT; proxy = HEADER_CONNECT;
else else
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ?
HEADER_PROXY : HEADER_SERVER; HEADER_PROXY : HEADER_SERVER;
switch(proxy) { switch(proxy) {
@ -1368,7 +1377,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
Connection: */ Connection: */
checkprefix("Connection:", compare)) checkprefix("Connection:", compare))
; ;
else if((conn->httpversion >= 20) && else if((httpversion >= 20) &&
checkprefix("Transfer-Encoding:", compare)) checkprefix("Transfer-Encoding:", compare))
/* HTTP/2 does not support chunked requests */ /* 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, static CURLcode http_req_set_reader(struct Curl_easy *data,
Curl_HttpReq httpreq, Curl_HttpReq httpreq, int httpversion,
const char **tep) const char **tep)
{ {
CURLcode result = CURLE_OK; CURLcode result = CURLE_OK;
@ -1920,9 +1929,7 @@ static CURLcode http_req_set_reader(struct Curl_easy *data,
data->req.upload_chunky = data->req.upload_chunky =
Curl_compareheader(ptr, Curl_compareheader(ptr,
STRCONST("Transfer-Encoding:"), STRCONST("chunked")); STRCONST("Transfer-Encoding:"), STRCONST("chunked"));
if(data->req.upload_chunky && if(data->req.upload_chunky && (httpversion >= 20)) {
use_http_1_1plus(data, data->conn) &&
(data->conn->httpversion >= 20)) {
infof(data, "suppressing chunked transfer encoding on connection " infof(data, "suppressing chunked transfer encoding on connection "
"using HTTP version 2 or higher"); "using HTTP version 2 or higher");
data->req.upload_chunky = FALSE; data->req.upload_chunky = FALSE;
@ -1933,10 +1940,10 @@ static CURLcode http_req_set_reader(struct Curl_easy *data,
if(req_clen < 0) { if(req_clen < 0) {
/* indeterminate request content length */ /* 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 /* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not
* need it */ * need it */
data->req.upload_chunky = (data->conn->httpversion < 20); data->req.upload_chunky = (httpversion < 20);
} }
else { else {
failf(data, "Chunky upload is not supported by HTTP 1.0"); 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, static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
bool *announced_exp100) int httpversion, bool *announced_exp100)
{ {
CURLcode result; CURLcode result;
char *ptr; char *ptr;
@ -1974,9 +1981,7 @@ static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
*announced_exp100 = *announced_exp100 =
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
} }
else if(!data->state.disableexpect && else if(!data->state.disableexpect && (httpversion == 11)) {
use_http_1_1plus(data, data->conn) &&
(data->conn->httpversion < 20)) {
/* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an /* 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 Expect: 100-continue to the headers which actually speeds up post
operations (as there is one packet coming back from the web server) */ 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, 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; CURLcode result = CURLE_OK;
curl_off_t req_clen; curl_off_t req_clen;
@ -2052,7 +2058,7 @@ static CURLcode http_req_complete(struct Curl_easy *data,
goto out; goto out;
} }
} }
result = addexpect(data, r, &announced_exp100); result = addexpect(data, r, httpversion, &announced_exp100);
if(result) if(result)
goto out; goto out;
break; break;
@ -2326,6 +2332,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
struct dynbuf req; struct dynbuf req;
char *altused = NULL; char *altused = NULL;
const char *p_accept; /* Accept: string */ const char *p_accept; /* Accept: string */
unsigned char httpversion;
/* Always consider the DO phase done after this function call, even if there /* 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 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) { switch(conn->alpn) {
case CURL_HTTP_VERSION_3: case CURL_HTTP_VERSION_3:
DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET)); DEBUGASSERT(Curl_conn_http_version(data) == 30);
break; break;
case CURL_HTTP_VERSION_2: case CURL_HTTP_VERSION_2:
#ifndef CURL_DISABLE_PROXY #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 conn->bits.proxy && !conn->bits.tunnel_proxy
) { ) {
result = Curl_http2_switch(data, conn, FIRSTSOCKET); result = Curl_http2_switch(data);
if(result) if(result)
goto fail; goto fail;
} }
else else
#endif #endif
DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET)); DEBUGASSERT(Curl_conn_http_version(data) == 20);
break; break;
case CURL_HTTP_VERSION_1_1: case CURL_HTTP_VERSION_1_1:
/* continue with HTTP/1.x when explicitly requested */ /* continue with HTTP/1.x when explicitly requested */
break; break;
default: default:
/* Check if user wants to use HTTP/2 with clear TCP */ /* 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")); DEBUGF(infof(data, "HTTP/2 over clean TCP"));
result = Curl_http2_switch(data, conn, FIRSTSOCKET); result = Curl_http2_switch(data);
if(result) if(result)
goto fail; goto fail;
} }
@ -2420,7 +2427,10 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
goto fail; goto fail;
#endif #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) if(result)
goto fail; goto fail;
@ -2431,8 +2441,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(result) if(result)
goto fail; goto fail;
httpstring = get_http_string(data, conn);
/* initialize a dynamic send-buffer */ /* initialize a dynamic send-buffer */
Curl_dyn_init(&req, DYN_HTTP_REQUEST); Curl_dyn_init(&req, DYN_HTTP_REQUEST);
@ -2526,8 +2534,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
goto fail; goto fail;
} }
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) &&
conn->httpversion < 20 &&
(data->state.httpwant == CURL_HTTP_VERSION_2)) { (data->state.httpwant == CURL_HTTP_VERSION_2)) {
/* append HTTP2 upgrade magic stuff to the HTTP request if it is not done /* append HTTP2 upgrade magic stuff to the HTTP request if it is not done
over SSL */ over SSL */
@ -2546,19 +2553,19 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(!result) if(!result)
result = Curl_add_timecondition(data, &req); result = Curl_add_timecondition(data, &req);
if(!result) if(!result)
result = Curl_add_custom_headers(data, FALSE, &req); result = Curl_add_custom_headers(data, FALSE, httpversion, &req);
if(!result) { if(!result) {
/* req_send takes ownership of the 'req' memory on success */ /* 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) if(!result)
result = Curl_req_send(data, &req); result = Curl_req_send(data, &req, httpversion);
} }
Curl_dyn_free(&req); Curl_dyn_free(&req);
if(result) if(result)
goto fail; 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, /* 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 but is disabled here again to avoid that the chunked encoded version is
actually used when sending the request body over h2 */ 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; )) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL;
if(v) { if(v) {
/* the ALPN of the current request */ /* the ALPN of the current request */
enum alpnid id = (conn->httpversion == 30) ? ALPN_h3 : enum alpnid id = (k->httpversion == 30) ? ALPN_h3 :
(conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; (k->httpversion == 20) ? ALPN_h2 : ALPN_h1;
return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name, return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name,
curlx_uitous((unsigned int)conn->remote_port)); curlx_uitous((unsigned int)conn->remote_port));
} }
@ -2753,7 +2760,7 @@ static CURLcode http_header(struct Curl_easy *data,
streamclose(conn, "Connection: close used"); streamclose(conn, "Connection: close used");
return CURLE_OK; return CURLE_OK;
} }
if((conn->httpversion == 10) && if((k->httpversion == 10) &&
HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) { HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) {
/* /*
* An HTTP/1.0 reply with the 'Connection: keep-alive' line * 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 #ifndef CURL_DISABLE_PROXY
v = HD_VAL(hd, hdlen, "Proxy-Connection:"); v = HD_VAL(hd, hdlen, "Proxy-Connection:");
if(v) { 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")) { HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) {
/* /*
* When an HTTP/1.0 reply comes when using a proxy, the * 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 */ connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */
infof(data, "HTTP/1.0 proxy connection set to keep alive"); 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")) { HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) {
/* /*
* We get an HTTP/1.1 response from a proxy and it says it will * 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: case 30:
#endif #endif
/* no major version switch mid-connection */ /* no major version switch mid-connection */
if(conn->httpversion && if(k->httpversion_sent &&
(k->httpversion/10 != conn->httpversion/10)) { (k->httpversion/10 != k->httpversion_sent/10)) {
failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)", failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)",
conn->httpversion/10, k->httpversion/10); k->httpversion_sent/10, k->httpversion/10);
return CURLE_UNSUPPORTED_PROTOCOL; return CURLE_WEIRD_SERVER_REPLY;
} }
break; break;
default: default:
@ -3058,7 +3065,7 @@ static CURLcode http_statusline(struct Curl_easy *data,
data->info.httpcode = k->httpcode; data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion; 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) if(!data->state.httpversion || data->state.httpversion > k->httpversion)
/* store the lowest server version we encounter */ /* 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) { if(k->upgr101 == UPGR101_RECEIVED) {
/* supposedly upgraded to http2 now */ /* supposedly upgraded to http2 now */
if(conn->httpversion != 20) if(data->req.httpversion != 20)
infof(data, "Lying server, not serving HTTP/2"); infof(data, "Lying server, not serving HTTP/2");
} }
@ -3274,8 +3281,7 @@ static CURLcode http_on_response(struct Curl_easy *data,
break; break;
case 101: case 101:
/* Switching Protocols only allowed from HTTP/1.1 */ /* Switching Protocols only allowed from HTTP/1.1 */
if(k->httpversion_sent != 11) {
if(conn->httpversion != 11) {
/* invalid for other HTTP versions */ /* invalid for other HTTP versions */
failf(data, "unexpected 101 response code"); failf(data, "unexpected 101 response code");
result = CURLE_WEIRD_SERVER_REPLY; 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 */ /* We expect more response from HTTP/2 later */
k->header = TRUE; k->header = TRUE;
k->headerline = 0; /* restart the header line counter */ 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 /* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */ * be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); 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 && if((k->size == -1) && !k->chunk && !conn->bits.close &&
(conn->httpversion == 11) && (k->httpversion == 11) &&
!(conn->handler->protocol & CURLPROTO_RTSP) && !(conn->handler->protocol & CURLPROTO_RTSP) &&
data->state.httpreq != HTTPREQ_HEAD) { data->state.httpreq != HTTPREQ_HEAD) {
/* On HTTP 1.1, when connection is not to get closed, but no /* 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 like to call http2_handle_stream_close to properly close a
stream. In order to do this, we keep reading until we stream. In order to do this, we keep reading until we
close the stream. */ close the stream. */
if(0 == k->maxdownload if((0 == k->maxdownload) && (k->httpversion_sent < 20))
&& !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
&& !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
k->download_done = TRUE; k->download_done = TRUE;
/* final response without error, prepare to receive the body */ /* final response without error, prepare to receive the body */
@ -3573,7 +3578,7 @@ static CURLcode http_rw_hd(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])) {
k->httpversion = 10 + (p[1] - '0'); k->httpversion = (unsigned char)(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 +
@ -3593,7 +3598,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data,
case '3': case '3':
if(!ISBLANK(p[1])) if(!ISBLANK(p[1]))
break; break;
k->httpversion = (*p - '0') * 10; k->httpversion = (unsigned char)((*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 +
@ -3723,10 +3728,11 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
Curl_dyn_len(&data->state.headerb)); Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) { 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; k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator"); 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"); failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY; return CURLE_WEIRD_SERVER_REPLY;
} }
@ -3761,8 +3767,9 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
Curl_dyn_len(&data->state.headerb)); Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) { if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator"); streamclose(conn, "bad HTTP: No end-of-message indicator");
/* this is not the beginning of a protocol first header line */ /* this is not the beginning of a protocol first header line.
if(conn->httpversion >= 10) { * Cannot be 0.9 if version was detected or connection was reused. */
if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line"); failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY; 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_timecondition(struct Curl_easy *data, struct dynbuf *req);
CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, 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, CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect,
struct dynhds *hds); struct dynhds *hds);

View File

@ -2723,6 +2723,9 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
} }
break; break;
} }
case CF_QUERY_HTTP_VERSION:
*pres1 = 20;
return CURLE_OK;
default: default:
break; break;
} }
@ -2733,7 +2736,7 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_nghttp2 = { struct Curl_cftype Curl_cft_nghttp2 = {
"HTTP/2", "HTTP/2",
CF_TYPE_MULTIPLEX, CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
CURL_LOG_LVL_NONE, CURL_LOG_LVL_NONE,
cf_h2_destroy, cf_h2_destroy,
cf_h2_connect, cf_h2_connect,
@ -2807,35 +2810,12 @@ out:
return result; return result;
} }
static bool cf_is_http2(struct Curl_cfilter *cf, bool Curl_http2_may_switch(struct Curl_easy *data)
const struct Curl_easy *data)
{ {
(void)data; if(Curl_conn_http_version(data) < 20 &&
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) &&
data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
#ifndef CURL_DISABLE_PROXY #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 /* We do not support HTTP/2 proxies yet. Also it is debatable
whether or not this setting should apply to HTTP/2 proxies. */ whether or not this setting should apply to HTTP/2 proxies. */
infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); 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; return FALSE;
} }
CURLcode Curl_http2_switch(struct Curl_easy *data, CURLcode Curl_http2_switch(struct Curl_easy *data)
struct connectdata *conn, int sockindex)
{ {
struct Curl_cfilter *cf; struct Curl_cfilter *cf;
CURLcode result; 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) if(result)
return result; return result;
CURL_TRC_CF(data, cf, "switching connection to HTTP/2"); CURL_TRC_CF(data, cf, "switching connection to HTTP/2");
conn->httpversion = 20; /* we know we are on HTTP/2 now */ data->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi); Curl_multi_connchanged(data->multi);
if(cf->next) { 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; struct Curl_cfilter *cf_h2;
CURLcode result; CURLcode result;
DEBUGASSERT(!cf_is_http2(cf, data)); DEBUGASSERT(Curl_conn_http_version(data) < 20);
result = http2_cfilter_insert_after(cf, data, FALSE); result = http2_cfilter_insert_after(cf, data, FALSE);
if(result) if(result)
return result; return result;
cf_h2 = cf->next; 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 */ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi); Curl_multi_connchanged(data->multi);
@ -2902,7 +2879,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
struct cf_h2_ctx *ctx; struct cf_h2_ctx *ctx;
CURLcode result; CURLcode result;
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); DEBUGASSERT(Curl_conn_http_version(data) < 20);
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE); 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); " after upgrade: len=%zu", nread);
} }
conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi); Curl_multi_connchanged(data->multi);
@ -2950,7 +2926,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
CURLE_HTTP2_STREAM error! */ CURLE_HTTP2_STREAM error! */
bool Curl_h2_http_1_1_error(struct Curl_easy *data) 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); int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
return err == NGHTTP2_HTTP_1_1_REQUIRED; 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 */ /* 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_h2_http_1_1_error(struct Curl_easy *data);
bool Curl_conn_is_http2(const struct Curl_easy *data, bool Curl_http2_may_switch(struct Curl_easy *data);
const struct connectdata *conn,
int sockindex);
bool Curl_http2_may_switch(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
CURLcode Curl_http2_switch(struct Curl_easy *data, CURLcode Curl_http2_switch(struct Curl_easy *data);
struct connectdata *conn, int sockindex);
CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, 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 */ #else /* USE_NGHTTP2 */
#define Curl_cf_is_http2(a,b) FALSE #define Curl_http2_may_switch(a) FALSE
#define Curl_conn_is_http2(a,b,c) FALSE
#define Curl_http2_may_switch(a,b,c) FALSE
#define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL #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_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_h2_http_1_1_error(x) 0 #define Curl_h2_http_1_1_error(x) 0
#endif #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, static CURLcode dynhds_add_custom(struct Curl_easy *data,
bool is_connect, bool is_connect, int httpversion,
struct dynhds *hds) struct dynhds *hds)
{ {
struct connectdata *conn = data->conn; struct connectdata *conn = data->conn;
@ -169,9 +169,9 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data,
Connection: */ Connection: */
hd_name_eq(name, namelen, STRCONST("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:"))) 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:")) || else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
hd_name_eq(name, namelen, STRCONST("Cookie:"))) && hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
@ -221,11 +221,18 @@ CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
return CURLE_OK; 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, CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
struct Curl_cfilter *cf, struct Curl_cfilter *cf,
struct Curl_easy *data, struct Curl_easy *data,
int http_version_major) int http_version_major)
{ {
struct cf_proxy_ctx *ctx = cf->ctx;
const char *hostname = NULL; const char *hostname = NULL;
char *authority = NULL; char *authority = NULL;
int port; int port;
@ -286,7 +293,7 @@ CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
goto out; goto out;
} }
result = dynhds_add_custom(data, TRUE, &req->headers); result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers);
out: out:
if(result && req) { if(result && req) {
@ -298,12 +305,6 @@ out:
return result; 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, static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data, struct Curl_easy *data,
bool blocking, bool *done) bool blocking, bool *done)
@ -325,6 +326,7 @@ connect_sub:
*done = FALSE; *done = FALSE;
if(!ctx->cf_protocol) { if(!ctx->cf_protocol) {
struct Curl_cfilter *cf_protocol = NULL; struct Curl_cfilter *cf_protocol = NULL;
int httpversion = 0;
int alpn = Curl_conn_cf_is_ssl(cf->next) ? int alpn = Curl_conn_cf_is_ssl(cf->next) ?
cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
@ -340,6 +342,7 @@ connect_sub:
if(result) if(result)
goto out; goto out;
cf_protocol = cf->next; cf_protocol = cf->next;
httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
break; break;
#ifdef USE_NGHTTP2 #ifdef USE_NGHTTP2
case CURL_HTTP_VERSION_2: case CURL_HTTP_VERSION_2:
@ -349,6 +352,7 @@ connect_sub:
if(result) if(result)
goto out; goto out;
cf_protocol = cf->next; cf_protocol = cf->next;
httpversion = 20;
break; break;
#endif #endif
default: default:
@ -358,6 +362,7 @@ connect_sub:
} }
ctx->cf_protocol = cf_protocol; ctx->cf_protocol = cf_protocol;
ctx->httpversion = httpversion;
/* after we installed the filter "below" us, we call connect /* after we installed the filter "below" us, we call connect
* on out sub-chain again. * on out sub-chain again.
*/ */

View File

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

View File

@ -81,10 +81,11 @@ 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;
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 */ enum upgrade101 upgr101; /* 101 upgrade state */
/* Client Writer stack, handles transfer- and content-encodings, protocol /* 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. * bytes are really send.
* @param data the transfer making the request * @param data the transfer making the request
* @param buf the complete header bytes, no body * @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. * @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. * 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; Curl_RtspReq rtspreq = data->set.rtspreq;
struct RTSP *rtsp = data->req.p.rtsp; struct RTSP *rtsp = data->req.p.rtsp;
struct dynbuf req_buffer; 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_request = NULL;
const char *p_session_id = NULL; const char *p_session_id = NULL;
@ -499,7 +500,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
goto out; goto out;
} }
result = Curl_add_custom_headers(data, FALSE, &req_buffer); result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
if(result) if(result)
goto out; 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); Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE);
/* issue the request */ /* issue the request */
result = Curl_req_send(data, &req_buffer); result = Curl_req_send(data, &req_buffer, httpversion);
if(result) { if(result) {
failf(data, "Failed sending RTSP request"); failf(data, "Failed sending RTSP request");
goto out; goto out;

View File

@ -782,7 +782,7 @@ static void xfer_setup(
DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1)); DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1));
DEBUGASSERT(!shutdown || (sockindex == -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! */ /* when multiplexing, the read/write sockets need to be the same! */
conn->sockfd = sockindex == -1 ? conn->sockfd = sockindex == -1 ?
((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) : ((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 && if(match->may_multiplex &&
(data->state.httpwant == CURL_HTTP_VERSION_2_0) && (data->state.httpwant == CURL_HTTP_VERSION_2_0) &&
(needle->handler->protocol & CURLPROTO_HTTP) && (needle->handler->protocol & CURLPROTO_HTTP) &&
!conn->httpversion) { !conn->httpversion_seen) {
if(data->set.pipewait) { if(data->set.pipewait) {
infof(data, "Server upgrade does not support multiplex yet, wait"); infof(data, "Server upgrade does not support multiplex yet, wait");
match->found = NULL; match->found = NULL;
@ -1036,17 +1036,18 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
* so we take any existing connection. */ * so we take any existing connection. */
if((needle->handler->protocol & PROTO_FAMILY_HTTP) && if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
(data->state.httpwant != CURL_HTTP_VERSION_2TLS)) { (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)) { (data->state.httpwant < CURL_HTTP_VERSION_2_0)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h2", " 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)) { (data->state.httpwant < CURL_HTTP_VERSION_3)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h3", " with httpversion=%d, we want a version less than h3",
conn->connection_id, conn->httpversion)); conn->connection_id, httpversion));
return FALSE; return FALSE;
} }
} }
@ -3135,14 +3136,14 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
/* protocol version switch */ /* protocol version switch */
switch(as->dst.alpnid) { switch(as->dst.alpnid) {
case ALPN_h1: case ALPN_h1:
conn->httpversion = 11; data->state.httpwant = CURL_HTTP_VERSION_1_1;
break; break;
case ALPN_h2: case ALPN_h2:
conn->httpversion = 20; data->state.httpwant = CURL_HTTP_VERSION_2_0;
break; break;
case ALPN_h3: case ALPN_h3:
conn->transport = TRNSPRT_QUIC; conn->transport = TRNSPRT_QUIC;
conn->httpversion = 30; data->state.httpwant = CURL_HTTP_VERSION_3;
break; break;
default: /* should not be possible */ default: /* should not be possible */
break; break;

View File

@ -950,7 +950,9 @@ struct connectdata {
#endif #endif
unsigned char transport; /* one of the TRNSPRT_* defines */ unsigned char transport; /* one of the TRNSPRT_* defines */
unsigned char ip_version; /* copied from the Curl_easy at creation time */ 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 connect_only;
unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */ 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) { if(ctx->handshake_succeeded) {
CURL_TRC_CF(data, cf, "handshake succeeded"); CURL_TRC_CF(data, cf, "handshake succeeded");
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->httpversion = 30;
cf->connected = TRUE; cf->connected = TRUE;
cf->conn->alpn = CURL_HTTP_VERSION_3; cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE; *done = TRUE;
@ -1025,6 +1024,9 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at; *when = ctx->handshake_at;
return CURLE_OK; return CURLE_OK;
} }
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default: default:
break; break;
} }
@ -1047,7 +1049,7 @@ static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = { struct Curl_cftype Curl_cft_http3 = {
"HTTP/3", "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, 0,
cf_msh3_destroy, cf_msh3_destroy,
cf_msh3_connect, 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->handshake_at = Curl_now();
ctx->tls_handshake_complete = TRUE; ctx->tls_handshake_complete = TRUE;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 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, ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf,
data, &ctx->peer); data, &ctx->peer);
@ -2594,6 +2593,9 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
*when = ctx->handshake_at; *when = ctx->handshake_at;
return CURLE_OK; return CURLE_OK;
} }
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default: default:
break; break;
} }
@ -2656,7 +2658,7 @@ out:
struct Curl_cftype Curl_cft_http3 = { struct Curl_cftype Curl_cft_http3 = {
"HTTP/3", "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, 0,
cf_ngtcp2_destroy, cf_ngtcp2_destroy,
cf_ngtcp2_connect, 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; struct cf_osslq_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 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); 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; *when = ctx->handshake_at;
return CURLE_OK; return CURLE_OK;
} }
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default: default:
break; break;
} }
@ -2369,7 +2371,7 @@ static CURLcode cf_osslq_query(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = { struct Curl_cftype Curl_cft_http3 = {
"HTTP/3", "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, 0,
cf_osslq_destroy, cf_osslq_destroy,
cf_osslq_connect, 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; struct cf_quiche_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 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); 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; *when = ctx->handshake_at;
return CURLE_OK; return CURLE_OK;
} }
case CF_QUERY_HTTP_VERSION:
*pres1 = 30;
return CURLE_OK;
default: default:
break; break;
} }
@ -1613,7 +1615,7 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
struct Curl_cftype Curl_cft_http3 = { struct Curl_cftype Curl_cft_http3 = {
"HTTP/3", "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, 0,
cf_quiche_destroy, cf_quiche_destroy,
cf_quiche_connect, cf_quiche_connect,

View File

@ -689,24 +689,6 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
#endif #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, CURLcode Curl_conn_may_http3(struct Curl_easy *data,
const struct connectdata *conn) const struct connectdata *conn)
{ {

View File

@ -46,16 +46,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
const struct Curl_addrinfo *ai, const struct Curl_addrinfo *ai,
int transport); 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; extern struct Curl_cftype Curl_cft_http3;
#else /* USE_HTTP3 */
#define Curl_conn_is_http3(a,b,c) FALSE
#endif /* !USE_HTTP3 */ #endif /* !USE_HTTP3 */
CURLcode Curl_conn_may_http3(struct Curl_easy *data, CURLcode Curl_conn_may_http3(struct Curl_easy *data,

View File

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

View File

@ -98,6 +98,7 @@ class TestReuse:
for s in r.stats: for s in r.stats:
assert s['http_version'] == '3', f'{s}' 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): def test_12_04_alt_svc_h3h2(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs() httpd.clear_extra_configs()
httpd.reload() httpd.reload()
@ -115,11 +116,12 @@ class TestReuse:
'--alt-svc', f'{asfile}', '--alt-svc', f'{asfile}',
]) ])
r.check_response(count=count, http_status=200) 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 assert r.total_connects == 1
for s in r.stats: for s in r.stats:
assert s['http_version'] == '2', f'{s}' 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): def test_12_05_alt_svc_h3h1(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs() httpd.clear_extra_configs()
httpd.reload() httpd.reload()
@ -137,9 +139,7 @@ class TestReuse:
'--alt-svc', f'{asfile}', '--alt-svc', f'{asfile}',
]) ])
r.check_response(count=count, http_status=200) 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 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: for s in r.stats:
assert s['http_version'] == '2', f'{s}' assert s['http_version'] == '1.1', f'{s}'