From acf2c13e65329de1d3744c35f9f9d6a0983087c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 24 Feb 2014 20:17:42 +0100 Subject: Add cow_http_hd:parse_{connection,content_length,transfer_encoding}/1 Initially imported from Cowboy, then optimized. --- src/cow_http_hd.erl | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/cow_http_hd.erl (limited to 'src') 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 +%% +%% 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]). -- cgit v1.2.3