MEDIUM: h2: parse Extended CONNECT request to htx

Support for the rfc 8441 Bootstraping WebSockets with HTTP/2

Convert an Extended CONNECT HTTP/2 request into a htx representation.
The htx message uses the GET method with an Upgrade header field to be
fully compatible with the equivalent HTTP/1.1 Upgrade mechanism.

The Extended CONNECT is of the following form :

:method = CONNECT
:protocol = websocket
:scheme = https
:path = /chat
:authority = server.example.com

The new pseudo-header :protocol has been defined and is used to identify
an Extended CONNECT method. Contrary to standard CONNECT, Extended
CONNECT must have :scheme, :path and :authority defined.
This commit is contained in:
Amaury Denoyelle 2020-12-11 17:53:09 +01:00 committed by Christopher Faulet
parent efe2276a9e
commit c9a0afcc32
2 changed files with 66 additions and 17 deletions

View File

@ -50,6 +50,7 @@ enum {
H2_PHDR_IDX_SCHM = 4, /* :scheme = 6..7 */ H2_PHDR_IDX_SCHM = 4, /* :scheme = 6..7 */
H2_PHDR_IDX_STAT = 5, /* :status = 8..14 */ H2_PHDR_IDX_STAT = 5, /* :status = 8..14 */
H2_PHDR_IDX_HOST = 6, /* Host, never returned, just a place-holder */ H2_PHDR_IDX_HOST = 6, /* Host, never returned, just a place-holder */
H2_PHDR_IDX_PROT = 7, /* :protocol from rfc 8441 Extended Connect */
H2_PHDR_NUM_ENTRIES /* must be last */ H2_PHDR_NUM_ENTRIES /* must be last */
}; };
@ -64,6 +65,7 @@ enum {
H2_PHDR_FND_SCHM = 1 << H2_PHDR_IDX_SCHM, H2_PHDR_FND_SCHM = 1 << H2_PHDR_IDX_SCHM,
H2_PHDR_FND_STAT = 1 << H2_PHDR_IDX_STAT, H2_PHDR_FND_STAT = 1 << H2_PHDR_IDX_STAT,
H2_PHDR_FND_HOST = 1 << H2_PHDR_IDX_HOST, H2_PHDR_FND_HOST = 1 << H2_PHDR_IDX_HOST,
H2_PHDR_FND_PROT = 1 << H2_PHDR_IDX_PROT,
}; };
/* frame types, from the standard */ /* frame types, from the standard */
@ -305,6 +307,7 @@ static inline int h2_str_to_phdr(const struct ist str)
else if (isteq(str, ist(":scheme"))) return H2_PHDR_IDX_SCHM; else if (isteq(str, ist(":scheme"))) return H2_PHDR_IDX_SCHM;
else if (isteq(str, ist(":status"))) return H2_PHDR_IDX_STAT; else if (isteq(str, ist(":status"))) return H2_PHDR_IDX_STAT;
else if (isteq(str, ist(":authority"))) return H2_PHDR_IDX_AUTH; else if (isteq(str, ist(":authority"))) return H2_PHDR_IDX_AUTH;
else if (isteq(str, ist(":protocol"))) return H2_PHDR_IDX_PROT;
/* all other names starting with ':' */ /* all other names starting with ':' */
return -1; return -1;

View File

@ -173,12 +173,44 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
*/ */
static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, struct htx *htx, unsigned int *msgf) static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, struct htx *htx, unsigned int *msgf)
{ {
struct ist uri; struct ist uri, meth_sl;
unsigned int flags = HTX_SL_F_NONE; unsigned int flags = HTX_SL_F_NONE;
struct htx_sl *sl; struct htx_sl *sl;
size_t i; size_t i;
if ((fields & H2_PHDR_FND_METH) && isteq(phdr[H2_PHDR_IDX_METH], ist("CONNECT"))) { if ((fields & H2_PHDR_FND_METH) && isteq(phdr[H2_PHDR_IDX_METH], ist("CONNECT"))) {
if (fields & H2_PHDR_FND_PROT) {
/* rfc 8441 Extended Connect Protocol
* #4 :scheme and :path must be present, as well as
* :authority like all h2 requests
*/
if (!(fields & H2_PHDR_FND_SCHM)) {
/* missing scheme */
goto fail;
}
else if (!(fields & H2_PHDR_FND_PATH)) {
/* missing path */
goto fail;
}
else if (!(fields & H2_PHDR_FND_AUTH)) {
/* missing authority */
goto fail;
}
flags |= HTX_SL_F_HAS_SCHM;
if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("http")))
flags |= HTX_SL_F_SCHM_HTTP;
else if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("https")))
flags |= HTX_SL_F_SCHM_HTTPS;
meth_sl = ist("GET");
*msgf |= H2_MSGF_EXT_CONNECT;
/* no ES on the HEADERS frame but no body either for
* Extended CONNECT */
*msgf &= ~H2_MSGF_BODY;
}
else {
/* RFC 7540 #8.2.6 regarding CONNECT: ":scheme" and ":path" /* RFC 7540 #8.2.6 regarding CONNECT: ":scheme" and ":path"
* MUST be omitted ; ":authority" contains the host and port * MUST be omitted ; ":authority" contains the host and port
* to connect to. * to connect to.
@ -195,6 +227,10 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
/* missing authority */ /* missing authority */
goto fail; goto fail;
} }
meth_sl = phdr[H2_PHDR_IDX_METH];
}
*msgf |= H2_MSGF_BODY_TUNNEL; *msgf |= H2_MSGF_BODY_TUNNEL;
} }
else if ((fields & (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) != else if ((fields & (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) !=
@ -230,6 +266,8 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
flags |= HTX_SL_F_SCHM_HTTP; flags |= HTX_SL_F_SCHM_HTTP;
else if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("https"))) else if (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("https")))
flags |= HTX_SL_F_SCHM_HTTPS; flags |= HTX_SL_F_SCHM_HTTPS;
meth_sl = phdr[H2_PHDR_IDX_METH];
} }
if (!(flags & HTX_SL_F_HAS_SCHM)) { if (!(flags & HTX_SL_F_HAS_SCHM)) {
@ -289,11 +327,11 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
flags |= HTX_SL_F_VER_11; // V2 in fact flags |= HTX_SL_F_VER_11; // V2 in fact
flags |= HTX_SL_F_XFER_LEN; // xfer len always known with H2 flags |= HTX_SL_F_XFER_LEN; // xfer len always known with H2
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, phdr[H2_PHDR_IDX_METH], uri, ist("HTTP/2.0")); sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_sl, uri, ist("HTTP/2.0"));
if (!sl) if (!sl)
goto fail; goto fail;
sl->info.req.meth = find_http_meth(phdr[H2_PHDR_IDX_METH].ptr, phdr[H2_PHDR_IDX_METH].len); sl->info.req.meth = find_http_meth(meth_sl.ptr, meth_sl.len);
if (sl->info.req.meth == HTTP_METH_HEAD) if (sl->info.req.meth == HTTP_METH_HEAD)
*msgf |= H2_MSGF_BODYLESS_RSP; *msgf |= H2_MSGF_BODYLESS_RSP;
return sl; return sl;
@ -457,6 +495,14 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
htx->flags |= HTX_FL_EOM; htx->flags |= HTX_FL_EOM;
} }
if (*msgf & H2_MSGF_EXT_CONNECT) {
if (!htx_add_header(htx, ist("upgrade"), phdr_val[H2_PHDR_IDX_PROT]))
goto fail;
if (!htx_add_header(htx, ist("connection"), ist("upgrade")))
goto fail;
sl_flags |= HTX_SL_F_CONN_UPG;
}
/* update the start line with last detected header info */ /* update the start line with last detected header info */
sl->flags |= sl_flags; sl->flags |= sl_flags;