Browse Source

Don't send the content-length header in empty 304 responses

It's OK to send it when set explicitly, as it can be set
to what the representation's size would have been.
Loïc Hoguin 6 years ago
parent
commit
292039362a
4 changed files with 74 additions and 5 deletions
  1. 2 0
      src/cowboy_http.erl
  2. 6 2
      src/cowboy_req.erl
  3. 3 0
      test/handlers/resp_h.erl
  4. 63 3
      test/rfc7230_SUITE.erl

+ 2 - 0
src/cowboy_http.erl

@@ -963,6 +963,8 @@ commands(State0=#state{socket=Socket, transport=Transport, streams=Streams0, out
 	{State1, Headers1} = case {Status, ContentLength, Version} of
 		{204, _, 'HTTP/1.1'} ->
 			{State0#state{out_state=done}, Headers0};
+		{304, _, 'HTTP/1.1'} ->
+			{State0#state{out_state=done}, Headers0};
 		{_, undefined, 'HTTP/1.1'} ->
 			{State0#state{out_state=chunked}, Headers0#{<<"transfer-encoding">> => <<"chunked">>}};
 		%% Close the connection after streaming without content-length to HTTP/1.0 client.

+ 6 - 2
src/cowboy_req.erl

@@ -771,11 +771,15 @@ reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)
 	do_reply(Status, Headers#{
 		<<"content-length">> => integer_to_binary(Len)
 	}, SendFile, Req);
-%% 204 responses must not include content-length. (RFC7230 3.3.1, RFC7230 3.3.2)
-reply(Status=204, Headers, Body, Req) ->
+%% 204 responses must not include content-length. 304 responses may
+%% but only when set explicitly. (RFC7230 3.3.1, RFC7230 3.3.2)
+reply(Status, Headers, Body, Req)
+		when Status =:= 204; Status =:= 304 ->
 	do_reply(Status, Headers, Body, Req);
 reply(Status= <<"204",_/bits>>, Headers, Body, Req) ->
 	do_reply(Status, Headers, Body, Req);
+reply(Status= <<"304",_/bits>>, Headers, Body, Req) ->
+	do_reply(Status, Headers, Body, Req);
 reply(Status, Headers, Body, Req)
 		when is_integer(Status); is_binary(Status) ->
 	do_reply(Status, Headers#{

+ 3 - 0
test/handlers/resp_h.erl

@@ -186,6 +186,9 @@ do(<<"stream_reply2">>, Req0, Opts) ->
 		<<"204">> ->
 			Req = cowboy_req:stream_reply(204, Req0),
 			{ok, Req, Opts};
+		<<"304">> ->
+			Req = cowboy_req:stream_reply(304, Req0),
+			{ok, Req, Opts};
 		Status ->
 			Req = cowboy_req:stream_reply(binary_to_integer(Status), Req0),
 			stream_body(Req),

+ 63 - 3
test/rfc7230_SUITE.erl

@@ -1745,9 +1745,55 @@ no_body_in_head_response(Config) ->
 %no_body_in_100_response(Config) ->
 %no_body_in_101_response(Config) ->
 %no_body_in_102_response(Config) ->
-%no_body_in_204_response(Config) ->
-%no_body_in_304_response(Config) ->
-%1xx, 204 and 304 responses never include a message body. (RFC7230 3.3)
+%1xx responses never include a message body. (RFC7230 3.3)
+
+no_body_in_204_response(Config) ->
+	doc("204 responses never include a message body. (RFC7230 3.3)"),
+	Client = raw_open(Config),
+	ok = raw_send(Client, [
+		"GET /resp/reply2/204 HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"\r\n"]),
+	{_, 204, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
+	{_, <<>>} = cow_http:parse_headers(Rest),
+	{error, timeout} = raw_recv(Client, 1, 1000),
+	ok.
+
+no_body_in_204_response_stream(Config) ->
+	doc("204 responses never include a message body. (RFC7230 3.3)"),
+	Client = raw_open(Config),
+	ok = raw_send(Client, [
+		"GET /resp/stream_reply2/204 HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"\r\n"]),
+	{_, 204, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
+	{_, <<>>} = cow_http:parse_headers(Rest),
+	{error, timeout} = raw_recv(Client, 1, 1000),
+	ok.
+
+no_body_in_304_response(Config) ->
+	doc("304 responses never include a message body. (RFC7230 3.3)"),
+	Client = raw_open(Config),
+	ok = raw_send(Client, [
+		"GET /resp/reply2/304 HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"\r\n"]),
+	{_, 304, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
+	{_, <<>>} = cow_http:parse_headers(Rest),
+	{error, timeout} = raw_recv(Client, 1, 1000),
+	ok.
+
+no_body_in_304_response_stream(Config) ->
+	doc("304 responses never include a message body. (RFC7230 3.3)"),
+	Client = raw_open(Config),
+	ok = raw_send(Client, [
+		"GET /resp/stream_reply2/304 HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"\r\n"]),
+	{_, 304, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
+	{_, <<>>} = cow_http:parse_headers(Rest),
+	{error, timeout} = raw_recv(Client, 1, 1000),
+	ok.
 
 same_content_length_as_get_in_head_response(Config) ->
 	doc("Responses to HEAD requests can include a content-length header. "
@@ -1802,6 +1848,20 @@ no_content_length_in_204_response(Config) ->
 	false = lists:keyfind(<<"content-length">>, 1, Headers),
 	ok.
 
+no_content_length_in_empty_304_response(Config) ->
+	doc("304 responses should not include a content-length header, "
+		"unless it matches the resource's and was therefore set "
+		"explicitly by the user. (RFC7230 3.3.1, RFC7230 3.3.2)"),
+	Client = raw_open(Config),
+	ok = raw_send(Client, [
+		"GET /resp/reply3/304 HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"\r\n"]),
+	{_, 304, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
+	{Headers, <<>>} = cow_http:parse_headers(Rest),
+	false = lists:keyfind(<<"content-length">>, 1, Headers),
+	ok.
+
 %%% @todo CONNECT no_content_length_in_2xx_response_to_connect_request(Config) ->
 %no_transfer_encoding_in_100_response(Config) ->
 %no_transfer_encoding_in_101_response(Config) ->