4 use HTTP::Date qw(str2time parse_date time2str);
5 use HTTP::Headers::Util qw(_split_header_words join_header_words);
7 use vars qw($VERSION $EPOCH_OFFSET);
10 # Legacy: because "use "HTTP::Cookies" used be the ONLY way
11 # to load the class HTTP::Cookies::Netscape.
12 require HTTP::Cookies::Netscape;
14 $EPOCH_OFFSET = 0; # difference from Unix epoch
17 $EPOCH_OFFSET = Time::Local::timelocal(0,0,0,1,0,70);
20 # A HTTP::Cookies object is a hash. The main attribute is the
21 # COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}.
31 $self->{lc($_)} = $cnf{$_};
41 my $request = shift || return;
42 my $url = $request->uri;
43 my $scheme = $url->scheme;
44 unless ($scheme =~ /^https?\z/) {
48 my $domain = _host($request, $url);
49 $domain = "$domain.local" unless $domain =~ /\./;
50 my $secure_request = ($scheme eq "https");
51 my $req_path = _url_path($url);
52 my $req_port = $url->port;
54 _normalize_path($req_path) if $req_path =~ /%/;
56 my @cval; # cookie values for the "Cookie" header
58 my $netscape_only = 0; # An exact domain match applies to any cookie
60 while ($domain =~ /\./) {
61 # Checking $domain for cookies"
62 my $cookies = $self->{COOKIES}{$domain};
64 if ($self->{delayload} && defined($cookies->{'//+delayload'})) {
65 my $cookie_data = $cookies->{'//+delayload'}{'cookie'};
66 delete $self->{COOKIES}{$domain};
67 $self->load_cookie($cookie_data->[1]);
68 $cookies = $self->{COOKIES}{$domain};
69 next unless $cookies; # should not really happen
72 # Want to add cookies corresponding to the most specific paths
73 # first (i.e. longest path first)
75 for $path (sort {length($b) <=> length($a) } keys %$cookies) {
76 if (index($req_path, $path) != 0) {
81 while (($key,$array) = each %{$cookies->{$path}}) {
82 my($version,$val,$port,$path_spec,$secure,$expires) = @$array;
83 if ($secure && !$secure_request) {
86 if ($expires && $expires < $now) {
91 if ($port =~ s/^_//) {
92 # The corresponding Set-Cookie attribute was empty
93 $found++ if $port eq $req_port;
98 for $p (split(/,/, $port)) {
99 $found++, last if $p eq $req_port;
106 if ($version > 0 && $netscape_only) {
110 # set version number of cookie header.
111 # XXX: What should it be if multiple matching
112 # Set-Cookie headers have different versions themselves
115 push(@cval, "\$Version=$version");
117 elsif (!$self->{hide_cookie2}) {
118 $request->header(Cookie2 => '$Version="1"');
122 # do we need to quote the value
123 if ($val =~ /\W/ && $version) {
124 $val =~ s/([\\\"])/\\$1/g;
128 # and finally remember this cookie
129 push(@cval, "$key=$val");
131 push(@cval, qq(\$Path="$path")) if $path_spec;
132 push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./;
135 $p .= qq(="$port") if length $port;
144 # Try with a more general domain, alternately stripping
145 # leading name components and leading dots. When this
146 # results in a domain with no leading dot, it is for
147 # Netscape cookie compatibility only:
149 # a.b.c.net Any cookie
150 # .b.c.net Any cookie
151 # b.c.net Netscape cookie only
154 if ($domain =~ s/^\.+//) {
158 $domain =~ s/[^.]*//;
164 if (my $old = $request->header("Cookie")) {
165 unshift(@cval, $old);
167 $request->header(Cookie => join("; ", @cval));
177 my $response = shift || return;
179 my @set = _split_header_words($response->_header("Set-Cookie2"));
180 my @ns_set = $response->_header("Set-Cookie");
182 return $response unless @set || @ns_set; # quick exit
184 my $request = $response->request;
185 my $url = $request->uri;
186 my $req_host = _host($request, $url);
187 $req_host = "$req_host.local" unless $req_host =~ /\./;
188 my $req_port = $url->port;
189 my $req_path = _url_path($url);
190 _normalize_path($req_path) if $req_path =~ /%/;
193 # The old Netscape cookie format for Set-Cookie
194 # http://curl.haxx.se/rfc/cookie_spec.html
195 # can for instance contain an unquoted "," in the expires
196 # field, so we have to use this ad-hoc parser.
199 # Build a hash of cookies that was present in Set-Cookie2
200 # headers. We need to skip them if we also find them in a
214 for $param (split(/;\s*/, $set)) {
215 next unless length($param);
216 my($k,$v) = split(/\s*=\s*/, $param, 2);
223 #print "$k => undef";
225 if (!$first_param && lc($k) eq "expires") {
226 my $etime = str2time($v);
227 if (defined $etime) {
228 push(@cur, "Max-Age" => $etime - $now);
232 # parse_date can deal with years outside the range of time_t,
233 my($year, $mon, $day, $hour, $min, $sec, $tz) = parse_date($v);
235 my $thisyear = (gmtime)[5] + 1900;
236 if ($year < $thisyear) {
237 push(@cur, "Max-Age" => -1); # any negative value will do
240 elsif ($year >= $thisyear + 10) {
241 # the date is at least 10 years into the future, just replace
242 # it with something approximate
243 push(@cur, "Max-Age" => 10 * 365 * 24 * 60 * 60);
249 elsif (!$first_param && lc($k) =~ /^(?:version|discard|ns-cookie)/) {
253 push(@cur, $k => $v);
258 next if $in_set2{$cur[0]};
260 # push(@cur, "Port" => $req_port);
261 push(@cur, "Discard" => undef) unless $expires;
262 push(@cur, "Version" => 0);
263 push(@cur, "ns-cookie" => 1);
270 next unless @$set >= 2;
272 my $key = shift @$set;
273 my $val = shift @$set;
280 # don't loose case distinction for unknown fields
281 $k = $lc if $lc =~ /^(?:discard|domain|max-age|
282 path|port|secure|version)$/x;
283 if ($k eq "discard" || $k eq "secure") {
284 $v = 1 unless defined $v;
286 next if exists $hash{$k}; # only first value is significant
290 my %orig_hash = %hash;
291 my $version = delete $hash{version};
292 $version = 1 unless defined($version);
293 my $discard = delete $hash{discard};
294 my $secure = delete $hash{secure};
295 my $maxage = delete $hash{'max-age'};
296 my $ns_cookie = delete $hash{'ns-cookie'};
299 my $domain = delete $hash{domain};
300 $domain = lc($domain) if defined $domain;
302 && $domain ne $req_host && $domain ne ".$req_host") {
303 if ($domain !~ /\./ && $domain ne "local") {
306 $domain = ".$domain" unless $domain =~ /^\./;
307 if ($domain =~ /\.\d+$/) {
310 my $len = length($domain);
311 unless (substr($req_host, -$len) eq $domain) {
314 my $hostpre = substr($req_host, 0, length($req_host) - $len);
315 if ($hostpre =~ /\./ && !$ns_cookie) {
323 my $path = delete $hash{path};
325 if (defined $path && $path ne '') {
327 _normalize_path($path) if $path =~ /%/;
329 substr($req_path, 0, length($path)) ne $path) {
335 $path =~ s,/[^/]*$,,;
336 $path = "/" unless length($path);
340 if (exists $hash{port}) {
341 $port = delete $hash{port};
345 for my $p (split(/,/, $port)) {
346 unless ($p =~ /^\d+$/) {
349 $found++ if $p eq $req_port;
356 $port = "_$req_port";
359 $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash)
360 if $self->set_cookie_ok(\%orig_hash);
376 $key, $val, $path, $domain, $port,
377 $path_spec, $secure, $maxage, $discard, $rest) = @_;
379 # path and key can not be empty (key can't start with '$')
380 return $self if !defined($path) || $path !~ m,^/, ||
381 !defined($key) || $key =~ m,^\$,;
385 return $self unless $port =~ /^_?\d+(?:,\d+)*$/;
389 if (defined $maxage) {
391 delete $self->{COOKIES}{$domain}{$path}{$key};
394 $expires = time() + $maxage;
396 $version = 0 unless defined $version;
398 my @array = ($version, $val,$port,
400 $secure, $expires, $discard);
401 push(@array, {%$rest}) if defined($rest) && %$rest;
402 # trim off undefined values at end
403 pop(@array) while !defined $array[-1];
405 $self->{COOKIES}{$domain}{$path}{$key} = \@array;
413 my $file = shift || $self->{'file'} || return;
415 open(FILE, ">$file") or die "Can't open $file: $!";
416 print FILE "#LWP-Cookies-1.0\n";
417 print FILE $self->as_string(!$self->{ignore_discard});
426 my $file = shift || $self->{'file'} || return;
428 local $/ = "\n"; # make sure we got standard record separator
429 open(FILE, $file) or return;
431 unless ($magic =~ /^\#LWP-Cookies-(\d+\.\d+)/) {
432 warn "$file does not seem to contain cookies";
436 next unless s/^Set-Cookie3:\s*//;
439 for $cookie (_split_header_words($_)) {
440 my($key,$val) = splice(@$cookie, 0, 2);
443 my $k = shift @$cookie;
444 my $v = shift @$cookie;
447 my $version = delete $hash{version};
448 my $path = delete $hash{path};
449 my $domain = delete $hash{domain};
450 my $port = delete $hash{port};
451 my $expires = str2time(delete $hash{expires});
453 my $path_spec = exists $hash{path_spec}; delete $hash{path_spec};
454 my $secure = exists $hash{secure}; delete $hash{secure};
455 my $discard = exists $hash{discard}; delete $hash{discard};
457 my @array = ($version,$val,$port,
458 $path_spec,$secure,$expires,$discard);
459 push(@array, \%hash) if %hash;
460 $self->{COOKIES}{$domain}{$path}{$key} = \@array;
480 $self->{COOKIES} = {};
483 delete $self->{COOKIES}{$_[0]};
486 delete $self->{COOKIES}{$_[0]}{$_[1]};
489 delete $self->{COOKIES}{$_[0]}{$_[1]}{$_[2]};
493 Carp::carp('Usage: $c->clear([domain [,path [,key]]])');
499 sub clear_temporary_cookies
504 if($_[9] or # "Discard" flag set
505 not $_[8]) { # No expire field?
506 $_[8] = -1; # Set the expire/max_age field
507 $self->set_cookie(@_); # Clear the cookie
516 local($., $@, $!, $^E, $?);
517 $self->save if $self->{'autosave'};
524 my($domain,$path,$key);
525 for $domain (sort keys %{$self->{COOKIES}}) {
526 for $path (sort keys %{$self->{COOKIES}{$domain}}) {
527 for $key (sort keys %{$self->{COOKIES}{$domain}{$path}}) {
528 my($version,$val,$port,$path_spec,
529 $secure,$expires,$discard,$rest) =
530 @{$self->{COOKIES}{$domain}{$path}{$key}};
531 $rest = {} unless defined($rest);
532 &$cb($version,$key,$val,$path,$domain,$port,
533 $path_spec,$secure,$expires,$discard,$rest);
542 my($self, $skip_discard) = @_;
545 my($version,$key,$val,$path,$domain,$port,
546 $path_spec,$secure,$expires,$discard,$rest) = @_;
547 return if $discard && $skip_discard;
548 my @h = ($key, $val);
549 push(@h, "path", $path);
550 push(@h, "domain" => $domain);
551 push(@h, "port" => $port) if defined $port;
552 push(@h, "path_spec" => undef) if $path_spec;
553 push(@h, "secure" => undef) if $secure;
554 push(@h, "expires" => HTTP::Date::time2isoz($expires)) if $expires;
555 push(@h, "discard" => undef) if $discard;
557 for $k (sort keys %$rest) {
558 push(@h, $k, $rest->{$k});
560 push(@h, "version" => $version);
561 push(@res, "Set-Cookie3: " . join_header_words(\@h));
563 join("\n", @res, "");
568 my($request, $url) = @_;
569 if (my $h = $request->header("Host")) {
570 $h =~ s/:\d+$//; # might have a port as well
573 return lc($url->host);
580 if($url->can('epath')) {
581 $path = $url->epath; # URI::URL method
584 $path = $url->path; # URI::_generic method
586 $path = "/" unless length $path;
590 sub _normalize_path # so that plain string compare can be used
593 $_[0] =~ s/%([0-9a-fA-F][0-9a-fA-F])/
595 $x eq "2F" || $x eq "25" ? "%$x" :
598 $_[0] =~ s/([\0-\x20\x7f-\xff])/sprintf("%%%02X",ord($1))/eg;
607 HTTP::Cookies - HTTP cookie jars
612 $cookie_jar = HTTP::Cookies->new(
613 file => "$ENV{'HOME'}/lwp_cookies.dat',
618 my $browser = LWP::UserAgent->new;
619 $browser->cookie_jar($cookie_jar);
621 Or for an empty and temporary cookie jar:
624 my $browser = LWP::UserAgent->new;
625 $browser->cookie_jar( {} );
629 This class is for objects that represent a "cookie jar" -- that is, a
630 database of all the HTTP cookies that a given LWP::UserAgent object
633 Cookies are a general mechanism which server side connections can use
634 to both store and retrieve information on the client side of the
635 connection. For more information about cookies refer to
636 <URL:http://curl.haxx.se/rfc/cookie_spec.html> and
637 <URL:http://www.cookiecentral.com/>. This module also implements the
638 new style cookies described in I<RFC 2965>.
639 The two variants of cookies are supposed to be able to coexist happily.
641 Instances of the class I<HTTP::Cookies> are able to store a collection
642 of Set-Cookie2: and Set-Cookie: headers and are able to use this
643 information to initialize Cookie-headers in I<HTTP::Request> objects.
644 The state of a I<HTTP::Cookies> object can be saved in and restored from
649 The following methods are provided:
653 =item $cookie_jar = HTTP::Cookies->new
655 The constructor takes hash style parameters. The following
656 parameters are recognized:
658 file: name of the file to restore cookies from and save cookies to
659 autosave: save during destruction (bool)
660 ignore_discard: save even cookies that are requested to be discarded (bool)
661 hide_cookie2: do not add Cookie2 header to requests
663 Future parameters might include (not yet implemented):
666 max_cookies_per_domain 20
669 no_cookies list of domain names that we never return cookies to
671 =item $cookie_jar->add_cookie_header( $request )
673 The add_cookie_header() method will set the appropriate Cookie:-header
674 for the I<HTTP::Request> object given as argument. The $request must
675 have a valid url attribute before this method is called.
677 =item $cookie_jar->extract_cookies( $response )
679 The extract_cookies() method will look for Set-Cookie: and
680 Set-Cookie2: headers in the I<HTTP::Response> object passed as
681 argument. Any of these headers that are found are used to update
682 the state of the $cookie_jar.
684 =item $cookie_jar->set_cookie( $version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest )
686 The set_cookie() method updates the state of the $cookie_jar. The
687 $key, $val, $domain, $port and $path arguments are strings. The
688 $path_spec, $secure, $discard arguments are boolean values. The $maxage
689 value is a number indicating number of seconds that this cookie will
690 live. A value <= 0 will delete this cookie. %rest defines
691 various other attributes like "Comment" and "CommentURL".
693 =item $cookie_jar->save
695 =item $cookie_jar->save( $file )
697 This method file saves the state of the $cookie_jar to a file.
698 The state can then be restored later using the load() method. If a
699 filename is not specified we will use the name specified during
700 construction. If the attribute I<ignore_discard> is set, then we
701 will even save cookies that are marked to be discarded.
703 The default is to save a sequence of "Set-Cookie3" lines.
704 "Set-Cookie3" is a proprietary LWP format, not known to be compatible
705 with any browser. The I<HTTP::Cookies::Netscape> sub-class can
706 be used to save in a format compatible with Netscape.
708 =item $cookie_jar->load
710 =item $cookie_jar->load( $file )
712 This method reads the cookies from the file and adds them to the
713 $cookie_jar. The file must be in the format written by the save()
716 =item $cookie_jar->revert
718 This method empties the $cookie_jar and re-loads the $cookie_jar
719 from the last save file.
721 =item $cookie_jar->clear
723 =item $cookie_jar->clear( $domain )
725 =item $cookie_jar->clear( $domain, $path )
727 =item $cookie_jar->clear( $domain, $path, $key )
729 Invoking this method without arguments will empty the whole
730 $cookie_jar. If given a single argument only cookies belonging to
731 that domain will be removed. If given two arguments, cookies
732 belonging to the specified path within that domain are removed. If
733 given three arguments, then the cookie with the specified key, path
734 and domain is removed.
736 =item $cookie_jar->clear_temporary_cookies
738 Discard all temporary cookies. Scans for all cookies in the jar
739 with either no expire field or a true C<discard> flag. To be
740 called when the user agent shuts down according to RFC 2965.
742 =item $cookie_jar->scan( \&callback )
744 The argument is a subroutine that will be invoked for each cookie
745 stored in the $cookie_jar. The subroutine will be invoked with
746 the following arguments:
760 =item $cookie_jar->as_string
762 =item $cookie_jar->as_string( $skip_discardables )
764 The as_string() method will return the state of the $cookie_jar
765 represented as a sequence of "Set-Cookie3" header lines separated by
766 "\n". If $skip_discardables is TRUE, it will not return lines for
767 cookies with the I<Discard> attribute.
773 L<HTTP::Cookies::Netscape>, L<HTTP::Cookies::Microsoft>
777 Copyright 1997-2002 Gisle Aas
779 This library is free software; you can redistribute it and/or
780 modify it under the same terms as Perl itself.