Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix parse_set_cookie/1 and format_set_cookie/1 functions

1. According to the RFCs 2109 and 2965, multiple cookies can be set in a
single 'Set-Cookie' header. So, yaws_api:parse_set_cookie/1 now returns a
list of #setcookie{} records. If no cookie was found or if an error occurred,
it returns []. The parsing is also improved.
Note that this fix breaks the compatibility with previous versions.

2. In yaws_api:format_set_cookie/1, options are now always formated as
quoted-strings.

3. 2 new functions are added, yaws_api:parse_cookie/1 and
yaws_api:format_cookie/1, to parse and format 'Cookie' headers. To let these
functions to work, the #cookie{} record was introduced.

Documentation and testsuite are updated accordingly.
  • Loading branch information...
commit 3a8e0710c569f7e5c2702ab807fa0a994d221ad3 1 parent be0babd
Christopher Faulet capflam authored
16 include/yaws_api.hrl
View
@@ -90,19 +90,29 @@
-record(setcookie,{
key,
value,
- quoted,
+ quoted = false,
comment,
comment_url,
- discard,
+ discard = false,
domain,
max_age,
expires,
path,
port,
- secure,
+ secure = false,
version}).
+-record(cookie,{
+ key,
+ value,
+ quoted = false,
+ version = "0",
+ domain,
+ path,
+ port}).
+
+
-record(redir_self, {
host, % string() - our own host
scheme, % http | https
64 man/yaws_api.5
View
@@ -131,16 +131,76 @@ Sets a cookie to the browser.
\fBfind_cookie_val(Cookie, Header)\fR
This function can be used to search for a cookie that was previously
set by \fBsetcookie/2-6\fR. For example if we set a cookie
-as \fByaws_api:setcookie("sid",SomeRandomSid) \fR, then on subsequent requests
+as \fByaws_api:setcookie("sid",SomeRandomSid)\fR, then on subsequent requests
from the browser we can call:
\fBfind_cookie("sid",(Arg#arg.headers)#headers.cookie)\fR
The function returns [] if no cookie was found, otherwise the actual cookie
is returned as a string.
+.TP
+\fBparse_set_cookie(Str)\fR
+This function parses the value of a \fBSet-Cookie\fR header. Because multiple
+cookies can be set in a single \fBSet-Cookie\fR header, this function returns a
+list of \fI#setcookie{}\fR records. If no cookie was found or if an error
+occurred, it returns [].
+
+\fI#setcookie{}\fR record is defined in \fIyaws_api.hrl\fR:
+\fI
+.nf
+
+-record(setcookie, {
+ key,
+ value,
+ quoted = false,
+ comment,
+ comment_url,
+ discard = false,
+ domain,
+ max_age,
+ expires,
+ path,
+ port,
+ secure = false,
+ version
+ }).
+.fi
+\fR
+
+.TP
+\fBparse_cookie(Str)\fR
+This function does the same thing than \fBparse_set_cookie/1\fR but for the
+value of a \fBCookie\fR header. It returns a list of \fI#cookie{}\fR records. If
+no cookie was found or if an error occurred, it returns [].
+
+\fI#cookie{}\fR record is defined in \fIyaws_api.hrl\fR:
+\fI
+.nf
+
+-record(cookie, {
+ key,
+ value,
+ quoted = false,
+ version = "0",
+ domain,
+ path,
+ port}).
+ }).
+.fi
+\fR
+
+.TP
+\fBformat_set_cookie(Str)\fR
+Build a cookie string from a \fI#setcookie{}\fR record like returned by
+\fBparse_set_cookie/1\fR.
+
+.TP
+\fBformat_cookie(Str)\fR
+Build a cookie string from a \fI#cookie{}\fR record like returned by
+\fBparse_cookie/1\fR.
.TP
-\fBredirect(Url\fR
+\fBredirect(Url)\fR
This function generates a redirect to the browser.
It will clear any previously set headers. So to generate
a redirect \fBand\fR set a cookie, we need to set the cookie after
446 src/yaws_api.erl
View
@@ -63,8 +63,8 @@
-export([ehtml_expand/1, ehtml_expander/1, ehtml_apply/2,
ehtml_expander_test/0]).
--export([parse_set_cookie/1, format_set_cookie/1,
- postvar/2, queryvar/2, getvar/2]).
+-export([parse_set_cookie/1, parse_cookie/1, format_set_cookie/1,
+ format_cookie/1, postvar/2, queryvar/2, getvar/2]).
-export([binding/1,binding_exists/1,
dir_listing/1, dir_listing/2, redirect_self/1]).
@@ -705,56 +705,26 @@ setcookie(Name, Value, Path, Expire, Domain, Secure) ->
%% Str if found
%% if several cookies with the same name are passed fron the browser,
%% only the first match is returned
-
-find_cookie_val(Cookie, A) when is_record(A, arg) ->
- find_cookie_val(Cookie, (A#arg.headers)#headers.cookie);
-%%
-find_cookie_val(_Cookie, []) ->
+find_cookie_val(Name, #arg{}=A) ->
+ find_cookie_val(Name, (A#arg.headers)#headers.cookie);
+find_cookie_val(_, []) ->
[];
-find_cookie_val(Cookie, [FullCookie | FullCookieList]) ->
- case eat_cookie(Cookie, FullCookie) of
- [] ->
- find_cookie_val(Cookie, FullCookieList);
- Val ->
- Val
- end.
-
-%% Remove leading spaces before eating.
-eat_cookie([], _) -> [];
-eat_cookie([$\s|T], Str) -> eat_cookie(T, Str);
-eat_cookie(_, []) -> [];
-eat_cookie(Cookie, [$\s|T]) -> eat_cookie(Cookie, T);
-eat_cookie(Cookie, Str) when is_list(Cookie),is_list(Str) ->
- try
- eat_cookie2(Cookie++"=", Str, Cookie)
- catch
- _:_ -> []
+find_cookie_val(Name, Cookies) ->
+ find_cookie_val2(yaws:to_lower(Name), Cookies).
+
+find_cookie_val2(Name, [Cookie|Rest]) ->
+ case parse_cookie(Cookie) of
+ error ->
+ find_cookie_val(Name, Rest);
+ L ->
+ case lists:keyfind(Name, #cookie.key, L) of
+ #cookie{value=undefined} -> [];
+ #cookie{value=Value} -> Value;
+ false -> find_cookie_val(Name, Rest)
+ end
end.
-%% Look for the Cookie and extract its value.
-eat_cookie2(_, [], _) ->
- throw("not found");
-eat_cookie2([H1|T], [H2|R], C) ->
- case string:to_lower(H1) =:= string:to_lower(H2) of
- true ->
- eat_cookie2(T, R, C);
- false ->
- {_,Rest} = eat_until(R, $;),
- eat_cookie(C, Rest)
- end;
-eat_cookie2([], L, _) ->
- {Meat,_} = eat_until(L, $;),
- Meat.
-
-eat_until(L, U) ->
- eat_until(L, U, []).
-
-eat_until([H|T], H, Acc) -> {lists:reverse(Acc), T};
-eat_until([H|T], U, Acc) when H =/= U -> eat_until(T, U, [H|Acc]);
-eat_until([], _, Acc) -> {lists:reverse(Acc), []}.
-
-
-
+%%
url_decode([$%, Hi, Lo | Tail]) ->
Hex = yaws:hex_to_integer([Hi, Lo]),
[Hex | url_decode(Tail)];
@@ -1994,148 +1964,312 @@ deepmember(C,[N|Cs]) when C /= N ->
deepmember(C, Cs).
-%% Parse a Set-Cookie header.
+%% Parse a Set-Cookie(2)/Cookie header.
%%
%% RFC (2109) ports are from RFC 2965
%%
-%% "Cookie:" cookie-version 1*((";" | ",") cookie-value)
-%% "Set-Cookie:" cookies
-%% "Set-Cookie2:" cookies
-%% cookie-value = NAME "=" VALUE [";" path] [";" domain] [";" port]
-%% cookie = NAME "=" VALUE *( ";" cookie-av )
-%% cookie-version = "$Version" "=" value
-%% NAME = attr
-%% VALUE = value
-%% path = "$Path" "=" value
-%% domain = "$Domain" "=" value
-%% port = "$Port" "=" <"> value <">
+%% "Set-Cookie:" cookies
+%% "Set-Cookie2:" cookies
+%% cookies = 1#cookie
+%% cookie = NAME "=" VALUE *(";" set-cookie-av)
+%% NAME = attr
+%% VALUE = value
+%% set-cookie-av = "Comment" "=" value
+%% | "CommentURL" "=" <"> http_URL <">
+%% | "Discard"
+%% | "Domain" "=" value
+%% | "Max-Age" "=" value
+%% | "Path" "=" value
+%% | "Port" [ "=" <"> portlist <"> ]
+%% | "Secure"
+%% | "Version" "=" 1*DIGIT
%%
-%% cookie-av = "Comment" "=" value
-%% | "CommentURL" "=" <"> http_URL <">
-%% | "Discard"
-%% | "Domain" "=" value
-%% | "Max-Age" "=" value
-%% | "Path" "=" value
-%% | "Port" [ "=" <"> portlist <"> ]
-%% | "Secure"
-%% | "Version" "=" 1*DIGIT
%%
-
+%% "Cookie:" cookie-version 1*((";" | ",") cookie-value)
+%% cookie-value = NAME "=" VALUE [";" path] [";" domain] [";" port]
+%% cookie-version = "$Version" "=" value
+%% NAME = attr
+%% VALUE = value
+%% path = "$Path" "=" value
+%% domain = "$Domain" "=" value
+%% port = "$Port" [ "=" <"> value <"> ]
+%%
parse_set_cookie(Str) ->
- parse_set_cookie(Str, #setcookie{}).
-
-parse_set_cookie([], Cookie) ->
- Cookie;
-parse_set_cookie(Str, Cookie) ->
- Rest00 = skip_space(Str),
- {Key,Rest0} = parse_set_cookie_key(Rest00, []),
- Rest1 = skip_space(Rest0),
- case Rest1 of
- [$=|Rest2] ->
- {Value,Quoted,Rest3} = parse_set_cookie_value(Rest2),
- NewC=add_set_cookie(Cookie,yaws:to_lower(Key),Value,Quoted),
- parse_set_cookie(Rest3,NewC);
- [$;|Rest2] ->
- NewC =add_set_cookie(Cookie,yaws:to_lower(Key),undefined,false),
- parse_set_cookie(Rest2,NewC);
+ parse_set_cookie(Str, []).
+
+parse_set_cookie([], SetCookies) ->
+ lists:reverse(SetCookies);
+parse_set_cookie(Str, SetCookies) ->
+ case do_parse_set_cookie(Str) of
+ {C, Rest} -> parse_set_cookie(Rest, [C|SetCookies]);
+ error -> []
+ end.
+
+do_parse_set_cookie(Str) ->
+ {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
+ case yaws:to_lower(Key) of
+ [] ->
+ error;
+ K ->
+ Cookie0 = #setcookie{key=K, quoted=false},
+ case skip_space(Rest0) of
+ [$=|Rest1] ->
+ {V, Q, Rest2} = parse_cookie_token(skip_space(Rest1)),
+ Cookie1 = Cookie0#setcookie{value=V, quoted=Q},
+ case skip_space(Rest2) of
+ [$;|Rest3] -> parse_set_cookie_options(Rest3, Cookie1);
+ [$,|Rest3] -> {Cookie1, skip_space(Rest3)};
+ [] -> {Cookie1, []};
+ _ -> error
+ end;
+
+ [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
+ [$,|Rest1] -> {Cookie0, skip_space(Rest1)};
+ [] -> {Cookie0, []};
+ _ -> error
+ end
+ end.
+
+parse_set_cookie_options(Str, Cookie0) ->
+ {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
+ case yaws:to_lower(Key) of
+ K when K == "secure"; K == "discard" ->
+ Cookie1 = add_cookie_opt(Cookie0, K, true),
+ case skip_space(Rest0) of
+ [$=|_] -> error;
+ [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie1);
+ [$,|Rest1] -> {Cookie1, skip_space(Rest1)};
+ [] -> {Cookie1, []};
+ _ -> error
+ end;
+ K ->
+ case skip_space(Rest0) of
+ [$=|Rest1] ->
+ {Value, _, Rest2} = parse_cookie_token(skip_space(Rest1)),
+ Cookie1 = add_cookie_opt(Cookie0, K, Value),
+ parse_set_cookie_options(Rest2, Cookie1);
+
+ [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
+ [$,|Rest1] -> {Cookie0, skip_space(Rest1)};
+ [] -> {Cookie0, []};
+ _ -> error
+ end
+ end.
+
+%%
+parse_cookie(Str) ->
+ case parse_cookie_version(Str) of
+ {Vers, Rest} -> parse_cookie(Rest, Vers, []);
+ error -> []
+ end.
+
+parse_cookie([], _, Cookies) ->
+ lists:reverse(Cookies);
+parse_cookie(Str, Vers, Cookies) ->
+ case do_parse_cookie(Str) of
+ {C, Rest} -> parse_cookie(Rest, Vers, [C#cookie{version=Vers}|Cookies]);
+ error -> []
+ end.
+
+parse_cookie_version(Str) ->
+ {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
+ case yaws:to_lower(Key) of
+ [] ->
+ error;
+ "$version" ->
+ case skip_space(Rest0) of
+ [$=|Rest1] ->
+ {V, _, Rest2} = parse_cookie_token(skip_space(Rest1)),
+ case skip_space(Rest2) of
+ [$;|Rest3] -> {V, Rest3};
+ [$,|Rest3] -> {V, Rest3};
+ _ -> error
+ end;
+ _ ->
+ error
+ end;
+ _ ->
+ {"0", Str}
+ end.
+
+do_parse_cookie(Str) ->
+ {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
+ case yaws:to_lower(Key) of
+ [] ->
+ error;
+ K ->
+ Cookie0 = #cookie{key=K, quoted=false},
+ case skip_space(Rest0) of
+ [$=|Rest1] ->
+ {V, Q, Rest2} = parse_cookie_token(skip_space(Rest1)),
+ Cookie1 = Cookie0#cookie{value=V, quoted=Q},
+ case skip_space(Rest2) of
+ [$;|Rest3] -> parse_cookie_options(Rest3, Cookie1);
+ [$,|Rest3] -> parse_cookie_options(Rest3, Cookie1);
+ [] -> {Cookie1, []};
+ _ -> error
+ end;
+
+ [$;|Rest1] -> parse_cookie_options(Rest1, Cookie0);
+ [$,|Rest1] -> parse_cookie_options(Rest1, Cookie0);
+ [] -> {Cookie0, []};
+ _ -> error
+ end
+ end.
+
+parse_cookie_options(Str, Cookie0) ->
+ {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
+ case skip_space(Rest0) of
+ [$=|Rest1] ->
+ {Value, _, Rest2} = parse_cookie_token(skip_space(Rest1)),
+ case add_cookie_opt(Cookie0, yaws:to_lower(Key), Value) of
+ undefined ->
+ {Cookie0, Str};
+ Cookie1 ->
+ case skip_space(Rest2) of
+ [$;|Rest3] -> parse_cookie_options(Rest3, Cookie1);
+ [$,|Rest3] -> parse_cookie_options(Rest3, Cookie1);
+ [] -> {Cookie1, []};
+ _ -> error
+ end
+ end;
_ ->
- Cookie
+ {Cookie0, Str}
end.
+
%%
+add_cookie_opt(C=#setcookie{}, "comment", V) -> C#setcookie{comment=V};
+add_cookie_opt(C=#setcookie{}, "commenturl", V) -> C#setcookie{comment_url=V};
+add_cookie_opt(C=#setcookie{}, "discard", V) -> C#setcookie{discard=V};
+add_cookie_opt(C=#setcookie{}, "domain", V) -> C#setcookie{domain=V};
+add_cookie_opt(C=#setcookie{}, "max-age", V) -> C#setcookie{max_age=V};
+add_cookie_opt(C=#setcookie{}, "expires", V) -> C#setcookie{expires=V};
+add_cookie_opt(C=#setcookie{}, "path", V) -> C#setcookie{path=V};
+add_cookie_opt(C=#setcookie{}, "port", V) -> C#setcookie{port=V};
+add_cookie_opt(C=#setcookie{}, "secure", V) -> C#setcookie{secure=V};
+add_cookie_opt(C=#setcookie{}, "version", V) -> C#setcookie{version=V};
+add_cookie_opt(C=#setcookie{}, _, _) -> C;
+
+add_cookie_opt(C=#cookie{}, "$domain", V) -> C#cookie{domain=V};
+add_cookie_opt(C=#cookie{}, "$path", V) -> C#cookie{path=V};
+add_cookie_opt(C=#cookie{}, "$port", V) -> C#cookie{port=V};
+add_cookie_opt(#cookie{}, _, _) -> undefined.
+
-parse_set_cookie_key([], Acc) ->
+%%
+parse_cookie_key([], Acc) ->
{lists:reverse(Acc), []};
-parse_set_cookie_key(T=[$=|_], Acc) ->
+parse_cookie_key(T=[$=|_], Acc) ->
+ {lists:reverse(Acc), T};
+parse_cookie_key(T=[$;|_], Acc) ->
{lists:reverse(Acc), T};
-parse_set_cookie_key(T=[$;|_], Acc) ->
+parse_cookie_key(T=[$,|_], Acc) ->
{lists:reverse(Acc), T};
-parse_set_cookie_key([C|T], Acc) ->
- parse_set_cookie_key(T, [C|Acc]).
+parse_cookie_key([C|T], Acc) when C > 32 andalso C < 127 andalso
+ C /= $( andalso C /= $) andalso
+ C /= $< andalso C /= $> andalso
+ C /= $[ andalso C /= $] andalso
+ C /= $@ andalso C /= $, andalso
+ C /= $; andalso C /= $: andalso
+ C /= $/ andalso C /= $\\ andalso
+ C /= ${ andalso C /= $} andalso
+ C /= $= andalso C /= $" andalso
+ C /= $? ->
+ parse_cookie_key(T, [C|Acc]);
+parse_cookie_key(T, Acc) ->
+ {lists:reverse(Acc), T}.
-%%
-parse_set_cookie_value([$"|T]) ->
- parse_quoted(T,[]);
-parse_set_cookie_value(T) ->
- parse_set_cookie_value(T,[]).
+%%
+parse_cookie_token([$"|T]) ->
+ parse_cookie_quoted(T,[]);
+parse_cookie_token(T) ->
+ parse_cookie_token(T,[]).
-parse_set_cookie_value([],Acc) ->
+parse_cookie_token([],Acc) ->
{lists:reverse(Acc), false, []};
-parse_set_cookie_value(T=[$;|_], Acc) ->
- {lists:reverse(Acc), false, T};
-parse_set_cookie_value([C|T], Acc) ->
- parse_set_cookie_value(T, [C|Acc]).
-
-parse_quoted([], Acc) ->
+parse_cookie_token([C|T], Acc) when C > 32 andalso C < 127 andalso
+ C /= $( andalso C /= $) andalso
+ C /= $< andalso C /= $> andalso
+ C /= $[ andalso C /= $] andalso
+ C /= $@ andalso C /= $, andalso
+ C /= $; andalso C /= $: andalso
+ C /= ${ andalso C /= $} andalso
+ C /= $= andalso C /= $" andalso
+ C /= $? andalso C /= $\\ ->
+ parse_cookie_token(T, [C|Acc]);
+parse_cookie_token(T, Acc) ->
+ {lists:reverse(Acc), false, T}.
+
+
+parse_cookie_quoted([], Acc) ->
{lists:reverse(Acc), true, []};
-parse_quoted([$"|T], Acc) ->
+parse_cookie_quoted([$"|T], Acc) ->
{lists:reverse(Acc), true, T};
-parse_quoted([$\\,C|T], Acc) ->
- parse_quoted(T,[C,$\\|Acc]);
-parse_quoted([C|T], Acc) ->
- parse_quoted(T,[C|Acc]).
-%%
+parse_cookie_quoted([$\\,C|T], Acc) ->
+ parse_cookie_quoted(T,[C,$\\|Acc]);
+parse_cookie_quoted([C|T], Acc) when C > 31 andalso C < 127 ->
+ parse_cookie_quoted(T,[C|Acc]);
+parse_cookie_quoted([$\r,$\n,C|T], Acc) when C == 32 orelse C == 9 ->
+ parse_cookie_quoted(T,[C,$\n,$\r|Acc]);
+parse_cookie_quoted(T, Acc) ->
+ {lists:reverse(Acc), true, T}.
-add_set_cookie(C, Key, Value, Quoted) when C#setcookie.key==undefined ->
- C#setcookie{key=Key,value=Value,quoted=Quoted};
-add_set_cookie(C, "comment", Value, _Quoted) ->
- C#setcookie{comment=Value};
-add_set_cookie(C, "commenturl", Value, _Quoted) ->
- C#setcookie{comment_url=Value};
-add_set_cookie(C, "discard", Value, _Quoted) ->
- C#setcookie{discard=Value};
-add_set_cookie(C, "domain", Value, _Quoted) ->
- C#setcookie{domain=Value};
-add_set_cookie(C, "max-age", Value, _Quoted) ->
- C#setcookie{max_age=Value};
-add_set_cookie(C, "path", Value, _Quoted) ->
- C#setcookie{path=Value};
-add_set_cookie(C, "port", Value, _Quoted) ->
- C#setcookie{port=Value};
-add_set_cookie(C, "secure", Value, _Quoted) ->
- C#setcookie{secure=Value};
-add_set_cookie(C, "version", Value, _Quoted) ->
- C#setcookie{version=Value};
-add_set_cookie(C, _Key, _Value, _Quoted) ->
- C.
%%
-
format_set_cookie(C) when C#setcookie.value == undefined ->
- [C#setcookie.key|format_set_cookie_opts(C)];
+ [C#setcookie.key|format_cookie_opts(C)];
format_set_cookie(C) when C#setcookie.quoted ->
- [C#setcookie.key,$=,$",C#setcookie.value,$"|
- format_set_cookie_opts(C)];
+ [C#setcookie.key,$=,$",C#setcookie.value,$"|format_cookie_opts(C)];
format_set_cookie(C) ->
- [C#setcookie.key,$=,C#setcookie.value|
- format_set_cookie_opts(C)].
-
-add_opt(_Key,undefined) -> [];
-add_opt(Key,Opt) -> [$;,Key,$=,Opt].
-
-format_set_cookie_opts(C) ->
- [add_opt("Path",C#setcookie.path),
- add_opt("Port",C#setcookie.port),
- add_opt("Domain",C#setcookie.domain),
- add_opt("Secure",C#setcookie.secure),
- add_opt("Expires",C#setcookie.expires),
- add_opt("Max-Age",C#setcookie.max_age),
- add_opt("Discard",C#setcookie.discard),
- add_opt("Comment",C#setcookie.comment),
- add_opt("CommentURL",C#setcookie.comment_url),
- add_opt("version",C#setcookie.version)].
+ [C#setcookie.key,$=,C#setcookie.value|format_cookie_opts(C)].
+
+%%
+format_cookie(C) when C#cookie.value == undefined ->
+ ["$Version",$=,C#cookie.version,$;,$\ ,
+ C#cookie.key|format_cookie_opts(C)];
+format_cookie(C) when C#cookie.quoted ->
+ ["$Version",$=,C#cookie.version,$;,$\ ,
+ C#cookie.key,$=,$",C#cookie.value,$"|format_cookie_opts(C)];
+format_cookie(C) ->
+ ["$Version",$=,C#cookie.version,$;,$\ ,
+ C#cookie.key,$=,C#cookie.value|format_cookie_opts(C)].
%%
+format_cookie_opts(C=#setcookie{}) ->
+ [add_opt("Path", C#setcookie.path),
+ add_opt("Port", C#setcookie.port),
+ add_opt("Domain", C#setcookie.domain),
+ add_opt("Secure", C#setcookie.secure),
+ add_opt("Expires", C#setcookie.expires),
+ add_opt("Max-Age", C#setcookie.max_age),
+ add_opt("Discard", C#setcookie.discard),
+ add_opt("Comment", C#setcookie.comment),
+ add_opt("CommentURL", C#setcookie.comment_url),
+ add_opt("Version", C#setcookie.version)];
+
+format_cookie_opts(C=#cookie{}) ->
+ [add_opt("$Path", C#cookie.path),
+ add_opt("$Domain", C#cookie.domain),
+ add_opt("$Port", C#cookie.port)].
+
+
+add_opt(_, undefined) -> [];
+add_opt(_, false) -> [];
+add_opt(Key, true) -> [$;,$\ ,Key];
+add_opt(Key, Opt) -> [$;,$\ ,Key,$=,$",Opt,$"].
+
+%%
skip_space([]) -> [];
skip_space([$ |T]) -> skip_space(T);
skip_space([$\t|T]) -> skip_space(T);
+skip_space([$\r,$\n|T]) -> skip_space(T);
skip_space(T) -> T.
-%%
-
+%%
getvar(ARG,Key) when is_atom(Key) ->
getvar(ARG, atom_to_list(Key));
getvar(ARG,Key) ->
271 test/eunit/cookies.erl
View
@@ -3,6 +3,277 @@
-include("../../include/yaws_api.hrl").
-include_lib("eunit/include/eunit.hrl").
+parse_set_cookies_test() ->
+ %% Set-Cookie: value
+ ?assertEqual(
+ [#setcookie{key="value", quoted=false}],
+ yaws_api:parse_set_cookie("value")
+ ),
+
+ %% Set-Cookie: name=value
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false}],
+ yaws_api:parse_set_cookie("name=value")
+ ),
+
+ %% Set-Cookie: name="value [quoted]"
+ ?assertEqual(
+ [#setcookie{key="name", value="value [quoted]", quoted=true}],
+ yaws_api:parse_set_cookie("name=\"value [quoted]\"")
+ ),
+
+ %% Set-Cookie: name=value; path=/
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, path="/"}],
+ yaws_api:parse_set_cookie("name=value; path=/")
+ ),
+
+ %% Set-Cookie: name=value; domain=test.com
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, domain="test.com"}],
+ yaws_api:parse_set_cookie("name=value; domain=test.com")
+ ),
+
+ %% Set-Cookie: name=value; comment="This is a comment"
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, comment="This is a comment"}],
+ yaws_api:parse_set_cookie("name=value; comment=\"This is a comment\"")
+ ),
+
+ %% Set-Cookie: name=value; comment_url="http://localhost"
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, comment_url="http://localhost"}],
+ yaws_api:parse_set_cookie("name=value; commentURL=\"http://localhost\"")
+ ),
+
+ %% Set-Cookie: name=value; discard
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, discard=true}],
+ yaws_api:parse_set_cookie("name=value; discard")
+ ),
+
+ %% Set-Cookie: name=value; secure
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, secure=true}],
+ yaws_api:parse_set_cookie("name=value; secure")
+ ),
+
+ %% Set-Cookie: name=value; max-age=86400
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, max_age="86400"}],
+ yaws_api:parse_set_cookie("name=value; max-age=86400")
+ ),
+
+ %% Set-Cookie: name=value; expires="Sat, 02 May 2009 23:38:25 GMT"
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, expires="Sat, 02 May 2009 23:38:25 GMT"}],
+ yaws_api:parse_set_cookie("name=value; expires=\"Sat, 02 May 2009 23:38:25 GMT\"")
+ ),
+
+ %% Set-Cookie: name=value; port=443
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, port="443"}],
+ yaws_api:parse_set_cookie("name=value; port=443")
+ ),
+
+ %% Set-Cookie: name=value; version=2
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false, version="2"}],
+ yaws_api:parse_set_cookie("name=value; version=2")
+ ),
+
+ %% Set-Cookie: name=value; unkown-opt=skipped
+ ?assertEqual(
+ [#setcookie{key="name", value="value", quoted=false}],
+ yaws_api:parse_set_cookie("name=value; unkown-opt=skipped")
+ ),
+
+ %% Set-Cookie: value1, value2, name=value
+ ?assertEqual(
+ [#setcookie{key="value1", quoted=false},
+ #setcookie{key="value2", quoted=false},
+ #setcookie{key="name", value="value", quoted=false}],
+ yaws_api:parse_set_cookie("value1 , value2, name=value")
+ ),
+
+ %% Set-Cookie: value1; comment="c1", value2; comment="c2", name=value
+ ?assertEqual(
+ [#setcookie{key="value1", quoted=false, comment="c1"},
+ #setcookie{key="value2", quoted=false, comment="c2"},
+ #setcookie{key="name", value="value", quoted=false}],
+ yaws_api:parse_set_cookie("value1; comment=\"c1\", value2; comment=\"c2\", name=value")
+ ),
+ ok.
+
+parse_cookies_test() ->
+ %% Cookie: value
+ ?assertEqual(
+ [#cookie{key="value", quoted=false}],
+ yaws_api:parse_cookie("value")
+ ),
+
+ %% Cookie: name=value
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false}],
+ yaws_api:parse_cookie("name=value")
+ ),
+
+ %% Cookie: name="value [quoted]"
+ ?assertEqual(
+ [#cookie{key="name", value="value [quoted]", quoted=true}],
+ yaws_api:parse_cookie("name=\"value [quoted]\"")
+ ),
+
+ %% Cookie: name=value; $path=/
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false, path="/"}],
+ yaws_api:parse_cookie("name=value; $path=/")
+ ),
+
+ %% Cookie: name=value; $port=443
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false, port="443"}],
+ yaws_api:parse_cookie("name=value; $port=443")
+ ),
+
+ %% Cookie: name=value; $domain=test.com
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false, domain="test.com"}],
+ yaws_api:parse_cookie("name=value; $domain=test.com")
+ ),
+
+ %% Cookie: name=value, $path=/
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false, path="/"}],
+ yaws_api:parse_cookie("name=value, $path=/")
+ ),
+
+ %% Cookie: $version=1, name=value; $path=/
+ ?assertEqual(
+ [#cookie{key="name", value="value", quoted=false, version="1", path="/"}],
+ yaws_api:parse_cookie("$version=1, name=value; $path=/")
+ ),
+
+ %% Cookie: $version=1, value1, value2; name=value
+ ?assertEqual(
+ [#cookie{key="value1", quoted=false, version="1"},
+ #cookie{key="value2", quoted=false, version="1"},
+ #cookie{key="name", value="value", quoted=false, version="1"}],
+ yaws_api:parse_cookie("$version=1, value1, value2; name=value")
+ ),
+ ok.
+
+parse_invalid_set_cookies_test() ->
+ ?assertEqual([], yaws_api:parse_set_cookie("name=value [quoted]")),
+ ?assertEqual([], yaws_api:parse_set_cookie("name1=value1 name2=value2")),
+ ?assertEqual([], yaws_api:parse_set_cookie("name1=value1 domain=test.com path=/")),
+ ?assertEqual([], yaws_api:parse_set_cookie("name=value; secure=1")),
+ ?assertEqual([], yaws_api:parse_set_cookie("name=value; discard=1")),
+ ok.
+
+parse_invalid_cookies_test() ->
+ ?assertEqual([], yaws_api:parse_cookie("name=value [quoted]")),
+ ?assertEqual([], yaws_api:parse_cookie("name1=value1 name2=value2")),
+ ?assertEqual([], yaws_api:parse_cookie("$version")),
+ ?assertEqual([], yaws_api:parse_cookie("$version; name=value")),
+ ok.
+
+format_set_cookies_test() ->
+
+ %% Set-Cookie: value
+ ?assertEqual(
+ "value",
+ lists:flatten(
+ yaws_api:format_set_cookie(
+ #setcookie{key="value", quoted=false}
+ )
+ )
+ ),
+
+ %% Set-Cookie: name=value
+ ?assertEqual(
+ "name=value",
+ lists:flatten(
+ yaws_api:format_set_cookie(
+ #setcookie{key="name", value="value", quoted=false}
+ )
+ )
+ ),
+
+ %% Set-Cookie: name="value [quoted]"
+ ?assertEqual(
+ "name=\"value [quoted]\"",
+ lists:flatten(
+ yaws_api:format_set_cookie(
+ #setcookie{key="name", value="value [quoted]", quoted=true}
+ )
+ )
+ ),
+
+ %% Set-Cookie: name=value; Path="/"; Port="443"; Domain="test.com";
+ %% Secure; Expires="Sat, 02 May 2009 23:38:25 GMT";
+ %% Max-Age="86400"; Discard; Comment="This is a comment";
+ %% CommentURL="http://localhost"; Version="1"
+ ?assertEqual(
+ "name=value; Path=\"/\"; Port=\"443\"; Domain=\"test.com\"; "
+ "Secure; Expires=\"Sat, 02 May 2009 23:38:25 GMT\"; Max-Age=\"86400\"; "
+ "Discard; Comment=\"This is a comment\"; CommentURL=\"http://localhost\"; "
+ "Version=\"1\"",
+ lists:flatten(
+ yaws_api:format_set_cookie(
+ #setcookie{key="name", value="value", quoted=false, comment="This is a comment",
+ comment_url="http://localhost", discard=true, domain="test.com",
+ max_age="86400", expires="Sat, 02 May 2009 23:38:25 GMT",
+ path="/", port="443", secure=true, version="1"}
+ )
+ )
+ ),
+ ok.
+
+format_cookies_test() ->
+ %% Cookie: $Version=0; value
+ ?assertEqual(
+ "$Version=0; value",
+ lists:flatten(
+ yaws_api:format_cookie(
+ #cookie{key="value", quoted=false}
+ )
+ )
+ ),
+
+ %% Cookie: $Version=0; name=value
+ ?assertEqual(
+ "$Version=0; name=value",
+ lists:flatten(
+ yaws_api:format_cookie(
+ #cookie{key="name", value="value", quoted=false}
+ )
+ )
+ ),
+
+ %% Cookie: $Version=0; name="value [quoted]"
+ ?assertEqual(
+ "$Version=0; name=\"value [quoted]\"",
+ lists:flatten(
+ yaws_api:format_cookie(
+ #cookie{key="name", value="value [quoted]", quoted=true}
+ )
+ )
+ ),
+
+ %% Cookie: $Version=1; name=value; $Path="/"; $Domain="test.com"; $Port="443"
+ ?assertEqual(
+ "$Version=0; name=value; $Path=\"/\"; $Domain=\"test.com\"; $Port=\"443\"",
+ lists:flatten(
+ yaws_api:format_cookie(
+ #cookie{key="name", value="value", quoted=false, path="/",
+ domain="test.com", port="443"}
+ )
+ )
+ ),
+ ok.
+
+
get_multiple_cookies_test() ->
?assertEqual("1234", yaws_api:find_cookie_val("abc", ["abc=1234;def=5678"])),
?assertEqual("5678", yaws_api:find_cookie_val("def", ["abc=1234;def=5678"])),

3 comments on commit 3a8e071

yushli

It seems commits like that change a lot of code. How can we ensure the quality of such large changes? I know there is unit test added. But maybe a more thorough test and code review would be necessary to guard any performance degradation and compatibility break before it is commited into main branch.
Also a suggestion: yaws_api:find_cookie_val accepts either Arg or a list as the second argument. But we can change them into two functions so that we don't need to get to pattern matching the first one during each tail recursion for a list. like adding a function yaws_api:find_cookie_val_from_list(Name,List).

Christopher Faulet
Collaborator

First, about yaws_api:find_cookie_val/2, keeping API unchanged has advantage to not break backward compatibility. But, you're right, it's useless and inefficient to recurse on this function. find_cookie_val2 is a better choice for that. So, I will fix that quickly (I also found a small inconsistency in this function...).

Then, about this commit, changes are not so huge. The only thing that really changed is the function yaws_api:parse_set_cookie/1 that was buggy (according to the rfc2109 and rfc2965). yaws_api:find_cookie_val/2 will be slower and can be certainly improved. But, because the cookie parsing was improved, this function is less buggy (I hope so :). I've thought that changes was small enough to be merged in the master.

Steve and I should work again on this part to deal with rfc6265. So, discussions are always opened and all feedback are welcomed. Thanks for your comments.

yushli

Thanks for the quick response. That clears the things up.

I also opened another issue about the commit that adds an extra parameter IPPort to 'GET','POST' functions etc. Would you like to look into it as well? Thank you

Please sign in to comment.
Something went wrong with that request. Please try again.