Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

765 lines
21 KiB

  1. package HTTP::Cookies;
  2. # Based on draft-ietf-http-state-man-mec-08.txt and
  3. # http://www.netscape.com/newsref/std/cookie_spec.html
  4. use strict;
  5. use HTTP::Date qw(str2time time2str);
  6. use HTTP::Headers::Util qw(split_header_words join_header_words);
  7. use LWP::Debug ();
  8. use vars qw($VERSION);
  9. $VERSION = sprintf("%d.%02d", q$Revision: 1.14 $ =~ /(\d+)\.(\d+)/);
  10. my $EPOCH_OFFSET = 0; # difference from Unix epoch
  11. if ($^O eq "MacOS") {
  12. require Time::Local;
  13. $EPOCH_OFFSET = Time::Local::timelocal(0,0,0,1,0,70);
  14. }
  15. =head1 NAME
  16. HTTP::Cookies - Cookie storage and management
  17. =head1 SYNOPSIS
  18. use HTTP::Cookies;
  19. $cookie_jar = HTTP::Cookies->new;
  20. $cookie_jar->add_cookie_header($request);
  21. $cookie_jar->extract_cookies($response);
  22. =head1 DESCRIPTION
  23. Cookies are a general mechanism which server side connections can use
  24. to both store and retrieve information on the client side of the
  25. connection. For more information about cookies refer to
  26. <URL:http://www.netscape.com/newsref/std/cookie_spec.html> and
  27. <URL:http://www.cookiecentral.com/>. This module also implements the
  28. new style cookies described in I<draft-ietf-http-state-man-mec-08.txt>.
  29. The two variants of cookies are supposed to be able to coexist happily.
  30. Instances of the class I<HTTP::Cookies> are able to store a collection
  31. of Set-Cookie2: and Set-Cookie: headers and are able to use this
  32. information to initialize Cookie-headers in I<HTTP::Request> objects.
  33. The state of a I<HTTP::Cookies> object can be saved in and restored from
  34. files.
  35. =head1 METHODS
  36. The following methods are provided:
  37. =over 4
  38. =cut
  39. # A HTTP::Cookies object is a hash. The main attribute is the
  40. # COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}.
  41. =item $cookie_jar = HTTP::Cookies->new;
  42. The constructor takes hash style parameters. The following
  43. parameters are recognized:
  44. file: name of the file to restore cookies from and save cookies to
  45. autosave: save during destruction (bool)
  46. ignore_discard: save even cookies that are requested to be discarded (bool)
  47. Future parameters might include (not yet implemented):
  48. max_cookies 300
  49. max_cookies_per_domain 20
  50. max_cookie_size 4096
  51. no_cookies list of domain names that we never return cookies to
  52. =cut
  53. sub new
  54. {
  55. my $class = shift;
  56. my $self = bless {
  57. COOKIES => {},
  58. }, $class;
  59. my %cnf = @_;
  60. for (keys %cnf) {
  61. $self->{lc($_)} = $cnf{$_};
  62. }
  63. $self->load;
  64. $self;
  65. }
  66. =item $cookie_jar->add_cookie_header($request);
  67. The add_cookie_header() method will set the appropriate Cookie:-header
  68. for the I<HTTP::Request> object given as argument. The $request must
  69. have a valid url attribute before this method is called.
  70. =cut
  71. sub add_cookie_header
  72. {
  73. my $self = shift;
  74. my $request = shift || return;
  75. my $url = $request->url;
  76. my $domain = $url->host;
  77. $domain = "$domain.local" unless $domain =~ /\./;
  78. my $secure_request = ($url->scheme eq "https");
  79. my $req_path = _url_path($url);
  80. my $req_port = $url->port;
  81. my $now = time();
  82. _normalize_path($req_path) if $req_path =~ /%/;
  83. my @cval; # cookie values for the "Cookie" header
  84. my $set_ver;
  85. my $netscape_only = 0; # An exact domain match applies to any cookie
  86. while (($domain =~ tr/././) >= 2 || # must be at least 2 dots
  87. $domain =~ /\.local$/)
  88. {
  89. LWP::Debug::debug("Checking $domain for cookies");
  90. my $cookies = $self->{COOKIES}{$domain};
  91. next unless $cookies;
  92. # Want to add cookies corresponding to the most specific paths
  93. # first (i.e. longest path first)
  94. my $path;
  95. for $path (sort {length($b) <=> length($a) } keys %$cookies) {
  96. LWP::Debug::debug("- checking cookie path=$path");
  97. if (index($req_path, $path) != 0) {
  98. LWP::Debug::debug(" path $path:$req_path does not fit");
  99. next;
  100. }
  101. my($key,$array);
  102. while (($key,$array) = each %{$cookies->{$path}}) {
  103. my($version,$val,$port,$path_spec,$secure,$expires) = @$array;
  104. LWP::Debug::debug(" - checking cookie $key=$val");
  105. if ($secure && !$secure_request) {
  106. LWP::Debug::debug(" not a secure requests");
  107. next;
  108. }
  109. if ($expires && $expires < $now) {
  110. LWP::Debug::debug(" expired");
  111. next;
  112. }
  113. if ($port) {
  114. my $found;
  115. if ($port =~ s/^_//) {
  116. # The correponding Set-Cookie attribute was empty
  117. $found++ if $port eq $req_port;
  118. $port = "";
  119. } else {
  120. my $p;
  121. for $p (split(/,/, $port)) {
  122. $found++, last if $p eq $req_port;
  123. }
  124. }
  125. unless ($found) {
  126. LWP::Debug::debug(" port $port:$req_port does not fit");
  127. next;
  128. }
  129. }
  130. if ($version > 0 && $netscape_only) {
  131. LWP::Debug::debug(" domain $domain applies to " .
  132. "Netscape-style cookies only");
  133. next;
  134. }
  135. LWP::Debug::debug(" it's a match");
  136. # set version number of cookie header.
  137. # XXX: What should it be if multiple matching
  138. # Set-Cookie headers have different versions themselves
  139. if (!$set_ver++) {
  140. if ($version >= 1) {
  141. push(@cval, "\$Version=$version");
  142. } else {
  143. $request->header(Cookie2 => "\$Version=1");
  144. }
  145. }
  146. # do we need to quote the value
  147. if ($val =~ /\W/ && $version) {
  148. $val =~ s/([\\\"])/\\$1/g;
  149. $val = qq("$val");
  150. }
  151. # and finally remember this cookie
  152. push(@cval, "$key=$val");
  153. if ($version >= 1) {
  154. push(@cval, qq(\$Path="$path")) if $path_spec;
  155. push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./;
  156. if (defined $port) {
  157. my $p = '$Port';
  158. $p .= qq(="$port") if length $port;
  159. push(@cval, $p);
  160. }
  161. }
  162. }
  163. }
  164. } continue {
  165. # Try with a more general domain, alternately stripping
  166. # leading name components and leading dots. When this
  167. # results in a domain with no leading dot, it is for
  168. # Netscape cookie compatibility only:
  169. #
  170. # a.b.c.net Any cookie
  171. # .b.c.net Any cookie
  172. # b.c.net Netscape cookie only
  173. # .c.net Any cookie
  174. if ($domain =~ s/^\.+//) {
  175. $netscape_only = 1;
  176. } else {
  177. $domain =~ s/[^.]*//;
  178. $netscape_only = 0;
  179. }
  180. }
  181. $request->header(Cookie => join("; ", @cval)) if @cval;
  182. $request;
  183. }
  184. =item $cookie_jar->extract_cookies($response);
  185. The extract_cookies() method will look for Set-Cookie: and
  186. Set-Cookie2: headers in the I<HTTP::Response> object passed as
  187. argument. Any of these headers that are found are used to update
  188. the state of the $cookie_jar.
  189. =cut
  190. sub extract_cookies
  191. {
  192. my $self = shift;
  193. my $response = shift || return;
  194. my @set = split_header_words($response->_header("Set-Cookie2"));
  195. my $netscape_cookies;
  196. unless (@set) {
  197. @set = $response->_header("Set-Cookie");
  198. return $response unless @set;
  199. $netscape_cookies++;
  200. }
  201. my $url = $response->request->url;
  202. my $req_host = $url->host;
  203. $req_host = "$req_host.local" unless $req_host =~ /\./;
  204. my $req_port = $url->port;
  205. my $req_path = _url_path($url);
  206. _normalize_path($req_path) if $req_path =~ /%/;
  207. if ($netscape_cookies) {
  208. # The old Netscape cookie format for Set-Cookie
  209. # http://www.netscape.com/newsref/std/cookie_spec.html
  210. # can for instance contain an unquoted "," in the expires
  211. # field, so we have to use this ad-hoc parser.
  212. my $now = time();
  213. my @old = @set;
  214. @set = ();
  215. my $set;
  216. for $set (@old) {
  217. my @cur;
  218. my $param;
  219. my $expires;
  220. for $param (split(/\s*;\s*/, $set)) {
  221. my($k,$v) = split(/\s*=\s*/, $param, 2);
  222. #print "$k => $v\n";
  223. my $lc = lc($k);
  224. if ($lc eq "expires") {
  225. my $etime = str2time($v);
  226. if ($etime) {
  227. push(@cur, "Max-Age" => str2time($v) - $now);
  228. $expires++;
  229. }
  230. } else {
  231. push(@cur, $k => $v);
  232. }
  233. }
  234. # push(@cur, "Port" => $req_port);
  235. push(@cur, "Discard" => undef) unless $expires;
  236. push(@cur, "Version" => 0);
  237. push(@set, \@cur);
  238. }
  239. }
  240. SET_COOKIE:
  241. for my $set (@set) {
  242. next unless @$set >= 2;
  243. my $key = shift @$set;
  244. my $val = shift @$set;
  245. LWP::Debug::debug("Set cookie $key => $val");
  246. my %hash;
  247. while (@$set) {
  248. my $k = shift @$set;
  249. my $v = shift @$set;
  250. my $lc = lc($k);
  251. # don't loose case distinction for unknown fields
  252. $k = $lc if $lc =~ /^(?:discard|domain|max-age|
  253. path|port|secure|version)$/x;
  254. if ($k eq "discard" || $k eq "secure") {
  255. $v = 1 unless defined $v;
  256. }
  257. next if exists $hash{$k}; # only first value is signigicant
  258. $hash{$k} = $v;
  259. };
  260. my %orig_hash = %hash;
  261. my $version = delete $hash{version};
  262. $version = 1 unless defined($version);
  263. my $discard = delete $hash{discard};
  264. my $secure = delete $hash{secure};
  265. my $maxage = delete $hash{'max-age'};
  266. # Check domain
  267. my $domain = delete $hash{domain};
  268. if (defined($domain) && $domain ne $req_host) {
  269. if ($domain !~ /\./ && $domain ne "local") {
  270. LWP::Debug::debug("Domain $domain contains no dot");
  271. next SET_COOKIE;
  272. }
  273. $domain = ".$domain" unless $domain =~ /^\./;
  274. if ($domain =~ /\.\d+$/) {
  275. LWP::Debug::debug("IP-address $domain illeagal as domain");
  276. next SET_COOKIE;
  277. }
  278. my $len = length($domain);
  279. unless (substr($req_host, -$len) eq $domain) {
  280. LWP::Debug::debug("Domain $domain does not match host $req_host");
  281. next SET_COOKIE;
  282. }
  283. my $hostpre = substr($req_host, 0, length($req_host) - $len);
  284. if ($hostpre =~ /\./ && !$netscape_cookies) {
  285. LWP::Debug::debug("Host prefix contain a dot: $hostpre => $domain");
  286. next SET_COOKIE;
  287. }
  288. } else {
  289. $domain = $req_host;
  290. }
  291. my $path = delete $hash{path};
  292. my $path_spec;
  293. if (defined $path && $path ne '') {
  294. $path_spec++;
  295. _normalize_path($path) if $path =~ /%/;
  296. if (!$netscape_cookies &&
  297. substr($req_path, 0, length($path)) ne $path) {
  298. LWP::Debug::debug("Path $path is not a prefix of $req_path");
  299. next SET_COOKIE;
  300. }
  301. } else {
  302. $path = $req_path;
  303. $path =~ s,/[^/]*$,,;
  304. $path = "/" unless length($path);
  305. }
  306. my $port;
  307. if (exists $hash{port}) {
  308. $port = delete $hash{port};
  309. if (defined $port) {
  310. $port =~ s/\s+//g;
  311. my $found;
  312. for my $p (split(/,/, $port)) {
  313. unless ($p =~ /^\d+$/) {
  314. LWP::Debug::debug("Bad port $port (not numeric)");
  315. next SET_COOKIE;
  316. }
  317. $found++ if $p eq $req_port;
  318. }
  319. unless ($found) {
  320. LWP::Debug::debug("Request port ($req_port) not found in $port");
  321. next SET_COOKIE;
  322. }
  323. } else {
  324. $port = "_$req_port";
  325. }
  326. }
  327. $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash)
  328. if $self->set_cookie_ok(\%orig_hash);
  329. }
  330. $response;
  331. }
  332. sub set_cookie_ok { 1 };
  333. =item $cookie_jar->set_cookie($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest)
  334. The set_cookie() method updates the state of the $cookie_jar. The
  335. $key, $val, $domain, $port and $path arguments are strings. The
  336. $path_spec, $secure, $discard arguments are boolean values. The $maxage
  337. value is a number indicating number of seconds that this cookie will
  338. live. A value <= 0 will delete this cookie. %rest defines
  339. various other attributes like "Comment" and "CommentURL".
  340. =cut
  341. sub set_cookie
  342. {
  343. my $self = shift;
  344. my($version,
  345. $key, $val, $path, $domain, $port,
  346. $path_spec, $secure, $maxage, $discard, $rest) = @_;
  347. # there must always be at least 2 dots in a domain
  348. return $self if ($domain =~ tr/././) < 2 &&
  349. $domain !~ /\.local$/;
  350. # path and key can not be empty (key can't start with '$')
  351. return $self if !defined($path) || $path !~ m,^/, ||
  352. !defined($key) || $key !~ m,[^\$],;
  353. # ensure legal port
  354. if (defined $port) {
  355. return $self unless $port =~ /^_?\d+(?:,\d+)*$/;
  356. }
  357. my $expires;
  358. if (defined $maxage) {
  359. if ($maxage <= 0) {
  360. delete $self->{COOKIES}{$domain}{$path}{$key};
  361. return $self;
  362. }
  363. $expires = time() + $maxage;
  364. }
  365. $version = 0 unless defined $version;
  366. my @array = ($version, $val,$port,
  367. $path_spec,
  368. $secure, $expires, $discard);
  369. push(@array, {%$rest}) if defined($rest) && %$rest;
  370. # trim off undefined values at end
  371. pop(@array) while !defined $array[-1];
  372. $self->{COOKIES}{$domain}{$path}{$key} = \@array;
  373. $self;
  374. }
  375. =item $cookie_jar->save( [$file] );
  376. This method file saves the state of the $cookie_jar to a file.
  377. The state can then be restored later using the load() method. If a
  378. filename is not specified we will use the name specified during
  379. construction. If the attribute I<ignore_discared> is set, then we
  380. will even save cookies that are marked to be discarded.
  381. The default is to save a sequence of "Set-Cookie3" lines.
  382. "Set-Cookie3" is a proprietary LWP format, not known to be compatible
  383. with any browser. The I<HTTP::Cookies::Netscape> sub-class can
  384. be used to save in a format compatible with Netscape.
  385. =cut
  386. sub save
  387. {
  388. my $self = shift;
  389. my $file = shift || $self->{'file'} || return;
  390. local(*FILE);
  391. open(FILE, ">$file") or die "Can't open $file: $!";
  392. print FILE "#LWP-Cookies-1.0\n";
  393. print FILE $self->as_string(!$self->{ignore_discard});
  394. close(FILE);
  395. 1;
  396. }
  397. =item $cookie_jar->load( [$file] );
  398. This method reads the cookies from the file and adds them to the
  399. $cookie_jar. The file must be in the format written by the save()
  400. method.
  401. =cut
  402. sub load
  403. {
  404. my $self = shift;
  405. my $file = shift || $self->{'file'} || return;
  406. local(*FILE, $_);
  407. local $/ = "\n"; # make sure we got standard record separator
  408. open(FILE, $file) or return;
  409. my $magic = <FILE>;
  410. unless ($magic =~ /^\#LWP-Cookies-(\d+\.\d+)/) {
  411. warn "$file does not seem to contain cookies";
  412. return;
  413. }
  414. while (<FILE>) {
  415. next unless s/^Set-Cookie3:\s*//;
  416. chomp;
  417. my $cookie;
  418. for $cookie (split_header_words($_)) {
  419. my($key,$val) = splice(@$cookie, 0, 2);
  420. my %hash;
  421. while (@$cookie) {
  422. my $k = shift @$cookie;
  423. my $v = shift @$cookie;
  424. $hash{$k} = $v;
  425. }
  426. my $version = delete $hash{version};
  427. my $path = delete $hash{path};
  428. my $domain = delete $hash{domain};
  429. my $port = delete $hash{port};
  430. my $expires = str2time(delete $hash{expires});
  431. my $path_spec = exists $hash{path_spec}; delete $hash{path_spec};
  432. my $secure = exists $hash{secure}; delete $hash{secure};
  433. my $discard = exists $hash{discard}; delete $hash{discard};
  434. my @array = ($version,$val,$port,
  435. $path_spec,$secure,$expires,$discard);
  436. push(@array, \%hash) if %hash;
  437. $self->{COOKIES}{$domain}{$path}{$key} = \@array;
  438. }
  439. }
  440. close(FILE);
  441. 1;
  442. }
  443. =item $cookie_jar->revert;
  444. This method empties the $cookie_jar and re-loads the $cookie_jar
  445. from the last save file.
  446. =cut
  447. sub revert
  448. {
  449. my $self = shift;
  450. $self->clear->load;
  451. $self;
  452. }
  453. =item $cookie_jar->clear( [$domain, [$path, [$key] ] ]);
  454. Invoking this method without arguments will empty the whole
  455. $cookie_jar. If given a single argument only cookies belonging to
  456. that domain will be removed. If given two arguments, cookies
  457. belonging to the specified path within that domain are removed. If
  458. given three arguments, then the cookie with the specified key, path
  459. and domain is removed.
  460. =cut
  461. sub clear
  462. {
  463. my $self = shift;
  464. if (@_ == 0) {
  465. $self->{COOKIES} = {};
  466. } elsif (@_ == 1) {
  467. delete $self->{COOKIES}{$_[0]};
  468. } elsif (@_ == 2) {
  469. delete $self->{COOKIES}{$_[0]}{$_[1]};
  470. } elsif (@_ == 3) {
  471. delete $self->{COOKIES}{$_[0]}{$_[1]}{$_[2]};
  472. } else {
  473. require Carp;
  474. Carp::carp('Usage: $c->clear([domain [,path [,key]]])');
  475. }
  476. $self;
  477. }
  478. sub DESTROY
  479. {
  480. my $self = shift;
  481. $self->save if $self->{'autosave'};
  482. }
  483. =item $cookie_jar->scan( \&callback );
  484. The argument is a subroutine that will be invoked for each cookie
  485. stored in the $cookie_jar. The subroutine will be invoked with
  486. the following arguments:
  487. 0 version
  488. 1 key
  489. 2 val
  490. 3 path
  491. 4 domain
  492. 5 port
  493. 6 path_spec
  494. 7 secure
  495. 8 expires
  496. 9 discard
  497. 10 hash
  498. =cut
  499. sub scan
  500. {
  501. my($self, $cb) = @_;
  502. my($domain,$path,$key);
  503. for $domain (sort keys %{$self->{COOKIES}}) {
  504. for $path (sort keys %{$self->{COOKIES}{$domain}}) {
  505. for $key (sort keys %{$self->{COOKIES}{$domain}{$path}}) {
  506. my($version,$val,$port,$path_spec,
  507. $secure,$expires,$discard,$rest) =
  508. @{$self->{COOKIES}{$domain}{$path}{$key}};
  509. $rest = {} unless defined($rest);
  510. &$cb($version,$key,$val,$path,$domain,$port,
  511. $path_spec,$secure,$expires,$discard,$rest);
  512. }
  513. }
  514. }
  515. }
  516. =item $cookie_jar->as_string( [$skip_discard] );
  517. The as_string() method will return the state of the $cookie_jar
  518. represented as a sequence of "Set-Cookie3" header lines separated by
  519. "\n". If $skip_discard is TRUE, it will not return lines for
  520. cookies with the I<Discard> attribute.
  521. =cut
  522. sub as_string
  523. {
  524. my($self, $skip_discard) = @_;
  525. my @res;
  526. $self->scan(sub {
  527. my($version,$key,$val,$path,$domain,$port,
  528. $path_spec,$secure,$expires,$discard,$rest) = @_;
  529. return if $discard && $skip_discard;
  530. my @h = ($key, $val);
  531. push(@h, "path", $path);
  532. push(@h, "domain" => $domain);
  533. push(@h, "port" => $port) if defined $port;
  534. push(@h, "path_spec" => undef) if $path_spec;
  535. push(@h, "secure" => undef) if $secure;
  536. push(@h, "expires" => HTTP::Date::time2isoz($expires)) if $expires;
  537. push(@h, "discard" => undef) if $discard;
  538. my $k;
  539. for $k (sort keys %$rest) {
  540. push(@h, $k, $rest->{$k});
  541. }
  542. push(@h, "version" => $version);
  543. push(@res, "Set-Cookie3: " . join_header_words(\@h));
  544. });
  545. join("\n", @res, "");
  546. }
  547. sub _url_path
  548. {
  549. my $url = shift;
  550. my $path = eval { $url->epath }; # URI::URL method
  551. $path = $url->path if $@; # URI::_generic method
  552. $path;
  553. }
  554. sub _normalize_path # so that plain string compare can be used
  555. {
  556. my $x;
  557. $_[0] =~ s/%([0-9a-fA-F][0-9a-fA-F])/
  558. $x = uc($1);
  559. $x eq "2F" || $x eq "25" ? "%$x" :
  560. pack("c", hex($x));
  561. /eg;
  562. $_[0] =~ s/([\0-\x20\x7f-\xff])/sprintf("%%%02X",ord($1))/eg;
  563. }
  564. =back
  565. =head1 SUB CLASSES
  566. We also provide a subclass called I<HTTP::Cookies::Netscape> which
  567. loads and saves Netscape compatible cookie files. You
  568. should be able to have LWP share Netscape's cookies by constructing
  569. your $cookie_jar like this:
  570. $cookie_jar = HTTP::Cookies::Netscape->new(
  571. File => "$ENV{HOME}/.netscape/cookies",
  572. AutoSave => 1,
  573. );
  574. Please note that the Netscape cookie file format is not able to store
  575. all the information available in the Set-Cookie2 headers, so you will
  576. probably loose some information if you save in this format.
  577. =cut
  578. package HTTP::Cookies::Netscape;
  579. use vars qw(@ISA);
  580. @ISA=qw(HTTP::Cookies);
  581. sub load
  582. {
  583. my($self, $file) = @_;
  584. $file ||= $self->{'file'} || return;
  585. local(*FILE, $_);
  586. local $/ = "\n"; # make sure we got standard record separator
  587. my @cookies;
  588. open(FILE, $file) || return;
  589. my $magic = <FILE>;
  590. unless ($magic =~ /^\# Netscape HTTP Cookie File/) {
  591. warn "$file does not look like a netscape cookies file" if $^W;
  592. close(FILE);
  593. return;
  594. }
  595. my $now = time() - $EPOCH_OFFSET;
  596. while (<FILE>) {
  597. next if /^\s*\#/;
  598. next if /^\s*$/;
  599. chomp;
  600. my($domain,$bool1,$path,$secure, $expires,$key,$val) = split(/\t/, $_);
  601. $secure = ($secure eq "TRUE");
  602. $self->set_cookie(undef,$key,$val,$path,$domain,undef,
  603. 0,$secure,$expires-$now, 0);
  604. }
  605. close(FILE);
  606. 1;
  607. }
  608. sub save
  609. {
  610. my($self, $file) = @_;
  611. $file ||= $self->{'file'} || return;
  612. local(*FILE, $_);
  613. open(FILE, ">$file") || return;
  614. print FILE <<EOT;
  615. # Netscape HTTP Cookie File
  616. # http://www.netscape.com/newsref/std/cookie_spec.html
  617. # This is a generated file! Do not edit.
  618. EOT
  619. my $now = time - $EPOCH_OFFSET;
  620. $self->scan(sub {
  621. my($version,$key,$val,$path,$domain,$port,
  622. $path_spec,$secure,$expires,$discard,$rest) = @_;
  623. return if $discard && !$self->{ignore_discard};
  624. $expires = $expires ? $expires - $EPOCH_OFFSET : 0;
  625. return if $now > $expires;
  626. $secure = $secure ? "TRUE" : "FALSE";
  627. my $bool = $domain =~ /^\./ ? "TRUE" : "FALSE";
  628. print FILE join("\t", $domain, $bool, $path, $secure, $expires, $key, $val), "\n";
  629. });
  630. close(FILE);
  631. 1;
  632. }
  633. 1;
  634. __END__
  635. =head1 COPYRIGHT
  636. Copyright 1997-1999 Gisle Aas
  637. This library is free software; you can redistribute it and/or
  638. modify it under the same terms as Perl itself.
  639. =cut