package HTTP::Cookies::Microsoft; use strict; our $VERSION = '6.11'; require HTTP::Cookies; our @ISA=qw(HTTP::Cookies); sub load_cookies_from_file { my ($file) = @_; my @cookies; open (my $fh, '<', $file) || return; while (my $key = <$fh>) { chomp $key; my ($value, $domain_path, $flags, $lo_expire, $hi_expire); my ($lo_create, $hi_create, $sep); chomp($value = <$fh>); chomp($domain_path= <$fh>); chomp($flags = <$fh>); # 0x0001 bit is for secure chomp($lo_expire = <$fh>); chomp($hi_expire = <$fh>); chomp($lo_create = <$fh>); chomp($hi_create = <$fh>); chomp($sep = <$fh>); if (!defined($key) || !defined($value) || !defined($domain_path) || !defined($flags) || !defined($hi_expire) || !defined($lo_expire) || !defined($hi_create) || !defined($lo_create) || !defined($sep) || ($sep ne '*')) { last; } if ($domain_path =~ /^([^\/]+)(\/.*)$/) { my $domain = $1; my $path = $2; push @cookies, { KEY => $key, VALUE => $value, DOMAIN => $domain, PATH => $path, FLAGS =>$flags, HIXP =>$hi_expire, LOXP => $lo_expire, HICREATE => $hi_create, LOCREATE => $lo_create }; } } return \@cookies; } sub get_user_name { use Win32; use locale; my $user = lc(Win32::LoginName()); return $user; } # MSIE stores create and expire times as Win32 FILETIME, # which is 64 bits of 100 nanosecond intervals since Jan 01 1601 # # But Cookies code expects time in 32-bit value expressed # in seconds since Jan 01 1970 # sub epoch_time_offset_from_win32_filetime { my ($high, $low) = @_; #-------------------------------------------------------- # USEFUL CONSTANT #-------------------------------------------------------- # 0x019db1de 0xd53e8000 is 1970 Jan 01 00:00:00 in Win32 FILETIME # # 100 nanosecond intervals == 0.1 microsecond intervals my $filetime_low32_1970 = 0xd53e8000; my $filetime_high32_1970 = 0x019db1de; #------------------------------------ # ALGORITHM #------------------------------------ # To go from 100 nanosecond intervals to seconds since 00:00 Jan 01 1970: # # 1. Adjust 100 nanosecond intervals to Jan 01 1970 base # 2. Divide by 10 to get to microseconds (1/millionth second) # 3. Divide by 1000000 (10 ^ 6) to get to seconds # # We can combine Step 2 & 3 into one divide. # # After much trial and error, I came up with the following code which # avoids using Math::BigInt or floating pt, but still gives correct answers # If the filetime is before the epoch, return 0 if (($high < $filetime_high32_1970) || (($high == $filetime_high32_1970) && ($low < $filetime_low32_1970))) { return 0; } # Can't multiply by 0x100000000, (1 << 32), # without Perl issuing an integer overflow warning # # So use two multiplies by 0x10000 instead of one multiply by 0x100000000 # # The result is the same. # my $date1970 = (($filetime_high32_1970 * 0x10000) * 0x10000) + $filetime_low32_1970; my $time = (($high * 0x10000) * 0x10000) + $low; $time -= $date1970; $time /= 10000000; return $time; } sub load_cookie { my($self, $file) = @_; my $now = time() - $HTTP::Cookies::EPOCH_OFFSET; my $cookie_data; if (-f $file) { # open the cookie file and get the data $cookie_data = load_cookies_from_file($file); foreach my $cookie (@{$cookie_data}) { my $secure = ($cookie->{FLAGS} & 1) != 0; my $expires = epoch_time_offset_from_win32_filetime($cookie->{HIXP}, $cookie->{LOXP}); $self->set_cookie(undef, $cookie->{KEY}, $cookie->{VALUE}, $cookie->{PATH}, $cookie->{DOMAIN}, undef, 0, $secure, $expires-$now, 0); } } } sub load { my($self, $cookie_index) = @_; my $now = time() - $HTTP::Cookies::EPOCH_OFFSET; my $cookie_dir = ''; my $delay_load = (defined($self->{'delayload'}) && $self->{'delayload'}); my $user_name = get_user_name(); my $data; $cookie_index ||= $self->{'file'} || return; if ($cookie_index =~ /[\\\/][^\\\/]+$/) { $cookie_dir = $` . "\\"; } open (my $fh, '<:raw', $cookie_index) || return; if (256 != read($fh, $data, 256)) { warn "$cookie_index file is not large enough"; return; } # Cookies' index.dat file starts with 32 bytes of signature # followed by an offset to the first record, stored as a little-endian DWORD my ($sig, $size) = unpack('a32 V', $data); # check that sig is valid (only tested in IE6.0) if (($sig !~ /^Client UrlCache MMF Ver 5\.2/) || (0x4000 != $size)) { warn "$cookie_index ['$sig' $size] does not seem to contain cookies"; return; } # move the file ptr to start of the first record if (0 == seek($fh, $size, 0)) { return; } # Cookies are usually stored in 'URL ' records in two contiguous 0x80 byte sectors (256 bytes) # so read in two 0x80 byte sectors and adjust if not a Cookie. while (256 == read($fh, $data, 256)) { # each record starts with a 4-byte signature # and a count (little-endian DWORD) of 0x80 byte sectors for the record ($sig, $size) = unpack('a4 V', $data); # Cookies are found in 'URL ' records if ('URL ' ne $sig) { # skip over uninteresting record: I've seen 'HASH' and 'LEAK' records if (($sig eq 'HASH') || ($sig eq 'LEAK')) { # '-2' takes into account the two 0x80 byte sectors we've just read in if (($size > 0) && ($size != 2)) { if (0 == seek($fh, ($size-2)*0x80, 1)) { # Seek failed. Something's wrong. Gonna stop. last; } } } next; } #$REMOVE Need to check if URL records in Cookies' index.dat will # ever use more than two 0x80 byte sectors if ($size > 2) { my $more_data = ($size-2)*0x80; if ($more_data != read($fh, $data, $more_data, 256)) { last; } } (my $user_name2 = $user_name) =~ s/ /_/g; if ($data =~ /Cookie:\Q$user_name\E@([\x21-\xFF]+).*?((?:\Q$user_name\E|\Q$user_name2\E)@[\x21-\xFF]+\.txt)/) { my $cookie_file = $cookie_dir . $2; # form full pathname if (!$delay_load) { $self->load_cookie($cookie_file); } else { my $domain = $1; # grab only the domain name, drop everything from the first dir sep on if ($domain =~ m{[\\/]}) { $domain = $`; } # set the delayload cookie for this domain with # the cookie_file as cookie for later-loading info $self->set_cookie(undef, 'cookie', $cookie_file, '//+delayload', $domain, undef, 0, 0, $now+86_400, 0); } } } 1; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Cookies::Microsoft - Access to Microsoft cookies files =head1 VERSION version 6.11 =head1 SYNOPSIS use LWP; use HTTP::Cookies::Microsoft; use Win32::TieRegistry(Delimiter => "/"); my $cookies_dir = $Registry-> {"CUser/Software/Microsoft/Windows/CurrentVersion/Explorer/Shell Folders/Cookies"}; $cookie_jar = HTTP::Cookies::Microsoft->new( file => "$cookies_dir\\index.dat", 'delayload' => 1, ); my $browser = LWP::UserAgent->new; $browser->cookie_jar( $cookie_jar ); =head1 DESCRIPTION This is a subclass of C which loads Microsoft Internet Explorer 5.x and 6.x for Windows (MSIE) cookie files. See the documentation for L. =head1 METHODS The following methods are provided: =over 4 =item $cookie_jar = HTTP::Cookies::Microsoft->new; The constructor takes hash style parameters. In addition to the regular HTTP::Cookies parameters, HTTP::Cookies::Microsoft recognizes the following: delayload: delay loading of cookie data until a request is actually made. This results in faster runtime unless you use most of the cookies since only the domain's cookie data is loaded on demand. =back =head1 CAVEATS Please note that the code DOESN'T support saving to the MSIE cookie file format. =head1 AUTHOR Johnny Lee =head1 COPYRIGHT Copyright 2002 Johnny Lee This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2002 by Gisle Aas. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __END__ #ABSTRACT: Access to Microsoft cookies files