diff options
Diffstat (limited to 'src/couch_inets/http_cookie.erl')
-rw-r--r-- | src/couch_inets/http_cookie.erl | 389 |
1 files changed, 0 insertions, 389 deletions
diff --git a/src/couch_inets/http_cookie.erl b/src/couch_inets/http_cookie.erl deleted file mode 100644 index a8e68651..00000000 --- a/src/couch_inets/http_cookie.erl +++ /dev/null @@ -1,389 +0,0 @@ -%% ``The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved via the world wide web at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. -%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings -%% AB. All Rights Reserved.'' -%% -%% $Id$ -%% Description: Cookie handling according to RFC 2109 - --module(http_cookie). - --include("httpc_internal.hrl"). - --export([header/4, cookies/3, open_cookie_db/1, close_cookie_db/1, insert/2]). - -%%%========================================================================= -%%% API -%%%========================================================================= -header(Scheme, {Host, _}, Path, CookieDb) -> - case lookup_cookies(Host, Path, CookieDb) of - [] -> - {"cookie", ""}; - Cookies -> - {"cookie", cookies_to_string(Scheme, Cookies)} - end. - -cookies(Headers, RequestPath, RequestHost) -> - Cookies = parse_set_cookies(Headers, {RequestPath, RequestHost}), - accept_cookies(Cookies, RequestPath, RequestHost). - -open_cookie_db({{_, only_session_cookies}, SessionDbName}) -> - EtsDb = ets:new(SessionDbName, [protected, bag, - {keypos, #http_cookie.domain}]), - {undefined, EtsDb}; - -open_cookie_db({{DbName, Dbdir}, SessionDbName}) -> - File = filename:join(Dbdir, atom_to_list(DbName)), - {ok, DetsDb} = dets:open_file(DbName, [{keypos, #http_cookie.domain}, - {type, bag}, - {file, File}, - {ram_file, true}]), - EtsDb = ets:new(SessionDbName, [protected, bag, - {keypos, #http_cookie.domain}]), - {DetsDb, EtsDb}. - -close_cookie_db({undefined, EtsDb}) -> - ets:delete(EtsDb); - -close_cookie_db({DetsDb, EtsDb}) -> - dets:close(DetsDb), - ets:delete(EtsDb). - -%% If no persistent cookie database is defined we -%% treat all cookies as if they where session cookies. -insert(Cookie = #http_cookie{max_age = Int}, - Dbs = {undefined, _}) when is_integer(Int) -> - insert(Cookie#http_cookie{max_age = session}, Dbs); - -insert(Cookie = #http_cookie{domain = Key, name = Name, - path = Path, max_age = session}, - Db = {_, CookieDb}) -> - case ets:match_object(CookieDb, #http_cookie{domain = Key, - name = Name, - path = Path, - _ = '_'}) of - [] -> - ets:insert(CookieDb, Cookie); - [NewCookie] -> - delete(NewCookie, Db), - ets:insert(CookieDb, Cookie) - end, - ok; -insert(#http_cookie{domain = Key, name = Name, - path = Path, max_age = 0}, - Db = {CookieDb, _}) -> - case dets:match_object(CookieDb, #http_cookie{domain = Key, - name = Name, - path = Path, - _ = '_'}) of - [] -> - ok; - [NewCookie] -> - delete(NewCookie, Db) - end, - ok; -insert(Cookie = #http_cookie{domain = Key, name = Name, path = Path}, - Db = {CookieDb, _}) -> - case dets:match_object(CookieDb, #http_cookie{domain = Key, - name = Name, - path = Path, - _ = '_'}) of - [] -> - dets:insert(CookieDb, Cookie); - [NewCookie] -> - delete(NewCookie, Db), - dets:insert(CookieDb, Cookie) - end, - ok. - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== -lookup_cookies(Key, {undefined, Ets}) -> - ets:match_object(Ets, #http_cookie{domain = Key, - _ = '_'}); -lookup_cookies(Key, {Dets,Ets}) -> - SessionCookies = ets:match_object(Ets, #http_cookie{domain = Key, - _ = '_'}), - Cookies = dets:match_object(Dets, #http_cookie{domain = Key, - _ = '_'}), - Cookies ++ SessionCookies. - -delete(Cookie = #http_cookie{max_age = session}, {_, CookieDb}) -> - ets:delete_object(CookieDb, Cookie); -delete(Cookie, {CookieDb, _}) -> - dets:delete_object(CookieDb, Cookie). - -lookup_cookies(Host, Path, Db) -> - Cookies = - case http_util:is_hostname(Host) of - true -> - HostCookies = lookup_cookies(Host, Db), - [_| DomainParts] = string:tokens(Host, "."), - lookup_domain_cookies(DomainParts, Db, HostCookies); - false -> % IP-adress - lookup_cookies(Host, Db) - end, - ValidCookies = valid_cookies(Cookies, [], Db), - lists:filter(fun(Cookie) -> - lists:prefix(Cookie#http_cookie.path, Path) - end, ValidCookies). - -%% For instance if Host=localhost -lookup_domain_cookies([], _, AccCookies) -> - lists:flatten(AccCookies); -%% Top domains can not have cookies -lookup_domain_cookies([_], _, AccCookies) -> - lists:flatten(AccCookies); -lookup_domain_cookies([Next | DomainParts], CookieDb, AccCookies) -> - Domain = merge_domain_parts(DomainParts, [Next ++ "."]), - lookup_domain_cookies(DomainParts, CookieDb, - [lookup_cookies(Domain, CookieDb) - | AccCookies]). - -merge_domain_parts([Part], Merged) -> - lists:flatten(["." | lists:reverse([Part | Merged])]); -merge_domain_parts([Part| Rest], Merged) -> - merge_domain_parts(Rest, [".", Part | Merged]). - -cookies_to_string(Scheme, Cookies = [Cookie | _]) -> - Version = "$Version=" ++ Cookie#http_cookie.version ++ "; ", - cookies_to_string(Scheme, path_sort(Cookies), [Version]). - -cookies_to_string(_, [], CookieStrs) -> - case length(CookieStrs) of - 1 -> - ""; - _ -> - lists:flatten(lists:reverse(CookieStrs)) - end; - -cookies_to_string(https, [Cookie = #http_cookie{secure = true}| Cookies], - CookieStrs) -> - Str = case Cookies of - [] -> - cookie_to_string(Cookie); - _ -> - cookie_to_string(Cookie) ++ "; " - end, - cookies_to_string(https, Cookies, [Str | CookieStrs]); - -cookies_to_string(Scheme, [#http_cookie{secure = true}| Cookies], - CookieStrs) -> - cookies_to_string(Scheme, Cookies, CookieStrs); - -cookies_to_string(Scheme, [Cookie | Cookies], CookieStrs) -> - Str = case Cookies of - [] -> - cookie_to_string(Cookie); - _ -> - cookie_to_string(Cookie) ++ "; " - end, - cookies_to_string(Scheme, Cookies, [Str | CookieStrs]). - -cookie_to_string(Cookie = #http_cookie{name = Name, value = Value}) -> - Str = Name ++ "=" ++ Value, - add_domain(add_path(Str, Cookie), Cookie). - -add_path(Str, #http_cookie{path_default = true}) -> - Str; -add_path(Str, #http_cookie{path = Path}) -> - Str ++ "; $Path=" ++ Path. - -add_domain(Str, #http_cookie{domain_default = true}) -> - Str; -add_domain(Str, #http_cookie{domain = Domain}) -> - Str ++ "; $Domain=" ++ Domain. - -parse_set_cookies(OtherHeaders, DefaultPathDomain) -> - SetCookieHeaders = lists:foldl(fun({"set-cookie", Value}, Acc) -> - [string:tokens(Value, ",")| Acc]; - (_, Acc) -> - Acc - end, [], OtherHeaders), - - lists:flatten(lists:map(fun(CookieHeader) -> - NewHeader = - fix_netscape_cookie(CookieHeader, - []), - parse_set_cookie(NewHeader, [], - DefaultPathDomain) end, - SetCookieHeaders)). - -parse_set_cookie([], AccCookies, _) -> - AccCookies; -parse_set_cookie([CookieHeader | CookieHeaders], AccCookies, - Defaults = {DefaultPath, DefaultDomain}) -> - [CookieStr | Attributes] = case string:tokens(CookieHeader, ";") of - [CStr] -> - [CStr, ""]; - [CStr | Attr] -> - [CStr, Attr] - end, - Pos = string:chr(CookieStr, $=), - Name = string:substr(CookieStr, 1, Pos - 1), - Value = string:substr(CookieStr, Pos + 1), - Cookie = #http_cookie{name = string:strip(Name), - value = string:strip(Value)}, - NewAttributes = parse_set_cookie_attributes(Attributes), - TmpCookie = cookie_attributes(NewAttributes, Cookie), - %% Add runtime defult values if necessary - NewCookie = domain_default(path_default(TmpCookie, DefaultPath), - DefaultDomain), - parse_set_cookie(CookieHeaders, [NewCookie | AccCookies], Defaults). - -parse_set_cookie_attributes([]) -> - []; -parse_set_cookie_attributes([Attributes]) -> - lists:map(fun(Attr) -> - [AttrName, AttrValue] = - case string:tokens(Attr, "=") of - %% All attributes have the form - %% Name=Value except "secure"! - [Name] -> - [Name, ""]; - [Name, Value] -> - [Name, Value]; - %% Anything not expected will be - %% disregarded - _ -> - ["Dummy",""] - end, - {http_util:to_lower(string:strip(AttrName)), - string:strip(AttrValue)} - end, Attributes). - -cookie_attributes([], Cookie) -> - Cookie; -cookie_attributes([{"comment", Value}| Attributes], Cookie) -> - cookie_attributes(Attributes, - Cookie#http_cookie{comment = Value}); -cookie_attributes([{"domain", Value}| Attributes], Cookie) -> - cookie_attributes(Attributes, - Cookie#http_cookie{domain = Value}); -cookie_attributes([{"max-age", Value}| Attributes], Cookie) -> - ExpireTime = cookie_expires(list_to_integer(Value)), - cookie_attributes(Attributes, - Cookie#http_cookie{max_age = ExpireTime}); -%% Backwards compatibility with netscape cookies -cookie_attributes([{"expires", Value}| Attributes], Cookie) -> - Time = http_util:convert_netscapecookie_date(Value), - ExpireTime = calendar:datetime_to_gregorian_seconds(Time), - cookie_attributes(Attributes, - Cookie#http_cookie{max_age = ExpireTime}); -cookie_attributes([{"path", Value}| Attributes], Cookie) -> - cookie_attributes(Attributes, - Cookie#http_cookie{path = Value}); -cookie_attributes([{"secure", _}| Attributes], Cookie) -> - cookie_attributes(Attributes, - Cookie#http_cookie{secure = true}); -cookie_attributes([{"version", Value}| Attributes], Cookie) -> - cookie_attributes(Attributes, - Cookie#http_cookie{version = Value}); -%% Disregard unknown attributes. -cookie_attributes([_| Attributes], Cookie) -> - cookie_attributes(Attributes, Cookie). - -domain_default(Cookie = #http_cookie{domain = undefined}, - DefaultDomain) -> - Cookie#http_cookie{domain = DefaultDomain, domain_default = true}; -domain_default(Cookie, _) -> - Cookie. - -path_default(Cookie = #http_cookie{path = undefined}, - DefaultPath) -> - Cookie#http_cookie{path = skip_right_most_slash(DefaultPath), - path_default = true}; -path_default(Cookie, _) -> - Cookie. - -%% Note: if the path is only / that / will be keept -skip_right_most_slash("/") -> - "/"; -skip_right_most_slash(Str) -> - string:strip(Str, right, $/). - -accept_cookies(Cookies, RequestPath, RequestHost) -> - lists:filter(fun(Cookie) -> - accept_cookie(Cookie, RequestPath, RequestHost) - end, Cookies). - -accept_cookie(Cookie, RequestPath, RequestHost) -> - accept_path(Cookie, RequestPath) and accept_domain(Cookie, RequestHost). - -accept_path(#http_cookie{path = Path}, RequestPath) -> - lists:prefix(Path, RequestPath). - -accept_domain(#http_cookie{domain = RequestHost}, RequestHost) -> - true; - -accept_domain(#http_cookie{domain = Domain}, RequestHost) -> - HostCheck = case http_util:is_hostname(RequestHost) of - true -> - (lists:suffix(Domain, RequestHost) andalso - (not - lists:member($., - string:substr(RequestHost, 1, - (length(RequestHost) - - length(Domain)))))); - false -> - false - end, - HostCheck andalso (hd(Domain) == $.) - andalso (length(string:tokens(Domain, ".")) > 1). - -cookie_expires(0) -> - 0; -cookie_expires(DeltaSec) -> - NowSec = calendar:datetime_to_gregorian_seconds({date(), time()}), - NowSec + DeltaSec. - -is_cookie_expired(#http_cookie{max_age = session}) -> - false; -is_cookie_expired(#http_cookie{max_age = ExpireTime}) -> - NowSec = calendar:datetime_to_gregorian_seconds({date(), time()}), - ExpireTime - NowSec =< 0. - -valid_cookies([], Valid, _) -> - Valid; - -valid_cookies([Cookie | Cookies], Valid, Db) -> - case is_cookie_expired(Cookie) of - true -> - delete(Cookie, Db), - valid_cookies(Cookies, Valid, Db); - false -> - valid_cookies(Cookies, [Cookie | Valid], Db) - end. - -path_sort(Cookies)-> - lists:reverse(lists:keysort(#http_cookie.path, Cookies)). - - -%% Informally, the Set-Cookie response header comprises the token -%% Set-Cookie:, followed by a comma-separated list of one or more -%% cookies. Netscape cookies expires attribute may also have a -%% , in this case the header list will have been incorrectly split -%% in parse_set_cookies/2 this functions fixs that problem. -fix_netscape_cookie([Cookie1, Cookie2 | Rest], Acc) -> - case regexp:match(Cookie1, "expires=") of - {_, _, _} -> - fix_netscape_cookie(Rest, [Cookie1 ++ Cookie2 | Acc]); - nomatch -> - fix_netscape_cookie([Cookie2 |Rest], [Cookie1| Acc]) - end; -fix_netscape_cookie([Cookie | Rest], Acc) -> - fix_netscape_cookie(Rest, [Cookie | Acc]); - -fix_netscape_cookie([], Acc) -> - Acc. |