package HTTP::Cookies::Microsoft; use strict; our $VERSION = '6.12'; 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.12 =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