~ubuntu-branches/ubuntu/saucy/rabbitmq-server/saucy

« back to all changes in this revision

Viewing changes to plugins-src/cowboy-wrapper/cowboy-git/src/cowboy_cookies.erl

  • Committer: Package Import Robot
  • Author(s): Emile Joubert
  • Date: 2012-11-19 11:42:31 UTC
  • mfrom: (0.2.18) (0.1.32 sid)
  • Revision ID: package-import@ubuntu.com-20121119114231-hvapkn4akng09etr
Tags: 3.0.0-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%% Copyright 2007 Mochi Media, Inc.
 
2
%% Copyright 2011 Thomas Burdick <thomas.burdick@gmail.com>
 
3
%%
 
4
%% Permission to use, copy, modify, and/or distribute this software for any
 
5
%% purpose with or without fee is hereby granted, provided that the above
 
6
%% copyright notice and this permission notice appear in all copies.
 
7
%%
 
8
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 
9
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 
10
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 
11
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 
12
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 
13
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 
14
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
15
 
 
16
%% @doc HTTP Cookie parsing and generating (RFC 2965).
 
17
 
 
18
-module(cowboy_cookies).
 
19
 
 
20
-export([parse_cookie/1, cookie/3, cookie/2]). %% API.
 
21
 
 
22
%% Types.
 
23
-type kv() :: {Name::binary(), Value::binary()}.
 
24
-type kvlist() :: [kv()].
 
25
-type cookie_option() :: {max_age, integer()}
 
26
                                | {local_time, calendar:datetime()}
 
27
                                | {domain, binary()} | {path, binary()}
 
28
                                | {secure, true | false} | {http_only, true | false}.
 
29
-export_type([kv/0, kvlist/0, cookie_option/0]).
 
30
 
 
31
-define(QUOTE, $\").
 
32
 
 
33
-include_lib("eunit/include/eunit.hrl").
 
34
 
 
35
%% API.
 
36
 
 
37
%% @doc Parse the contents of a Cookie header field, ignoring cookie
 
38
%% attributes, and return a simple property list.
 
39
-spec parse_cookie(binary()) -> kvlist().
 
40
parse_cookie(<<>>) ->
 
41
        [];
 
42
parse_cookie(Cookie) when is_binary(Cookie) ->
 
43
        parse_cookie(Cookie, []).
 
44
 
 
45
%% @equiv cookie(Key, Value, [])
 
46
-spec cookie(binary(), binary()) -> kv().
 
47
cookie(Key, Value) when is_binary(Key) andalso is_binary(Value) ->
 
48
        cookie(Key, Value, []).
 
49
 
 
50
%% @doc Generate a Set-Cookie header field tuple.
 
51
-spec cookie(binary(), binary(), [cookie_option()]) -> kv().
 
52
cookie(Key, Value, Options) when is_binary(Key)
 
53
                andalso is_binary(Value) andalso is_list(Options) ->
 
54
        Cookie = <<(any_to_binary(Key))/binary, "=",
 
55
                (quote(Value))/binary, "; Version=1">>,
 
56
        %% Set-Cookie:
 
57
        %%    Comment, Domain, Max-Age, Path, Secure, Version
 
58
        ExpiresPart =
 
59
                case proplists:get_value(max_age, Options) of
 
60
                        undefined ->
 
61
                                <<"">>;
 
62
                        RawAge ->
 
63
                                When = case proplists:get_value(local_time, Options) of
 
64
                                                undefined ->
 
65
                                                        calendar:local_time();
 
66
                                                LocalTime ->
 
67
                                                        LocalTime
 
68
                                        end,
 
69
                                Age = case RawAge < 0 of
 
70
                                                true ->
 
71
                                                        0;
 
72
                                                false ->
 
73
                                                        RawAge
 
74
                                        end,
 
75
                                AgeBinary = quote(Age),
 
76
                                CookieDate = age_to_cookie_date(Age, When),
 
77
                                <<"; Expires=", CookieDate/binary,
 
78
                                "; Max-Age=", AgeBinary/binary>>
 
79
                end,
 
80
        SecurePart =
 
81
                case proplists:get_value(secure, Options) of
 
82
                        true ->
 
83
                                <<"; Secure">>;
 
84
                        _ ->
 
85
                                <<"">>
 
86
                end,
 
87
        DomainPart =
 
88
                case proplists:get_value(domain, Options) of
 
89
                        undefined ->
 
90
                                <<"">>;
 
91
                        Domain ->
 
92
                                <<"; Domain=", (quote(Domain))/binary>>
 
93
                end,
 
94
        PathPart =
 
95
                case proplists:get_value(path, Options) of
 
96
                        undefined ->
 
97
                                <<"">>;
 
98
                        Path ->
 
99
                                <<"; Path=", (quote(Path))/binary>>
 
100
                end,
 
101
        HttpOnlyPart =
 
102
                case proplists:get_value(http_only, Options) of
 
103
                        true ->
 
104
                                <<"; HttpOnly">>;
 
105
                        _ ->
 
106
                                <<"">>
 
107
                end,
 
108
        CookieParts = <<Cookie/binary, ExpiresPart/binary, SecurePart/binary,
 
109
                DomainPart/binary, PathPart/binary, HttpOnlyPart/binary>>,
 
110
        {<<"Set-Cookie">>, CookieParts}.
 
111
 
 
112
%% Internal.
 
113
 
 
114
%% @doc Check if a character is a white space character.
 
115
is_whitespace($\s) -> true;
 
116
is_whitespace($\t) -> true;
 
117
is_whitespace($\r) -> true;
 
118
is_whitespace($\n) -> true;
 
119
is_whitespace(_) -> false.
 
120
 
 
121
%% @doc Check if a character is a seperator.
 
122
is_separator(C) when C < 32 -> true;
 
123
is_separator($\s) -> true;
 
124
is_separator($\t) -> true;
 
125
is_separator($() -> true;
 
126
is_separator($)) -> true;
 
127
is_separator($<) -> true;
 
128
is_separator($>) -> true;
 
129
is_separator($@) -> true;
 
130
is_separator($,) -> true;
 
131
is_separator($;) -> true;
 
132
is_separator($:) -> true;
 
133
is_separator($\\) -> true;
 
134
is_separator(?QUOTE) -> true;
 
135
is_separator($/) -> true;
 
136
is_separator($[) -> true;
 
137
is_separator($]) -> true;
 
138
is_separator($?) -> true;
 
139
is_separator($=) -> true;
 
140
is_separator(${) -> true;
 
141
is_separator($}) -> true;
 
142
is_separator(_) -> false.
 
143
 
 
144
%% @doc Check if a binary has an ASCII seperator character.
 
145
has_seperator(<<>>) ->
 
146
        false;
 
147
has_seperator(<<$/, Rest/binary>>) ->
 
148
        has_seperator(Rest);
 
149
has_seperator(<<C, Rest/binary>>) ->
 
150
        case is_separator(C) of
 
151
                true ->
 
152
                        true;
 
153
                false ->
 
154
                        has_seperator(Rest)
 
155
        end.
 
156
 
 
157
%% @doc Convert to a binary and raise an error if quoting is required. Quoting
 
158
%% is broken in different ways for different browsers. Its better to simply
 
159
%% avoiding doing it at all.
 
160
%% @end
 
161
-spec quote(term()) -> binary().
 
162
quote(V0) ->
 
163
        V = any_to_binary(V0),
 
164
        case has_seperator(V) of
 
165
                true ->
 
166
                        erlang:error({cookie_quoting_required, V});
 
167
                false ->
 
168
                        V
 
169
        end.
 
170
 
 
171
-spec add_seconds(integer(), calendar:datetime()) -> calendar:datetime().
 
172
add_seconds(Secs, LocalTime) ->
 
173
        Greg = calendar:datetime_to_gregorian_seconds(LocalTime),
 
174
        calendar:gregorian_seconds_to_datetime(Greg + Secs).
 
175
 
 
176
-spec age_to_cookie_date(integer(), calendar:datetime()) -> binary().
 
177
age_to_cookie_date(Age, LocalTime) ->
 
178
        cowboy_clock:rfc2109(add_seconds(Age, LocalTime)).
 
179
 
 
180
-spec parse_cookie(binary(), kvlist()) -> kvlist().
 
181
parse_cookie(<<>>, Acc) ->
 
182
        lists:reverse(Acc);
 
183
parse_cookie(String, Acc) ->
 
184
        {{Token, Value}, Rest} = read_pair(String),
 
185
        Acc1 = case Token of
 
186
                        <<"">> ->
 
187
                                Acc;
 
188
                        <<"$", _R/binary>> ->
 
189
                                Acc;
 
190
                        _ ->
 
191
                                [{Token, Value} | Acc]
 
192
                end,
 
193
        parse_cookie(Rest, Acc1).
 
194
 
 
195
-spec read_pair(binary()) -> {{binary(), binary()}, binary()}.
 
196
read_pair(String) ->
 
197
        {Token, Rest} = read_token(skip_whitespace(String)),
 
198
        {Value, Rest1} = read_value(skip_whitespace(Rest)),
 
199
        {{Token, Value}, skip_past_separator(Rest1)}.
 
200
 
 
201
-spec read_value(binary()) -> {binary(), binary()}.
 
202
read_value(<<"=",  Value/binary>>) ->
 
203
        Value1 = skip_whitespace(Value),
 
204
        case Value1 of
 
205
                <<?QUOTE, _R/binary>> ->
 
206
                        read_quoted(Value1);
 
207
                _ ->
 
208
                        read_token(Value1)
 
209
        end;
 
210
read_value(String) ->
 
211
        {<<"">>, String}.
 
212
 
 
213
-spec read_quoted(binary()) -> {binary(), binary()}.
 
214
read_quoted(<<?QUOTE, String/binary>>) ->
 
215
        read_quoted(String, <<"">>).
 
216
 
 
217
-spec read_quoted(binary(), binary()) -> {binary(), binary()}.
 
218
read_quoted(<<"">>, Acc) ->
 
219
        {Acc, <<"">>};
 
220
read_quoted(<<?QUOTE, Rest/binary>>, Acc) ->
 
221
        {Acc, Rest};
 
222
read_quoted(<<$\\, Any, Rest/binary>>, Acc) ->
 
223
        read_quoted(Rest, <<Acc/binary, Any>>);
 
224
read_quoted(<<C, Rest/binary>>, Acc) ->
 
225
        read_quoted(Rest, <<Acc/binary, C>>).
 
226
 
 
227
%% @doc Drop characters while a function returns true.
 
228
binary_dropwhile(_F, <<"">>) ->
 
229
        <<"">>;
 
230
binary_dropwhile(F, String) ->
 
231
        <<C, Rest/binary>> = String,
 
232
        case F(C) of
 
233
                true ->
 
234
                        binary_dropwhile(F, Rest);
 
235
                false ->
 
236
                        String
 
237
        end.
 
238
 
 
239
%% @doc Remove leading whitespace.
 
240
-spec skip_whitespace(binary()) -> binary().
 
241
skip_whitespace(String) ->
 
242
        binary_dropwhile(fun is_whitespace/1, String).
 
243
 
 
244
%% @doc Split a binary when the current character causes F to return true.
 
245
binary_splitwith(_F, Head, <<>>) ->
 
246
        {Head, <<>>};
 
247
binary_splitwith(F, Head, Tail) ->
 
248
        <<C, NTail/binary>> = Tail,
 
249
        case F(C) of
 
250
                true ->
 
251
                        {Head, Tail};
 
252
                false ->
 
253
                        binary_splitwith(F, <<Head/binary, C>>, NTail)
 
254
        end.
 
255
 
 
256
%% @doc Split a binary with a function returning true or false on each char.
 
257
binary_splitwith(F, String) ->
 
258
        binary_splitwith(F, <<>>, String).
 
259
 
 
260
%% @doc Split the binary when the next seperator is found.
 
261
-spec read_token(binary()) -> {binary(), binary()}.
 
262
read_token(String) ->
 
263
        binary_splitwith(fun is_separator/1, String).
 
264
 
 
265
%% @doc Return string after ; or , characters.
 
266
-spec skip_past_separator(binary()) -> binary().
 
267
skip_past_separator(<<"">>) ->
 
268
        <<"">>;
 
269
skip_past_separator(<<";", Rest/binary>>) ->
 
270
        Rest;
 
271
skip_past_separator(<<",", Rest/binary>>) ->
 
272
        Rest;
 
273
skip_past_separator(<<_C, Rest/binary>>) ->
 
274
        skip_past_separator(Rest).
 
275
 
 
276
-spec any_to_binary(binary() | string() | atom() | integer()) -> binary().
 
277
any_to_binary(V) when is_binary(V) ->
 
278
        V;
 
279
any_to_binary(V) when is_list(V) ->
 
280
        erlang:list_to_binary(V);
 
281
any_to_binary(V) when is_atom(V) ->
 
282
        erlang:atom_to_binary(V, latin1);
 
283
any_to_binary(V) when is_integer(V) ->
 
284
        list_to_binary(integer_to_list(V)).
 
285
 
 
286
%% Tests.
 
287
 
 
288
-ifdef(TEST).
 
289
 
 
290
quote_test() ->
 
291
        %% ?assertError eunit macro is not compatible with coverage module
 
292
        _ = try quote(<<":wq">>)
 
293
        catch error:{cookie_quoting_required, <<":wq">>} -> ok
 
294
        end,
 
295
        ?assertEqual(<<"foo">>,quote(foo)),
 
296
        ok.
 
297
 
 
298
parse_cookie_test() ->
 
299
        %% RFC example
 
300
        C1 = <<"$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\";
 
301
        Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\";
 
302
        Shipping=\"FedEx\"; $Path=\"/acme\"">>,
 
303
        ?assertEqual(
 
304
        [{<<"Customer">>,<<"WILE_E_COYOTE">>},
 
305
                {<<"Part_Number">>,<<"Rocket_Launcher_0001">>},
 
306
                {<<"Shipping">>,<<"FedEx">>}],
 
307
        parse_cookie(C1)),
 
308
        %% Potential edge cases
 
309
        ?assertEqual(
 
310
        [{<<"foo">>, <<"x">>}],
 
311
        parse_cookie(<<"foo=\"\\x\"">>)),
 
312
        ?assertEqual(
 
313
        [],
 
314
        parse_cookie(<<"=">>)),
 
315
        ?assertEqual(
 
316
        [{<<"foo">>, <<"">>}, {<<"bar">>, <<"">>}],
 
317
        parse_cookie(<<"  foo ; bar  ">>)),
 
318
        ?assertEqual(
 
319
        [{<<"foo">>, <<"">>}, {<<"bar">>, <<"">>}],
 
320
        parse_cookie(<<"foo=;bar=">>)),
 
321
        ?assertEqual(
 
322
        [{<<"foo">>, <<"\";">>}, {<<"bar">>, <<"">>}],
 
323
        parse_cookie(<<"foo = \"\\\";\";bar ">>)),
 
324
        ?assertEqual(
 
325
        [{<<"foo">>, <<"\";bar">>}],
 
326
        parse_cookie(<<"foo=\"\\\";bar">>)),
 
327
        ?assertEqual(
 
328
        [],
 
329
        parse_cookie(<<"">>)),
 
330
        ?assertEqual(
 
331
        [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"wibble">>}],
 
332
        parse_cookie(<<"foo=bar , baz=wibble ">>)),
 
333
        ok.
 
334
 
 
335
domain_test() ->
 
336
        ?assertEqual(
 
337
        {<<"Set-Cookie">>,
 
338
                <<"Customer=WILE_E_COYOTE; "
 
339
                "Version=1; "
 
340
                "Domain=acme.com; "
 
341
                "HttpOnly">>},
 
342
        cookie(<<"Customer">>, <<"WILE_E_COYOTE">>,
 
343
                        [{http_only, true}, {domain, <<"acme.com">>}])),
 
344
        ok.
 
345
 
 
346
local_time_test() ->
 
347
        {<<"Set-Cookie">>, B} = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>,
 
348
                                                        [{max_age, 111}, {secure, true}]),
 
349
 
 
350
        ?assertMatch(
 
351
        [<<"Customer=WILE_E_COYOTE">>,
 
352
                <<" Version=1">>,
 
353
                <<" Expires=", _R/binary>>,
 
354
                <<" Max-Age=111">>,
 
355
                <<" Secure">>],
 
356
        binary:split(B, <<";">>, [global])),
 
357
        ok.
 
358
 
 
359
-spec cookie_test() -> no_return(). %% Not actually true, just a bad option.
 
360
cookie_test() ->
 
361
        C1 = {<<"Set-Cookie">>,
 
362
                <<"Customer=WILE_E_COYOTE; "
 
363
                "Version=1; "
 
364
                "Path=/acme">>},
 
365
        C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]),
 
366
 
 
367
        C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>,
 
368
                                [{path, <<"/acme">>}, {badoption, <<"negatory">>}]),
 
369
 
 
370
        {<<"Set-Cookie">>,<<"=NoKey; Version=1">>}
 
371
                = cookie(<<"">>, <<"NoKey">>, []),
 
372
        {<<"Set-Cookie">>,<<"=NoKey; Version=1">>}
 
373
                = cookie(<<"">>, <<"NoKey">>),
 
374
        LocalTime = calendar:universal_time_to_local_time(
 
375
                {{2007, 5, 15}, {13, 45, 33}}),
 
376
        C2 = {<<"Set-Cookie">>,
 
377
                <<"Customer=WILE_E_COYOTE; "
 
378
                "Version=1; "
 
379
                "Expires=Tue, 15 May 2007 13:45:33 GMT; "
 
380
                "Max-Age=0">>},
 
381
        C2 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>,
 
382
                                [{max_age, -111}, {local_time, LocalTime}]),
 
383
        C3 = {<<"Set-Cookie">>,
 
384
                <<"Customer=WILE_E_COYOTE; "
 
385
                "Version=1; "
 
386
                "Expires=Wed, 16 May 2007 13:45:50 GMT; "
 
387
                "Max-Age=86417">>},
 
388
        C3 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>,
 
389
                                [{max_age, 86417}, {local_time, LocalTime}]),
 
390
        ok.
 
391
 
 
392
-endif.