for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_MULTIPLEX)
return TRUE;
- if(cf->cft->flags & CF_TYPE_IP_CONNECT
- || cf->cft->flags & CF_TYPE_SSL)
+ if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL))
return FALSE;
}
return FALSE;
}
+unsigned char Curl_conn_http_version(struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result = CURLE_UNKNOWN_OPTION;
+ unsigned char v = 0;
+
+ cf = data->conn ? data->conn->cfilter[FIRSTSOCKET] : NULL;
+ for(; cf; cf = cf->next) {
+ if(cf->cft->flags & CF_TYPE_HTTP) {
+ int value = 0;
+ result = cf->cft->query(cf, data, CF_QUERY_HTTP_VERSION, &value, NULL);
+ if(!result && ((value < 0) || (value > 255)))
+ result = CURLE_FAILED_INIT;
+ else
+ v = (unsigned char)value;
+ break;
+ }
+ if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL))
+ break;
+ }
+ return result ? 0 : v;
+}
+
bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
{
struct Curl_cfilter *cf;
#define CF_QUERY_STREAM_ERROR 6 /* error code - */
#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */
#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */
+#define CF_QUERY_HTTP_VERSION 9 /* number (10/11/20/30) - */
/**
* Query the cfilter for properties. Filters ignorant of a query will
* CF_TYPE_SSL: provide SSL/TLS
* CF_TYPE_MULTIPLEX: provides multiplexing of easy handles
* CF_TYPE_PROXY provides proxying
+ * CF_TYPE_HTTP implement a version of the HTTP protocol
*/
#define CF_TYPE_IP_CONNECT (1 << 0)
#define CF_TYPE_SSL (1 << 1)
#define CF_TYPE_MULTIPLEX (1 << 2)
#define CF_TYPE_PROXY (1 << 3)
+#define CF_TYPE_HTTP (1 << 4)
/* A connection filter type, e.g. specific implementation. */
struct Curl_cftype {
*/
bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
+/**
+ * Return the HTTP version used on the FIRSTSOCKET connection filters
+ * or 0 if unknown. Value otherwise is 09, 10, 11, etc.
+ */
+unsigned char Curl_conn_http_version(struct Curl_easy *data);
+
/**
* Close the filter chain at `sockindex` for connection `data->conn`.
* Filters remain in place and may be connected again afterwards.
static CURLcode http_range(struct Curl_easy *data,
Curl_HttpReq httpreq);
static CURLcode http_req_complete(struct Curl_easy *data,
- struct dynbuf *r, Curl_HttpReq httpreq);
+ struct dynbuf *r, int httpversion,
+ Curl_HttpReq httpreq);
static CURLcode http_req_set_reader(struct Curl_easy *data,
- Curl_HttpReq httpreq,
+ Curl_HttpReq httpreq, int httpversion,
const char **tep);
static CURLcode http_size(struct Curl_easy *data);
static CURLcode http_statusline(struct Curl_easy *data,
#ifdef HAVE_LIBZ
static CURLcode http_transferencode(struct Curl_easy *data);
#endif
-static bool use_http_1_1plus(const struct Curl_easy *data,
- const struct connectdata *conn);
/*
else
data->info.httpauthpicked = data->state.authhost.picked;
if(data->state.authhost.picked == CURLAUTH_NTLM &&
- conn->httpversion > 11) {
+ (data->req.httpversion_sent > 11)) {
infof(data, "Forcing HTTP/1.1 for NTLM");
connclose(conn, "Force HTTP/1.1 connection");
data->state.httpwant = CURL_HTTP_VERSION_1_1;
return CURLE_OK;
}
-/*
- * Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons
- * to avoid it include:
- *
- * - if the user specifically requested HTTP 1.0
- * - if the server we are connected to only supports 1.0
- * - if any server previously contacted to handle this request only supports
- * 1.0.
- */
-static bool use_http_1_1plus(const struct Curl_easy *data,
- const struct connectdata *conn)
+/* Determine if we may use HTTP 1.1 for this request. */
+static bool http_may_use_1_1(const struct Curl_easy *data)
{
- if((data->state.httpversion == 10) || (conn->httpversion == 10))
+ const struct connectdata *conn = data->conn;
+ /* We have seen a previous response for *this* transfer with 1.0,
+ * on another connection or the same one. */
+ if(data->state.httpversion == 10)
+ return FALSE;
+ /* We have seen a previous response on *this* connection with 1.0. */
+ if(conn->httpversion_seen == 10)
return FALSE;
+ /* We want 1.0 and have seen no previous response on *this* connection
+ with a higher version (maybe no response at all yet). */
if((data->state.httpwant == CURL_HTTP_VERSION_1_0) &&
- (conn->httpversion <= 10))
+ (conn->httpversion_seen <= 10))
return FALSE;
+ /* We want something newer than 1.0 or have no preferences. */
return (data->state.httpwant == CURL_HTTP_VERSION_NONE) ||
(data->state.httpwant >= CURL_HTTP_VERSION_1_1);
}
-static const char *get_http_string(const struct Curl_easy *data,
- const struct connectdata *conn)
+static unsigned char http_request_version(struct Curl_easy *data)
+{
+ unsigned char httpversion = Curl_conn_http_version(data);
+ if(!httpversion) {
+ /* No specific HTTP connection filter installed. */
+ httpversion = http_may_use_1_1(data) ? 11 : 10;
+ }
+ return httpversion;
+}
+
+static const char *get_http_string(int httpversion)
{
- if(Curl_conn_is_http3(data, conn, FIRSTSOCKET))
- return "3";
- if(Curl_conn_is_http2(data, conn, FIRSTSOCKET))
- return "2";
- if(use_http_1_1plus(data, conn))
- return "1.1";
-
- return "1.0";
+ switch(httpversion) {
+ case 30:
+ return "3";
+ case 20:
+ return "2";
+ case 11:
+ return "1.1";
+ default:
+ return "1.0";
+ }
}
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
- bool is_connect,
+ bool is_connect, int httpversion,
struct dynbuf *req)
{
- struct connectdata *conn = data->conn;
char *ptr;
struct curl_slist *h[2];
struct curl_slist *headers;
if(is_connect)
proxy = HEADER_CONNECT;
else
- proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ?
+ proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ?
HEADER_PROXY : HEADER_SERVER;
switch(proxy) {
Connection: */
checkprefix("Connection:", compare))
;
- else if((conn->httpversion >= 20) &&
+ else if((httpversion >= 20) &&
checkprefix("Transfer-Encoding:", compare))
/* HTTP/2 does not support chunked requests */
;
}
static CURLcode http_req_set_reader(struct Curl_easy *data,
- Curl_HttpReq httpreq,
+ Curl_HttpReq httpreq, int httpversion,
const char **tep)
{
CURLcode result = CURLE_OK;
data->req.upload_chunky =
Curl_compareheader(ptr,
STRCONST("Transfer-Encoding:"), STRCONST("chunked"));
- if(data->req.upload_chunky &&
- use_http_1_1plus(data, data->conn) &&
- (data->conn->httpversion >= 20)) {
- infof(data, "suppressing chunked transfer encoding on connection "
- "using HTTP version 2 or higher");
- data->req.upload_chunky = FALSE;
+ if(data->req.upload_chunky && (httpversion >= 20)) {
+ infof(data, "suppressing chunked transfer encoding on connection "
+ "using HTTP version 2 or higher");
+ data->req.upload_chunky = FALSE;
}
}
else {
if(req_clen < 0) {
/* indeterminate request content length */
- if(use_http_1_1plus(data, data->conn)) {
+ if(httpversion > 10) {
/* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not
* need it */
- data->req.upload_chunky = (data->conn->httpversion < 20);
+ data->req.upload_chunky = (httpversion < 20);
}
else {
failf(data, "Chunky upload is not supported by HTTP 1.0");
}
static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
- bool *announced_exp100)
+ int httpversion, bool *announced_exp100)
{
CURLcode result;
char *ptr;
*announced_exp100 =
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
}
- else if(!data->state.disableexpect &&
- use_http_1_1plus(data, data->conn) &&
- (data->conn->httpversion < 20)) {
+ else if(!data->state.disableexpect && (httpversion == 11)) {
/* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an
Expect: 100-continue to the headers which actually speeds up post
operations (as there is one packet coming back from the web server) */
}
static CURLcode http_req_complete(struct Curl_easy *data,
- struct dynbuf *r, Curl_HttpReq httpreq)
+ struct dynbuf *r, int httpversion,
+ Curl_HttpReq httpreq)
{
CURLcode result = CURLE_OK;
curl_off_t req_clen;
goto out;
}
}
- result = addexpect(data, r, &announced_exp100);
+ result = addexpect(data, r, httpversion, &announced_exp100);
if(result)
goto out;
break;
struct dynbuf req;
char *altused = NULL;
const char *p_accept; /* Accept: string */
+ unsigned char httpversion;
/* Always consider the DO phase done after this function call, even if there
may be parts of the request that are not yet sent, since we can deal with
switch(conn->alpn) {
case CURL_HTTP_VERSION_3:
- DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET));
+ DEBUGASSERT(Curl_conn_http_version(data) == 30);
break;
case CURL_HTTP_VERSION_2:
#ifndef CURL_DISABLE_PROXY
- if(!Curl_conn_is_http2(data, conn, FIRSTSOCKET) &&
+ if((Curl_conn_http_version(data) != 20) &&
conn->bits.proxy && !conn->bits.tunnel_proxy
) {
- result = Curl_http2_switch(data, conn, FIRSTSOCKET);
+ result = Curl_http2_switch(data);
if(result)
goto fail;
}
else
#endif
- DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
+ DEBUGASSERT(Curl_conn_http_version(data) == 20);
break;
case CURL_HTTP_VERSION_1_1:
/* continue with HTTP/1.x when explicitly requested */
break;
default:
/* Check if user wants to use HTTP/2 with clear TCP */
- if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) {
+ if(Curl_http2_may_switch(data)) {
DEBUGF(infof(data, "HTTP/2 over clean TCP"));
- result = Curl_http2_switch(data, conn, FIRSTSOCKET);
+ result = Curl_http2_switch(data);
if(result)
goto fail;
}
goto fail;
#endif
- result = http_req_set_reader(data, httpreq, &te);
+ httpversion = http_request_version(data);
+ httpstring = get_http_string(httpversion);
+
+ result = http_req_set_reader(data, httpreq, httpversion, &te);
if(result)
goto fail;
if(result)
goto fail;
- httpstring = get_http_string(data, conn);
-
/* initialize a dynamic send-buffer */
Curl_dyn_init(&req, DYN_HTTP_REQUEST);
goto fail;
}
- if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) &&
- conn->httpversion < 20 &&
+ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) &&
(data->state.httpwant == CURL_HTTP_VERSION_2)) {
/* append HTTP2 upgrade magic stuff to the HTTP request if it is not done
over SSL */
if(!result)
result = Curl_add_timecondition(data, &req);
if(!result)
- result = Curl_add_custom_headers(data, FALSE, &req);
+ result = Curl_add_custom_headers(data, FALSE, httpversion, &req);
if(!result) {
/* req_send takes ownership of the 'req' memory on success */
- result = http_req_complete(data, &req, httpreq);
+ result = http_req_complete(data, &req, httpversion, httpreq);
if(!result)
- result = Curl_req_send(data, &req);
+ result = Curl_req_send(data, &req, httpversion);
}
Curl_dyn_free(&req);
if(result)
goto fail;
- if((conn->httpversion >= 20) && data->req.upload_chunky)
+ if((httpversion >= 20) && data->req.upload_chunky)
/* upload_chunky was set above to set up the request in a chunky fashion,
but is disabled here again to avoid that the chunked encoded version is
actually used when sending the request body over h2 */
)) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL;
if(v) {
/* the ALPN of the current request */
- enum alpnid id = (conn->httpversion == 30) ? ALPN_h3 :
- (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
+ enum alpnid id = (k->httpversion == 30) ? ALPN_h3 :
+ (k->httpversion == 20) ? ALPN_h2 : ALPN_h1;
return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name,
curlx_uitous((unsigned int)conn->remote_port));
}
streamclose(conn, "Connection: close used");
return CURLE_OK;
}
- if((conn->httpversion == 10) &&
+ if((k->httpversion == 10) &&
HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) {
/*
* An HTTP/1.0 reply with the 'Connection: keep-alive' line
#ifndef CURL_DISABLE_PROXY
v = HD_VAL(hd, hdlen, "Proxy-Connection:");
if(v) {
- if((conn->httpversion == 10) && conn->bits.httpproxy &&
+ if((k->httpversion == 10) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) {
/*
* When an HTTP/1.0 reply comes when using a proxy, the
connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */
infof(data, "HTTP/1.0 proxy connection set to keep alive");
}
- else if((conn->httpversion == 11) && conn->bits.httpproxy &&
+ else if((k->httpversion == 11) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) {
/*
* We get an HTTP/1.1 response from a proxy and it says it will
case 30:
#endif
/* no major version switch mid-connection */
- if(conn->httpversion &&
- (k->httpversion/10 != conn->httpversion/10)) {
+ if(k->httpversion_sent &&
+ (k->httpversion/10 != k->httpversion_sent/10)) {
failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)",
- conn->httpversion/10, k->httpversion/10);
- return CURLE_UNSUPPORTED_PROTOCOL;
+ k->httpversion_sent/10, k->httpversion/10);
+ return CURLE_WEIRD_SERVER_REPLY;
}
break;
default:
data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion;
- conn->httpversion = (unsigned char)k->httpversion;
+ conn->httpversion_seen = (unsigned char)k->httpversion;
if(!data->state.httpversion || data->state.httpversion > k->httpversion)
/* store the lowest server version we encounter */
if(k->upgr101 == UPGR101_RECEIVED) {
/* supposedly upgraded to http2 now */
- if(conn->httpversion != 20)
+ if(data->req.httpversion != 20)
infof(data, "Lying server, not serving HTTP/2");
}
break;
case 101:
/* Switching Protocols only allowed from HTTP/1.1 */
-
- if(conn->httpversion != 11) {
+ if(k->httpversion_sent != 11) {
/* invalid for other HTTP versions */
failf(data, "unexpected 101 response code");
result = CURLE_WEIRD_SERVER_REPLY;
/* We expect more response from HTTP/2 later */
k->header = TRUE;
k->headerline = 0; /* restart the header line counter */
+ k->httpversion_sent = 20; /* It's a HTTP/2 request now */
/* Any remaining `buf` bytes are already HTTP/2 and passed to
* be processed. */
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
}
if((k->size == -1) && !k->chunk && !conn->bits.close &&
- (conn->httpversion == 11) &&
+ (k->httpversion == 11) &&
!(conn->handler->protocol & CURLPROTO_RTSP) &&
data->state.httpreq != HTTPREQ_HEAD) {
/* On HTTP 1.1, when connection is not to get closed, but no
like to call http2_handle_stream_close to properly close a
stream. In order to do this, we keep reading until we
close the stream. */
- if(0 == k->maxdownload
- && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
- && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
+ if((0 == k->maxdownload) && (k->httpversion_sent < 20))
k->download_done = TRUE;
/* final response without error, prepare to receive the body */
p++;
if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
if(ISBLANK(p[2])) {
- k->httpversion = 10 + (p[1] - '0');
+ k->httpversion = (unsigned char)(10 + (p[1] - '0'));
p += 3;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
case '3':
if(!ISBLANK(p[1]))
break;
- k->httpversion = (*p - '0') * 10;
+ k->httpversion = (unsigned char)((*p - '0') * 10);
p += 2;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
- /* this is not the beginning of a protocol first header line */
+ /* this is not the beginning of a protocol first header line.
+ * Cannot be 0.9 if version was detected or connection was reused. */
k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator");
- if(conn->httpversion >= 10) {
+ if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
Curl_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator");
- /* this is not the beginning of a protocol first header line */
- if(conn->httpversion >= 10) {
+ /* this is not the beginning of a protocol first header line.
+ * Cannot be 0.9 if version was detected or connection was reused. */
+ if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
CURLcode Curl_add_timecondition(struct Curl_easy *data, struct dynbuf *req);
CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect,
- struct dynbuf *req);
+ int httpversion, struct dynbuf *req);
CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect,
struct dynhds *hds);
}
break;
}
+ case CF_QUERY_HTTP_VERSION:
+ *pres1 = 20;
+ return CURLE_OK;
default:
break;
}
struct Curl_cftype Curl_cft_nghttp2 = {
"HTTP/2",
- CF_TYPE_MULTIPLEX,
+ CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
CURL_LOG_LVL_NONE,
cf_h2_destroy,
cf_h2_connect,
return result;
}
-static bool cf_is_http2(struct Curl_cfilter *cf,
- const struct Curl_easy *data)
-{
- (void)data;
- for(; cf; cf = cf->next) {
- if(cf->cft == &Curl_cft_nghttp2)
- return TRUE;
- if(cf->cft->flags & CF_TYPE_IP_CONNECT)
- return FALSE;
- }
- return FALSE;
-}
-
-bool Curl_conn_is_http2(const struct Curl_easy *data,
- const struct connectdata *conn,
- int sockindex)
-{
- return conn ? cf_is_http2(conn->cfilter[sockindex], data) : FALSE;
-}
-
-bool Curl_http2_may_switch(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex)
+bool Curl_http2_may_switch(struct Curl_easy *data)
{
- (void)sockindex;
- if(!Curl_conn_is_http2(data, conn, sockindex) &&
+ if(Curl_conn_http_version(data) < 20 &&
data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
#ifndef CURL_DISABLE_PROXY
- if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
+ if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) {
/* We do not support HTTP/2 proxies yet. Also it is debatable
whether or not this setting should apply to HTTP/2 proxies. */
infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
return FALSE;
}
-CURLcode Curl_http2_switch(struct Curl_easy *data,
- struct connectdata *conn, int sockindex)
+CURLcode Curl_http2_switch(struct Curl_easy *data)
{
struct Curl_cfilter *cf;
CURLcode result;
- DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+ DEBUGASSERT(Curl_conn_http_version(data) < 20);
- result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE);
+ result = http2_cfilter_add(&cf, data, data->conn, FIRSTSOCKET, FALSE);
if(result)
return result;
CURL_TRC_CF(data, cf, "switching connection to HTTP/2");
- conn->httpversion = 20; /* we know we are on HTTP/2 now */
- conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ data->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
if(cf->next) {
struct Curl_cfilter *cf_h2;
CURLcode result;
- DEBUGASSERT(!cf_is_http2(cf, data));
+ DEBUGASSERT(Curl_conn_http_version(data) < 20);
result = http2_cfilter_insert_after(cf, data, FALSE);
if(result)
return result;
cf_h2 = cf->next;
- cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
struct cf_h2_ctx *ctx;
CURLcode result;
- DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+ DEBUGASSERT(Curl_conn_http_version(data) < 20);
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE);
" after upgrade: len=%zu", nread);
}
- conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
Curl_multi_connchanged(data->multi);
CURLE_HTTP2_STREAM error! */
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
{
- if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) {
+ if(Curl_conn_http_version(data) == 20) {
int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
return err == NGHTTP2_HTTP_1_1_REQUIRED;
}
/* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */
bool Curl_h2_http_1_1_error(struct Curl_easy *data);
-bool Curl_conn_is_http2(const struct Curl_easy *data,
- const struct connectdata *conn,
- int sockindex);
-bool Curl_http2_may_switch(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex);
+bool Curl_http2_may_switch(struct Curl_easy *data);
-CURLcode Curl_http2_switch(struct Curl_easy *data,
- struct connectdata *conn, int sockindex);
+CURLcode Curl_http2_switch(struct Curl_easy *data);
CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data);
#else /* USE_NGHTTP2 */
-#define Curl_cf_is_http2(a,b) FALSE
-#define Curl_conn_is_http2(a,b,c) FALSE
-#define Curl_http2_may_switch(a,b,c) FALSE
+#define Curl_http2_may_switch(a) FALSE
#define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_switch(a,b,c) CURLE_UNSUPPORTED_PROTOCOL
+#define Curl_http2_switch(a) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_h2_http_1_1_error(x) 0
#endif
}
static CURLcode dynhds_add_custom(struct Curl_easy *data,
- bool is_connect,
+ bool is_connect, int httpversion,
struct dynhds *hds)
{
struct connectdata *conn = data->conn;
Connection: */
hd_name_eq(name, namelen, STRCONST("Connection:")))
;
- else if((conn->httpversion >= 20) &&
+ else if((httpversion >= 20) &&
hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
- /* HTTP/2 does not support chunked requests */
+ /* HTTP/2 and HTTP/3 do not support chunked requests */
;
else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
return CURLE_OK;
}
+struct cf_proxy_ctx {
+ /* the protocol specific sub-filter we install during connect */
+ struct Curl_cfilter *cf_protocol;
+ int httpversion; /* HTTP version used to CONNECT */
+};
+
CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
struct Curl_cfilter *cf,
struct Curl_easy *data,
int http_version_major)
{
+ struct cf_proxy_ctx *ctx = cf->ctx;
const char *hostname = NULL;
char *authority = NULL;
int port;
goto out;
}
- result = dynhds_add_custom(data, TRUE, &req->headers);
+ result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers);
out:
if(result && req) {
return result;
}
-
-struct cf_proxy_ctx {
- /* the protocol specific sub-filter we install during connect */
- struct Curl_cfilter *cf_protocol;
-};
-
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
*done = FALSE;
if(!ctx->cf_protocol) {
struct Curl_cfilter *cf_protocol = NULL;
+ int httpversion = 0;
int alpn = Curl_conn_cf_is_ssl(cf->next) ?
cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
if(result)
goto out;
cf_protocol = cf->next;
+ httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
break;
#ifdef USE_NGHTTP2
case CURL_HTTP_VERSION_2:
if(result)
goto out;
cf_protocol = cf->next;
+ httpversion = 20;
break;
#endif
default:
}
ctx->cf_protocol = cf_protocol;
+ ctx->httpversion = httpversion;
/* after we installed the filter "below" us, we call connect
* on out sub-chain again.
*/
req->headerbytecount = 0;
req->allheadercount = 0;
req->deductheadercount = 0;
-
+ req->httpversion_sent = 0;
+ req->httpversion = 0;
result = Curl_client_start(data);
if(result)
return result;
return CURLE_OK;
}
-CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req)
+CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req,
+ unsigned char httpversion)
{
CURLcode result;
const char *buf;
if(!data || !data->conn)
return CURLE_FAILED_INIT;
+ data->req.httpversion_sent = httpversion;
buf = Curl_dyn_ptr(req);
blen = Curl_dyn_len(req);
if(!Curl_creader_total_length(data)) {
first one */
curl_off_t offset; /* possible resume offset read from the
Content-Range: header */
- int httpversion; /* Version in response (09, 10, 11, etc.) */
int httpcode; /* error code from the 'HTTP/1.? XXX' or
'RTSP/1.? XXX' line */
int keepon;
+ unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */
+ unsigned char httpversion; /* Version in response (09, 10, 11, etc.) */
enum upgrade101 upgr101; /* 101 upgrade state */
/* Client Writer stack, handles transfer- and content-encodings, protocol
* bytes are really send.
* @param data the transfer making the request
* @param buf the complete header bytes, no body
+ * @param httpversion version used in request (09, 10, 11, etc.)
* @return CURLE_OK (on blocking with *pnwritten == 0) or error.
*/
-CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf);
+CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf,
+ unsigned char httpversion);
/**
* TRUE iff the request has sent all request headers and data.
Curl_RtspReq rtspreq = data->set.rtspreq;
struct RTSP *rtsp = data->req.p.rtsp;
struct dynbuf req_buffer;
+ unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */
const char *p_request = NULL;
const char *p_session_id = NULL;
goto out;
}
- result = Curl_add_custom_headers(data, FALSE, &req_buffer);
+ result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
if(result)
goto out;
Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE);
/* issue the request */
- result = Curl_req_send(data, &req_buffer);
+ result = Curl_req_send(data, &req_buffer, httpversion);
if(result) {
failf(data, "Failed sending RTSP request");
goto out;
DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1));
DEBUGASSERT(!shutdown || (sockindex == -1) || (writesockindex == -1));
- if(conn->bits.multiplex || conn->httpversion >= 20 || want_send) {
+ if(Curl_conn_is_multiplex(conn, FIRSTSOCKET) || want_send) {
/* when multiplexing, the read/write sockets need to be the same! */
conn->sockfd = sockindex == -1 ?
((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :
if(match->may_multiplex &&
(data->state.httpwant == CURL_HTTP_VERSION_2_0) &&
(needle->handler->protocol & CURLPROTO_HTTP) &&
- !conn->httpversion) {
+ !conn->httpversion_seen) {
if(data->set.pipewait) {
infof(data, "Server upgrade does not support multiplex yet, wait");
match->found = NULL;
* so we take any existing connection. */
if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
(data->state.httpwant != CURL_HTTP_VERSION_2TLS)) {
- if((conn->httpversion >= 20) &&
+ unsigned char httpversion = Curl_conn_http_version(data);
+ if((httpversion >= 20) &&
(data->state.httpwant < CURL_HTTP_VERSION_2_0)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h2",
- conn->connection_id, conn->httpversion));
+ conn->connection_id, httpversion));
}
- if((conn->httpversion >= 30) &&
+ if((httpversion >= 30) &&
(data->state.httpwant < CURL_HTTP_VERSION_3)) {
DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T
" with httpversion=%d, we want a version less than h3",
- conn->connection_id, conn->httpversion));
+ conn->connection_id, httpversion));
return FALSE;
}
}
/* protocol version switch */
switch(as->dst.alpnid) {
case ALPN_h1:
- conn->httpversion = 11;
+ data->state.httpwant = CURL_HTTP_VERSION_1_1;
break;
case ALPN_h2:
- conn->httpversion = 20;
+ data->state.httpwant = CURL_HTTP_VERSION_2_0;
break;
case ALPN_h3:
conn->transport = TRNSPRT_QUIC;
- conn->httpversion = 30;
+ data->state.httpwant = CURL_HTTP_VERSION_3;
break;
default: /* should not be possible */
break;
#endif
unsigned char transport; /* one of the TRNSPRT_* defines */
unsigned char ip_version; /* copied from the Curl_easy at creation time */
- unsigned char httpversion; /* the HTTP version*10 reported by the server */
+ /* HTTP version last responded with by the server.
+ * 0 at start, then one of 09, 10, 11, etc. */
+ unsigned char httpversion_seen;
unsigned char connect_only;
unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */
};
if(ctx->handshake_succeeded) {
CURL_TRC_CF(data, cf, "handshake succeeded");
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- cf->conn->httpversion = 30;
cf->connected = TRUE;
cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
*when = ctx->handshake_at;
return CURLE_OK;
}
+ case CF_QUERY_HTTP_VERSION:
+ *pres1 = 30;
+ return CURLE_OK;
default:
break;
}
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
- CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_msh3_destroy,
cf_msh3_connect,
ctx->handshake_at = Curl_now();
ctx->tls_handshake_complete = TRUE;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- cf->conn->httpversion = 30;
ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf,
data, &ctx->peer);
*when = ctx->handshake_at;
return CURLE_OK;
}
+ case CF_QUERY_HTTP_VERSION:
+ *pres1 = 30;
+ return CURLE_OK;
default:
break;
}
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
- CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_ngtcp2_destroy,
cf_ngtcp2_connect,
struct cf_osslq_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- cf->conn->httpversion = 30;
return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
}
*when = ctx->handshake_at;
return CURLE_OK;
}
+ case CF_QUERY_HTTP_VERSION:
+ *pres1 = 30;
+ return CURLE_OK;
default:
break;
}
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
- CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_osslq_destroy,
cf_osslq_connect,
struct cf_quiche_ctx *ctx = cf->ctx;
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- cf->conn->httpversion = 30;
return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
}
*when = ctx->handshake_at;
return CURLE_OK;
}
+ case CF_QUERY_HTTP_VERSION:
+ *pres1 = 30;
+ return CURLE_OK;
default:
break;
}
struct Curl_cftype Curl_cft_http3 = {
"HTTP/3",
- CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
0,
cf_quiche_destroy,
cf_quiche_connect,
#endif
}
-bool Curl_conn_is_http3(const struct Curl_easy *data,
- const struct connectdata *conn,
- int sockindex)
-{
-#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
- return Curl_conn_is_ngtcp2(data, conn, sockindex);
-#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)
- return Curl_conn_is_osslq(data, conn, sockindex);
-#elif defined(USE_QUICHE)
- return Curl_conn_is_quiche(data, conn, sockindex);
-#elif defined(USE_MSH3)
- return Curl_conn_is_msh3(data, conn, sockindex);
-#else
- return (conn->handler->protocol & PROTO_FAMILY_HTTP) &&
- (conn->httpversion == 30);
-#endif
-}
-
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
const struct connectdata *conn)
{
const struct Curl_addrinfo *ai,
int transport);
-bool Curl_conn_is_http3(const struct Curl_easy *data,
- const struct connectdata *conn,
- int sockindex);
-
extern struct Curl_cftype Curl_cft_http3;
-#else /* USE_HTTP3 */
-
-#define Curl_conn_is_http3(a,b,c) FALSE
-
#endif /* !USE_HTTP3 */
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
</protocol>
# curl: (1) Version mismatch (from HTTP/1 to HTTP/2)
<errorcode>
-1
+8
</errorcode>
</verify>
</testcase>
for s in r.stats:
assert s['http_version'] == '3', f'{s}'
+ @pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
def test_12_04_alt_svc_h3h2(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs()
httpd.reload()
'--alt-svc', f'{asfile}',
])
r.check_response(count=count, http_status=200)
- # We expect the connection to be reused
+ # We expect the connection to be reused and use HTTP/2
assert r.total_connects == 1
for s in r.stats:
assert s['http_version'] == '2', f'{s}'
+ @pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
def test_12_05_alt_svc_h3h1(self, env: Env, httpd, nghttpx):
httpd.clear_extra_configs()
httpd.reload()
'--alt-svc', f'{asfile}',
])
r.check_response(count=count, http_status=200)
- # We expect the connection to be reused
+ # We expect the connection to be reused and use HTTP/1.1
assert r.total_connects == 1
- # When using http/1.1 from alt-svc, we ALPN-negotiate 'h2,http/1.1' anyway
- # which means our server gives us h2
for s in r.stats:
- assert s['http_version'] == '2', f'{s}'
+ assert s['http_version'] == '1.1', f'{s}'