File Coverage

blib/lib/Finnigan/OLE2File.pm
Criterion Covered Total %
statement 120 135 88.8
branch 16 32 50.0
condition 1 6 16.6
subroutine 17 19 89.4
pod 13 13 100.0
total 167 205 81.4


line stmt bran cond sub pod time code
1             package Finnigan::OLE2File;
2              
3 2     2   10 use strict;
  2         5  
  2         90  
4 2     2   10 use warnings FATAL => qw( all );
  2         4  
  2         83  
5             our $VERSION = 0.0206;
6              
7 2     2   9 use Finnigan;
  2         4  
  2         38  
8 2     2   9 use base 'Finnigan::Decoder';
  2         4  
  2         141  
9 2     2   11 use Carp qw/confess/;
  2         10  
  2         102  
10              
11 2     2   11 use overload ('""' => 'stringify');
  2         4  
  2         17  
12              
13             my $NDIF = 109;
14             my $FAT_POINTER_SIZE = 4; # 32 bits
15             my $PROPERTY_SIZE = 128;
16             my $UNUSED = 0xFFFFFFFF; # -1
17             my $END_OF_CHAIN = 0xFFFFFFFE; # -2
18             my $FAT_SECTOR = 0xFFFFFFFD; # -3
19             my $DIF_SECTOR = 0xFFFFFFFC; # -4
20              
21             my %SPECIAL = ($END_OF_CHAIN => 1, $UNUSED => 1, $FAT_SECTOR => 1, $DIF_SECTOR => 1);
22              
23             sub decode {
24 1     1 1 3 my ($class, $stream, $version) = @_;
25              
26 1         6 my @fields = (
27             "magic" => ['a8', 'RawBytes'],
28             "header" => ['object', 'Finnigan::OLE2Header'],
29             );
30              
31 1         6 my $self = Finnigan::Decoder->read($stream, \@fields, $version);
32 1         10 bless $self, $class;
33              
34 1 50       7 die "incorect OLE2 id" unless $self->magic eq "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
35              
36             # configure values
37 1         4 $self->{stream} = $stream;
38 1         5 $self->{"sector size"} = (1 << $self->header->bb_log);
39 1         3 $self->{"fat count"} = $self->header->bb_count;
40 1         5 $self->{"items per fat"} = $self->{"sector size"} / 4; # (size of sector pointer)
41 1         9 $self->{"ss size"} = (1 << $self->header->sb_log);
42 1         4 $self->{"items per minifat"} = $self->{"items per fat"};
43              
44             # read the DIF (Double-Indirect FAT)
45 1         6 $self->SUPER::decode(
46             $stream,
47             ["dif" => ['object', 'Finnigan::OLE2DIF']],
48             [$self->header->dif_start, $self->header->dif_count],
49             );
50              
51 1         6 $self->{"block ref addr"} =
52             $self->{addr}
53             + $self->{data}->{magic}->{size}
54             + $self->{data}->{header}->{size}
55             + $NDIF * $FAT_POINTER_SIZE;
56              
57             # read the big FAT
58 1         4 $self->{fat} = [];
59 1         3 my $start = 0;
60 1         8 my $count = $self->{"items per fat"};
61 1         2 for my $index ( 0 .. scalar @{$self->dif->sect} - 1 ) {
  1         6  
62 2         6 my $block = $self->dif->sect->[$index];
63 2 100       8 last if $block == $UNUSED;
64 1         5 $self->seek_block($block);
65 1         10 $self->SUPER::decode(
66             $stream,
67             ["fat[$block]" => ['object', 'Finnigan::OLE2FAT']],
68             [$start, $count],
69             );
70 1         4 push @{$self->{fat}}, $self->{data}->{"fat[$block]"}->{value};
  1         7  
71             }
72              
73             # Read the mini-FAT
74 1         4 $self->{minifat} = [];
75 1         4 my @chain = $self->get_chain($self->header->sb_start, 'big');
76 1         3 $start = 0;
77 1         3 $count = $self->{"items per minifat"};
78 1         3 for my $index ( 0 .. $#chain ) {
79 1         4 my $block = $chain[$index];
80 1         4 $self->seek_block($block);
81 1         8 $self->SUPER::decode(
82             $stream,
83             ["minifat[$block]" => ['object', 'Finnigan::OLE2FAT']],
84             [$start, $count],
85             );
86 1         4 push @{$self->{minifat}}, $self->{data}->{"minifat[$block]"}->{value};
  1         6  
87 1         4 $start += $count;
88             }
89            
90             # Read properties
91 1         12 @chain = $self->get_chain($self->header->bb_start, 'big');
92 1         5 my $prop_per_sector = int( $self->{"sector size"} / $PROPERTY_SIZE );
93 1         5 $self->{properties} = [];
94 1         3 foreach my $block ( @chain ) {
95 2         6 $self->seek_block($block);
96 2         6 foreach my $index ( 1 .. $prop_per_sector ) {
97             # read the property name and return to start
98 6         10 my $addr = tell $stream;
99 6         7 my $bytes_to_read = 64;
100 6         8 my $rec;
101 6         27 my $nbytes = CORE::read $stream, $rec, $bytes_to_read;
102 6 50       21 $nbytes == $bytes_to_read
103             or die "could not read the $bytes_to_read bytes of property name at $addr";
104 6         48 seek $stream, $addr, 0;
105              
106 6         8 my $charset;
107 6 100       35 if ( $rec =~ /^\w\0\w\0/ ) {
    50          
    50          
108 5         10 $charset = "UTF-16-LE";
109             }
110             elsif ( $rec =~ /^\0\w\0\w/ ) {
111 0         0 $charset = "UTF-16-BE";
112             }
113             elsif ( $rec =~ /^\0{64}/ ) {
114 1         4 last; # a null entry indicates the end of directory
115             }
116             else {
117 0 0       0 my @hex = map { if (/[A-z]/) {$_} else { sprintf "%2.2x", ord($_) } } split("", $rec);
  0         0  
  0         0  
  0         0  
118 0         0 confess "unknown encoding or not a string at $addr (@hex)";
119             }
120              
121 5         24 $self->SUPER::decode(
122             $stream,
123             ["property[$block][$index]" => ['object', 'Finnigan::OLE2Property']],
124             [$self->header->bb_log, $charset]
125             );
126 5         182 push @{$self->{properties}}, $self->{data}->{"property[$block][$index]"}->{value};
  5         26  
127             }
128             }
129              
130             # read the small block depot (which resides in the root entry's data stream)
131 1         14 my $dir = new Finnigan::OLE2DirectoryEntry($self, 0);
132 1         3 $self->{rootdir} = $dir;
133 1         6 $self->{sbd} = $dir->data;
134              
135 1         9 return $self;
136             }
137              
138             sub list {
139 0     0 1 0 shift->{rootdir}->list();
140             }
141              
142             sub stream {
143 6     6 1 98 shift->{stream};
144             }
145              
146             sub magic {
147 1     1 1 65 shift->{data}->{magic}->{value};
148             }
149              
150             sub header {
151 16     16 1 120 shift->{data}->{header}->{value};
152             }
153              
154             sub dif {
155 6     6 1 40 shift->{data}->{dif}->{value};
156             }
157              
158             sub fat {
159 0     0 1 0 my ( $self, $block ) = @_;
160 0         0 shift->{data}->{"fat[$block]"}->{value};
161             }
162              
163             sub stringify {
164 1     1 1 3 my $self = shift;
165              
166 1         2 my $n = scalar @{$self->{properties}};
  1         3  
167 1         8 return "Windows Compound Binary File: $n nodes";
168             }
169              
170             sub sector_size {
171 51     51 1 80 my ( $self, $stream_size ) = @_;
172 51 50       92 return $self->{"sector size"} unless $stream_size; # big by default
173 51 50 33     379 return $self->{"sector size"} if $stream_size and $stream_size eq 'big';
174 0 0 0     0 return $self->{"ss size"} if $stream_size and $stream_size eq 'mini';
175             }
176              
177             sub seek_block {
178 7     7 1 12 my ($self, $block) = @_;
179 7         24 seek $self->{stream}, $self->{"block ref addr"} + $block * $self->sector_size('big'), 0;
180             }
181              
182             sub read {
183 3     3 1 8 my ($self, $stream_size, $start, $blocks_to_read) = @_;
184 3         15 my $sector_size = $self->sector_size($stream_size);
185 3 50       11 if ( $stream_size eq 'big' ) {
186 3         7 my $rec;
187 3         10 my $addr = tell $self->stream;
188 3         6 my $bytes_to_read = $blocks_to_read * $sector_size;
189 3         8 $self->seek_block($start);
190 3         10 my $nbytes = CORE::read $self->stream, $rec, $bytes_to_read;
191 3 50       19 $nbytes == $bytes_to_read
192             or die "could not read $bytes_to_read bytes of property name at $addr";
193 3         58 return $rec;
194             }
195             else {
196 0         0 return substr $self->{sbd}, $start*$sector_size, $blocks_to_read*$sector_size;
197             }
198             }
199              
200             sub get_chain {
201 6     6 1 14 my ($self, $start, $stream_size) = @_;
202              
203 6         11 my ($fat, $items_per_fat, $err_prefix);
204 0         0 my @result;
205 6 50       19 if ( $stream_size eq 'mini' ) {
206 0         0 $fat = $self->{minifat};
207 0         0 $items_per_fat = $self->{"items per minifat"};
208 0         0 $err_prefix = "SFAT chain";
209             }
210             else {
211 6         12 $fat = $self->{fat};
212 6         11 $items_per_fat = $self->{"items per fat"};
213 6         8 $err_prefix = "FAT chain";
214             }
215              
216 6         8 my $block = $start;
217 6         9 my %block_set;
218 6         9 my $previous = $block;
219 6         25 while ( $block != $END_OF_CHAIN ) {
220 47 50       106 die (
221             sprintf "%s: Invalid block index (0x%08x), previous=%s", $err_prefix, $block, $previous
222             ) if $SPECIAL{$block};
223 47 50       101 die ( sprintf "%s: Found a loop (%s=>%s)", $err_prefix, $previous, $block )
224             if $block_set{$block};
225 47         92 $block_set{$block}++;
226 47         73 push @result, $block;
227 47         59 $previous = $block;
228 47         65 my $index = int($block / $items_per_fat);
229 47         134 $block = $fat->[$index]->sect->[$block];
230 47 50       148 last unless defined $block;
231             }
232              
233 6         54 return @result;
234             }
235              
236             sub find {
237 1     1 1 4 my ($self, $path) = @_;
238 1         10 $self->{rootdir}->find($path);
239             }
240              
241             1;
242             __END__