📄 cookies.pm
字号:
package HTTP::Cookies;use 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 $EPOCH_OFFSET);$VERSION = sprintf("%d.%02d", q$Revision: 1.39 $ =~ /(\d+)\.(\d+)/);# Legacy: because "use "HTTP::Cookies" used be the ONLY way# to load the class HTTP::Cookies::Netscape.require HTTP::Cookies::Netscape;$EPOCH_OFFSET = 0; # difference from Unix epochif ($^O eq "MacOS") { require Time::Local; $EPOCH_OFFSET = Time::Local::timelocal(0,0,0,1,0,70);}# A HTTP::Cookies object is a hash. The main attribute is the# COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}.sub new{ my $class = shift; my $self = bless { COOKIES => {}, }, $class; my %cnf = @_; for (keys %cnf) { $self->{lc($_)} = $cnf{$_}; } $self->load; $self;}sub add_cookie_header{ my $self = shift; my $request = shift || return; my $url = $request->url; my $scheme = $url->scheme; unless ($scheme =~ /^https?\z/) { LWP::Debug::debug("Will not add cookies to non-HTTP requests"); return; } my $domain = _host($request, $url); $domain = "$domain.local" unless $domain =~ /\./; my $secure_request = ($scheme eq "https"); my $req_path = _url_path($url); my $req_port = $url->port; my $now = time(); _normalize_path($req_path) if $req_path =~ /%/; my @cval; # cookie values for the "Cookie" header my $set_ver; my $netscape_only = 0; # An exact domain match applies to any cookie while ($domain =~ /\./) { LWP::Debug::debug("Checking $domain for cookies"); my $cookies = $self->{COOKIES}{$domain}; next unless $cookies; if ($self->{delayload} && defined($cookies->{'//+delayload'})) { my $cookie_data = $cookies->{'//+delayload'}{'cookie'}; delete $self->{COOKIES}{$domain}; $self->load_cookie($cookie_data->[1]); $cookies = $self->{COOKIES}{$domain}; next unless $cookies; # should not really happen } # 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; } } if ($version > 0 && $netscape_only) { LWP::Debug::debug(" domain $domain applies to " . "Netscape-style cookies only"); 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"); } elsif (!$self->{hide_cookie2}) { $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, alternately stripping # leading name components and leading dots. When this # results in a domain with no leading dot, it is for # Netscape cookie compatibility only: # # a.b.c.net Any cookie # .b.c.net Any cookie # b.c.net Netscape cookie only # .c.net Any cookie if ($domain =~ s/^\.+//) { $netscape_only = 1; } else { $domain =~ s/[^.]*//; $netscape_only = 0; } } $request->header(Cookie => join("; ", @cval)) if @cval; $request;}sub extract_cookies{ my $self = shift; my $response = shift || return; my @set = split_header_words($response->_header("Set-Cookie2")); my @ns_set = $response->_header("Set-Cookie"); return $response unless @set || @ns_set; # quick exit my $request = $response->request; my $url = $request->url; my $req_host = _host($request, $url); $req_host = "$req_host.local" unless $req_host =~ /\./; my $req_port = $url->port; my $req_path = _url_path($url); _normalize_path($req_path) if $req_path =~ /%/; if (@ns_set) { # 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(); # Build a hash of cookies that was present in Set-Cookie2 # headers. We need to skip them if we also find them in a # Set-Cookie header. my %in_set2; for (@set) { $in_set2{$_->[0]}++; } my $set; for $set (@ns_set) { my @cur; my $param; my $expires; my $first_param = 1; for $param (split(/;\s*/, $set)) { my($k,$v) = split(/\s*=\s*/, $param, 2); if (defined $v) { $v =~ s/\s+$//; #print "$k => $v\n"; } else { $k =~ s/\s+$//; #print "$k => undef"; } if (!$first_param && lc($k) eq "expires") { my $etime = str2time($v); if ($etime) { push(@cur, "Max-Age" => str2time($v) - $now); $expires++; } } else { push(@cur, $k => $v); } $first_param = 0; } next if $in_set2{$cur[0]};# push(@cur, "Port" => $req_port); push(@cur, "Discard" => undef) unless $expires; push(@cur, "Version" => 0); push(@cur, "ns-cookie" => 1); 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'}; my $ns_cookie = delete $hash{'ns-cookie'}; # Check domain my $domain = delete $hash{domain}; $domain = lc($domain) if defined $domain; if (defined($domain) && $domain ne $req_host && $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 =~ /\./ && !$ns_cookie) { 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 ne '') { $path_spec++; _normalize_path($path) if $path =~ /%/; if (!$ns_cookie && 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;}sub set_cookie_ok{ 1;}sub set_cookie{ my $self = shift; my($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, $rest) = @_; # path and key can not be empty (key can't start with '$') return $self if !defined($path) || $path !~ m,^/, || !defined($key) || $key =~ m,^\$,; # ensure legal port if (defined $port) { return $self unless $port =~ /^_?\d+(?:,\d+)*$/; } my $expires; if (defined $maxage) { if ($maxage <= 0) { delete $self->{COOKIES}{$domain}{$path}{$key}; return $self; } $expires = time() + $maxage;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -