4 use HTTP::Date qw(str2time time2str);
5 use HTTP::Headers::Util qw(split_header_words join_header_words);
8 use vars qw($VERSION $EPOCH_OFFSET);
11 # Legacy: because "use "HTTP::Cookies" used be the ONLY way
12 # to load the class HTTP::Cookies::Netscape.
13 require HTTP::Cookies::Netscape;
15 $EPOCH_OFFSET = 0; # difference from Unix epoch
18 $EPOCH_OFFSET = Time::Local::timelocal(0,0,0,1,0,70);
21 # A HTTP::Cookies object is a hash. The main attribute is the
22 # COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}.
32 $self->{lc($_)} = $cnf{$_};
42 my $request = shift || return;
43 my $url = $request->url;
44 my $scheme = $url->scheme;
45 unless ($scheme =~ /^https?\z/) {
46 LWP::Debug::debug("Will not add cookies to non-HTTP requests");
50 my $domain = _host($request, $url);
51 $domain = "$domain.local" unless $domain =~ /\./;
52 my $secure_request = ($scheme eq "https");
53 my $req_path = _url_path($url);
54 my $req_port = $url->port;
56 _normalize_path($req_path) if $req_path =~ /%/;
58 my @cval; # cookie values for the "Cookie" header
60 my $netscape_only = 0; # An exact domain match applies to any cookie
62 while ($domain =~ /\./) {
64 LWP::Debug::debug("Checking $domain for cookies");
65 my $cookies = $self->{COOKIES}{$domain};
67 if ($self->{delayload} && defined($cookies->{'//+delayload'})) {
68 my $cookie_data = $cookies->{'//+delayload'}{'cookie'};
69 delete $self->{COOKIES}{$domain};
70 $self->load_cookie($cookie_data->[1]);
71 $cookies = $self->{COOKIES}{$domain};
72 next unless $cookies; # should not really happen
75 # Want to add cookies corresponding to the most specific paths
76 # first (i.e. longest path first)
78 for $path (sort {length($b) <=> length($a) } keys %$cookies) {
79 LWP::Debug::debug("- checking cookie path=$path");
80 if (index($req_path, $path) != 0) {
81 LWP::Debug::debug(" path $path:$req_path does not fit");
86 while (($key,$array) = each %{$cookies->{$path}}) {
87 my($version,$val,$port,$path_spec,$secure,$expires) = @$array;
88 LWP::Debug::debug(" - checking cookie $key=$val");
89 if ($secure && !$secure_request) {
90 LWP::Debug::debug(" not a secure requests");
93 if ($expires && $expires < $now) {
94 LWP::Debug::debug(" expired");
99 if ($port =~ s/^_//) {
100 # The correponding Set-Cookie attribute was empty
101 $found++ if $port eq $req_port;
106 for $p (split(/,/, $port)) {
107 $found++, last if $p eq $req_port;
111 LWP::Debug::debug(" port $port:$req_port does not fit");
115 if ($version > 0 && $netscape_only) {
116 LWP::Debug::debug(" domain $domain applies to " .
117 "Netscape-style cookies only");
121 LWP::Debug::debug(" it's a match");
123 # set version number of cookie header.
124 # XXX: What should it be if multiple matching
125 # Set-Cookie headers have different versions themselves
128 push(@cval, "\$Version=$version");
130 elsif (!$self->{hide_cookie2}) {
131 $request->header(Cookie2 => '$Version="1"');
135 # do we need to quote the value
136 if ($val =~ /\W/ && $version) {
137 $val =~ s/([\\\"])/\\$1/g;
141 # and finally remember this cookie
142 push(@cval, "$key=$val");
144 push(@cval, qq(\$Path="$path")) if $path_spec;
145 push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./;
148 $p .= qq(="$port") if length $port;
157 # Try with a more general domain, alternately stripping
158 # leading name components and leading dots. When this
159 # results in a domain with no leading dot, it is for
160 # Netscape cookie compatibility only:
162 # a.b.c.net Any cookie
163 # .b.c.net Any cookie
164 # b.c.net Netscape cookie only
167 if ($domain =~ s/^\.+//) {
171 $domain =~ s/[^.]*//;
176 $request->header(Cookie => join("; ", @cval)) if @cval;
185 my $response = shift || return;
187 my @set = split_header_words($response->_header("Set-Cookie2"));
188 my @ns_set = $response->_header("Set-Cookie");
190 return $response unless @set || @ns_set; # quick exit
192 my $request = $response->request;
193 my $url = $request->url;
194 my $req_host = _host($request, $url);
195 $req_host = "$req_host.local" unless $req_host =~ /\./;
196 my $req_port = $url->port;
197 my $req_path = _url_path($url);
198 _normalize_path($req_path) if $req_path =~ /%/;
201 # The old Netscape cookie format for Set-Cookie
202 # http://wp.netscape.com/newsref/std/cookie_spec.html
203 # can for instance contain an unquoted "," in the expires
204 # field, so we have to use this ad-hoc parser.
207 # Build a hash of cookies that was present in Set-Cookie2
208 # headers. We need to skip them if we also find them in a
221 for $param (split(/;\s*/, $set)) {
222 my($k,$v) = split(/\s*=\s*/, $param, 2);
229 #print "$k => undef";
231 if (!$first_param && lc($k) eq "expires") {
232 my $etime = str2time($v);
234 push(@cur, "Max-Age" => str2time($v) - $now);
239 push(@cur, $k => $v);
244 next if $in_set2{$cur[0]};
246 # push(@cur, "Port" => $req_port);
247 push(@cur, "Discard" => undef) unless $expires;
248 push(@cur, "Version" => 0);
249 push(@cur, "ns-cookie" => 1);
256 next unless @$set >= 2;
258 my $key = shift @$set;
259 my $val = shift @$set;
261 LWP::Debug::debug("Set cookie $key => $val");
268 # don't loose case distinction for unknown fields
269 $k = $lc if $lc =~ /^(?:discard|domain|max-age|
270 path|port|secure|version)$/x;
271 if ($k eq "discard" || $k eq "secure") {
272 $v = 1 unless defined $v;
274 next if exists $hash{$k}; # only first value is signigicant
278 my %orig_hash = %hash;
279 my $version = delete $hash{version};
280 $version = 1 unless defined($version);
281 my $discard = delete $hash{discard};
282 my $secure = delete $hash{secure};
283 my $maxage = delete $hash{'max-age'};
284 my $ns_cookie = delete $hash{'ns-cookie'};
287 my $domain = delete $hash{domain};
288 $domain = lc($domain) if defined $domain;
290 && $domain ne $req_host && $domain ne ".$req_host") {
291 if ($domain !~ /\./ && $domain ne "local") {
292 LWP::Debug::debug("Domain $domain contains no dot");
295 $domain = ".$domain" unless $domain =~ /^\./;
296 if ($domain =~ /\.\d+$/) {
297 LWP::Debug::debug("IP-address $domain illeagal as domain");
300 my $len = length($domain);
301 unless (substr($req_host, -$len) eq $domain) {
302 LWP::Debug::debug("Domain $domain does not match host $req_host");
305 my $hostpre = substr($req_host, 0, length($req_host) - $len);
306 if ($hostpre =~ /\./ && !$ns_cookie) {
307 LWP::Debug::debug("Host prefix contain a dot: $hostpre => $domain");
315 my $path = delete $hash{path};
317 if (defined $path && $path ne '') {
319 _normalize_path($path) if $path =~ /%/;
321 substr($req_path, 0, length($path)) ne $path) {
322 LWP::Debug::debug("Path $path is not a prefix of $req_path");
328 $path =~ s,/[^/]*$,,;
329 $path = "/" unless length($path);
333 if (exists $hash{port}) {
334 $port = delete $hash{port};
338 for my $p (split(/,/, $port)) {
339 unless ($p =~ /^\d+$/) {
340 LWP::Debug::debug("Bad port $port (not numeric)");
343 $found++ if $p eq $req_port;
346 LWP::Debug::debug("Request port ($req_port) not found in $port");
351 $port = "_$req_port";
354 $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash)
355 if $self->set_cookie_ok(\%orig_hash);
371 $key, $val, $path, $domain, $port,
372 $path_spec, $secure, $maxage, $discard, $rest) = @_;
374 # path and key can not be empty (key can't start with '$')
375 return $self if !defined($path) || $path !~ m,^/, ||
376 !defined($key) || $key =~ m,^\$,;
380 return $self unless $port =~ /^_?\d+(?:,\d+)*$/;
384 if (defined $maxage) {
386 delete $self->{COOKIES}{$domain}{$path}{$key};
389 $expires = time() + $maxage;
391 $version = 0 unless defined $version;
393 my @array = ($version, $val,$port,
395 $secure, $expires, $discard);
396 push(@array, {%$rest}) if defined($rest) && %$rest;
397 # trim off undefined values at end
398 pop(@array) while !defined $array[-1];
400 $self->{COOKIES}{$domain}{$path}{$key} = \@array;
408 my $file = shift || $self->{'file'} || return;
410 open(FILE, ">$file") or die "Can't open $file: $!";
411 print FILE "#LWP-Cookies-1.0\n";
412 print FILE $self->as_string(!$self->{ignore_discard});
421 my $file = shift || $self->{'file'} || return;
423 local $/ = "\n"; # make sure we got standard record separator
424 open(FILE, $file) or return;
426 unless ($magic =~ /^\#LWP-Cookies-(\d+\.\d+)/) {
427 warn "$file does not seem to contain cookies";
431 next unless s/^Set-Cookie3:\s*//;
434 for $cookie (split_header_words($_)) {
435 my($key,$val) = splice(@$cookie, 0, 2);
438 my $k = shift @$cookie;
439 my $v = shift @$cookie;
442 my $version = delete $hash{version};
443 my $path = delete $hash{path};
444 my $domain = delete $hash{domain};
445 my $port = delete $hash{port};
446 my $expires = str2time(delete $hash{expires});
448 my $path_spec = exists $hash{path_spec}; delete $hash{path_spec};
449 my $secure = exists $hash{secure}; delete $hash{secure};
450 my $discard = exists $hash{discard}; delete $hash{discard};
452 my @array = ($version,$val,$port,
453 $path_spec,$secure,$expires,$discard);
454 push(@array, \%hash) if %hash;
455 $self->{COOKIES}{$domain}{$path}{$key} = \@array;
475 $self->{COOKIES} = {};
478 delete $self->{COOKIES}{$_[0]};
481 delete $self->{COOKIES}{$_[0]}{$_[1]};
484 delete $self->{COOKIES}{$_[0]}{$_[1]}{$_[2]};
488 Carp::carp('Usage: $c->clear([domain [,path [,key]]])');
494 sub clear_temporary_cookies
499 if($_[9] or # "Discard" flag set
500 not $_[8]) { # No expire field?
501 $_[8] = -1; # Set the expire/max_age field
502 $self->set_cookie(@_); # Clear the cookie
511 $self->save if $self->{'autosave'};
518 my($domain,$path,$key);
519 for $domain (sort keys %{$self->{COOKIES}}) {
520 for $path (sort keys %{$self->{COOKIES}{$domain}}) {
521 for $key (sort keys %{$self->{COOKIES}{$domain}{$path}}) {
522 my($version,$val,$port,$path_spec,
523 $secure,$expires,$discard,$rest) =
524 @{$self->{COOKIES}{$domain}{$path}{$key}};
525 $rest = {} unless defined($rest);
526 &$cb($version,$key,$val,$path,$domain,$port,
527 $path_spec,$secure,$expires,$discard,$rest);
536 my($self, $skip_discard) = @_;
539 my($version,$key,$val,$path,$domain,$port,
540 $path_spec,$secure,$expires,$discard,$rest) = @_;
541 return if $discard && $skip_discard;
542 my @h = ($key, $val);
543 push(@h, "path", $path);
544 push(@h, "domain" => $domain);
545 push(@h, "port" => $port) if defined $port;
546 push(@h, "path_spec" => undef) if $path_spec;
547 push(@h, "secure" => undef) if $secure;
548 push(@h, "expires" => HTTP::Date::time2isoz($expires)) if $expires;
549 push(@h, "discard" => undef) if $discard;
551 for $k (sort keys %$rest) {
552 push(@h, $k, $rest->{$k});
554 push(@h, "version" => $version);
555 push(@res, "Set-Cookie3: " . join_header_words(\@h));
557 join("\n", @res, "");
562 my($request, $url) = @_;
563 if (my $h = $request->header("Host")) {
564 $h =~ s/:\d+$//; # might have a port as well
567 return lc($url->host);
574 if($url->can('epath')) {
575 $path = $url->epath; # URI::URL method
578 $path = $url->path; # URI::_generic method
580 $path = "/" unless length $path;
584 sub _normalize_path # so that plain string compare can be used
587 $_[0] =~ s/%([0-9a-fA-F][0-9a-fA-F])/
589 $x eq "2F" || $x eq "25" ? "%$x" :
592 $_[0] =~ s/([\0-\x20\x7f-\xff])/sprintf("%%%02X",ord($1))/eg;
601 HTTP::Cookies - HTTP cookie jars
606 $cookie_jar = HTTP::Cookies->new(
607 file => "$ENV{'HOME'}/lwp_cookies.dat',
612 my $browser = LWP::UserAgent->new;
613 $browser->cookie_jar($cookie_jar);
615 Or for an empty and temporary cookie jar:
618 my $browser = LWP::UserAgent->new;
619 $browser->cookie_jar( {} );
623 This class is for objects that represent a "cookie jar" -- that is, a
624 database of all the HTTP cookies that a given LWP::UserAgent object
627 Cookies are a general mechanism which server side connections can use
628 to both store and retrieve information on the client side of the
629 connection. For more information about cookies refer to
630 <URL:http://wp.netscape.com/newsref/std/cookie_spec.html> and
631 <URL:http://www.cookiecentral.com/>. This module also implements the
632 new style cookies described in I<RFC 2965>.
633 The two variants of cookies are supposed to be able to coexist happily.
635 Instances of the class I<HTTP::Cookies> are able to store a collection
636 of Set-Cookie2: and Set-Cookie: headers and are able to use this
637 information to initialize Cookie-headers in I<HTTP::Request> objects.
638 The state of a I<HTTP::Cookies> object can be saved in and restored from
643 The following methods are provided:
647 =item $cookie_jar = HTTP::Cookies->new
649 The constructor takes hash style parameters. The following
650 parameters are recognized:
652 file: name of the file to restore cookies from and save cookies to
653 autosave: save during destruction (bool)
654 ignore_discard: save even cookies that are requested to be discarded (bool)
655 hide_cookie2: do not add Cookie2 header to requests
657 Future parameters might include (not yet implemented):
660 max_cookies_per_domain 20
663 no_cookies list of domain names that we never return cookies to
665 =item $cookie_jar->add_cookie_header( $request )
667 The add_cookie_header() method will set the appropriate Cookie:-header
668 for the I<HTTP::Request> object given as argument. The $request must
669 have a valid url attribute before this method is called.
671 =item $cookie_jar->extract_cookies( $response )
673 The extract_cookies() method will look for Set-Cookie: and
674 Set-Cookie2: headers in the I<HTTP::Response> object passed as
675 argument. Any of these headers that are found are used to update
676 the state of the $cookie_jar.
678 =item $cookie_jar->set_cookie( $version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest )
680 The set_cookie() method updates the state of the $cookie_jar. The
681 $key, $val, $domain, $port and $path arguments are strings. The
682 $path_spec, $secure, $discard arguments are boolean values. The $maxage
683 value is a number indicating number of seconds that this cookie will
684 live. A value <= 0 will delete this cookie. %rest defines
685 various other attributes like "Comment" and "CommentURL".
687 =item $cookie_jar->save
689 =item $cookie_jar->save( $file )
691 This method file saves the state of the $cookie_jar to a file.
692 The state can then be restored later using the load() method. If a
693 filename is not specified we will use the name specified during
694 construction. If the attribute I<ignore_discard> is set, then we
695 will even save cookies that are marked to be discarded.
697 The default is to save a sequence of "Set-Cookie3" lines.
698 "Set-Cookie3" is a proprietary LWP format, not known to be compatible
699 with any browser. The I<HTTP::Cookies::Netscape> sub-class can
700 be used to save in a format compatible with Netscape.
702 =item $cookie_jar->load
704 =item $cookie_jar->load( $file )
706 This method reads the cookies from the file and adds them to the
707 $cookie_jar. The file must be in the format written by the save()
710 =item $cookie_jar->revert
712 This method empties the $cookie_jar and re-loads the $cookie_jar
713 from the last save file.
715 =item $cookie_jar->clear
717 =item $cookie_jar->clear( $domain )
719 =item $cookie_jar->clear( $domain, $path )
721 =item $cookie_jar->clear( $domain, $path, $key )
723 Invoking this method without arguments will empty the whole
724 $cookie_jar. If given a single argument only cookies belonging to
725 that domain will be removed. If given two arguments, cookies
726 belonging to the specified path within that domain are removed. If
727 given three arguments, then the cookie with the specified key, path
728 and domain is removed.
730 =item $cookie_jar->clear_temporary_cookies
732 Discard all temporary cookies. Scans for all cookies in the jar
733 with either no expire field or a true C<discard> flag. To be
734 called when the user agent shuts down according to RFC 2965.
736 =item $cookie_jar->scan( \&callback )
738 The argument is a subroutine that will be invoked for each cookie
739 stored in the $cookie_jar. The subroutine will be invoked with
740 the following arguments:
754 =item $cookie_jar->as_string
756 =item $cookie_jar->as_string( $skip_discardables )
758 The as_string() method will return the state of the $cookie_jar
759 represented as a sequence of "Set-Cookie3" header lines separated by
760 "\n". If $skip_discardables is TRUE, it will not return lines for
761 cookies with the I<Discard> attribute.
767 L<HTTP::Cookies::Netscape>, L<HTTP::Cookies::Microsoft>
771 Copyright 1997-2002 Gisle Aas
773 This library is free software; you can redistribute it and/or
774 modify it under the same terms as Perl itself.