aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cow_http_hd.erl194
1 files changed, 194 insertions, 0 deletions
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
new file mode 100644
index 0000000..35cf2f4
--- /dev/null
+++ b/src/cow_http_hd.erl
@@ -0,0 +1,194 @@
+%% Copyright (c) 2014, Loïc Hoguin <[email protected]>
+%%
+%% 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]).