Browse Source

Add more rfc7230 tests

Also fixes the handling of the max_headers option for HTTP/1.1.
It is now a strict limit and not dependent on whether data is
already in the buffer.
Loïc Hoguin 7 years ago
parent
commit
62bf505d33
2 changed files with 103 additions and 36 deletions
  1. 11 5
      src/cowboy_http.erl
  2. 92 31
      test/rfc7230_SUITE.erl

+ 11 - 5
src/cowboy_http.erl

@@ -454,18 +454,24 @@ parse_header(Rest, State=#state{in_state=PS}, Headers) when byte_size(Rest) < 2
 parse_header(<< $\r, $\n, Rest/bits >>, S, Headers) ->
 	request(Rest, S, Headers);
 parse_header(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
-	MaxLength = maps:get(max_header_name_length, Opts, 64),
 	MaxHeaders = maps:get(max_headers, Opts, 100),
 	NumHeaders = maps:size(Headers),
+	if
+		NumHeaders >= MaxHeaders ->
+			error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
+				{connection_error, limit_reached,
+					'The number of headers is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+		true ->
+			parse_header_colon(Buffer, State, Headers)
+	end.
+
+parse_header_colon(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
+	MaxLength = maps:get(max_header_name_length, Opts, 64),
 	case match_colon(Buffer, 0) of
 		nomatch when byte_size(Buffer) > MaxLength ->
 			error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
 				{connection_error, limit_reached,
 					'A header name is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
-		nomatch when NumHeaders >= MaxHeaders ->
-			error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
-				{connection_error, limit_reached,
-					'The number of headers is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
 		nomatch ->
 			{more, State#state{in_state=PS#ps_header{headers=Headers}}, Buffer};
 		_ ->

+ 92 - 31
test/rfc7230_SUITE.erl

@@ -711,21 +711,47 @@ reject_invalid_whitespace_after_version(Config) ->
 %
 %OWS = *( SP / HTAB )
 %```
-%
-%lower_case_header(Config) ->
-%upper_case_header(Config) ->
-%mixed_case_header(Config) ->
-%The header field name is case insensitive. (RFC7230 3.2)
-%
-%reject_whitespace_before_header_name(Config) ->
-%Messages that contain whitespace before the header name must
-%be rejected with a 400 status code and the closing of the
-%connection. (RFC7230 3.2.4)
-%
-%reject_whitespace_between_header_name_and_colon(Config) ->
-%Messages that contain whitespace between the header name and
-%colon must be rejected with a 400 status code and the closing
-%of the connection. (RFC7230 3.2.4)
+
+lower_case_header(Config) ->
+	doc("The header field name is case insensitive. (RFC7230 3.2)"),
+	#{code := 200} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"host: localhost\r\n"
+		"\r\n"]).
+
+upper_case_header(Config) ->
+	doc("The header field name is case insensitive. (RFC7230 3.2)"),
+	#{code := 200} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"HOST: localhost\r\n"
+		"\r\n"]).
+
+mixed_case_header(Config) ->
+	doc("The header field name is case insensitive. (RFC7230 3.2)"),
+	#{code := 200} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"hOsT: localhost\r\n"
+		"\r\n"]).
+
+reject_whitespace_before_header_name(Config) ->
+	doc("Messages that contain whitespace before the header name must "
+		"be rejected with a 400 status code and the closing of the "
+		"connection. (RFC7230 3.2.4)"),
+	#{code := 400, client := Client} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		" Host: localhost\r\n"
+		"\r\n"]),
+	{error, closed} = raw_recv(Client, 0, 1000).
+
+reject_whitespace_between_header_name_and_colon(Config) ->
+	doc("Messages that contain whitespace between the header name and "
+		"colon must be rejected with a 400 status code and the closing "
+		"of the connection. (RFC7230 3.2.4)"),
+	#{code := 400, client := Client} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"Host : localhost\r\n"
+		"\r\n"]),
+	{error, closed} = raw_recv(Client, 0, 1000).
 
 limit_header_name(Config) ->
 	doc("The header name must be subject to a configurable limit. A "
@@ -753,11 +779,26 @@ limit_header_value(Config) ->
 		"\r\n"]),
 	{error, closed} = raw_recv(Client, 0, 1000).
 
-%drop_whitespace_before_header_value(Config) ->
-%drop_whitespace_after_header_value(Config) ->
-%Optional whitespace before and after the header value is not
-%part of the value and must be dropped.
-%
+drop_whitespace_before_header_value(Config) ->
+	doc("Optional whitespace before and after the header value is not "
+		"part of the value and must be dropped."),
+	#{code := 200} = do_raw(Config, [
+		"POST / HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"Content-length:      \t     12\r\n"
+		"\r\n"
+		"Hello world!"]).
+
+drop_whitespace_after_header_value(Config) ->
+	doc("Optional whitespace before and after the header value is not "
+		"part of the value and must be dropped."),
+	#{code := 200} = do_raw(Config, [
+		"POST / HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"Content-length: 12     \t     \r\n"
+		"\r\n"
+		"Hello world!"]).
+
 %@todo
 %The order of header fields with differing names is not significant. (RFC7230 3.2.2)
 %
@@ -777,10 +818,17 @@ reject_duplicate_content_length_header(Config) ->
 		"Hello world!"]),
 	{error, closed} = raw_recv(Client, 0, 1000).
 
-%reject_duplicate_host_header(Config) ->
-%Requests with duplicate content-length or host headers must be rejected
-%with a 400 status code and the closing of the connection. (RFC7230 3.3.2)
-%
+reject_duplicate_host_header(Config) ->
+	doc("Requests with duplicate host headers must be rejected "
+		"with a 400 status code and the closing of the connection. (RFC7230 3.3.2)"),
+	#{code := 400, client := Client} = do_raw(Config, [
+		"POST / HTTP/1.1\r\n"
+		"Host: localhost\r\n"
+		"Host: localhost\r\n"
+		"\r\n"
+		"Hello world!"]),
+	{error, closed} = raw_recv(Client, 0, 1000).
+
 %combine_duplicate_headers(Config) ->
 %Other duplicate header fields must be combined by inserting a comma
 %between the values in the order they were received. (RFC7230 3.2.2)
@@ -792,13 +840,26 @@ reject_duplicate_content_length_header(Config) ->
 %
 %wait_for_eoh_before_processing_request(Config) ->
 %The request must not be processed until all headers have arrived. (RFC7230 3.2.2)
-%
-%limit_headers(Config) ->
-%The number of headers allowed in a request must be subject to
-%a configurable limit. There is no recommendations for the default.
-%100 headers is known to work well. Such a request must be rejected
-%with a 431 status code and the closing of the connection. (RFC7230 3.2.5, RFC6585 5)
-%
+
+limit_headers(Config) ->
+	doc("The number of headers allowed in a request must be subject to "
+		"a configurable limit. There is no recommendations for the default. "
+		"100 headers is known to work well. Such a request must be rejected "
+		"with a 431 status code and the closing of the connection. (RFC7230 3.2.5, RFC6585 5)"),
+	%% 100 headers.
+	#{code := 200} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"Host: localhost\r\n",
+		[["H-", integer_to_list(N), ": value\r\n"] || N <- lists:seq(1, 99)],
+		"\r\n"]),
+	%% 101 headers.
+	#{code := 431, client := Client} = do_raw(Config, [
+		"GET / HTTP/1.1\r\n"
+		"Host: localhost\r\n",
+		[["H-", integer_to_list(N), ": value\r\n"] || N <- lists:seq(1, 100)],
+		"\r\n"]),
+	{error, closed} = raw_recv(Client, 0, 1000).
+
 %@todo
 %When parsing header field values, the server must ignore empty
 %list elements, and not count those as the count of elements present. (RFC7230 7)