--- AnyEvent-HTTP/HTTP.pm 2010/12/31 19:22:18 1.68 +++ AnyEvent-HTTP/HTTP.pm 2010/12/31 20:50:58 1.71 @@ -154,9 +154,9 @@ The request headers to use. Currently, C may provide its own C, C, C and C headers and -will provide defaults for C, C and C (this can -be suppressed by using C for these headers in which case they won't -be sent at all). +will provide defaults at least for C, C and C +(this can be suppressed by using C for these headers in which case +they won't be sent at all). =item timeout => $seconds @@ -185,15 +185,19 @@ based on the original netscape specification. The C<$hash_ref> must be an (initially empty) hash reference which will -get updated automatically. It is possible to save the cookie_jar to +get updated automatically. It is possible to save the cookie jar to persistent storage with something like JSON or Storable, but this is not -recommended, as expiry times are currently being ignored. +recommended, as session-only cookies might survive longer than expected. -Note that this cookie implementation is not of very high quality, nor -meant to be complete. If you want complete cookie management you have to -do that on your own. C is meant as a quick fix to get some -cookie-using sites working. Cookies are a privacy disaster, do not use -them unless required to. +Note that this cookie implementation is not meant to be complete. If +you want complete cookie management you have to do that on your +own. C is meant as a quick fix to get some cookie-using sites +working. Cookies are a privacy disaster, do not use them unless required +to. + +When cookie processing is enabled, the C and C +headers will be set and handled by this module, otherwise they will be +left untouched. =item tls_ctx => $scheme | $tls_ctx @@ -362,6 +366,52 @@ _slot_schedule $_[0]; } +sub cookie_jar_extract($$$$) { + my ($jar, $uscheme, $uhost, $upath) = @_; + + %$jar = () if $jar->{version} != 1; + + my @cookies; + + while (my ($chost, $paths) = each %$jar) { + next unless ref $paths; + + if ($chost =~ /^\./) { + next unless $chost eq substr $uhost, -length $chost; + } elsif ($chost =~ /\./) { + next unless $chost eq $uhost; + } else { + next; + } + + while (my ($cpath, $cookies) = each %$paths) { + next unless $cpath eq substr $upath, 0, length $cpath; + + while (my ($cookie, $kv) = each %$cookies) { + next if $uscheme ne "https" && exists $kv->{secure}; + + if (exists $kv->{expires}) { + if (AE::now > parse_date ($kv->{expires})) { + delete $cookies->{$cookie}; + next; + } + } + + my $value = $kv->{value}; + + if ($value =~ /[=;,[:space:]]/) { + $value =~ s/([\\"])/\\$1/g; + $value = "\"$value\""; + } + + push @cookies, "$cookie=$value"; + } + } + } + + \@cookies +} + # continue to parse $_ for headers and place them into the arg sub parse_hdr() { my %hdr; @@ -446,33 +496,10 @@ # cookie processing if (my $jar = $arg{cookie_jar}) { - %$jar = () if $jar->{version} != 1; - - my @cookie; - - while (my ($chost, $v) = each %$jar) { - if ($chost =~ /^\./) { - next unless $chost eq substr $uhost, -length $chost; - } elsif ($chost =~ /\./) { - next unless $chost eq $uhost; - } else { - next; - } - - while (my ($cpath, $v) = each %$v) { - next unless $cpath eq substr $upath, 0, length $cpath; - - while (my ($k, $v) = each %$v) { - next if $uscheme ne "https" && exists $v->{secure}; - my $value = $v->{value}; - $value =~ s/([\\"])/\\$1/g; - push @cookie, "$k=\"$value\""; - } - } - } - - $hdr{cookie} = join "; ", @cookie - if @cookie; + my $cookies = cookie_jar_extract $jar, $uscheme, $uhost, $upath; + + $hdr{cookie} = join "; ", @$cookies + if @$cookies; } my ($rhost, $rport, $rscheme, $rpath); # request host, port, path @@ -644,16 +671,29 @@ # parse NAME=VALUE my @kv; - while (/\G\s* ([^=;,[:space:]]+) \s*=\s* (?: "((?:[^\\"]+|\\.)*)" | ([^=;,[:space:]]*) )/gcxs) { - my $name = $1; - my $value = $3; - - unless ($value) { - $value = $2; + while ( + m{ + \G\s* + (?: + expires \s*=\s* ([A-Z][a-z][a-z],\ [^,;]+) + | ([^=;,[:space:]]+) \s*=\s* (?: "((?:[^\\"]+|\\.)*)" | ([^=;,[:space:]]*) ) + ) + }gcxsi + ) { + my $name = $2; + my $value = $4; + + unless (defined $name) { + # expires + $name = "expires"; + $value = $1; + } elsif (!defined $value) { + # quoted + $value = $3; $value =~ s/\\(.)/$1/gs; } - push @kv, $name => $value; + push @kv, lc $name, $value; last unless /\G\s*;/gc; } @@ -663,6 +703,9 @@ my $name = shift @kv; my %kv = (value => shift @kv, @kv); + $kv{expires} ||= format_date (AE::now + $kv{"max-age"}) + if exists $kv{"max-age"}; + my $cdom; my $cpath = (delete $kv{path}) || "/"; @@ -901,8 +944,9 @@ =item $timestamp = AnyEvent::HTTP::parse_date $date -Takes a HTTP Date (RFC 2616) and returns the corresponding POSIX -timestamp, or C if the date cannot be parsed. +Takes a HTTP Date (RFC 2616) or a Cookie date (netscape cookie spec) and +returns the corresponding POSIX timestamp, or C if the date cannot +be parsed. =item $AnyEvent::HTTP::MAX_RECURSE @@ -951,8 +995,10 @@ my ($d, $m, $y, $H, $M, $S); - if ($date =~ /^[A-Z][a-z][a-z], ([0-9][0-9]) ([A-Z][a-z][a-z]) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) GMT$/) { - # RFC 822/1123, required by RFC 2616 + if ($date =~ /^[A-Z][a-z][a-z], ([0-9][0-9])[\- ]([A-Z][a-z][a-z])[\- ]([0-9][0-9][0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) GMT$/) { + # RFC 822/1123, required by RFC 2616 (with " ") + # cookie dates (with "-") + ($d, $m, $y, $H, $M, $S) = ($1, $2, $3, $4, $5, $6); } elsif ($date =~ /^[A-Z][a-z]+, ([0-9][0-9])-([A-Z][a-z][a-z])-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) GMT$/) {