Skip to content

An HTTP/2 204 (No Content) response containing a Content-Length header is considered invalid #140

@iqltd

Description

@iqltd

As the title suggests, I think the library is too strict in regards to 204 (No Content) responses that contain a Content-Length response header, in the context of HTTP/2.

While the HTTP/1.1 spec explicitly forbid this case (RFC 7230 section 3.3.2, as well as RFC 9110 section 8.6), my understanding is that the HTTP/2 spec does not. According to RFC 9113, chapter 8.1.1, a response without content may have a content-length header field:

A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the content, unless the message is defined as having no content. For example, 204 or 304 responses contain no content, as does the response to a HEAD request. A response that is defined to have no content, as described in Section 6.4.1 of [HTTP], MAY have a non-zero content-length header field, even though no content is included in DATA frames.

Versions used

gun 2.1.0
cowlib 2.13.0
erlang/otp 26

Steps to reproduce

Using an HTTP/2 server that responds with a 204 (No Content) response with or without a Content-Length response header (in my case https://github.com/iqltd/go-h2-yourself):

  • when the 204 (No Content) response doesn't contain a Content-Length response header, the response is handled correctly
  • when the 204 (No Content) response contains a Content-Length response header, we get a stream_error
Eshell V14.2.4 (press Ctrl+G to abort, type help(). for help)
1> application:ensure_all_started([gun]).
{ok,[cowlib,gun]}
2> ConnectionOpts = #{ transport => tls, tls_opts => [{verify, verify_none}] }.
#{transport => tls,tls_opts => [{verify,verify_none}]}
3> {ok, Pid} = gun:open("0.0.0.0", 8000, ConnectionOpts).
{ok,<0.642.0>}
4> gun:await_up(Pid, 1000).
{ok,http2}
5> Ref = gun:get(Pid, "/without-content-length", []).
#Ref<0.1964309148.1156317188.132308>
6> gun:await(Pid, Ref).
{response,fin,204,
          [{<<"content-type">>,<<"application/json">>},
           {<<"date">>,<<"Tue, 27 Aug 2024 10:01:38 GMT">>}]}
7> Ref2 = gun:get(Pid, "/with-content-length", []).
#Ref<0.1964309148.1156317188.132351>
8> gun:await(Pid, Ref2).
{error,{stream_error,{stream_error,protocol_error,
                                   'Content-length header received in a 204 response. (RFC7230 3.3.2)'}}}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions