Browse Source

Add cow_http_hd:parse_{connection,content_length,transfer_encoding}/1

Initially imported from Cowboy, then optimized.
Loïc Hoguin 11 years ago
parent
commit
acf2c13e65
1 changed files with 194 additions and 0 deletions
  1. 194 0
      src/cow_http_hd.erl

+ 194 - 0
src/cow_http_hd.erl

@@ -0,0 +1,194 @@
+%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http_hd).
+
+-export([parse_connection/1]).
+-export([parse_content_length/1]).
+-export([parse_transfer_encoding/1]).
+
+-include("cow_inline.hrl").
+
+%% @doc Parse the Connection header.
+
+-spec parse_connection(binary()) -> [binary()].
+parse_connection(<<"close">>) ->
+	[<<"close">>];
+parse_connection(<<"keep-alive">>) ->
+	[<<"keep-alive">>];
+parse_connection(Connection) ->
+	nonempty(token_ci_list(Connection, [])).
+
+-ifdef(TEST).
+parse_connection_test_() ->
+	Tests = [
+		{<<"close">>, [<<"close">>]},
+		{<<"ClOsE">>, [<<"close">>]},
+		{<<"Keep-Alive">>, [<<"keep-alive">>]},
+		{<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
+	],
+	[{V, fun() -> R = parse_connection(V) end} || {V, R} <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_connection_close() ->
+	horse:repeat(200000,
+		parse_connection(<<"close">>)
+	).
+
+horse_parse_connection_keepalive() ->
+	horse:repeat(200000,
+		parse_connection(<<"keep-alive">>)
+	).
+
+horse_parse_connection_keepalive_upgrade() ->
+	horse:repeat(200000,
+		parse_connection(<<"keep-alive, upgrade">>)
+	).
+-endif.
+
+%% @doc Parse the Content-Length header.
+%%
+%% The value has at least one digit, and may be followed by whitespace.
+
+-spec parse_content_length(binary()) -> non_neg_integer().
+parse_content_length(<< $0 >>) -> 0;
+parse_content_length(<< $0, R/bits >>) -> number(R, 0);
+parse_content_length(<< $1, R/bits >>) -> number(R, 1);
+parse_content_length(<< $2, R/bits >>) -> number(R, 2);
+parse_content_length(<< $3, R/bits >>) -> number(R, 3);
+parse_content_length(<< $4, R/bits >>) -> number(R, 4);
+parse_content_length(<< $5, R/bits >>) -> number(R, 5);
+parse_content_length(<< $6, R/bits >>) -> number(R, 6);
+parse_content_length(<< $7, R/bits >>) -> number(R, 7);
+parse_content_length(<< $8, R/bits >>) -> number(R, 8);
+parse_content_length(<< $9, R/bits >>) -> number(R, 9).
+
+-ifdef(TEST).
+parse_content_length_test_() ->
+	Tests = [
+		{<<"0">>, 0},
+		{<<"42    ">>, 42},
+		{<<"69\t">>, 69},
+		{<<"1337">>, 1337},
+		{<<"1234567890">>, 1234567890},
+		{<<"1234567890     ">>, 1234567890}
+	],
+	[{V, fun() -> R = parse_content_length(V) end} || {V, R} <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_content_length_zero() ->
+	horse:repeat(100000,
+		parse_content_length(<<"0">>)
+	).
+
+horse_parse_content_length_giga() ->
+	horse:repeat(100000,
+		parse_content_length(<<"1234567890">>)
+	).
+-endif.
+
+%% @doc Parse the Transfer-Encoding header.
+%%
+%% @todo Extension parameters.
+
+-spec parse_transfer_encoding(binary()) -> [binary()].
+parse_transfer_encoding(<<"chunked">>) ->
+	[<<"chunked">>];
+parse_transfer_encoding(TransferEncoding) ->
+	nonempty(token_ci_list(TransferEncoding, [])).
+
+-ifdef(TEST).
+parse_transfer_encoding_test_() ->
+	Tests = [
+		{<<"a , , , ">>, [<<"a">>]},
+		{<<" , , , a">>, [<<"a">>]},
+		{<<"a , , b">>, [<<"a">>, <<"b">>]},
+		{<<"chunked">>, [<<"chunked">>]},
+		{<<"chunked, something">>, [<<"chunked">>, <<"something">>]}
+	],
+	[{V, fun() -> R = parse_transfer_encoding(V) end} || {V, R} <- Tests].
+
+parse_transfer_encoding_error_test_() ->
+	Tests = [
+		<<>>,
+		<<" ">>,
+		<<" , ">>,
+		<<",,,">>,
+		<<"a b">>
+	],
+	[{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
+		|| V <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_transfer_encoding_chunked() ->
+	horse:repeat(200000,
+		parse_transfer_encoding(<<"chunked">>)
+	).
+
+horse_parse_transfer_encoding_custom() ->
+	horse:repeat(200000,
+		parse_transfer_encoding(<<"chunked, something">>)
+	).
+-endif.
+
+%% Internal.
+
+%% Only return if the list is not empty.
+nonempty(L) when L =/= [] -> L.
+
+%% Parse a number optionally followed by whitespace.
+number(<< $0, R/bits >>, Acc) -> number(R, Acc * 10);
+number(<< $1, R/bits >>, Acc) -> number(R, Acc * 10 + 1);
+number(<< $2, R/bits >>, Acc) -> number(R, Acc * 10 + 2);
+number(<< $3, R/bits >>, Acc) -> number(R, Acc * 10 + 3);
+number(<< $4, R/bits >>, Acc) -> number(R, Acc * 10 + 4);
+number(<< $5, R/bits >>, Acc) -> number(R, Acc * 10 + 5);
+number(<< $6, R/bits >>, Acc) -> number(R, Acc * 10 + 6);
+number(<< $7, R/bits >>, Acc) -> number(R, Acc * 10 + 7);
+number(<< $8, R/bits >>, Acc) -> number(R, Acc * 10 + 8);
+number(<< $9, R/bits >>, Acc) -> number(R, Acc * 10 + 9);
+number(<< $\s, R/bits >>, Acc) -> ws_end(R), Acc;
+number(<< $\t, R/bits >>, Acc) -> ws_end(R), Acc;
+number(<<>>, Acc) -> Acc.
+
+ws_end(<< $\s, R/bits >>) -> ws_end(R);
+ws_end(<< $\t, R/bits >>) -> ws_end(R);
+ws_end(<<>>) -> ok.
+
+%% Parse a list of case insensitive tokens.
+token_ci_list(<<>>, Acc) -> lists:reverse(Acc);
+token_ci_list(<< $\s, R/bits >>, Acc) -> token_ci_list(R, Acc);
+token_ci_list(<< $\t, R/bits >>, Acc) -> token_ci_list(R, Acc);
+token_ci_list(<< $,, R/bits >>, Acc) -> token_ci_list(R, Acc);
+token_ci_list(<< C, R/bits >>, Acc) ->
+	case C of
+		?INLINE_LOWERCASE(token_ci_list, R, Acc, <<>>)
+	end.
+
+token_ci_list(<<>>, Acc, T) -> lists:reverse([T|Acc]);
+token_ci_list(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
+token_ci_list(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
+token_ci_list(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]);
+token_ci_list(<< C, R/bits >>, Acc, T) ->
+	case C of
+		?INLINE_LOWERCASE(token_ci_list, R, Acc, T)
+	end.
+
+token_ci_list_sep(<<>>, Acc, T) -> lists:reverse([T|Acc]);
+token_ci_list_sep(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
+token_ci_list_sep(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
+token_ci_list_sep(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]).