aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http.erl
blob: 8d60f8275eb189a04effcef6b75ecc9dad4c7e09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
%% Copyright (c) 2011, 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(cowboy_http).

%% Parsing.
-export([parse_tokens_list/1]).

%% Interpretation.
-export([connection_to_atom/1]).

-include("include/http.hrl").
-include_lib("eunit/include/eunit.hrl").

%% Parsing.

%% @doc Parse a list of tokens, as is often found in HTTP headers.
%%
%% From the RFC:
%% <blockquote>Wherever this construct is used, null elements are allowed,
%% but do not contribute to the count of elements present.
%% That is, "(element), , (element) " is permitted, but counts
%% as only two elements. Therefore, where at least one element is required,
%% at least one non-null element MUST be present.</blockquote>
-spec parse_tokens_list(binary()) -> [binary()] | {error, badarg}.
parse_tokens_list(Value) ->
	case parse_tokens_list(Value, ws_or_sep, <<>>, []) of
		{error, badarg} ->
			{error, badarg};
		L when length(L) =:= 0 ->
			{error, badarg};
		L ->
			lists:reverse(L)
	end.

-spec parse_tokens_list(binary(), token | ws | ws_or_sep, binary(),
	[binary()]) -> [binary()] | {error, badarg}.
parse_tokens_list(<<>>, token, Token, Acc) ->
	[Token|Acc];
parse_tokens_list(<< C, Rest/bits >>, token, Token, Acc)
		when C =:= $\s; C =:= $\t ->
	parse_tokens_list(Rest, ws, <<>>, [Token|Acc]);
parse_tokens_list(<< $,, Rest/bits >>, token, Token, Acc) ->
	parse_tokens_list(Rest, ws_or_sep, <<>>, [Token|Acc]);
parse_tokens_list(<< C, Rest/bits >>, token, Token, Acc) ->
	parse_tokens_list(Rest, token, << Token/binary, C >>, Acc);
parse_tokens_list(<< C, Rest/bits >>, ws, <<>>, Acc)
		when C =:= $\s; C =:= $\t ->
	parse_tokens_list(Rest, ws, <<>>, Acc);
parse_tokens_list(<< $,, Rest/bits >>, ws, <<>>, Acc) ->
	parse_tokens_list(Rest, ws_or_sep, <<>>, Acc);
parse_tokens_list(<<>>, ws_or_sep, <<>>, Acc) ->
	Acc;
parse_tokens_list(<< C, Rest/bits >>, ws_or_sep, <<>>, Acc)
		when C =:= $\s; C =:= $\t ->
	parse_tokens_list(Rest, ws_or_sep, <<>>, Acc);
parse_tokens_list(<< $,, Rest/bits >>, ws_or_sep, <<>>, Acc) ->
	parse_tokens_list(Rest, ws_or_sep, <<>>, Acc);
parse_tokens_list(<< C, Rest/bits >>, ws_or_sep, <<>>, Acc) ->
	parse_tokens_list(Rest, token, << C >>, Acc);
parse_tokens_list(_Value, _State, _Token, _Acc) ->
	{error, badarg}.

%% Interpretation.

%% @doc Walk through a tokens list and return whether
%% the connection is keepalive or closed.
-spec connection_to_atom([binary()]) -> keepalive | close.
connection_to_atom([]) ->
	keepalive;
connection_to_atom([<<"keep-alive">>|_Tail]) ->
	keepalive;
connection_to_atom([<<"close">>|_Tail]) ->
	close;
connection_to_atom([Connection|Tail]) ->
	case cowboy_bstr:to_lower(Connection) of
		<<"close">> -> close;
		<<"keep-alive">> -> keepalive;
		_Any -> connection_to_atom(Tail)
	end.

%% Tests.

-ifdef(TEST).

parse_tokens_list_test_() ->
	%% {Value, Result}
	Tests = [
		{<<>>, {error, badarg}},
		{<<" ">>, {error, badarg}},
		{<<" , ">>, {error, badarg}},
		{<<",,,">>, {error, badarg}},
		{<<"a b">>, {error, badarg}},
		{<<"a , , , ">>, [<<"a">>]},
		{<<" , , , a">>, [<<"a">>]},
		{<<"a, , b">>, [<<"a">>, <<"b">>]},
		{<<"close">>, [<<"close">>]},
		{<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
	],
	[{V, fun() -> R = parse_tokens_list(V) end} || {V, R} <- Tests].

connection_to_atom_test_() ->
	%% {Tokens, Result}
	Tests = [
		{[<<"close">>], close},
		{[<<"ClOsE">>], close},
		{[<<"Keep-Alive">>], keepalive},
		{[<<"Keep-Alive">>, <<"Upgrade">>], keepalive}
	],
	[{lists:flatten(io_lib:format("~p", [T])),
		fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].

-endif.