mirror of
https://github.com/curl/curl.git
synced 2025-04-18 16:30:45 +08:00
http: version negotiation
Translate the `data->set.httpwant` which is one of the consts from the public API (CURL_HTTP_VERSION_*) into a major version mask plus additional flags for internal handling. `Curl_http_neg_init()` does the translation and flags setting in http.c, using new internal consts CURL_HTTP_V1x, CURL_HTTP_V2x and CURL_HTTP_V3x for the major versions. The flags are - only_10: when the application explicity asked fro HTTP/1.0 - h2_upgrade: when the application asks for upgrading 1.1 to 2. - h2_prior_knowledge: when directly talking h2 without ALPN - accept_09: when a HTTP/0.9 response is acceptable. The Alt-Svc and HTTPS RR redirections from one ALPN to another obey the allowed major versions. If a transfer has only h3 enabled, Alt-Svc redirection to h2 is ignored. This is the current implementation. It can be debated if Alt-Svc should be able to override the allowed major versions. Added test_12_06 to verify the current restriction. Closes #16100
This commit is contained in:
parent
f7fcbb8127
commit
db72b8d4d0
@ -651,67 +651,56 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data,
|
||||
(void)remotehost;
|
||||
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
switch(data->state.httpwant) {
|
||||
case CURL_HTTP_VERSION_NONE:
|
||||
/* No preferences by transfer setup. Choose best defaults */
|
||||
#ifdef USE_HTTPSRR
|
||||
if(conn->dns_entry && conn->dns_entry->hinfo &&
|
||||
!conn->dns_entry->hinfo->no_def_alpn) {
|
||||
size_t i, j;
|
||||
for(i = 0; i < CURL_ARRAYSIZE(conn->dns_entry->hinfo->alpns) &&
|
||||
alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) {
|
||||
bool present = FALSE;
|
||||
enum alpnid alpn = conn->dns_entry->hinfo->alpns[i];
|
||||
for(j = 0; j < alpn_count; ++j) {
|
||||
if(alpn == alpn_ids[j]) {
|
||||
present = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!present) {
|
||||
switch(alpn) {
|
||||
case ALPN_h3:
|
||||
if(Curl_conn_may_http3(data, conn))
|
||||
break; /* not possible */
|
||||
FALLTHROUGH();
|
||||
case ALPN_h2:
|
||||
case ALPN_h1:
|
||||
alpn_ids[alpn_count++] = alpn;
|
||||
break;
|
||||
default: /* ignore */
|
||||
break;
|
||||
}
|
||||
if(conn->dns_entry && conn->dns_entry->hinfo &&
|
||||
!conn->dns_entry->hinfo->no_def_alpn) {
|
||||
size_t i, j;
|
||||
for(i = 0; i < CURL_ARRAYSIZE(conn->dns_entry->hinfo->alpns) &&
|
||||
alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) {
|
||||
bool present = FALSE;
|
||||
enum alpnid alpn = conn->dns_entry->hinfo->alpns[i];
|
||||
for(j = 0; j < alpn_count; ++j) {
|
||||
if(alpn == alpn_ids[j]) {
|
||||
present = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(present)
|
||||
continue;
|
||||
switch(alpn) {
|
||||
case ALPN_h3:
|
||||
if(Curl_conn_may_http3(data, conn))
|
||||
break; /* not possible */
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V3x)
|
||||
alpn_ids[alpn_count++] = alpn;
|
||||
break;
|
||||
case ALPN_h2:
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V2x)
|
||||
alpn_ids[alpn_count++] = alpn;
|
||||
break;
|
||||
case ALPN_h1:
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V1x)
|
||||
alpn_ids[alpn_count++] = alpn;
|
||||
break;
|
||||
default: /* ignore */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(!alpn_count)
|
||||
|
||||
if(!alpn_count) {
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V3x) {
|
||||
result = Curl_conn_may_http3(data, conn);
|
||||
if(!result)
|
||||
alpn_ids[alpn_count++] = ALPN_h3;
|
||||
else if(data->state.http_neg.allowed == CURL_HTTP_V3x)
|
||||
goto out; /* only h3 allowed, not possible, error out */
|
||||
}
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V2x)
|
||||
alpn_ids[alpn_count++] = ALPN_h2;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3ONLY:
|
||||
result = Curl_conn_may_http3(data, conn);
|
||||
if(result) /* cannot do it */
|
||||
goto out;
|
||||
alpn_ids[alpn_count++] = ALPN_h3;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3:
|
||||
/* We assume that silently not even trying H3 is ok here */
|
||||
if(Curl_conn_may_http3(data, conn) == CURLE_OK)
|
||||
alpn_ids[alpn_count++] = ALPN_h3;
|
||||
alpn_ids[alpn_count++] = ALPN_h2;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_0:
|
||||
case CURL_HTTP_VERSION_2TLS:
|
||||
case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
|
||||
alpn_ids[alpn_count++] = ALPN_h2;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_1_0:
|
||||
case CURL_HTTP_VERSION_1_1:
|
||||
alpn_ids[alpn_count++] = ALPN_h1;
|
||||
break;
|
||||
default:
|
||||
alpn_ids[alpn_count++] = ALPN_h2;
|
||||
break;
|
||||
else if(data->state.http_neg.allowed & CURL_HTTP_V1x)
|
||||
alpn_ids[alpn_count++] = ALPN_h1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,13 +497,14 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
unsigned char Curl_conn_http_version(struct Curl_easy *data)
|
||||
unsigned char Curl_conn_http_version(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
struct Curl_cfilter *cf;
|
||||
CURLcode result = CURLE_UNKNOWN_OPTION;
|
||||
unsigned char v = 0;
|
||||
|
||||
cf = data->conn ? data->conn->cfilter[FIRSTSOCKET] : NULL;
|
||||
cf = conn->cfilter[FIRSTSOCKET];
|
||||
for(; cf; cf = cf->next) {
|
||||
if(cf->cft->flags & CF_TYPE_HTTP) {
|
||||
int value = 0;
|
||||
|
@ -399,7 +399,8 @@ 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);
|
||||
unsigned char Curl_conn_http_version(struct Curl_easy *data,
|
||||
struct connectdata *conn);
|
||||
|
||||
/**
|
||||
* Close the filter chain at `sockindex` for connection `data->conn`.
|
||||
|
82
lib/http.c
82
lib/http.c
@ -187,19 +187,54 @@ const struct Curl_handler Curl_handler_https = {
|
||||
|
||||
#endif
|
||||
|
||||
void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg)
|
||||
{
|
||||
memset(neg, 0, sizeof(*neg));
|
||||
neg->accept_09 = data->set.http09_allowed;
|
||||
switch(data->set.httpwant) {
|
||||
case CURL_HTTP_VERSION_1_0:
|
||||
neg->allowed = (CURL_HTTP_V1x);
|
||||
neg->only_10 = TRUE;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_1_1:
|
||||
neg->allowed = (CURL_HTTP_V1x);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_0:
|
||||
neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x);
|
||||
neg->h2_upgrade = TRUE;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2TLS:
|
||||
neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
|
||||
neg->allowed = (CURL_HTTP_V2x);
|
||||
data->state.http_neg.h2_prior_knowledge = TRUE;
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3:
|
||||
neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3ONLY:
|
||||
neg->allowed = (CURL_HTTP_V3x);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_NONE:
|
||||
default:
|
||||
neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CURLcode Curl_http_setup_conn(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
/* allocate the HTTP-specific struct for the Curl_easy, only to survive
|
||||
during this request */
|
||||
connkeep(conn, "HTTP default");
|
||||
|
||||
if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
|
||||
if(data->state.http_neg.allowed == CURL_HTTP_V3x) {
|
||||
/* only HTTP/3, needs to work */
|
||||
CURLcode result = Curl_conn_may_http3(data, conn);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
@ -538,7 +573,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data)
|
||||
(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;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V1x;
|
||||
}
|
||||
}
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
@ -1502,29 +1537,28 @@ static bool http_may_use_1_1(const struct Curl_easy *data)
|
||||
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)
|
||||
if(data->state.http_neg.rcvd_min == 10)
|
||||
return FALSE;
|
||||
/* We have seen a previous response on *this* connection with 1.0. */
|
||||
if(conn->httpversion_seen == 10)
|
||||
if(conn && 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_seen <= 10))
|
||||
if((data->state.http_neg.only_10) &&
|
||||
(!conn || 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);
|
||||
/* We are not restricted to use 1.0 only. */
|
||||
return !data->state.http_neg.only_10;
|
||||
}
|
||||
|
||||
static unsigned char http_request_version(struct Curl_easy *data)
|
||||
{
|
||||
unsigned char httpversion = Curl_conn_http_version(data);
|
||||
if(!httpversion) {
|
||||
unsigned char v = Curl_conn_http_version(data, data->conn);
|
||||
if(!v) {
|
||||
/* No specific HTTP connection filter installed. */
|
||||
httpversion = http_may_use_1_1(data) ? 11 : 10;
|
||||
v = http_may_use_1_1(data) ? 11 : 10;
|
||||
}
|
||||
return httpversion;
|
||||
return v;
|
||||
}
|
||||
|
||||
static const char *get_http_string(int httpversion)
|
||||
@ -2621,11 +2655,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
|
||||
|
||||
switch(conn->alpn) {
|
||||
case CURL_HTTP_VERSION_3:
|
||||
DEBUGASSERT(Curl_conn_http_version(data) == 30);
|
||||
DEBUGASSERT(Curl_conn_http_version(data, conn) == 30);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2:
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if((Curl_conn_http_version(data) != 20) &&
|
||||
if((Curl_conn_http_version(data, conn) != 20) &&
|
||||
conn->bits.proxy && !conn->bits.tunnel_proxy
|
||||
) {
|
||||
result = Curl_http2_switch(data);
|
||||
@ -2634,7 +2668,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
|
||||
}
|
||||
else
|
||||
#endif
|
||||
DEBUGASSERT(Curl_conn_http_version(data) == 20);
|
||||
DEBUGASSERT(Curl_conn_http_version(data, conn) == 20);
|
||||
break;
|
||||
case CURL_HTTP_VERSION_1_1:
|
||||
/* continue with HTTP/1.x when explicitly requested */
|
||||
@ -2815,7 +2849,8 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
|
||||
}
|
||||
|
||||
if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) &&
|
||||
(data->state.httpwant == CURL_HTTP_VERSION_2)) {
|
||||
(data->state.http_neg.allowed & CURL_HTTP_V2x) &&
|
||||
data->state.http_neg.h2_upgrade) {
|
||||
/* append HTTP2 upgrade magic stuff to the HTTP request if it is not done
|
||||
over SSL */
|
||||
result = Curl_http2_request_upgrade(&req, data);
|
||||
@ -3346,9 +3381,10 @@ static CURLcode http_statusline(struct Curl_easy *data,
|
||||
data->info.httpversion = k->httpversion;
|
||||
conn->httpversion_seen = (unsigned char)k->httpversion;
|
||||
|
||||
if(!data->state.httpversion || data->state.httpversion > k->httpversion)
|
||||
if(!data->state.http_neg.rcvd_min ||
|
||||
data->state.http_neg.rcvd_min > k->httpversion)
|
||||
/* store the lowest server version we encounter */
|
||||
data->state.httpversion = (unsigned char)k->httpversion;
|
||||
data->state.http_neg.rcvd_min = (unsigned char)k->httpversion;
|
||||
|
||||
/*
|
||||
* This code executes as part of processing the header. As a
|
||||
@ -4014,7 +4050,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
|
||||
failf(data, "Invalid status line");
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
if(!data->set.http09_allowed) {
|
||||
if(!data->state.http_neg.accept_09) {
|
||||
failf(data, "Received HTTP/0.9 when not allowed");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
@ -4051,7 +4087,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
|
||||
failf(data, "Invalid status line");
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
if(!data->set.http09_allowed) {
|
||||
if(!data->state.http_neg.accept_09) {
|
||||
failf(data, "Received HTTP/0.9 when not allowed");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
|
17
lib/http.h
17
lib/http.h
@ -53,6 +53,12 @@ typedef enum {
|
||||
FOLLOW_REDIR /* a full true redirect */
|
||||
} followtype;
|
||||
|
||||
#define CURL_HTTP_V1x (1 << 0)
|
||||
#define CURL_HTTP_V2x (1 << 1)
|
||||
#define CURL_HTTP_V3x (1 << 2)
|
||||
/* bitmask of CURL_HTTP_V* values */
|
||||
typedef unsigned char http_majors;
|
||||
|
||||
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
|
||||
@ -68,6 +74,17 @@ extern const struct Curl_handler Curl_handler_https;
|
||||
|
||||
struct dynhds;
|
||||
|
||||
struct http_negotiation {
|
||||
unsigned char rcvd_min; /* minimum version seen in responses, 09, 10, 11 */
|
||||
http_majors allowed; /* allowed major versions when talking to server */
|
||||
BIT(h2_upgrade); /* Do HTTP Upgrade from 1.1 to 2 */
|
||||
BIT(h2_prior_knowledge); /* Directly do HTTP/2 without ALPN/SSL */
|
||||
BIT(accept_09); /* Accept an HTTP/0.9 response */
|
||||
BIT(only_10); /* When using major version 1x, use only 1.0 */
|
||||
};
|
||||
|
||||
void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg);
|
||||
|
||||
CURLcode Curl_bump_headersize(struct Curl_easy *data,
|
||||
size_t delta,
|
||||
bool connect_only);
|
||||
|
13
lib/http2.c
13
lib/http2.c
@ -2794,8 +2794,9 @@ out:
|
||||
|
||||
bool Curl_http2_may_switch(struct Curl_easy *data)
|
||||
{
|
||||
if(Curl_conn_http_version(data) < 20 &&
|
||||
data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
|
||||
if(Curl_conn_http_version(data, data->conn) < 20 &&
|
||||
(data->state.http_neg.allowed & CURL_HTTP_V2x) &&
|
||||
data->state.http_neg.h2_prior_knowledge) {
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) {
|
||||
/* We do not support HTTP/2 proxies yet. Also it is debatable
|
||||
@ -2814,7 +2815,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data)
|
||||
struct Curl_cfilter *cf;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(Curl_conn_http_version(data) < 20);
|
||||
DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20);
|
||||
|
||||
result = http2_cfilter_add(&cf, data, data->conn, FIRSTSOCKET, FALSE);
|
||||
if(result)
|
||||
@ -2836,7 +2837,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
struct Curl_cfilter *cf_h2;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(Curl_conn_http_version(data) < 20);
|
||||
DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20);
|
||||
|
||||
result = http2_cfilter_insert_after(cf, data, FALSE);
|
||||
if(result)
|
||||
@ -2861,7 +2862,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
|
||||
struct cf_h2_ctx *ctx;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(Curl_conn_http_version(data) < 20);
|
||||
DEBUGASSERT(Curl_conn_http_version(data, conn) < 20);
|
||||
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
|
||||
|
||||
result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE);
|
||||
@ -2908,7 +2909,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_http_version(data) == 20) {
|
||||
if(Curl_conn_http_version(data, data->conn) == 20) {
|
||||
int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
|
||||
return err == NGHTTP2_HTTP_1_1_REQUIRED;
|
||||
}
|
||||
|
@ -1910,6 +1910,7 @@ static CURLMcode state_performing(struct Curl_easy *data,
|
||||
data->req.done = TRUE;
|
||||
}
|
||||
}
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
else if((CURLE_HTTP2_STREAM == result) &&
|
||||
Curl_h2_http_1_1_error(data)) {
|
||||
CURLcode ret = Curl_retry_request(data, &newurl);
|
||||
@ -1917,7 +1918,7 @@ static CURLMcode state_performing(struct Curl_easy *data,
|
||||
if(!ret) {
|
||||
infof(data, "Downgrades to HTTP/1.1");
|
||||
streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1");
|
||||
data->state.httpwant = CURL_HTTP_VERSION_1_1;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V1x;
|
||||
/* clear the error message bit too as we ignore the one we got */
|
||||
data->state.errorbuf = FALSE;
|
||||
if(!newurl)
|
||||
@ -1932,6 +1933,7 @@ static CURLMcode state_performing(struct Curl_easy *data,
|
||||
else
|
||||
result = ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(result) {
|
||||
/*
|
||||
|
@ -570,8 +570,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
|
||||
data->state.followlocation = 0; /* reset the location-follow counter */
|
||||
data->state.this_is_a_follow = FALSE; /* reset this */
|
||||
data->state.errorbuf = FALSE; /* no error has occurred */
|
||||
data->state.httpwant = data->set.httpwant;
|
||||
data->state.httpversion = 0;
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
Curl_http_neg_init(data, &data->state.http_neg);
|
||||
#endif
|
||||
data->state.authproblem = FALSE;
|
||||
data->state.authhost.want = data->set.httpauth;
|
||||
data->state.authproxy.want = data->set.proxyauth;
|
||||
|
153
lib/url.c
153
lib/url.c
@ -652,15 +652,20 @@ bool Curl_on_disconnect(struct Curl_easy *data,
|
||||
static bool xfer_may_multiplex(const struct Curl_easy *data,
|
||||
const struct connectdata *conn)
|
||||
{
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
/* If an HTTP protocol and multiplexing is enabled */
|
||||
if((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
|
||||
(!conn->bits.protoconnstart || !conn->bits.close)) {
|
||||
|
||||
if(Curl_multiplex_wanted(data->multi) &&
|
||||
(data->state.httpwant >= CURL_HTTP_VERSION_2))
|
||||
(data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)))
|
||||
/* allows HTTP/2 or newer */
|
||||
return TRUE;
|
||||
}
|
||||
#else
|
||||
(void)data;
|
||||
(void)conn;
|
||||
#endif
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -992,8 +997,9 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
if(match->may_multiplex &&
|
||||
(data->state.httpwant == CURL_HTTP_VERSION_2_0) &&
|
||||
(data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)) &&
|
||||
(needle->handler->protocol & CURLPROTO_HTTP) &&
|
||||
!conn->httpversion_seen) {
|
||||
if(data->set.pipewait) {
|
||||
@ -1005,6 +1011,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
|
||||
infof(data, "Server upgrade cannot be used");
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) {
|
||||
/* This protocol requires credentials per connection,
|
||||
@ -1025,27 +1032,38 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
|
||||
return FALSE;
|
||||
#endif
|
||||
|
||||
/* If looking for HTTP and the HTTP version we want is less
|
||||
* than the HTTP version of conn, continue looking.
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
/* If looking for HTTP and the HTTP versions allowed do not include
|
||||
* the HTTP version of conn, continue looking.
|
||||
* CURL_HTTP_VERSION_2TLS is default which indicates no preference,
|
||||
* so we take any existing connection. */
|
||||
if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
|
||||
(data->state.httpwant != CURL_HTTP_VERSION_2TLS)) {
|
||||
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, httpversion));
|
||||
}
|
||||
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, httpversion));
|
||||
return FALSE;
|
||||
if((needle->handler->protocol & PROTO_FAMILY_HTTP)) {
|
||||
switch(Curl_conn_http_version(data, conn)) {
|
||||
case 30:
|
||||
if(!(data->state.http_neg.allowed & CURL_HTTP_V3x)) {
|
||||
DEBUGF(infof(data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
|
||||
", we do not want h3", conn->connection_id));
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
case 20:
|
||||
if(!(data->state.http_neg.allowed & CURL_HTTP_V2x)) {
|
||||
DEBUGF(infof(data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
|
||||
", we do not want h2", conn->connection_id));
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(!(data->state.http_neg.allowed & CURL_HTTP_V1x)) {
|
||||
DEBUGF(infof(data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T
|
||||
", we do not want h1", conn->connection_id));
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSH
|
||||
else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) {
|
||||
if(!ssh_config_matches(needle, conn))
|
||||
@ -3054,75 +3072,47 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
|
||||
)) {
|
||||
/* no connect_to match, try alt-svc! */
|
||||
enum alpnid srcalpnid = ALPN_none;
|
||||
bool use_alt_svc = FALSE;
|
||||
bool hit = FALSE;
|
||||
struct altsvc *as = NULL;
|
||||
const int allowed_versions = ( ALPN_h1
|
||||
#ifdef USE_HTTP2
|
||||
| ALPN_h2
|
||||
#endif
|
||||
#ifdef USE_HTTP3
|
||||
| ALPN_h3
|
||||
#endif
|
||||
) & data->asi->flags;
|
||||
static enum alpnid alpn_ids[] = {
|
||||
#ifdef USE_HTTP3
|
||||
ALPN_h3,
|
||||
#endif
|
||||
#ifdef USE_HTTP2
|
||||
ALPN_h2,
|
||||
#endif
|
||||
ALPN_h1,
|
||||
};
|
||||
size_t i;
|
||||
int allowed_versions = ALPN_none;
|
||||
|
||||
switch(data->state.httpwant) {
|
||||
case CURL_HTTP_VERSION_1_0:
|
||||
break;
|
||||
case CURL_HTTP_VERSION_1_1:
|
||||
use_alt_svc = TRUE;
|
||||
srcalpnid = ALPN_h1; /* only regard alt-svc advice for http/1.1 */
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_0:
|
||||
use_alt_svc = TRUE;
|
||||
srcalpnid = ALPN_h2; /* only regard alt-svc advice for h2 */
|
||||
break;
|
||||
case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3:
|
||||
use_alt_svc = TRUE;
|
||||
srcalpnid = ALPN_h3; /* only regard alt-svc advice for h3 */
|
||||
break;
|
||||
case CURL_HTTP_VERSION_3ONLY:
|
||||
break;
|
||||
default: /* no specific HTTP version wanted, look at all of alt-svc */
|
||||
use_alt_svc = TRUE;
|
||||
srcalpnid = ALPN_none;
|
||||
break;
|
||||
}
|
||||
if(!use_alt_svc)
|
||||
return CURLE_OK;
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V3x)
|
||||
allowed_versions |= ALPN_h3;
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V2x)
|
||||
allowed_versions |= ALPN_h2;
|
||||
if(data->state.http_neg.allowed & CURL_HTTP_V1x)
|
||||
allowed_versions |= ALPN_h1;
|
||||
allowed_versions &= (int)data->asi->flags;
|
||||
|
||||
host = conn->host.rawalloc;
|
||||
DEBUGF(infof(data, "check Alt-Svc for host %s", host));
|
||||
if(srcalpnid == ALPN_none) {
|
||||
/* scan all alt-svc protocol ids in order or relevance */
|
||||
for(i = 0; !hit && (i < CURL_ARRAYSIZE(alpn_ids)); ++i) {
|
||||
srcalpnid = alpn_ids[i];
|
||||
hit = Curl_altsvc_lookup(data->asi,
|
||||
srcalpnid, host, conn->remote_port, /* from */
|
||||
&as /* to */,
|
||||
allowed_versions);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* look for a specific alt-svc protocol id */
|
||||
#ifdef USE_HTTP3
|
||||
if(!hit && (allowed_versions & ALPN_h3)) {
|
||||
srcalpnid = ALPN_h3;
|
||||
hit = Curl_altsvc_lookup(data->asi,
|
||||
srcalpnid, host, conn->remote_port, /* from */
|
||||
ALPN_h3, host, conn->remote_port, /* from */
|
||||
&as /* to */,
|
||||
allowed_versions);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HTTP2
|
||||
if(!hit && (allowed_versions & ALPN_h2) &&
|
||||
!data->state.http_neg.h2_prior_knowledge) {
|
||||
srcalpnid = ALPN_h2;
|
||||
hit = Curl_altsvc_lookup(data->asi,
|
||||
ALPN_h2, host, conn->remote_port, /* from */
|
||||
&as /* to */,
|
||||
allowed_versions);
|
||||
}
|
||||
#endif
|
||||
if(!hit && (allowed_versions & ALPN_h1) &&
|
||||
!data->state.http_neg.only_10) {
|
||||
srcalpnid = ALPN_h1;
|
||||
hit = Curl_altsvc_lookup(data->asi,
|
||||
ALPN_h1, host, conn->remote_port, /* from */
|
||||
&as /* to */,
|
||||
allowed_versions);
|
||||
}
|
||||
|
||||
|
||||
if(hit) {
|
||||
char *hostd = strdup((char *)as->dst.host);
|
||||
@ -3141,14 +3131,15 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
|
||||
/* protocol version switch */
|
||||
switch(as->dst.alpnid) {
|
||||
case ALPN_h1:
|
||||
data->state.httpwant = CURL_HTTP_VERSION_1_1;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V1x;
|
||||
data->state.http_neg.only_10 = FALSE;
|
||||
break;
|
||||
case ALPN_h2:
|
||||
data->state.httpwant = CURL_HTTP_VERSION_2_0;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V2x;
|
||||
break;
|
||||
case ALPN_h3:
|
||||
conn->transport = TRNSPRT_QUIC;
|
||||
data->state.httpwant = CURL_HTTP_VERSION_3;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V3x;
|
||||
break;
|
||||
default: /* should not be possible */
|
||||
break;
|
||||
|
@ -1313,10 +1313,9 @@ struct UrlState {
|
||||
char *proxypasswd;
|
||||
#endif
|
||||
} aptr;
|
||||
unsigned char httpwant; /* when non-zero, a specific HTTP version requested
|
||||
to be used in the library's request(s) */
|
||||
unsigned char httpversion; /* the lowest HTTP version*10 reported by any
|
||||
server involved in this request */
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
struct http_negotiation http_neg;
|
||||
#endif
|
||||
unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any)
|
||||
is this */
|
||||
unsigned char select_bits; /* != 0 -> bitmask of socket events for this
|
||||
|
@ -154,17 +154,19 @@ static const struct alpn_spec ALPN_SPEC_H2_H11 = {
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn)
|
||||
static const struct alpn_spec *
|
||||
alpn_get_spec(http_majors allowed, bool use_alpn)
|
||||
{
|
||||
if(!use_alpn)
|
||||
return NULL;
|
||||
#ifdef USE_HTTP2
|
||||
if(httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)
|
||||
if(allowed & CURL_HTTP_V2x) {
|
||||
if(allowed & CURL_HTTP_V1x)
|
||||
return &ALPN_SPEC_H2_H11;
|
||||
return &ALPN_SPEC_H2;
|
||||
if(httpwant >= CURL_HTTP_VERSION_2)
|
||||
return &ALPN_SPEC_H2_H11;
|
||||
}
|
||||
#else
|
||||
(void)httpwant;
|
||||
(void)allowed;
|
||||
#endif
|
||||
/* Use the ALPN protocol "http/1.1" for HTTP/1.x.
|
||||
Avoid "http/1.0" because some servers do not support it. */
|
||||
@ -1576,7 +1578,7 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
|
||||
|
||||
DEBUGASSERT(data->conn);
|
||||
|
||||
ctx = cf_ctx_new(data, alpn_get_spec(data->state.httpwant,
|
||||
ctx = cf_ctx_new(data, alpn_get_spec(data->state.http_neg.allowed,
|
||||
conn->bits.tls_enable_alpn));
|
||||
if(!ctx) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
@ -1627,16 +1629,16 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
|
||||
struct ssl_connect_data *ctx;
|
||||
CURLcode result;
|
||||
bool use_alpn = conn->bits.tls_enable_alpn;
|
||||
int httpwant = CURL_HTTP_VERSION_1_1;
|
||||
http_majors allowed = CURL_HTTP_V1x;
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(conn->http_proxy.proxytype == CURLPROXY_HTTPS2) {
|
||||
use_alpn = TRUE;
|
||||
httpwant = CURL_HTTP_VERSION_2;
|
||||
allowed = (CURL_HTTP_V1x|CURL_HTTP_V2x);
|
||||
}
|
||||
#endif
|
||||
|
||||
ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn));
|
||||
ctx = cf_ctx_new(data, alpn_get_spec(allowed, use_alpn));
|
||||
if(!ctx) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
|
4
lib/ws.c
4
lib/ws.c
@ -1308,7 +1308,9 @@ static CURLcode ws_setup_conn(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
/* WebSockets is 1.1 only (for now) */
|
||||
data->state.httpwant = CURL_HTTP_VERSION_1_1;
|
||||
data->state.http_neg.accept_09 = FALSE;
|
||||
data->state.http_neg.only_10 = FALSE;
|
||||
data->state.http_neg.allowed = CURL_HTTP_V1x;
|
||||
return Curl_http_setup_conn(data, conn);
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ class TestReuse:
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h2")}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}',
|
||||
'--alt-svc', f'{asfile}', '--http3',
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
# We expect the connection to be reused
|
||||
@ -111,9 +111,9 @@ class TestReuse:
|
||||
fd.write(f'h3 {env.domain1} {env.https_port} h2 {env.domain1} {env.https_port} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(asfile).readlines()}')
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h2")}/data.json?[0-{count-1}]'
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}',
|
||||
'--alt-svc', f'{asfile}', '--http3'
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
# We expect the connection to be reused and use HTTP/2
|
||||
@ -134,12 +134,35 @@ class TestReuse:
|
||||
fd.write(f'h3 {env.domain1} {env.https_port} http/1.1 {env.domain1} {env.https_port} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(asfile).readlines()}')
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h2")}/data.json?[0-{count-1}]'
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}',
|
||||
'--alt-svc', f'{asfile}', '--http3'
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
# We expect the connection to be reused and use HTTP/1.1
|
||||
assert r.total_connects == 1
|
||||
for s in r.stats:
|
||||
assert s['http_version'] == '1.1', f'{s}'
|
||||
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
|
||||
def test_12_06_alt_svc_h3h1_h3only(self, env: Env, httpd, nghttpx):
|
||||
httpd.clear_extra_configs()
|
||||
httpd.reload()
|
||||
count = 2
|
||||
# write a alt-svc file the advises h1 instead of h3
|
||||
asfile = os.path.join(env.gen_dir, 'alt-svc-12_05.txt')
|
||||
ts = datetime.now() + timedelta(hours=24)
|
||||
expires = f'{ts.year:04}{ts.month:02}{ts.day:02} {ts.hour:02}:{ts.minute:02}:{ts.second:02}'
|
||||
with open(asfile, 'w') as fd:
|
||||
fd.write(f'h3 {env.domain1} {env.https_port} http/1.1 {env.domain1} {env.https_port} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(asfile).readlines()}')
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}', '--http3-only'
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
# We expect the connection to be stay on h3, since we used --http3-only
|
||||
assert r.total_connects == 1
|
||||
for s in r.stats:
|
||||
assert s['http_version'] == '3', f'{s}'
|
||||
|
Loading…
x
Reference in New Issue
Block a user