diff options
| author | Loïc Hoguin <[email protected]> | 2017-11-15 14:32:51 +0100 | 
|---|---|---|
| committer | Loïc Hoguin <[email protected]> | 2017-11-15 14:32:51 +0100 | 
| commit | 7728c624028db160edad2646f34d069d16af0869 (patch) | |
| tree | 86f07ee306b8d6fc6396311fa83481638c537995 /src | |
| parent | c510f28cc0526899a6c6d8a6e9cdbdca432ff490 (diff) | |
| download | cowlib-7728c624028db160edad2646f34d069d16af0869.tar.gz cowlib-7728c624028db160edad2646f34d069d16af0869.tar.bz2 cowlib-7728c624028db160edad2646f34d069d16af0869.zip  | |
Add support for chunked transfer-encoding trailers
It considers all 0-sized chunks that aren't \r\n\r\n
to be trailers. There's no option for enabling/disabling
the behavior (for example when the te header was sent).
It doesn't parse the trailer, it's up to the user to
parse it separately via the new cow_http:headers/1 functions.
Note that this reuses the TotalLength part of the returned
'done' tuple to signal whether there are trailers. This value
has been ignored in Cowboy since 2.0 and was just a historical
leftover. I'm not aware of anyone using this module outside of
Gun or Cowboy, so I don't expect this to break anything. If it
does, well, it's not a documented function anyway. Your fault.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cow_http.erl | 8 | ||||
| -rw-r--r-- | src/cow_http_te.erl | 34 | 
2 files changed, 33 insertions, 9 deletions
diff --git a/src/cow_http.erl b/src/cow_http.erl index de079ea..85b5d7a 100644 --- a/src/cow_http.erl +++ b/src/cow_http.erl @@ -23,6 +23,7 @@  -export([request/4]).  -export([response/3]). +-export([headers/1]).  -export([version/1]).  -type version() :: 'HTTP/1.0' | 'HTTP/1.1'. @@ -254,8 +255,11 @@ request(Method, Path, Version, Headers) ->  -spec response(status() | binary(), version(), headers()) -> iodata().  response(Status, Version, Headers) ->  	[version(Version), <<" ">>, status(Status), <<"\r\n">>, -		[[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers], -		<<"\r\n">>]. +		headers(Headers), <<"\r\n">>]. + +-spec headers(headers()) -> iodata(). +headers(Headers) -> +	[[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers].  %% @doc Return the version as a binary. diff --git a/src/cow_http_te.erl b/src/cow_http_te.erl index e0e413d..b6290b7 100644 --- a/src/cow_http_te.erl +++ b/src/cow_http_te.erl @@ -199,8 +199,15 @@ chunked_len(<< $f, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);  %% chunk extensions (unlikely) we will need to change this clause too.  chunked_len(<< C, R/bits >>, S, A, Len) when C =/= $\r -> skip_chunk_ext(R, S, A, Len);  %% Final chunk. -chunked_len(<< "\r\n\r\n", R/bits >>, S, <<>>, 0) -> {done, S, R}; -chunked_len(<< "\r\n\r\n", R/bits >>, S, A, 0) -> {done, A, S, R}; +%% +%% When trailers are following we simply return them as the Rest. +%% Then the user code can decide to call the stream_trailers function +%% to parse them. The user can therefore ignore trailers as necessary +%% if they do not wish to handle them. +chunked_len(<< "\r\n\r\n", R/bits >>, _, <<>>, 0) -> {done, no_trailers, R}; +chunked_len(<< "\r\n\r\n", R/bits >>, _, A, 0) -> {done, A, no_trailers, R}; +chunked_len(<< "\r\n", R/bits >>, _, <<>>, 0) when byte_size(R) > 2 -> {done, trailers, R}; +chunked_len(<< "\r\n", R/bits >>, _, A, 0) when byte_size(R) > 2 -> {done, A, trailers, R};  chunked_len(_, _, _, 0) -> more;  %% Normal chunk. Add 2 to Len for the trailing \r\n.  chunked_len(<< "\r\n", R/bits >>, S, A, Len) -> {next, R, {Len + 2, S}, A}; @@ -229,7 +236,7 @@ last_chunk() ->  -ifdef(TEST).  stream_chunked_identity_test() -> -	{done, <<"Wikipedia in\r\n\r\nchunks.">>, 23, <<>>} +	{done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}  		= stream_chunked(iolist_to_binary([  			chunk("Wiki"),  			chunk("pedia"), @@ -239,8 +246,8 @@ stream_chunked_identity_test() ->  	ok.  stream_chunked_one_pass_test() -> -	{done, 0, <<>>} = stream_chunked(<<"0\r\n\r\n">>, {0, 0}), -	{done, <<"Wikipedia in\r\n\r\nchunks.">>, 23, <<>>} +	{done, no_trailers, <<>>} = stream_chunked(<<"0\r\n\r\n">>, {0, 0}), +	{done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}  		= stream_chunked(<<  			"4\r\n"  			"Wiki\r\n" @@ -251,7 +258,7 @@ stream_chunked_one_pass_test() ->  			"0\r\n"  			"\r\n">>, {0, 0}),  	%% Same but with extra spaces or chunk extensions. -	{done, <<"Wikipedia in\r\n\r\nchunks.">>, 23, <<>>} +	{done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}  		= stream_chunked(<<  			"4 \r\n"  			"Wiki\r\n" @@ -261,6 +268,19 @@ stream_chunked_one_pass_test() ->  			" in\r\n\r\nchunks.\r\n"  			"0;ext\r\n"  			"\r\n">>, {0, 0}), +	%% Same but with trailers. +	{done, <<"Wikipedia in\r\n\r\nchunks.">>, trailers, Rest} +		= stream_chunked(<< +			"4\r\n" +			"Wiki\r\n" +			"5\r\n" +			"pedia\r\n" +			"e\r\n" +			" in\r\n\r\nchunks.\r\n" +			"0\r\n" +			"x-foo-bar: bar foo\r\n" +			"\r\n">>, {0, 0}), +	{[{<<"x-foo-bar">>, <<"bar foo">>}], <<>>} = cow_http:parse_headers(Rest),  	ok.  stream_chunked_n_passes_test() -> @@ -270,7 +290,7 @@ stream_chunked_n_passes_test() ->  	{more, <<"Wiki">>, 0, S2} = stream_chunked(<<"Wiki\r\n">>, S1),  	{more, <<"pedia">>, <<"e\r">>, S3} = stream_chunked(<<"5\r\npedia\r\ne\r">>, S2),  	{more, <<" in\r\n\r\nchunks.">>, 2, S4} = stream_chunked(<<"e\r\n in\r\n\r\nchunks.">>, S3), -	{done, 23, <<>>} = stream_chunked(<<"\r\n0\r\n\r\n">>, S4), +	{done, no_trailers, <<>>} = stream_chunked(<<"\r\n0\r\n\r\n">>, S4),  	%% A few extra for coverage purposes.  	more = stream_chunked(<<"\n3">>, {1, 0}),  	{more, <<"abc">>, 2, {2, 3}} = stream_chunked(<<"\n3\r\nabc">>, {1, 0}),  | 
