File Coverage

lib/Win32/PEFile/SectionHandlers.pm
Criterion Covered Total %
statement 310 498 62.2
branch 68 174 39.0
condition 20 92 21.7
subroutine 32 63 50.7
pod 0 2 0.0
total 430 829 51.8


line stmt bran cond sub pod time code
1             package Win32::PEFile::SectionHandlers;
2            
3 1     1   5 use strict;
  1         2  
  1         31  
4 1     1   4 use warnings;
  1         2  
  1         23  
5 1     1   4 use Carp qw();
  1         1  
  1         19  
6 1     1   396 use Win32::PEFile::PEConstants;
  1         2  
  1         1060  
7            
8             our %_EntryPoints;
9             our %_SectionNames;
10            
11            
12             sub parseSection {
13 0     0 0 0 my ($section) = @_;
14             }
15            
16            
17             sub register {
18 4     4 0 13 my ($class, $sectionCode, @methods) = @_;
19            
20 4         7 $_SectionNames{$sectionCode} = $class;
21 4         25 $_EntryPoints{$_} = $class for @methods;
22            
23 4         38 my $doUIName = $class->can('uiName');
24            
25 4 50       14 $doUIName or die "'$class' requires a uiName method\n";
26            
27 4         8 my $uiName = $doUIName->();
28 4         24 (my $type = $class) =~ s/.*:(\w+)Handler$/$1/;
29            
30             $class->can("${_}_$type")
31             or die "'$class' requires a ${_}_$type method\n"
32 4   50     79 for '_calcSize', "_parse", "_assemble";
33             }
34            
35            
36             sub _readSectionDataEntry {
37 13     13   20 my ($self, $fh, $offset, $sectionOffset) = @_;
38 13         20 my $oldPos = tell $fh;
39 13         17 my %sect;
40            
41 13         178 seek $fh, $offset + $sectionOffset, 0;
42 13 50       97 read $fh, my $rData, 16 or die "file read error: $!\n";
43 13         34 my ($dataRVA, $size, $codePage) = unpack ('VVVV', $rData);
44 13         40 my $imageRVA = $self->{DataDir}{'.rsrc'}{imageRVA};
45 13         21 my $resDataOffset = $dataRVA - $imageRVA;
46            
47 13         26 $sect{sectCodepage} = $codePage;
48 13         24 $sect{sectOffset} = $offset;
49 13         93 seek $fh, $resDataOffset + $sectionOffset, 0;
50 13 50       123 read $fh, $sect{sectData}, $size or die "file read error: $!\n";
51            
52 13         96 seek $fh, $oldPos, 0;
53 13         57 return \%sect;
54             }
55            
56            
57             sub _readSzStr {
58 4     4   8 my ($self, $fh, $offset) = @_;
59 4         15 my $oldPos = tell $fh;
60 4         6 my $str = '';
61            
62 4 50       1266 seek $fh, $offset, 0 if defined $offset;
63 4 50       34 read $fh, my $strBytes, 2 or die "file read error: $!\n";
64 4         14 $strBytes = 2 * unpack ('v', $strBytes);
65            
66 4 50       11 if ($strBytes) {
67 4 50       17 read $fh, $str, $strBytes or die "file read error: $!\n";
68 4         22 $str = Encode::decode('UTF-16LE', $str);
69             }
70            
71 4 50       14478 seek $fh, $oldPos, 0 if defined $offset;
72 4         13 return $str;
73             }
74            
75            
76             package Win32::PEFile::rsrcHandler;
77            
78 1     1   7 use Win32::PEFile::PEConstants;
  1         1  
  1         6047  
79            
80             my @kDirTableFields = qw(
81             Characteristics TimeDate MajorVersion MinorVersion
82             NumNameEntries NumIDEntries
83             );
84            
85            
86             push @Win32::PEFile::rsrcHandler::ISA, 'Win32::PEFile::SectionHandlers';
87             Win32::PEFile::rsrcHandler->register(
88             '.rsrc',
89             qw'addResource addVersionResource
90             getVersionStrings getFixedVersionValues getResourceData
91             getVersionCount'
92             );
93            
94            
95             sub uiName {
96 1     1   3 return 'Resources';
97             }
98            
99            
100             sub addResource {
101 0     0   0 my ($self, %params) = @_;
102 0         0 my @requiredFields = qw{data lang name type};
103 0         0 my @missing = grep {!exists $params{$_}} @requiredFields;
  0         0  
104            
105 0 0       0 Carp::croak "The following fields are required by addResource: @missing"
106             if @missing;
107            
108 0   0     0 $self->{SecData}{'.rsrc'}{Entries} ||= {};
109            
110 0         0 my $rsrc = $self->_buildResourceEntry(
111             -path => [$params{type}, $params{name}, $params{lang}],
112             -root => $self->{SecData}{'.rsrc'},
113             %params
114             );
115            
116 0   0     0 my $header = $self->{SecData}{'.rsrc'}{header} ||= {};
117 0   0     0 my $dDir = $self->{DataDir}{'.rsrc'} ||= {};
118            
119 0         0 $header->{VirtualSize} += length $rsrc->{sectData};
120 0   0     0 $dDir->{size} ||= undef;
121 0   0     0 $dDir->{filePos} ||= undef;
122 0   0     0 $dDir->{fileBias} ||= undef;
123 0   0     0 $dDir->{imageRVA} ||= 16384;
124             }
125            
126            
127             sub addVersionResource {
128 0     0   0 my ($self, %params) = @_;
129 0         0 my %verStringKeys = map {$_ => undef} @kVersionStringKeys;
  0         0  
130 0         0 my @strings = grep {exists $verStringKeys{$_}} keys %params;
  0         0  
131            
132 0 0       0 $params{lang} = 0x0409 if ! defined $params{lang};
133 0         0 $params{strings}{$_} = $params{$_} for @strings;
134            
135 0 0       0 $params{name} = 1 if ! defined $params{name};
136 0         0 $self->addResource(
137             data => $self->_buildVersionInfo(%params),
138             lang => $params{lang},
139             type => 'VERSION',
140             name => $params{name},
141             );
142             }
143            
144            
145             sub getVersionStrings {
146 2     2   1124 my ($self, $lang) = @_;
147 2         6 my $verResource;
148            
149 2         11 ($verResource, $lang) = $self->getResourceData('VERSION', 1, $lang);
150            
151 2   50     13 my $entries = $self->{SecData}{'.rsrc'}{Entries} ||= {};
152            
153 2 100 66     16 return if !$verResource || !exists $entries->{VERSION}{1}{$lang};
154            
155 1         1370 $self->_parseVersionInfo($lang);
156            
157 1         51 my $strings = $entries->{VERSION}{1}{$lang}{StringFileInfo};
158 1 50       17 return wantarray ? ($strings, $lang) : $strings;
159             }
160            
161            
162             sub getVersionCount {
163 3     3   2278 my ($self, $lang) = @_;
164 3         7 my $count = 0;
165            
166 3         21 my ($verResource, $langDef) = $self->getResourceData('VERSION', 1, $lang);
167 3         10 my $entries = $self->{SecData}{'.rsrc'}{Entries}{VERSION};
168            
169 3         11 for my $version (keys %$entries) {
170 3 100       26 $count +=
171 3         6 grep {!defined $lang or $_ == $lang} keys %{$entries->{$version}};
  3         11  
172             }
173            
174 3         18 return $count;
175             }
176            
177            
178             sub getFixedVersionValues {
179 0     0   0 my ($self, $lang) = @_;
180 0         0 my $verResource;
181            
182 0         0 ($verResource, $lang) = $self->getResourceData('VERSION', 1, $lang);
183            
184 0         0 my $entries = $self->{SecData}{'.rsrc'}{Entries};
185            
186 0 0 0     0 return if !$verResource || !exists $entries->{VERSION}{1}{$lang};
187            
188 0         0 $self->_parseVersionInfo($lang);
189 0         0 my $info = $entries->{VERSION}{1}{$lang}{FixedFileInfo};
190 0 0       0 return wantarray ? ($info, $lang) : $info;
191             }
192            
193            
194             sub getResourceData {
195 5     5   13 my ($self, $type, $name, $lang) = @_;
196            
197             return
198 5 50       27 if !$self->_parse_rsrc(
199             'rsrc',
200             type => $type,
201             name => $name,
202             lang => $lang
203             );
204            
205 5         15 my $entries = $self->{SecData}{'.rsrc'}{Entries};
206            
207 5 100       20 return if !exists $entries->{$type};
208            
209 4 100 66     19 if ( !defined $lang
210             || !exists $entries->{$type}{$name}{$lang})
211             {
212 3         5 my %langs = map {$_ => 1} keys %{$entries->{$type}{$name}};
  3         15  
  3         17  
213            
214 3   100     14 $lang ||= 0x0409; # Default to US English
215 3 50       16 $lang = (keys %langs)[0] if !exists $langs{$lang};
216             }
217            
218 4         20 return $entries->{$type}{$name}{$lang}{sectData}, $lang;
219             }
220            
221            
222             sub _parse_rsrc {
223 5     5   10 my ($self) = @_;
224 5         14 my $fName = $self->{'-file'};
225            
226 5 50       145 return if !-f $fName;
227            
228 5         13 my $rsrcHdr = $self->{DataDir}{'.rsrc'};
229            
230 5 50       220 open my $peFile, '<', $fName or die "unable to open '$fName' - $!\n";
231 5         16 binmode ($peFile);
232            
233 5         18 $self->{SecData}{'.rsrc'}{Entries} = {};
234 5         1301 $self->_parseRsrcTable($self->{SecData}{'.rsrc'}{Entries},
235             $peFile, 0, $rsrcHdr->{filePos});
236 5         69 close $peFile;
237 5         38 return 1;
238             }
239            
240            
241             sub _parseRsrcTable {
242 31     31   52 my ($self, $rsrc, $fh, $filePos, $sectionOffset, $level) = @_;
243 31         50 my $oldPos = tell $fh;
244            
245 31         31 ++$level;
246 31         219 seek $fh, $filePos + $sectionOffset, 0;
247 31         1848 read $fh, (my $rData), 16;
248            
249 31         44 my %dirTable;
250 31         73 my $entries = $self->{SecData}{'.rsrc'}{Entries};
251            
252 31         219 @dirTable{@kDirTableFields} = unpack ('VVvvvv', $rData);
253 31         84 $self->{SecData}{'.rsrc'}{dirTable} = \%dirTable;
254            
255 31         76 my ($numNames, $numIDs) = @dirTable{qw(NumNameEntries NumIDEntries)};
256            
257             # Handle IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]
258 31   100     146 while ($numNames || $numIDs) {
259 39         98 read $fh, $rData, 8;
260            
261 39         75 my ($RVAOrID, $RVA) = unpack ('VV', $rData);
262 39         62 my $addr = ($RVA & ~0x80000000);
263 39         42 my $rsrcId;
264            
265 39 100       104 if ($numNames) {
    50          
266             # Fetch the entry name. $RVAOrID is the RVA
267 4         5 --$numNames;
268 4         20 $rsrcId =
269             $self->_readSzStr($fh,
270             ($RVAOrID & ~0x80000000) + $sectionOffset);
271            
272             } elsif ($numIDs) {
273             # Resource ID. $RVAOrID is the ID
274 35         34 --$numIDs;
275 35         36 $rsrcId = $RVAOrID;
276 35 100 66     2466 $rsrcId = $rsrcTypes{$rsrcId}
277             if $level == 1 && exists $rsrcTypes{$rsrcId};
278             }
279            
280 39 100       82 if (0 != ($RVA & 0x80000000)) {
281             # It's a sub table entry
282 26         1332 $rsrc->{$rsrcId} = {};
283 26         129 $self->_parseRsrcTable($rsrc->{$rsrcId}, $fh, $addr, $sectionOffset,
284             $level);
285             } else {
286             # It's a data entry
287 13         356 $rsrc->{$rsrcId} =
288             $self->_readSectionDataEntry($fh, $RVA, $sectionOffset);
289 13         88 next;
290             }
291             }
292            
293 31         260 seek $fh, $oldPos, 0;
294             }
295            
296            
297             sub _parseVersionInfo {
298 1     1   5 my ($self, $lang) = @_;
299 1   50     7 my $entries = $self->{SecData}{'.rsrc'}{Entries} ||= {};
300            
301 1 50 33     15 return $lang
302             if defined $lang
303             && exists $entries->{VERSION}{1}{$lang}{FixedFileInfo};
304 1 50       5 return if !exists $entries->{VERSION}{1};
305            
306             #struct VS_VERSIONINFO {
307             # WORD wLength;
308             # WORD wValueLength;
309             # WORD wType;
310             # WCHAR szKey[]; // "VS_VERSION_INFO".
311             # WORD Padding1[];
312             # VS_FIXEDFILEINFO Value;
313             # WORD Padding2[];
314             # WORD Children[];
315             #};
316            
317 1 50       7 if (!defined $lang) {
318             # Default to US-en if available, otherwise pick any
319 0         0 my @langs = grep {exists $entries->{VERSION}{1}{$_}} 0x0409,
  0         0  
320 0         0 keys %{$entries->{VERSION}{1}};
321 0         0 $lang = $langs[0];
322             }
323            
324 1         3 my $rsrcEntry = $entries->{VERSION}{1}{$lang};
325 1         35 open my $resIn, '<', \$rsrcEntry->{sectData};
326 1         3 binmode $resIn;
327            
328 1         8 while (read $resIn, (my $data), 6) {
329 3         11 my %header = (sectOffset => $rsrcEntry->{sectOffset});
330            
331 3         13 @header{qw(length valueLength isText)} = unpack ('vvv', $data);
332 3         8 read $resIn, $header{type}, 4;
333 3         12 $header{type} = Encode::decode('UTF-16LE', $header{type});
334            
335 3 100       98 if ($header{type} eq 'VS') {
    100          
    50          
336 1         17 $self->_parseFixedFileInfo($rsrcEntry, $resIn, \%header);
337             } elsif ($header{type} eq 'Va') {
338 1         16 $self->_parseVarFileInfo($rsrcEntry, $resIn, \%header);
339             } elsif ($header{type} eq 'St') {
340 1         13 $self->_parseStringFileInfo($rsrcEntry, $resIn, \%header);
341             } else {
342 0         0 die "Unknown version resource info prefix: $header{type}\n";
343             }
344             }
345            
346 1         2 close $resIn;
347 1         4 return $lang;
348             }
349            
350            
351             sub _parseFixedFileInfo {
352 1     1   2 my ($self, $rsrcEntry, $resIn, $header) = @_;
353            
354 1         4 read $resIn, (my $key), 26; # remainder of key
355 1         3 read $resIn, (my $data), 4; # null terminator and padding
356 1         6 $header->{type} = $header->{type} . Encode::decode('UTF-16LE', $key);
357            
358             #struct VS_FIXEDFILEINFO {
359             # DWORD dwSignature;
360             # DWORD dwStrucVersion;
361             # DWORD dwFileVersionMS;
362             # DWORD dwFileVersionLS;
363             # DWORD dwProductVersionMS;
364             # DWORD dwProductVersionLS;
365             # DWORD dwFileFlagsMask;
366             # DWORD dwFileFlags;
367             # DWORD dwFileOS;
368             # DWORD dwFileType;
369             # DWORD dwFileSubtype;
370             # DWORD dwFileDateMS;
371             # DWORD dwFileDateLS;
372             #};
373 1         24 my %fixedFileInfo;
374            
375 1         3 read $resIn, $data, 52;
376             @fixedFileInfo{
377 1         13 qw(
378             dwSignature dwStrucVersion dwFileVersionMS dwFileVersionLS
379             dwProductVersionMS dwProductVersionLS dwFileFlagsMask dwFileFlags
380             dwFileOS dwFileType dwFileSubtype dwFileDateMS dwFileDateLS
381             )
382             }
383             = unpack ('V13', $data);
384 1 50       7 die "Beyond eof in _parseFixedFileInfo\n" if eof $resIn;
385 1         4 seek $resIn, (tell $resIn) % 4, 1; # Skip padding bytes
386            
387 1         9 $rsrcEntry->{FixedFileInfo} = \%fixedFileInfo;
388             }
389            
390            
391             sub _parseStringFileInfo {
392 1     1   4 my ($self, $rsrcEntry, $resIn, $header) = @_;
393            
394 1         3 read $resIn, (my $key), 24; # remainder of key
395 1         6 $header->{type} = $header->{type} . Encode::decode('UTF-16LE', $key);
396            
397 1         22 my %stringFileInfo;
398            
399             #struct StringFileInfo {
400             # WORD wLength;
401             # WORD wValueLength;
402             # WORD wType;
403             # WCHAR szKey[]; // "StringFileInfo"
404             # WORD Padding[];
405             # StringTable Children[];
406             #};
407            
408 1         3 my $padding = (tell $resIn) % 4;
409 1 50       4 die "Beyond eof in _parseStringFileInfo\n" if eof $resIn;
410 1         2 seek $resIn, $padding, 1; # Skip padding bytes following key
411            
412             # Read the entire string file info record
413 1         2 my $pos = tell $resIn;
414 1         4 read $resIn, (my $strTables), $header->{length} - 34 - $padding;
415 1 50       4 die "Beyond eof in _parseStringFileInfo\n" if eof $resIn;
416 1         6 seek $resIn, (tell $resIn) % 4, 1; # Skip record end padding bytes
417 1         12 open my $strTblIn, '<', \$strTables;
418 1         3 binmode $strTblIn;
419            
420 1         6 while (read $strTblIn, (my $hdrData), 6) {
421            
422             #struct StringTable {
423             # WORD wLength;
424             # WORD wValueLength;
425             # WORD wType;
426             # WCHAR szKey[]; // 8 character Unicode string
427             # WORD Padding[];
428             # String Children[];
429             #};
430            
431 1         2 my %strTblHdr;
432            
433 1         5 @strTblHdr{qw(length valueLength isText)} = unpack ('vvv', $hdrData);
434 1         4 read $strTblIn, $strTblHdr{langCP}, 16;
435 1         5 $strTblHdr{langCP} = Encode::decode('UTF-16LE', $strTblHdr{langCP});
436 1 50       26 die "Beyond eof in _parseStringFileInfo\n" if eof $strTblIn;
437 1         2 seek $strTblIn, (tell $strTblIn) % 4, 1; # Skip padding bytes
438 1         5 read $strTblIn, (my $stringsData), $strTblHdr{length} - tell $strTblIn;
439 1         8 open my $stringsIn, '<', \$stringsData;
440 1         3 binmode $stringsIn;
441            
442 1         6 while (read $stringsIn, (my $strData), 6) {
443            
444             #struct String {
445             # WORD wLength;
446             # WORD wValueLength;
447             # WORD wType;
448             # WCHAR szKey[];
449             # WORD Padding[];
450             # WORD Value[];
451             #};
452            
453 8         9 my %strHdr;
454            
455 8         27 @strHdr{qw(length valueLength isText)} = unpack ('vvv', $strData);
456 8         21 read $stringsIn, $strData, $strHdr{length} - 6;
457 8         20 $strData = Encode::decode('UTF-16LE', $strData);
458 8         1454 $strData =~ s/\x00\x00+/\x00/g;
459 8         36 my ($name, $str) = split "\x00", $strData;
460 8         35 $stringFileInfo{$name} = $str;
461             # Skip padding bytes
462 8 100       58 seek $stringsIn, (tell $stringsIn) % 4, 1 if !eof $stringsIn;
463             }
464             }
465            
466 1         11 $rsrcEntry->{StringFileInfo} = \%stringFileInfo;
467             }
468            
469            
470             sub _parseVarFileInfo {
471 1     1   3 my ($self, $rsrcEntry, $resIn, $header) = @_;
472            
473 1         3 read $resIn, (my $key), 20; # remainder of key
474 1         4 $header->{type} = $header->{type} . Encode::decode('UTF-16LE', $key);
475            
476 1         21 my %varFileInfo;
477            
478             #struct VarFileInfo {
479             # WORD wLength;
480             # WORD wValueLength;
481             # WORD wType;
482             # WCHAR szKey[]; // "VarFileInfo"
483             # WORD Padding[];
484             # Var Children[];
485             #};
486            
487 1         13 my $padding = (tell $resIn) % 4;
488 1 50       5 die "Beyond eof in _parseVarFileInfo\n" if eof $resIn;
489 1         2 seek $resIn, $padding, 1; # Skip padding bytes following key
490            
491             # Read the entire var file info record
492 1         2 my $pos = tell $resIn;
493 1         5 read $resIn, (my $varData), $header->{length} - 28 - $padding;
494             # Skip record end padding bytes
495 1 50       5 seek $resIn, (tell $resIn) % 4, 1 if !eof $resIn;
496 1         11 open my $varIn, '<', \$varData;
497 1         4 binmode $varIn;
498            
499 1         6 while (read $varIn, (my $hdrData), 6) {
500            
501 1         2 my %varHdr;
502            
503             #struct Var {
504             # WORD wLength;
505             # WORD wValueLength;
506             # WORD wType;
507             # WCHAR szKey[];
508             # WORD Padding[];
509             # DWORD Value[];
510             #};
511            
512 1         5 @varHdr{qw(length valueLength isText)} = unpack ('vvv', $hdrData);
513 1         4 read $varIn, $varHdr{key}, 22;
514 1         4 $varHdr{key} = Encode::decode('UTF-16LE', $varHdr{key});
515 1         29 my $hPadding = (tell $varIn) % 4;
516             # Skip padding bytes following key
517 1 50       5 seek $varIn, $hPadding, 1 if !eof $varIn;
518 1         4 read $varIn, (my $value), $varHdr{length} - 28 - $hPadding;
519 1         4 @{$varFileInfo{langCPIds}} = unpack ('V*', $value);
  1         7  
520             }
521            
522 1         10 $rsrcEntry->{VarFileInfo} = \%varFileInfo;
523             }
524            
525            
526             sub _assemble_rsrc {
527 0     0   0 my ($self) = @_;
528            
529 0 0       0 return 1 if !exists $self->{SecData}{'.rsrc'}{added};
530 0         0 die "'" . ref ($self) . "'->_assemble not implemented\n";
531             }
532            
533            
534             sub _buildResourceEntry {
535 0     0   0 my ($self, %params) = @_;
536 0         0 my $root = $params{-root};
537 0         0 my $path = $params{-path};
538 0         0 my $entry = $root;
539            
540 0 0 0     0 Carp::croak "-root and -path parameters required in _buildResourceEntry"
541             if !ref $root || !ref $path;
542            
543 0   0     0 $entry = $entry->{$_} ||= {} for grep {defined} @$path;
  0         0  
544 0         0 $entry->{sectData} = $params{data};
545 0   0     0 $entry->{sectCodepage} = $params{codepage} || 0;
546 0         0 $entry->{sectOffset} = undef;
547 0         0 return $entry;
548             }
549            
550            
551             sub _buildVersionInfo {
552 0     0   0 my ($self, %params) = @_;
553 0         0 my $fixed = $self->_buildVS_FIXEDFILEINFO(%params);
554 0         0 my $string = $self->_buildStringFileInfo(%params);
555 0         0 my $var = $self->_buildVarFileInfo(%params);
556 0         0 my $tail = Encode::encode('UTF-16LE', 'StringFileInfo');
557            
558 0 0       0 $tail .= "\0\0" if 3 & length $tail;
559            
560 0         0 for my $entry ($fixed, $var, $string) {
561 0 0       0 next if !defined $entry;
562            
563 0         0 $tail .= $entry;
564             }
565            
566 0         0 return pack ('vvv', 6 + length $tail, length $fixed, 1) . $tail;
567             }
568            
569            
570             sub _buildVS_FIXEDFILEINFO {
571 0     0   0 my ($self, %params) = @_;
572 0         0 my $vData = Encode::encode('UTF-16LE', 'VS_VERSION_INFO');
573 0         0 my @items;
574            
575 0         0 push @items, 0xFEEF04BD; # Signature
576 0   0     0 push @items, $params{StrucVersion} ||= 65536; # 1.0
577 0   0     0 push @items, $params{FileVersionMS} ||= 65536; # 1.0
578 0   0     0 push @items, $params{FileVersionLS} ||= 0;
579 0   0     0 push @items, $params{ProductVersionMS} ||= 65536;
580 0   0     0 push @items, $params{ProductVersionLS} ||= 0;
581 0   0     0 push @items, $params{FileFlagsMask} ||= 0;
582 0   0     0 push @items, $params{FileFlags} ||= 0;
583 0   0     0 push @items, $params{FileOS} ||= 4;
584 0   0     0 push @items, $params{FileType} ||= 2;
585 0   0     0 push @items, $params{FileSubtype} ||= 0;
586 0   0     0 push @items, $params{FileDateMS} ||= 0;
587 0   0     0 push @items, $params{FileDateLS} ||= 0;
588            
589 0   0     0 $_ ||= 0 for @items;
590            
591 0         0 $vData .= ("\0") x 4; # null terminator and padding
592 0         0 $vData .= pack ('V13', @items);
593            
594 0         0 return {size => length $vData, data => $vData};
595             }
596            
597            
598             sub _buildStringFileInfo {
599 0     0   0 my ($self, %params) = @_;
600 0         0 my $tail = Encode::encode('UTF-16LE', 'StringFileInfo');
601            
602 0 0       0 $tail .= "\0\0" if 3 & length $tail;
603 0         0 $tail .= $self->_buildStringTable(%params);
604            
605 0         0 return pack ('vvv', 6 + length $tail, 0, 1) . $tail;
606             }
607            
608            
609             sub _buildStringTable {
610 0     0   0 my ($self, %params) = @_;
611            
612 0 0       0 Carp::confess "lang parameter required in _buildStringTable"
613             if !exists $params{lang};
614            
615 0   0     0 my $vData = Encode::encode('UTF-16LE', sprintf '%4x%4x',
616             $params{lang}, ($params{codePage} ||= 1200));
617 0         0 my @strings;
618            
619 0   0     0 $params{strings}{ProductVersion} ||= '1.0';
620 0   0     0 $params{strings}{FileVersion} ||= '1.0';
621 0   0     0 $params{strings}{OriginalFilename} ||= $self->{-file};
622 0   0     0 $params{strings}{InternalName} ||= $self->{-file};
623            
624 0         0 while (my @keyValue = each %{$params{strings}}) {
  0         0  
625 0         0 push @strings, $self->_buildString(@keyValue);
626             }
627            
628 0 0       0 $vData .= "\0\0" if 3 & (6 + length $vData);
629 0         0 $vData .= join '', @strings;
630 0         0 return pack ('vvv', 6 + length $vData, 0, 1) . $vData;
631             }
632            
633            
634             sub _buildString {
635 0     0   0 my ($self, $key, $value) = @_;
636            
637 0 0       0 $value = '' if !defined $value;
638             # Calculate length
639 0         0 my $len = 6 + 2 * length ($key) + 2; # header + len + null
640 0 0       0 my $padding1 = $len & 3 ? 2 : 0;
641            
642 0 0       0 $len += 2 if $len & 3;
643 0         0 $len += 2 * length ($value) + 2;
644            
645 0 0       0 my $padding2 = $len & 3 ? 2 : 0;
646            
647             # Generate record
648 0         0 my $tail = pack ('vvv', $len + $padding2, $len, 1);
649            
650 0         0 $tail .= Encode::encode('UTF-16LE', $key) . "\0\0";
651 0 0       0 $tail .= "\0\0" if $padding1;
652 0         0 $tail .= Encode::encode('UTF-16LE', $value) . "\0\0";
653 0 0       0 $tail .= "\0\0" if $padding2;
654            
655 0         0 return $tail;
656             }
657            
658            
659             sub _buildVarFileInfo {
660 0     0   0 my ($self, %params) = @_;
661            
662 0 0       0 return if !exists $params{langCPIds};
663            
664 0         0 Carp::croak "VarFileInfo not currently supported for VERSION resources";
665            
666 0         0 my $vData = Encode::encode('UTF-16LE', 'VarFileInfo');
667 0         0 my @items;
668            
669 0         0 $vData .= ("\0") x 4; # null terminator and padding
670            
671 0         0 return {size => length $vData, data => $vData};
672             }
673            
674            
675             sub _calcSize_rsrc {
676 0     0   0 my ($self, $sectionStart) = @_;
677            
678 0 0       0 return if !exists $self->{SecData}{'.edata'};
679            
680 0 0       0 if (exists $self->{SecData}{'.edata'}{added}) {
681 0         0 die "'" . ref ($self) . "'->_calcSize_edata not implemented\n";
682             }
683            
684 0         0 return $self->{DataDir}{'.edata'}{size};
685             #my $rsrcData = __PACKAGE__->__new();
686             #
687             #$self->_getResourceData($self->{SecData}{'.rsrc'}{Entries}, $rsrcData);
688             #$self->{DataDir}{'.rsrc'}{rawData} = $rsrcData->__rawData($sectionStart);
689             #$self->{DataDir}{'.rsrc'}{size} = $rsrcData->__length();
690             #return $self->{DataDir}{'.rsrc'}{size};
691             }
692            
693            
694             sub _getResourceData {
695 0     0   0 my ($self, $root, $rsrcData, $level) = @_;
696 0         0 my @nameEntries = grep {/\D/} sort keys %$root;
  0         0  
697 0         0 my @iDEntries = grep {!/\D/} sort keys %$root;
  0         0  
698            
699 0         0 $rsrcData->__addTable(time, 0 + @nameEntries, 0 + @iDEntries);
700            
701 0         0 for my $nameEntry (@nameEntries) {
702 0         0 $rsrcData->__addResDirNameEntry($nameEntry);
703             }
704            
705 0         0 for my $idEntry (@iDEntries) {
706 0         0 $rsrcData->__addResDirIdEntry($idEntry);
707             }
708            
709 0         0 return $self->{SecData}{'.rsrc'}{secData};
710             }
711            
712            
713             sub _getData {
714 0     0   0 my ($self) = @_;
715            
716 0         0 return $self->_getRsrcData();
717             }
718            
719            
720             sub __new {
721 0     0   0 my ($class, %params) = @_;
722            
723 0         0 return bless \%params, $class;
724             }
725            
726            
727             sub __rawData {
728 0     0   0 my ($self, $sectionStart) = @_;
729            
730 0         0 return $self->{rawData};
731             }
732            
733            
734             sub __addTable {
735 0     0   0 my ($self, $timeDate, $numNameEntries, $numIdEntries) = @_;
736            
737 0         0 $self->{tableData} .=
738             pack ('VVvvvv', 0, $timeDate, 0, 0, $numNameEntries, $numIdEntries);
739 0         0 push @{$self->{tablesEntries}}, [$numNameEntries, $numIdEntries];
  0         0  
740             }
741            
742            
743             sub __addResDirNameEntry {
744 0     0   0 my ($self, $nameOrID) = @_;
745 0         0 my $isName = $nameOrID =~ /\D/;
746            
747             }
748            
749            
750             sub __length {
751 0     0   0 my ($self) = @_;
752            
753 0         0 return length $self->{rawData};
754             }
755            
756            
757             package Win32::PEFile::edataHandler;
758            
759 1     1   13 use Win32::PEFile::PEConstants;
  1         1  
  1         2128  
760            
761             push @Win32::PEFile::edataHandler::ISA, 'Win32::PEFile::SectionHandlers';
762             Win32::PEFile::edataHandler->register('.edata',
763             qw'getEntryPoint getExportNames getExportOrdinalsCount haveExportEntry');
764            
765            
766             sub uiName {
767 1     1   3 return 'Exports';
768             }
769            
770            
771             sub getExportNames {
772 1     1   3744 my ($self) = @_;
773            
774 1 50       9 return if !exists $self->{DataDir}{'.edata'};
775            
776 1 50       9 $self->_parse_edata() if !exists $self->{SecData}{'.edata'}{Entries};
777 1         2 return keys %{$self->{SecData}{'.edata'}{Entries}};
  1         9  
778             }
779            
780            
781             sub getExportOrdinalsCount {
782 0     0   0 my ($self) = @_;
783            
784 0 0       0 return if !exists $self->{DataDir}{'.edata'};
785            
786 0 0       0 $self->_parse_edata() if !exists $self->{SecData}{'.edata'}{Entries};
787 0         0 return keys %{$self->{SecData}{'.edata'}{Entries}};
  0         0  
788             }
789            
790            
791             sub getEntryPoint {
792 0     0   0 my ($self, $entryPointName) = @_;
793            
794 0         0 return $self->haveExportEntry($entryPointName);
795             }
796            
797            
798             sub haveExportEntry {
799 2     2   1180 my ($self, $entryPointName) = @_;
800            
801 2 50       13 return if !exists $self->{DataDir}{'.edata'};
802 2 100       19 return exists $self->{SecData}{'.edata'}{Entries}{$entryPointName}
803             if exists $self->{SecData}{'.edata'}{Entries};
804            
805 1         17 $self->_parse_edata();
806 1         12 return exists $self->{SecData}{'.edata'}{Entries}{$entryPointName};
807             }
808            
809            
810             sub _parse_edata {
811 1     1   2 my ($self) = @_;
812 1         4 my $edataHdr = $self->{DataDir}{'.edata'};
813            
814 1 50       1362 open my $peFile, '<', $self->{'-file'}
815             or die "unable to open file - $!\n";
816 1         5 binmode $peFile;
817 1         14 seek $peFile, $edataHdr->{filePos}, 0;
818 1         27 read $peFile, (my $eData), $edataHdr->{size};
819            
820 1         2 my %dirTable;
821            
822             @dirTable{
823 1         18 qw(
824             Flags Timestamp VerMaj VerMin NameRVA Base ATEntries Names
825             ExportTabRVA NameTabRVA OrdTabRVA
826             )
827             }
828             = unpack ('VVvvVVVVVVV', $eData);
829 1         5 $self->{SecData}{'.edata'}{dirTable} = \%dirTable;
830            
831 1         4 my $nameTableFileAddr = $dirTable{NameTabRVA} - $edataHdr->{fileBias};
832            
833 1         9 seek $peFile, $nameTableFileAddr, 0;
834 1         9 read $peFile, (my $nameData), $dirTable{Names} * 4;
835            
836 1         5 for my $index (0 .. $dirTable{Names} - 1) {
837 1         6 my $addr = unpack ('V', substr $nameData, $index * 4, 4);
838            
839 1 50       4 next if !$addr;
840            
841 1         21 my $epName =
842             $self->_readNameStr($peFile, $addr - $edataHdr->{fileBias});
843            
844 1         6 $self->{SecData}{'.edata'}{Entries}{$epName} = $index;
845             }
846            
847 1         16 close $peFile;
848             }
849            
850            
851             sub _assemble_edata {
852 0     0   0 my ($self) = @_;
853            
854 0 0       0 return 1 if !exists $self->{SecData}{'.edata'}{added};
855 0         0 die "'" . ref ($self) . "'->_assemble_edata not implemented\n";
856             }
857            
858            
859             sub _calcSize_edata {
860 0     0   0 my ($self, $sectionStart) = @_;
861            
862 0 0       0 return if !exists $self->{SecData}{'.edata'};
863            
864 0 0       0 if (exists $self->{SecData}{'.edata'}{added}) {
865 0         0 die "'" . ref ($self) . "'->_calcSize_edata not implemented\n";
866             }
867            
868 0         0 return $self->{DataDir}{'.edata'}{size};
869             }
870            
871            
872             package Win32::PEFile::idataHandler;
873            
874 1     1   9 use Win32::PEFile::PEConstants;
  1         1  
  1         2841  
875            
876             push @Win32::PEFile::idataHandler::ISA, 'Win32::PEFile::SectionHandlers';
877             Win32::PEFile::idataHandler->register('.idata',
878             qw'getImportNames haveImportEntry');
879            
880            
881             sub uiName {
882 1     1   25 return 'Imports';
883             }
884            
885            
886             sub getImportNames {
887 1     1   1939 my ($self) = @_;
888 1         4 my %dlls;
889            
890 1 50       8 return if !exists $self->{DataDir}{'.idata'};
891            
892 1 50       5 $self->_parse_idata() if !exists $self->{SecData}{'.idata'};
893            
894 1         5 my $entries = $self->{SecData}{'.idata'}{Entries};
895            
896 1         5 for my $dllName (keys %$entries) {
897 2         6 my $entry = $entries->{$dllName};
898 2         2 $dlls{$dllName} = [@{$entry->{_entries}}];
  2         19  
899             }
900            
901 1         7 return %dlls;
902             }
903            
904            
905             sub haveImportEntry {
906 2     2   5 my ($self, $entryPath) = @_;
907            
908 2 50       13 return if !exists $self->{DataDir}{'.idata'};
909            
910 2 100       18 $self->_parse_idata() if !exists $self->{SecData}{'.idata'};
911 2 50       9 return if !exists $self->{SecData}{'.idata'};
912            
913 2         10 my ($dll, $routine) = split '/', $entryPath;
914            
915 2 50       13 return if !exists $self->{SecData}{'.idata'}{Entries}{$dll};
916 54         89 return !!grep {$routine eq $_}
  2         8  
917 2         3 @{$self->{SecData}{'.idata'}{Entries}{$dll}{_entries}};
918             }
919            
920            
921             sub _parse_idata {
922 1     1   3 my ($self) = @_;
923 1         4 my $idataHdr = $self->{DataDir}{'.idata'};
924            
925 1 50       8 return if ! exists $idataHdr->{filePos};
926            
927 1 50       60 open my $peFile, '<', $self->{'-file'}
928             or die "unable to open file - $!\n";
929 1         4 binmode $peFile;
930 1         6 seek $peFile, $idataHdr->{filePos}, 0;
931 1         26 read $peFile, (my $idata), $idataHdr->{size};
932            
933 1         2 my @dirTable;
934            
935 1         6 $self->{SecData}{'.idata'}{dirTable} = \@dirTable;
936 1     1   11 open my $dirFile, '<', \$idata;
  1         1  
  1         10  
  1         44  
937 1         4069 binmode $dirFile;
938            
939 1         3 while (1) {
940 3         7 my %dirEntry;
941            
942 3 50       24 read $dirFile, (my $entryData), 20 or last;
943             @dirEntry{
944 3         24 qw(ImportLUTableRVA TimeDate ForwarderChain dllNameRVA ThunkTableRVA)
945             } = unpack ('VVVVV', $entryData);
946            
947 3 100       13 last if !$dirEntry{dllNameRVA};
948            
949 2         6 my $dllNameFileAddr = $dirEntry{dllNameRVA} - $idataHdr->{fileBias};
950 2         19 my $dllName = $self->_readNameStr($peFile, $dllNameFileAddr);
951 2   100     21 my $entries = $self->{SecData}{'.idata'}{Entries} ||= {};
952            
953 2         9 $entries->{$dllName} = \%dirEntry;
954 2         23 $entries->{$dllName}{_entries} =
955             $self->_parseImportLUTable($peFile, $dllName);
956             }
957            
958 1         33 close $peFile;
959             }
960            
961            
962             sub _parseImportLUTable {
963 2     2   6 my ($self, $peFile, $dllName) = @_;
964 2         7 my $oldPos = tell $peFile;
965 2   50     12 my $dirEntry = $self->{SecData}{'.idata'}{Entries}{$dllName} ||= {};
966 2         7 my $idataHdr = $self->{DataDir}{'.idata'};
967 2         5 my $luAddr = $dirEntry->{ImportLUTableRVA} - $idataHdr->{fileBias};
968 2         5 my $isPEPlus = $self->{is32Plus};
969 2         2 my @entries;
970            
971 2         12 seek $peFile, $luAddr, 0;
972            
973 2         4 while (1) {
974 42 50       1148 read $peFile, my $lutEntry, ($isPEPlus ? 8 : 4);
975            
976 42         46 my $index;
977             my $isOrdinal;
978            
979 42 50       602 if ($isPEPlus) {
980 0         0 ($index, $isOrdinal) = unpack ('VV', $lutEntry);
981             } else {
982 42         85 $index = unpack ('V', $lutEntry);
983 42         47 $isOrdinal = $index & 0x80000000;
984 42         53 $index &= ~0x80000000;
985             }
986            
987 42 50 66     209 last if !$index && !$isOrdinal;
988            
989 40 50       1769 if ($isOrdinal) {
990 0         0 push @entries, $index;
991 0         0 next;
992             }
993            
994 40         67 my $hintAddr = $index - $idataHdr->{fileBias};
995 40         49 my $oldPos = tell $peFile;
996            
997 40         418 seek $peFile, $hintAddr, 0;
998 40         2109 read $peFile, my $hint, 2;
999 40         72 $hint = unpack ('v', $hint);
1000            
1001 40         289 my $entryName = $self->_readNameStr($peFile);
1002            
1003 40         74 push @entries, $entryName;
1004 40         711 seek $peFile, $oldPos, 0;
1005             }
1006            
1007 2         15 seek $peFile, $oldPos, 0;
1008 2         18 return \@entries;
1009             }
1010            
1011            
1012             sub _assemble_idata {
1013 0     0   0 my ($self) = @_;
1014            
1015 0 0       0 return 1 if !exists $self->{SecData}{'.idata'}{added};
1016 0         0 die "'" . ref ($self) . "'->_assemble_idata not implemented\n";
1017             }
1018            
1019            
1020             sub _calcSize_idata {
1021 0     0   0 my ($self, $sectionStart) = @_;
1022            
1023 0 0       0 return if !exists $self->{SecData}{'.idata'};
1024            
1025 0 0       0 if (exists $self->{SecData}{'.idata'}{added}) {
1026 0         0 die "'" . ref ($self) . "'->_calcSize_idata not implemented\n";
1027             }
1028            
1029 0         0 return $self->{DataDir}{'.idata'}{size};
1030             }
1031            
1032            
1033             package Win32::PEFile::certHandler;
1034            
1035 1     1   8 use Win32::PEFile::PEConstants;
  1         1  
  1         1362  
1036            
1037             push @Win32::PEFile::certHandler::ISA, 'Win32::PEFile::SectionHandlers';
1038             Win32::PEFile::certHandler->register('certTable',
1039             qw'addCertificate getCertificate');
1040            
1041            
1042             sub uiName {
1043 1     1   2 return 'Certificate';
1044             }
1045            
1046            
1047             sub addCertificate {
1048 0     0     my ($self) = @_;
1049            
1050             }
1051            
1052            
1053             sub getCertificate {
1054 0     0     my ($self) = @_;
1055            
1056             }
1057            
1058            
1059             sub _parse_cert {
1060 0     0     my ($self, $sectionStart) = @_;
1061            
1062 0           die "'" . ref ($self) . "'->_parse_cert not implemented\n";
1063             }
1064            
1065            
1066             sub _assemble_cert {
1067 0     0     my ($self) = @_;
1068            
1069 0 0         return 1 if !exists $self->{SecData}{certTable}{added};
1070 0           die "'" . ref ($self) . "'->_assemble_cert not implemented\n";
1071             }
1072            
1073            
1074             sub _calcSize_cert {
1075 0     0     my ($self, $sectionStart) = @_;
1076            
1077 0 0         return if !exists $self->{SecData}{certTable};
1078            
1079 0 0         if (exists $self->{SecData}{certTable}{added}) {
1080 0           die "'" . ref ($self) . "'->_calcSize_cert not implemented\n";
1081             }
1082            
1083 0           return $self->{DataDir}{certTable}{size};
1084             }
1085            
1086            
1087             1;
1088            
1089             =head1 NAME
1090            
1091             Win32::PEFile::SectionHandlers - PEFile section handlers
1092            
1093             =head1 OVERVIEW
1094            
1095             A PEFile contains a number of sections that provide the "interesting"
1096             information and data in the file. This module provides code to handle various
1097             section types. C is a base class that provides
1098             common construction code and hooks the derived section handler classes into the
1099             PEFile parser. This allows entry points provided by section handlers to be made
1100             available from PEFile objects as object methods.
1101            
1102             Section handlers call Win32::PEFile::SectionHandlers::register() passing their
1103             class, section tag ('.rsrc' for example) and the list of public methods
1104             provided by the section handler.
1105            
1106             Win32::PEFile uses AUTOLOAD to hook up section handlers to the Win32::PEFile
1107             class on demand.
1108            
1109             =cut