📄 cookies.pm
字号:
package HTTP::Cookies;# Based on draft-ietf-http-state-man-mec-08.txt and# http://www.netscape.com/newsref/std/cookie_spec.htmluse strict;use HTTP::Date qw(str2time time2str);use HTTP::Headers::Util qw(split_header_words join_header_words);use LWP::Debug ();use vars qw($VERSION);$VERSION = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/);=head1 NAMEHTTP::Cookies - Cookie storage and management=head1 SYNOPSIS use HTTP::Cookies; $cookie_jar = HTTP::Cookies->new; $cookie_jar->add_cookie_header($request); $cookie_jar->extract_cookies($response);=head1 DESCRIPTIONCookies are a general mechanism which server side connections can useto both store and retrieve information on the client side of theconnection. For more information about cookies referrer to<URL:http://www.netscape.com/newsref/std/cookie_spec.html> and<URL:http://www.cookiecentral.com/>. This module also implements thenew style cookies as described in I<draft-ietf-http-state-man-mec-08.txt>.The two variants of cookies is supposed to be able to coexist happily.Instances of the class I<HTTP::Cookies> are able to store a collectionof Set-Cookie2: and Set-Cookie:-headers and is able to use thisinformation to initialize Cookie-headers in I<HTTP::Request> objects.The state of the I<HTTP::Cookies> can be saved and restored fromfiles.=head1 METHODSThe following methods are provided:=over 4=cut# A HTTP::Cookies object is a hash. The main attribute is the# COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}.=item $cookie_jar = HTTP::Cookies->new;The constructor. Takes hash style parameters. The followingparameters are recognized: file: name of the file to restore and save cookies to autosave: should we save during destruction (bool) ignore_discard: save even cookies that are requested to be discarded (bool)Future parameters might include (not yet implemented): max_cookies 300 max_cookies_per_domain 20 max_cookie_size 4096 no_cookies list of domain names that we never return cookies to=cutsub new{ my $class = shift; my $self = bless { COOKIES => {}, }, $class; my %cnf = @_; for (keys %cnf) { $self->{lc($_)} = $cnf{$_}; } $self->load; $self;}=item $cookie_jar->add_cookie_header($request);The add_cookie_header() method will set the appropriate Cookie:-headerfor the I<HTTP::Request> object given as argument. The $request musthave a valid url() attribute before this method is called.=cutsub add_cookie_header{ my $self = shift; my $request = shift || return; my $url = $request->url; my $domain = $url->host; $domain = "$domain.local" unless $domain =~ /\./; my $secure_request = ($url->scheme eq "https"); my $req_path = $url->epath; my $req_port = $url->port; my $now = time(); $self->_normalize_path($req_path) if $req_path =~ /%/; my @cval; # cookie values for the "Cookie" header my $set_ver; while (($domain =~ tr/././) >= 2 || # must be at least 2 dots $domain =~ /\.local$/) { LWP::Debug::debug("Checking $domain for cookies"); my $cookies = $self->{COOKIES}{$domain}; next unless $cookies; # Want to add cookies corresponding to the most specific paths # first (i.e. longest path first) my $path; for $path (sort {length($b) <=> length($a) } keys %$cookies) { LWP::Debug::debug("- checking cookie path=$path"); if (index($req_path, $path) != 0) { LWP::Debug::debug(" path $path:$req_path does not fit"); next; } my($key,$array); while (($key,$array) = each %{$cookies->{$path}}) { my($version,$val,$port,$path_spec,$secure,$expires) = @$array; LWP::Debug::debug(" - checking cookie $key=$val"); if ($secure && !$secure_request) { LWP::Debug::debug(" not a secure requests"); next; } if ($expires && $expires < $now) { LWP::Debug::debug(" expired"); next; } if ($port) { my $found; if ($port =~ s/^_//) { # The correponding Set-Cookie attribute was empty $found++ if $port eq $req_port; $port = ""; } else { my $p; for $p (split(/,/, $port)) { $found++, last if $p eq $req_port; } } unless ($found) { LWP::Debug::debug(" port $port:$req_port does not fit"); next; } } LWP::Debug::debug(" it's a match"); # set version number of cookie header. # XXX: What should it be if multiple matching # Set-Cookie headers have different versions themselves if (!$set_ver++) { if ($version >= 1) { push(@cval, "\$Version=$version"); } else { $request->header(Cookie2 => "\$Version=1"); } } # do we need to quote the value if ($val =~ /\W/ && $version) { $val =~ s/([\\\"])/\\$1/g; $val = qq("$val"); } # and finally remember this cookie push(@cval, "$key=$val"); if ($version >= 1) { push(@cval, qq(\$Path="$path")) if $path_spec; push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./; if (defined $port) { my $p = '$Port'; $p .= qq(="$port") if length $port; push(@cval, $p); } } } } } continue { # Try with a more general domain: www.sol.no ==> .sol.no $domain =~ s/^\.?[^.]*//; } $request->header(Cookie => join("; ", @cval)) if @cval; $request;}=item $cookie_jar->extract_cookies($response);The extract_cookies() method will look for Set-Cookie: andSet-Cookie2:-headers in the I<HTTP::Response> object passed asargument. If some of these headers are found they are used to updatethe state of the $cookie_jar.=cutsub extract_cookies{ my $self = shift; my $response = shift || return; my @set = split_header_words($response->_header("Set-Cookie2")); my $netscape_cookies; unless (@set) { @set = $response->_header("Set-Cookie"); return $response unless @set; $netscape_cookies++; } my $url = $response->request->url; my $req_host = $url->host; $req_host = "$req_host.local" unless $req_host =~ /\./; my $req_port = $url->port; my $req_path = $url->epath; $self->_normalize_path($req_path) if $req_path =~ /%/; if ($netscape_cookies) { # The old Netscape cookie format for Set-Cookie # http://www.netscape.com/newsref/std/cookie_spec.html # can for instance contain an unquoted "," in the expires # field, so we have to use this ad-hoc parser. my $now = time(); my @old = @set; @set = (); my $set; for $set (@old) { my @cur; my $param; my $expires; for $param (split(/\s*;\s*/, $set)) { my($k,$v) = split(/\s*=\s*/, $param, 2); #print "$k => $v\n"; my $lc = lc($k); if ($lc eq "expires") { push(@cur, "Max-Age" => str2time($v) - $now); $expires++; } else { push(@cur, $k => $v); } } push(@cur, "Port" => $req_port); push(@cur, "Discard" => undef) unless $expires; push(@cur, "Version" => 0); push(@set, \@cur); } } SET_COOKIE: for my $set (@set) { next unless @$set >= 2; my $key = shift @$set; my $val = shift @$set; LWP::Debug::debug("Set cookie $key => $val"); my %hash; while (@$set) { my $k = shift @$set; my $v = shift @$set; my $lc = lc($k); # don't loose case distinction for unknown fields $k = $lc if $lc =~ /^(?:discard|domain|max-age| path|port|secure|version)$/x; if ($k eq "discard" || $k eq "secure") { $v = 1 unless defined $v; } next if exists $hash{$k}; # only first value is signigicant $hash{$k} = $v; }; my %orig_hash = %hash; my $version = delete $hash{version}; $version = 1 unless defined($version); my $discard = delete $hash{discard}; my $secure = delete $hash{secure}; my $maxage = delete $hash{'max-age'}; # Check domain my $domain = delete $hash{domain}; if (defined($domain) && $domain ne $req_host) { if ($domain !~ /\./ && $domain ne "local") { LWP::Debug::debug("Domain $domain contains no dot"); next SET_COOKIE; } $domain = ".$domain" unless $domain =~ /^\./; if ($domain =~ /\.\d+$/) { LWP::Debug::debug("IP-address $domain illeagal as domain"); next SET_COOKIE; } my $len = length($domain); unless (substr($req_host, -$len) eq $domain) { LWP::Debug::debug("Domain $domain does not match host $req_host"); next SET_COOKIE; } my $hostpre = substr($req_host, 0, length($req_host) - $len); if ($hostpre =~ /\./ && !$netscape_cookies) { LWP::Debug::debug("Host prefix contain a dot: $hostpre => $domain"); next SET_COOKIE; } } else { $domain = $req_host; } my $path = delete $hash{path}; my $path_spec; if (defined $path) { $path_spec++; $self->_normalize_path($path) if $path =~ /%/; if (!$netscape_cookies && substr($req_path, 0, length($path)) ne $path) { LWP::Debug::debug("Path $path is not a prefix of $req_path"); next SET_COOKIE; } } else { $path = $req_path; $path =~ s,/[^/]*$,,; $path = "/" unless length($path); } my $port; if (exists $hash{port}) { $port = delete $hash{port}; if (defined $port) { $port =~ s/\s+//g; my $found; for my $p (split(/,/, $port)) { unless ($p =~ /^\d+$/) { LWP::Debug::debug("Bad port $port (not numeric)"); next SET_COOKIE; } $found++ if $p eq $req_port; } unless ($found) { LWP::Debug::debug("Request port ($req_port) not found in $port"); next SET_COOKIE; } } else { $port = "_$req_port"; } } $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash) if $self->set_cookie_ok(\%orig_hash); } $response;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -