File Coverage

lib/Win32/UrlCache.pm
Criterion Covered Total %
statement 42 165 25.4
branch 1 48 2.0
condition 0 18 0.0
subroutine 14 30 46.6
pod 4 4 100.0
total 61 265 23.0


line stmt bran cond sub pod time code
1             package Win32::UrlCache;
2            
3 1     1   54620 use strict;
  1         3  
  1         46  
4 1     1   6 use warnings;
  1         2  
  1         34  
5 1     1   6 use Carp;
  1         5  
  1         141  
6            
7             BEGIN {
8 1 50   1   22 if ( $^O eq 'MSWin32' ) {
9 0         0 require Win32::UrlCache::FileTime;
10 0         0 Win32::UrlCache::FileTime->import;
11             }
12             else {
13 1         470 require Win32::UrlCache::FileTimePP;
14 1         155 Win32::UrlCache::FileTimePP->import;
15             }
16             }
17            
18             our $VERSION = '0.06';
19            
20 1     1   10 use constant BADFOOD => chr(0x0D).chr(0xF0).chr(0xAD).chr(0x0B);
  1         2  
  1         3223  
21            
22             sub new {
23 0     0 1   my $class = shift;
24 0           my $self = bless {}, $class;
25            
26 0           $self->_read_file( @_ );
27 0           $self->_version_check;
28 0           $self->_size_check;
29 0           $self->_get_pointer_to_first_hash;
30            
31 0           $self;
32             }
33            
34             sub _read_file {
35 0     0     my ($self, $file) = @_;
36            
37 0 0         open my $fh, '<', $file or croak $!;
38 0           binmode $fh;
39 0           sysread $fh, ( my $data ), ( my $size = -s $fh );
40 0           close $fh;
41            
42 0           $self->{_data} = $data;
43 0           $self->{_size} = $size;
44 0           $self->{_pos} = 0;
45             }
46            
47             sub _version_check {
48 0     0     my $self = shift;
49 0           my $header = 'Client UrlCache MMF Ver 5.2';
50 0           my $read = $self->_read_string;
51 0 0         unless ( $read eq $header ) {
52 0           croak "unsupported file type: $read";
53             }
54             }
55            
56             sub _size_check {
57 0     0     my $self = shift;
58 0           my $read = _to_int( $self->_read );
59 0 0         unless ( $read == $self->{_size} ) {
60 0           croak "index file seems broken: $read / ".$self->{_size};
61             }
62             }
63            
64             sub _get_pointer_to_first_hash {
65 0     0     my $self = shift;
66            
67 0           $self->{_from} = _to_int( $self->_read );
68             }
69            
70             sub _read_hashes {
71 0     0     my ($self, $target, %options) = @_;
72            
73 0           my $pointer = $self->{_from};
74            
75 0           while( $pointer ) {
76 0 0         unless ( $self->_read_from( $pointer ) eq 'HASH' ) {
77 0           croak "index file seems broken: HASH not found";
78             }
79 0           my $hash_length = _to_int( $self->_read );
80 0           my $next_hash = _to_int( $self->_read );
81 0           my $unknown = _to_int( $self->_read );
82 0           my $hash_end = $pointer + ( $hash_length * 0x80 );
83            
84 0           while ( $self->{_pos} < $hash_end ) {
85 0           my ( $hashkey, $offset ) = ( $self->_read, $self->_read );
86 0 0         next if $offset eq BADFOOD;
87            
88 0           my $int_offset = _to_int( $offset );
89 0 0         next unless $int_offset;
90            
91             # last of the offset should be 0x80/0x00 (not 0x03 etc)
92 0 0         next unless ( $int_offset & 0xf ) == 0;
93            
94 0           my $tag = $self->_test_from( $int_offset );
95 0 0         next if $tag eq BADFOOD;
96            
97 0 0         if ( $tag =~ /^(?:URL|REDR|LEAK)/ ) {
98 0 0 0       next if $target && $target ne $tag;
99            
100 0           my $pos = $self->{_pos};
101 0           $self->_read_entry( $int_offset, %options );
102 0           $self->{_pos} = $pos;
103             }
104             }
105 0 0         $pointer = $next_hash or last;
106             }
107             }
108            
109             sub _read_entry {
110 0     0     my ($self, $offset, %options) = @_;
111            
112 0           my $tag = $self->_read_from( $offset );
113 0           $tag =~ s/ $//;
114 0           my $class = 'Win32::UrlCache::'.$tag;
115            
116 0           my $item;
117 0 0         if ( $tag eq 'REDR' ) {
118 0           my $block = $self->_read;
119 0           my $unknown = $self->_read(8);
120 0           my $url = $self->_read_string;
121            
122 0           $item = { url => $url };
123             }
124 0 0 0       if ( $tag eq 'URL' or $tag eq 'LEAK' ) {
125 0           my $block = $self->_read;
126 0           my $last_modified = filetime( $self->_read(8) );
127 0           my $last_accessed = filetime( $self->_read(8) );
128 0           my $maybe_expire = $self->_read(8);
129 0           my $maybe_filesize = $self->_read(8);
130 0           my $unknown = $self->_read(20);
131 0           my $offset_to_filename = _to_int( $self->_read );
132 0           my $unknown2 = $self->_read;
133 0           my $offset_to_headers = _to_int( $self->_read );
134 0           my $unknown3 = $self->_read(32);
135 0           my $url = $self->_read_string;
136 0 0         my $filename = $offset_to_filename
137             ? $self->_read_string_from( $offset + $offset_to_filename )
138             : '';
139 0 0         my $headers = $offset_to_headers
140             ? $self->_read_string_from( $offset + $offset_to_headers )
141             : '';
142            
143 0           $item = {
144             url => $url,
145             filename => $filename,
146             headers => $headers,
147             filesize => $maybe_filesize,
148             last_modified => $last_modified,
149             last_accessed => $last_accessed,
150             };
151             }
152 0 0         return unless $item;
153            
154 0           my $object = bless $item, $class;
155 0 0         if ( $options{callback} ) {
156 0           my $ret = $options{callback}->( $object );
157 0 0         return unless $ret;
158             }
159            
160 0 0 0       if ( $options{extract_title} && $item->{filename} && $^O eq 'MSWin32' ) {
      0        
161 0           require Win32::UrlCache::Title;
162 0           Win32::UrlCache::Title->import;
163 0           $item->title( Win32::UrlCache::Title->extract( $item->filename ) );
164             }
165            
166 0   0       push @{ $self->{$tag} ||= [] }, $object;
  0            
167             }
168            
169             sub urls {
170 0     0 1   my $self = shift;
171 0           $self->_read_hashes( 'URL ', @_ );
172 0 0         return @{ $self->{URL} || [] };
  0            
173             }
174            
175             sub redrs {
176 0     0 1   my $self = shift;
177 0           $self->_read_hashes( 'REDR', @_ );
178 0 0         return @{ $self->{REDR} || [] };
  0            
179             }
180            
181             sub leaks {
182 0     0 1   my $self = shift;
183 0           $self->_read_hashes( 'LEAK', @_ );
184 0 0         return @{ $self->{LEAK} || [] };
  0            
185             }
186            
187             sub _to_int {
188 0     0     my $dword = shift;
189 0           my @bytes = split //, $dword;
190             return (
191 0           ord( $bytes[3] ) * (256 ** 3) +
192             ord( $bytes[2] ) * (256 ** 2) +
193             ord( $bytes[1] ) * (256 ** 1) +
194             ord( $bytes[0] ) * (256 ** 0)
195             );
196             }
197            
198             sub _read {
199 0     0     my ($self, $length) = @_;
200            
201 0   0       $length ||= 4;
202 0           my $str = substr( $self->{_data}, $self->{_pos}, $length );
203 0           $self->{_pos} += $length;
204 0           return $str;
205             }
206            
207             sub _read_from {
208 0     0     my ($self, $from, $length) = @_;
209 0           $self->{_pos} = $from;
210 0           $self->_read( $length );
211             }
212            
213             sub _read_string {
214 0     0     my $self = shift;
215 0           my $from = $self->{_pos};
216 0           my $to = index( $self->{_data}, "\000", $from );
217 0           my $str = substr( $self->{_data}, $from, $to - $from );
218 0           $self->{_pos} = $to + 1;
219 0           return $str;
220             }
221            
222             sub _read_string_from {
223 0     0     my ($self, $from) = @_;
224 0           $self->{_pos} = $from;
225 0           $self->_read_string;
226             }
227            
228             sub _test_from {
229 0     0     my ($self, $from, $length) = @_;
230 0   0       $length ||= 4;
231 0 0         $from = $self->{_pos} unless defined $from;
232 0           return substr( $self->{_data}, $from, $length );
233             }
234            
235             package #
236             Win32::UrlCache::URL;
237            
238 1     1   12 use strict;
  1         3  
  1         43  
239 1     1   8 use warnings;
  1         93  
  1         41  
240 1     1   5 use base qw( Class::Accessor::Fast );
  1         1  
  1         1191  
241            
242             __PACKAGE__->mk_accessors(qw(
243             url filename headers filesize last_modified last_accessed
244             title
245             ));
246            
247             package #
248             Win32::UrlCache::LEAK;
249            
250 1     1   3693 use strict;
  1         1  
  1         26  
251 1     1   5 use warnings;
  1         1  
  1         32  
252 1     1   4 use base qw( Class::Accessor::Fast );
  1         1  
  1         87  
253            
254             __PACKAGE__->mk_accessors(qw(
255             url filename headers filesize last_modified last_accessed
256             title
257             ));
258            
259             package #
260             Win32::UrlCache::REDR;
261            
262 1     1   6 use strict;
  1         2  
  1         40  
263 1     1   5 use warnings;
  1         3  
  1         30  
264 1     1   4 use base qw( Class::Accessor::Fast );
  1         1  
  1         87  
265            
266             __PACKAGE__->mk_accessors(qw( url ));
267            
268             1;
269            
270             __END__