mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
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.
This commit is contained in:
parent
d7761b5259
commit
62bf505d33
2 changed files with 103 additions and 36 deletions
|
@ -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) ->
|
parse_header(<< $\r, $\n, Rest/bits >>, S, Headers) ->
|
||||||
request(Rest, S, Headers);
|
request(Rest, S, Headers);
|
||||||
parse_header(Buffer, State=#state{opts=Opts, in_state=PS}, 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),
|
MaxHeaders = maps:get(max_headers, Opts, 100),
|
||||||
NumHeaders = maps:size(Headers),
|
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
|
case match_colon(Buffer, 0) of
|
||||||
nomatch when byte_size(Buffer) > MaxLength ->
|
nomatch when byte_size(Buffer) > MaxLength ->
|
||||||
error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
|
error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
|
||||||
{connection_error, limit_reached,
|
{connection_error, limit_reached,
|
||||||
'A header name is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
|
'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 ->
|
nomatch ->
|
||||||
{more, State#state{in_state=PS#ps_header{headers=Headers}}, Buffer};
|
{more, State#state{in_state=PS#ps_header{headers=Headers}}, Buffer};
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -711,21 +711,47 @@ reject_invalid_whitespace_after_version(Config) ->
|
||||||
%
|
%
|
||||||
%OWS = *( SP / HTAB )
|
%OWS = *( SP / HTAB )
|
||||||
%```
|
%```
|
||||||
%
|
|
||||||
%lower_case_header(Config) ->
|
lower_case_header(Config) ->
|
||||||
%upper_case_header(Config) ->
|
doc("The header field name is case insensitive. (RFC7230 3.2)"),
|
||||||
%mixed_case_header(Config) ->
|
#{code := 200} = do_raw(Config, [
|
||||||
%The header field name is case insensitive. (RFC7230 3.2)
|
"GET / HTTP/1.1\r\n"
|
||||||
%
|
"host: localhost\r\n"
|
||||||
%reject_whitespace_before_header_name(Config) ->
|
"\r\n"]).
|
||||||
%Messages that contain whitespace before the header name must
|
|
||||||
%be rejected with a 400 status code and the closing of the
|
upper_case_header(Config) ->
|
||||||
%connection. (RFC7230 3.2.4)
|
doc("The header field name is case insensitive. (RFC7230 3.2)"),
|
||||||
%
|
#{code := 200} = do_raw(Config, [
|
||||||
%reject_whitespace_between_header_name_and_colon(Config) ->
|
"GET / HTTP/1.1\r\n"
|
||||||
%Messages that contain whitespace between the header name and
|
"HOST: localhost\r\n"
|
||||||
%colon must be rejected with a 400 status code and the closing
|
"\r\n"]).
|
||||||
%of the connection. (RFC7230 3.2.4)
|
|
||||||
|
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) ->
|
limit_header_name(Config) ->
|
||||||
doc("The header name must be subject to a configurable limit. A "
|
doc("The header name must be subject to a configurable limit. A "
|
||||||
|
@ -753,11 +779,26 @@ limit_header_value(Config) ->
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{error, closed} = raw_recv(Client, 0, 1000).
|
{error, closed} = raw_recv(Client, 0, 1000).
|
||||||
|
|
||||||
%drop_whitespace_before_header_value(Config) ->
|
drop_whitespace_before_header_value(Config) ->
|
||||||
%drop_whitespace_after_header_value(Config) ->
|
doc("Optional whitespace before and after the header value is not "
|
||||||
%Optional whitespace before and after the header value is not
|
"part of the value and must be dropped."),
|
||||||
%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
|
%@todo
|
||||||
%The order of header fields with differing names is not significant. (RFC7230 3.2.2)
|
%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!"]),
|
"Hello world!"]),
|
||||||
{error, closed} = raw_recv(Client, 0, 1000).
|
{error, closed} = raw_recv(Client, 0, 1000).
|
||||||
|
|
||||||
%reject_duplicate_host_header(Config) ->
|
reject_duplicate_host_header(Config) ->
|
||||||
%Requests with duplicate content-length or host headers must be rejected
|
doc("Requests with duplicate host headers must be rejected "
|
||||||
%with a 400 status code and the closing of the connection. (RFC7230 3.3.2)
|
"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) ->
|
%combine_duplicate_headers(Config) ->
|
||||||
%Other duplicate header fields must be combined by inserting a comma
|
%Other duplicate header fields must be combined by inserting a comma
|
||||||
%between the values in the order they were received. (RFC7230 3.2.2)
|
%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) ->
|
%wait_for_eoh_before_processing_request(Config) ->
|
||||||
%The request must not be processed until all headers have arrived. (RFC7230 3.2.2)
|
%The request must not be processed until all headers have arrived. (RFC7230 3.2.2)
|
||||||
%
|
|
||||||
%limit_headers(Config) ->
|
limit_headers(Config) ->
|
||||||
%The number of headers allowed in a request must be subject to
|
doc("The number of headers allowed in a request must be subject to "
|
||||||
%a configurable limit. There is no recommendations for the default.
|
"a configurable limit. There is no recommendations for the default. "
|
||||||
%100 headers is known to work well. Such a request must be rejected
|
"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)
|
"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
|
%@todo
|
||||||
%When parsing header field values, the server must ignore empty
|
%When parsing header field values, the server must ignore empty
|
||||||
%list elements, and not count those as the count of elements present. (RFC7230 7)
|
%list elements, and not count those as the count of elements present. (RFC7230 7)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue