http_cookie.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 390 行

ERL
390
字号
%% ``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 cookieslookup_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 cookiescookie_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 keeptskip_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.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?