Re: RFC 9113 and :authority header field

> Am 29.06.2022 um 07:52 schrieb Willy Tarreau <w@1wt.eu>:
> 
> Hi Tatsuhiro,
> 
> On Wed, Jun 29, 2022 at 08:58:47AM +0900, Tatsuhiro Tsujikawa wrote:
>> RFC 7540 even says that :intermediary MUST omit :authority "when translating
>> from an HTTP/1.1 request that has a request target in
>> origin or asterisk form (see [RFC7230], Section 5.3)."
>> 
>> Now RFC 9113 has this text:
>> 
>>      An intermediary that forwards a request over HTTP/2 MUST construct
>>      an ":authority" pseudo-header field using the authority
>>      information from the control data of the original request, unless
>>      the original request's target URI does not contain authority
>>      information (in which case it MUST NOT generate ":authority").
>>      Note that the Host header field is not the sole source of this
>>      information; see Section 7.2 of [HTTP].
>> 
>> This means :authority must be included if the host header field exists in
>> an HTTP/1.1 request.
> 
> My understanding is that Host doesn't necessarily count as "control data"
> here, and that the goal was to accurately represent an HTTP/1.x request
> targetting an HTTP/1.0 server after being transported over HTTP/2. For
> example, let's say that a client passes this to a proxy:
> 
>     GET http://example.com/ HTTP/1.0
>     Proxy-connection: keep-alive
> 
> and nothing more. If instead it gets sent via a gateway that transports
> it over H2, it could make sense to consider that the scheme is "http",
> the authority is "example.com", that there's no host, hence the request
> would be passed as:
> 
>     :method: GET
>     :scheme: http
>     :authority: example.com
> 
> and that's all. Conversely, let's see the same HTTP/1.0 request sent
> directly to the origin server:
> 
>     GET / HTTP/1.0
> 
> There's no more authority nor host, so a gateway receiving that cannot
> invent one, unless it uses its own configured name corresponding to its
> own address, that it expects the client used to construct the request.
> 
> With HTTP/1.1 there are less ambiguities since Host is mandatory, but
> the distinction between "proxy requests" and origin requests is still
> relevant, especially when you don't know whether or not the origin
> server supports HTTP/1.1 or only 1.0 (and may be confused by the
> presence of an authority in the request line). For example, if a
> client sends:
> 
>  GET / HTTP/1.1
>  Host: example.com
> 
> to an HTTP/1.0 server that parses Host, it will work. If it sends
> 
>  GET http://example.com/ HTTP/1.1
>  Host: example.com
> 
> To an HTTP/1.1 server, it will work as well, but it may fail to an HTTP/1.0
> server (or worse, loop over itself if it supports proxing requests and
> resolves itself as example.com).
> 
> If the first request is transported over H2, thus converted from H1 to
> H2 then back from H2 to H1, adding an authority that was not initially
> present would introduce exactly this problem. By not adding it and using
> Host only, the request representation is preserved, and the origin server
> can receive the same request that the client took care to encode, and not
> be confused. That's why I'm saying that in this case it's clearly visible
> that Host isn't part of the "control data" and must not appear in an
> authority that was not initially encoded.
> 
> I know it's a bit complicated but we have to deal with history. What we're
> doing in haproxy is that both Host and :authority are used interchangeably
> after having been checked for proper matching, and are modified at the
> same time if needed, and we have a flag indicating if an authority was
> present in the incoming request to know if we have to produce one on
> output or not. That's in the end what seems to preserve the most accurate
> representation along a chain of multiple versions. This allows us to emit
> a Host field only if one was present, and an authority only if one was
> present, regardless of the HTTP version. I don't think that RFC9113 brings
> any changes regarding this, it might only be a matter of what constitutes
> "control data".
> 
> Hoping this helps,

Thanks. Trying to put all this in examples:

MUST WORK:
H2 :scheme: https, :path: /, Host: example.com
H2 :authority: example.com, :scheme: https, :path: /, Host: example.com:443

SHOULD FAIL:
H2 :authority: example.com, :scheme: https, :path: /, Host: badexample.com
  if not, become H1 GET / HTTP/1.1, Host: example.com

MUST FAIL:
H2 :scheme: https, :path: /


Conversions:

H1 GET / HTTP/1.1, Host: example.com
-> H2 :authority: example.com, :scheme: <context>
  -> H0 GET http://example.com/ HTTP/1.0
  -> H1 GET / HTTP/1.1, Host: example.com

H1 GET http://example.com/ HTTP/1.1, Host: example.com
-> H2 :authority: example.com, :scheme: http, Host: example.com
  -> H0 GET http://example.com/ HTTP/1.0
  -> H1 GET / HTTP/1.1, Host: example.com

H1 GET http://example.com/ HTTP/1.0
-> H2 :authority: example.com, :scheme: http
  -> H0 GET http://example.com/ HTTP/1.0
  -> H1 GET / HTTP/1.1, Host: example.com

H1 GET / HTTP/1.0
-> H2 :authority: <context>, :scheme: <context>

H1 GET urn:ietf:std:97 HTTP/1.1, Host:
-> H2 :authority: ???, :scheme: urn, :path: ???


Kind Regards,
Stefan

Received on Wednesday, 29 June 2022 09:18:36 UTC