(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;
}
}
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;
* 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`.
#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;
}
(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
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)
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);
}
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 */
}
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);
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
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;
}
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;
}
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
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);
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
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)
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)
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);
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;
}
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);
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)
else
result = ret;
}
+#endif
if(result) {
/*
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;
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;
}
}
#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) {
infof(data, "Server upgrade cannot be used");
return FALSE;
}
+#endif
if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) {
/* This protocol requires credentials per connection,
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))
)) {
/* 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);
- }
+#ifdef USE_HTTP3
+ if(!hit && (allowed_versions & ALPN_h3)) {
+ srcalpnid = ALPN_h3;
+ hit = Curl_altsvc_lookup(data->asi,
+ ALPN_h3, host, conn->remote_port, /* from */
+ &as /* to */,
+ allowed_versions);
}
- else {
- /* look for a specific alt-svc protocol id */
+ #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,
- srcalpnid, host, conn->remote_port, /* from */
+ 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);
/* 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;
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
};
#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. */
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;
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;
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);
}
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
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
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}'