From: Willy Tarreau Date: Tue, 8 Oct 2019 16:33:19 +0000 (+0200) Subject: MEDIUM: h2: use the normalized URI encoding for absolute form requests X-Git-Tag: v2.1-dev3~124 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=30ee1efe676e8264af16bab833c621d60a72a4d7;p=thirdparty%2Fhaproxy.git MEDIUM: h2: use the normalized URI encoding for absolute form requests H2 strongly recommends that clients exclusively use the absolute form for requests, which contains a scheme, an authority and a path, instead of the old format involving the Host header and a path. Thus there is no way to distinguish between a request intended for a proxy and an origin request, and as such proxied requests are lost. This patch makes sure to keep the encoding of all absolute form requests so that the URI is kept end-to-end. If the scheme is http or https, there is an uncertainty so the request is tagged as a normalized URI so that the other end (H1) can decide to emit it in origin form as this is by far the most commonly expected one, and it's certain that quite a number of H1 setups are not ready to cope with absolute URIs. There is a direct visible impact of this change, which is that the uri sample fetch will now return absolute URIs (as they really come on the wire) whenever these are used. It also means that default http logs will report absolute URIs. If a situation is once met where a client uses H2 to join an H1 proxy with haproxy in the middle, then it will be trivial to add an option to ask the H1 output to use absolute encoding for such requests. Later we may be able to consider that the normalized URI is the default output format and stop sending them in origin form unless an option is set. Now chaining multiple instances keeps the semantics as far as possible along the whole chain : 1) H1 to H1 H1:"GET /" --> H1:"GET /" # log: / H1:"GET http://" --> H1:"GET http://" # log: http:// H1:"GET ftp://" --> H1:"GET ftp://" # log: ftp:// 2) H2 to H1 H2:"GET /" --> H1:"GET /" # log: / H2:"GET http://" --> H1:"GET /" # log: http:// H2:"GET ftp://" --> H1:"GET ftp://" # log: ftp:// 3) H1 to H2 to H2 to H1 H1:"GET /" --> H2:"GET /" --> H2:"GET /" --> H1:"GET /" H1:"GET http://" --> H2:"GET http://" --> H2:"GET http://" --> H1:"GET /" H1:"GET ftp://" --> H2:"GET ftp://" --> H2:"GET ftp://" --> H1:"GET ftp://" Thus there is zero loss on H1->H1, H1->H2 nor H2->H2, and H2->H1 is normalized in origin format if ambiguous. --- diff --git a/src/h2.c b/src/h2.c index 254d977192..f034e3d569 100644 --- a/src/h2.c +++ b/src/h2.c @@ -217,11 +217,10 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, uri = phdr[H2_PHDR_IDX_AUTH]; flags |= HTX_SL_F_HAS_AUTHORITY; } - 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. + else if (fields & H2_PHDR_FND_AUTH) { + /* authority is present, 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); @@ -229,6 +228,19 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, if (!isteq(phdr[H2_PHDR_IDX_PATH], ist("*"))) istcat(&uri, phdr[H2_PHDR_IDX_PATH], trash.size); flags |= HTX_SL_F_HAS_AUTHORITY; + + if (flags & (HTX_SL_F_SCHM_HTTP|HTX_SL_F_SCHM_HTTPS)) { + /* we don't know if it was originally an absolute or a + * relative request because newer versions of HTTP use + * the absolute URI format by default, which we call + * the normalized URI format internally. This is the + * strongly recommended way of sending a request for + * a regular client, so we cannot distinguish this + * from a request intended for a proxy. For other + * schemes however there is no doubt. + */ + flags |= HTX_SL_F_NORMALIZED_URI; + } } else { /* usual schemes with or without authority, use origin form */