MEDIUM: h2: make the request parser rebuild a complete URI

Till now we've been producing path components of the URI and using the
:authority header only to be placed into the host part. But this practice
is not correct, as if we're used to convey H1 proxy requests over H2 then
over H1, the absolute URI is presented as a path on output, which is not
valid. In addition the scheme on output is not updated from the absolute
URI either.

Now the request parser will continue to deliver origin-form for request
received using the http/https schemes, but will use the absolute-form
when dealing with other schemes, by concatenating the scheme, the authority
and the path if it's not '*'.
This commit is contained in:
Willy Tarreau 2019-10-08 16:53:07 +02:00 committed by Christopher Faulet
parent 92916d343c
commit 92919f7fd5

View File

@ -30,6 +30,7 @@
#include <common/h2.h>
#include <common/http-hdr.h>
#include <common/ist.h>
#include <types/global.h>
struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES] = {
[H2_FT_DATA ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 0, .max_len = H2_MAX_FRAME_LEN, },
@ -152,7 +153,7 @@ 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)
{
int uri_idx;
struct ist uri;
unsigned int flags = HTX_SL_F_NONE;
struct htx_sl *sl;
size_t i;
@ -174,9 +175,6 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
/* missing authority */
goto fail;
}
// otherwise OK ; the URI is only made of the authority here
uri_idx = H2_PHDR_IDX_AUTH;
*msgf |= H2_MSGF_BODY_TUNNEL;
}
else if ((fields & (H2_PHDR_FND_METH|H2_PHDR_FND_SCHM|H2_PHDR_FND_PATH)) !=
@ -199,19 +197,51 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
}
}
else { /* regular methods */
/* origin-form requests are made only of the path */
uri_idx = H2_PHDR_IDX_PATH;
/* RFC3986#6.2.2.1: scheme is case-insensitive. We need to
* classify the scheme as "present/http", "present/https",
* "present/other", "absent" so as to decide whether or not
* we're facing a normalized URI that will have to be encoded
* in origin or absolute form. Indeed, 7540#8.1.2.3 says that
* clients should use the absolute form, thus we cannot infer
* whether or not the client wanted to use a proxy here.
*/
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;
}
if (!(flags & HTX_SL_F_HAS_SCHM)) {
/* no scheme, use authority only (CONNECT) */
uri = phdr[H2_PHDR_IDX_AUTH];
}
else if (!(flags & (HTX_SL_F_SCHM_HTTP|HTX_SL_F_SCHM_HTTPS)) && (fields & H2_PHDR_FND_AUTH)) {
/* non-http/https scheme + authority, let's use the absolute
* form. We simply use the trash to concatenate them since all
* of them MUST fit in a bufsize since it's where they come
* from.
*/
uri = ist2bin(trash.area, phdr[H2_PHDR_IDX_SCHM]);
istcat(&uri, ist("://"), trash.size);
istcat(&uri, phdr[H2_PHDR_IDX_AUTH], trash.size);
if (!isteq(phdr[H2_PHDR_IDX_PATH], ist("*")))
istcat(&uri, phdr[H2_PHDR_IDX_PATH], trash.size);
}
else {
/* usual schemes with or without authority, use origin form */
uri = phdr[H2_PHDR_IDX_PATH];
}
/* make sure the final URI isn't empty. Note that 7540#8.1.2.3 states
* that :path must not be empty.
*/
if (!phdr[uri_idx].len)
if (!uri.len)
goto fail;
/* The final URI must not contain LWS nor CTL characters */
for (i = 0; i < phdr[uri_idx].len; i++) {
unsigned char c = phdr[uri_idx].ptr[i];
for (i = 0; i < uri.len; i++) {
unsigned char c = uri.ptr[i];
if (HTTP_IS_LWS(c) || HTTP_IS_CTL(c))
htx->flags |= HTX_FL_PARSING_ERROR;
}
@ -220,13 +250,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_XFER_LEN; // xfer len always known with H2
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, phdr[H2_PHDR_IDX_METH], phdr[uri_idx], ist("HTTP/2.0"));
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, phdr[H2_PHDR_IDX_METH], uri, ist("HTTP/2.0"));
if (!sl)
goto fail;
sl->info.req.meth = find_http_meth(phdr[H2_PHDR_IDX_METH].ptr, phdr[H2_PHDR_IDX_METH].len);
sl->flags |= HTX_SL_F_HAS_SCHM;
sl->flags |= (isteqi(phdr[H2_PHDR_IDX_SCHM], ist("http")) ? HTX_SL_F_SCHM_HTTP : HTX_SL_F_SCHM_HTTPS);
return sl;
fail:
return NULL;