File Coverage

lib/Win32/PEFile/SectionHandlers.pm
Criterion Covered Total %
statement 323 511 63.2
branch 70 178 39.3
condition 20 92 21.7
subroutine 33 64 51.5
pod 0 2 0.0
total 446 847 52.6


line stmt bran cond sub pod time code
1             package Win32::PEFile::SectionHandlers;
2            
3 1     1   3 use strict;
  1         2  
  1         26  
4 1     1   3 use warnings;
  1         1  
  1         18  
5 1     1   3 use Carp qw();
  1         1  
  1         14  
6 1     1   267 use Win32::PEFile::PEConstants;
  1         1  
  1         550  
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 5 my ($class, $sectionCode, @methods) = @_;
19            
20 4         5 $_SectionNames{$sectionCode} = $class;
21 4         29 $_EntryPoints{$_} = $class for @methods;
22            
23 4         24 my $doUIName = $class->can('uiName');
24            
25 4 50       11 $doUIName or die "'$class' requires a uiName method\n";
26            
27 4         6 my $uiName = $doUIName->();
28 4         18 (my $type = $class) =~ s/.*:(\w+)Handler$/$1/;
29            
30             $class->can("${_}_$type")
31             or die "'$class' requires a ${_}_$type method\n"
32 4   50     47 for '_calcSize', "_parse", "_assemble";
33             }
34            
35            
36             sub _readSectionDataEntry {
37 13     13   14 my ($self, $fh, $offset, $sectionOffset) = @_;
38 13         16 my $oldPos = tell $fh;
39 13         10 my %sect;
40            
41 13         25 seek $fh, $offset + $sectionOffset, 0;
42 13 50       43 read $fh, my $rData, 16 or die "file read error: $!\n";
43 13         22 my ($dataRVA, $size, $codePage) = unpack ('VVVV', $rData);
44 13         23 my $imageRVA = $self->{DataDir}{'.rsrc'}{imageRVA};
45 13         13 my $resDataOffset = $dataRVA - $imageRVA;
46            
47 13         18 $sect{sectCodepage} = $codePage;
48 13         14 $sect{sectOffset} = $offset;
49 13         18 seek $fh, $resDataOffset + $sectionOffset, 0;
50 13 50       57 read $fh, $sect{sectData}, $size or die "file read error: $!\n";
51            
52 13         17 seek $fh, $oldPos, 0;
53 13         33 return \%sect;
54             }
55            
56            
57             sub _readSzStr {
58 4     4   6 my ($self, $fh, $offset) = @_;
59 4         4 my $oldPos = tell $fh;
60 4         5 my $str = '';
61            
62 4 50       15 seek $fh, $offset, 0 if defined $offset;
63 4 50       17 read $fh, my $strBytes, 2 or die "file read error: $!\n";
64 4         8 $strBytes = 2 * unpack ('v', $strBytes);
65            
66 4 50       8 if ($strBytes) {
67 4 50       13 read $fh, $str, $strBytes or die "file read error: $!\n";
68 4         13 $str = Encode::decode('UTF-16LE', $str);
69             }
70            
71 4 50       2964 seek $fh, $oldPos, 0 if defined $offset;
72 4         9 return $str;
73             }
74            
75            
76             package Win32::PEFile::rsrcHandler;
77            
78 1     1   4 use Win32::PEFile::PEConstants;
  1         1  
  1         2940  
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   1 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   314 my ($self, $lang) = @_;
147 2         5 my $verResource;
148            
149 2         8 ($verResource, $lang) = $self->getResourceData('VERSION', 1, $lang);
150            
151 2   50     67 my $entries = $self->{SecData}{'.rsrc'}{Entries} ||= {};
152            
153 2 100 66     12 return if !$verResource || !exists $entries->{VERSION}{1}{$lang};
154            
155 1         11 $self->_parseVersionInfo($lang);
156            
157 1         4 my $strings = $entries->{VERSION}{1}{$lang}{StringFileInfo};
158 1 50       6 return wantarray ? ($strings, $lang) : $strings;
159             }
160            
161            
162             sub getVersionCount {
163 3     3   650 my ($self, $lang) = @_;
164 3         5 my $count = 0;
165            
166 3         10 my ($verResource, $langDef) = $self->getResourceData('VERSION', 1, $lang);
167 3         6 my $entries = $self->{SecData}{'.rsrc'}{Entries}{VERSION};
168            
169 3         8 for my $version (keys %$entries) {
170 3 100       17 $count +=
171 3         3 grep {!defined $lang or $_ == $lang} keys %{$entries->{$version}};
  3         5  
172             }
173            
174 3         8 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   11 my ($self, $type, $name, $lang) = @_;
196            
197             return
198 5 50       18 if !$self->_parse_rsrc(
199             'rsrc',
200             type => $type,
201             name => $name,
202             lang => $lang
203             );
204            
205 5         9 my $entries = $self->{SecData}{'.rsrc'}{Entries};
206            
207 5 100       12 return if !exists $entries->{$type};
208            
209 4 100 66     16 if ( !defined $lang
210             || !exists $entries->{$type}{$name}{$lang})
211             {
212 3         4 my %langs = map {$_ => 1} keys %{$entries->{$type}{$name}};
  3         9  
  3         10  
213            
214 3   100     10 $lang ||= 0x0409; # Default to US English
215 3 50       11 $lang = (keys %langs)[0] if !exists $langs{$lang};
216             }
217            
218 4         10 return $entries->{$type}{$name}{$lang}{sectData}, $lang;
219             }
220            
221            
222             sub _parse_rsrc {
223 5     5   6 my ($self) = @_;
224 5         8 my $fName = $self->{'-file'};
225            
226 5 50       77 return if !-f $fName;
227            
228 5         9 my $rsrcHdr = $self->{DataDir}{'.rsrc'};
229            
230 5 50       132 open my $peFile, '<', $fName or die "unable to open '$fName' - $!\n";
231 5         10 binmode ($peFile);
232            
233 5         21 $self->{SecData}{'.rsrc'}{Entries} = {};
234 5         40 $self->_parseRsrcTable($self->{SecData}{'.rsrc'}{Entries},
235             $peFile, 0, $rsrcHdr->{filePos});
236 5         32 close $peFile;
237 5         23 return 1;
238             }
239            
240            
241             sub _parseRsrcTable {
242 31     31   35 my ($self, $rsrc, $fh, $filePos, $sectionOffset, $level) = @_;
243 31         30 my $oldPos = tell $fh;
244            
245 31         26 ++$level;
246 31         52 seek $fh, $filePos + $sectionOffset, 0;
247 31         122 read $fh, (my $rData), 16;
248            
249 31         26 my %dirTable;
250 31         42 my $entries = $self->{SecData}{'.rsrc'}{Entries};
251            
252 31         117 @dirTable{@kDirTableFields} = unpack ('VVvvvv', $rData);
253 31         48 $self->{SecData}{'.rsrc'}{dirTable} = \%dirTable;
254            
255 31         41 my ($numNames, $numIDs) = @dirTable{qw(NumNameEntries NumIDEntries)};
256            
257             # Handle IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]
258 31   100     99 while ($numNames || $numIDs) {
259 39         124 read $fh, $rData, 8;
260            
261 39         52 my ($RVAOrID, $RVA) = unpack ('VV', $rData);
262 39         39 my $addr = ($RVA & ~0x80000000);
263 39         29 my $rsrcId;
264            
265 39 100       67 if ($numNames) {
    50          
266             # Fetch the entry name. $RVAOrID is the RVA
267 4         4 --$numNames;
268 4         16 $rsrcId =
269             $self->_readSzStr($fh,
270             ($RVAOrID & ~0x80000000) + $sectionOffset);
271            
272             } elsif ($numIDs) {
273             # Resource ID. $RVAOrID is the ID
274 35         32 --$numIDs;
275 35         23 $rsrcId = $RVAOrID;
276 35 100 66     101 $rsrcId = $rsrcTypes{$rsrcId}
277             if $level == 1 && exists $rsrcTypes{$rsrcId};
278             }
279            
280 39 100       54 if (0 != ($RVA & 0x80000000)) {
281             # It's a sub table entry
282 26         42 $rsrc->{$rsrcId} = {};
283 26         54 $self->_parseRsrcTable($rsrc->{$rsrcId}, $fh, $addr, $sectionOffset,
284             $level);
285             } else {
286             # It's a data entry
287 13         38 $rsrc->{$rsrcId} =
288             $self->_readSectionDataEntry($fh, $RVA, $sectionOffset);
289 13         49 next;
290             }
291             }
292            
293 31         107 seek $fh, $oldPos, 0;
294             }
295            
296            
297             sub _parseVersionInfo {
298 1     1   2 my ($self, $lang) = @_;
299 1   50     4 my $entries = $self->{SecData}{'.rsrc'}{Entries} ||= {};
300            
301 1 50 33     10 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       3 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         2 my $rsrcEntry = $entries->{VERSION}{1}{$lang};
325 1         11 open my $resIn, '<', \$rsrcEntry->{sectData};
326 1         2 binmode $resIn;
327            
328 1         5 while (read $resIn, (my $data), 6) {
329 3         6 my %header = (sectOffset => $rsrcEntry->{sectOffset});
330            
331 3         9 @header{qw(length valueLength isText)} = unpack ('vvv', $data);
332 3         4 read $resIn, $header{type}, 4;
333 3         8 $header{type} = Encode::decode('UTF-16LE', $header{type});
334            
335 3 100       59 if ($header{type} eq 'VS') {
    100          
    50          
336 1         10 $self->_parseFixedFileInfo($rsrcEntry, $resIn, \%header);
337             } elsif ($header{type} eq 'Va') {
338 1         9 $self->_parseVarFileInfo($rsrcEntry, $resIn, \%header);
339             } elsif ($header{type} eq 'St') {
340 1         10 $self->_parseStringFileInfo($rsrcEntry, $resIn, \%header);
341             } else {
342 0         0 die "Unknown version resource info prefix: $header{type}\n";
343             }
344             }
345            
346 1         3 close $resIn;
347 1         3 return $lang;
348             }
349            
350            
351             sub _parseFixedFileInfo {
352 1     1   2 my ($self, $rsrcEntry, $resIn, $header) = @_;
353            
354 1         3 read $resIn, (my $key), 26; # remainder of key
355 1         2 read $resIn, (my $data), 4; # null terminator and padding
356 1         3 $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         14 my %fixedFileInfo;
374            
375 1         3 read $resIn, $data, 52;
376             @fixedFileInfo{
377 1         11 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       5 die "Beyond eof in _parseFixedFileInfo\n" if eof $resIn;
385 1         4 seek $resIn, (tell $resIn) % 4, 1; # Skip padding bytes
386            
387 1         6 $rsrcEntry->{FixedFileInfo} = \%fixedFileInfo;
388             }
389            
390            
391             sub _parseStringFileInfo {
392 1     1   2 my ($self, $rsrcEntry, $resIn, $header) = @_;
393            
394 1         2 read $resIn, (my $key), 24; # remainder of key
395 1         3 $header->{type} = $header->{type} . Encode::decode('UTF-16LE', $key);
396            
397 1         14 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         2 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         1 my $pos = tell $resIn;
414 1         3 read $resIn, (my $strTables), $header->{length} - 34 - $padding;
415 1 50       2 die "Beyond eof in _parseStringFileInfo\n" if eof $resIn;
416 1         2 seek $resIn, (tell $resIn) % 4, 1; # Skip record end padding bytes
417 1         7 open my $strTblIn, '<', \$strTables;
418 1         2 binmode $strTblIn;
419            
420 1         5 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         1 my %strTblHdr;
432            
433 1         4 @strTblHdr{qw(length valueLength isText)} = unpack ('vvv', $hdrData);
434 1         2 read $strTblIn, $strTblHdr{langCP}, 16;
435 1         3 $strTblHdr{langCP} = Encode::decode('UTF-16LE', $strTblHdr{langCP});
436 1 50       17 die "Beyond eof in _parseStringFileInfo\n" if eof $strTblIn;
437 1         2 seek $strTblIn, (tell $strTblIn) % 4, 1; # Skip padding bytes
438 1         3 read $strTblIn, (my $stringsData), $strTblHdr{length} - tell $strTblIn;
439 1         5 open my $stringsIn, '<', \$stringsData;
440 1         2 binmode $stringsIn;
441            
442 1         3 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         7 my %strHdr;
454            
455 8         15 @strHdr{qw(length valueLength isText)} = unpack ('vvv', $strData);
456 8         12 read $stringsIn, $strData, $strHdr{length} - 6;
457 8         12 $strData = Encode::decode('UTF-16LE', $strData);
458 8         114 $strData =~ s/\x00\x00+/\x00/g;
459 8         19 my ($name, $str) = split "\x00", $strData;
460 8         14 $stringFileInfo{$name} = $str;
461             # Skip padding bytes
462 8 100       39 seek $stringsIn, (tell $stringsIn) % 4, 1 if !eof $stringsIn;
463             }
464             }
465            
466 1         7 $rsrcEntry->{StringFileInfo} = \%stringFileInfo;
467             }
468            
469            
470             sub _parseVarFileInfo {
471 1     1   12 my ($self, $rsrcEntry, $resIn, $header) = @_;
472            
473 1         3 read $resIn, (my $key), 20; # remainder of key
474 1         3 $header->{type} = $header->{type} . Encode::decode('UTF-16LE', $key);
475            
476 1         15 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         2 my $padding = (tell $resIn) % 4;
488 1 50       7 die "Beyond eof in _parseVarFileInfo\n" if eof $resIn;
489 1         1 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         3 read $resIn, (my $varData), $header->{length} - 28 - $padding;
494             # Skip record end padding bytes
495 1 50       2 seek $resIn, (tell $resIn) % 4, 1 if !eof $resIn;
496 1         7 open my $varIn, '<', \$varData;
497 1         2 binmode $varIn;
498            
499 1         5 while (read $varIn, (my $hdrData), 6) {
500            
501 1         0 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         4 @varHdr{qw(length valueLength isText)} = unpack ('vvv', $hdrData);
513 1         2 read $varIn, $varHdr{key}, 22;
514 1         3 $varHdr{key} = Encode::decode('UTF-16LE', $varHdr{key});
515 1         16 my $hPadding = (tell $varIn) % 4;
516             # Skip padding bytes following key
517 1 50       4 seek $varIn, $hPadding, 1 if !eof $varIn;
518 1         2 read $varIn, (my $value), $varHdr{length} - 28 - $hPadding;
519 1         3 @{$varFileInfo{langCPIds}} = unpack ('V*', $value);
  1         5  
520             }
521            
522 1         8 $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   4 use Win32::PEFile::PEConstants;
  1         1  
  1         630  
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   2 return 'Exports';
768             }
769            
770            
771             sub getExportNames {
772 1     1   611 my ($self) = @_;
773            
774 1 50       5 return if !exists $self->{DataDir}{'.edata'};
775            
776 1 50       4 $self->_parse_edata() if !exists $self->{SecData}{'.edata'}{Entries};
777 1         2 return keys %{$self->{SecData}{'.edata'}{Entries}};
  1         5  
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   5 my ($self, $entryPointName) = @_;
800            
801 2 50       7 return if !exists $self->{DataDir}{'.edata'};
802 2 100       9 return exists $self->{SecData}{'.edata'}{Entries}{$entryPointName}
803             if exists $self->{SecData}{'.edata'}{Entries};
804            
805 1         6 $self->_parse_edata();
806 1         8 return exists $self->{SecData}{'.edata'}{Entries}{$entryPointName};
807             }
808            
809            
810             sub _parse_edata {
811 1     1   2 my ($self) = @_;
812 1         2 my $edataHdr = $self->{DataDir}{'.edata'};
813            
814 1 50       31 open my $peFile, '<', $self->{'-file'}
815             or die "unable to open file - $!\n";
816 1         2 binmode $peFile;
817 1         6 seek $peFile, $edataHdr->{filePos}, 0;
818 1         11 read $peFile, (my $eData), $edataHdr->{size};
819            
820 1         2 my %dirTable;
821            
822             @dirTable{
823 1         12 qw(
824             Flags Timestamp VerMaj VerMin NameRVA Base ATEntries Names
825             ExportTabRVA NameTabRVA OrdTabRVA
826             )
827             }
828             = unpack ('VVvvVVVVVVV', $eData);
829 1         4 $self->{SecData}{'.edata'}{dirTable} = \%dirTable;
830            
831 1         3 my $nameTableFileAddr = $dirTable{NameTabRVA} - $edataHdr->{fileBias};
832            
833 1         5 seek $peFile, $nameTableFileAddr, 0;
834 1         5 read $peFile, (my $nameData), $dirTable{Names} * 4;
835            
836 1         4 for my $index (0 .. $dirTable{Names} - 1) {
837 1         3 my $addr = unpack ('V', substr $nameData, $index * 4, 4);
838            
839 1 50       3 next if !$addr;
840            
841 1         9 my $epName =
842             $self->_readNameStr($peFile, $addr - $edataHdr->{fileBias});
843            
844 1         5 $self->{SecData}{'.edata'}{Entries}{$epName} = $index;
845             }
846            
847 1         12 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   5 use Win32::PEFile::PEConstants;
  1         1  
  1         946  
875            
876             push @Win32::PEFile::idataHandler::ISA, 'Win32::PEFile::SectionHandlers';
877             Win32::PEFile::idataHandler->register('.idata',
878             qw'getImportNames getImportNamesArray haveImportEntry');
879            
880            
881             sub uiName {
882 1     1   17 return 'Imports';
883             }
884            
885            
886             sub getImportNames {
887 1     1   283 my ($self) = @_;
888 1         2 my %dlls;
889            
890 1 50       5 return if !exists $self->{DataDir}{'.idata'};
891            
892 1 50       4 $self->_parse_idata() if !exists $self->{SecData}{'.idata'};
893            
894 1         3 my $entries = $self->{SecData}{'.idata'}{Entries};
895            
896 1         4 for my $dllName (keys %$entries) {
897 2         3 my $entry = $entries->{$dllName};
898 2         3 $dlls{$dllName} = [@{$entry->{_entries}}];
  2         21  
899             }
900            
901 1         5 return %dlls;
902             }
903            
904            
905             sub getImportNamesArray {
906 1     1   294 my ($self) = @_;
907 1         2 my %dlls;
908            
909 1 50       5 return if !exists $self->{DataDir}{'.idata'};
910            
911 1 50       4 $self->_parse_idata() if !exists $self->{SecData}{'.idata'};
912            
913 1         2 my $entries = $self->{SecData}{'.idata'}{Entries};
914            
915 1         4 for my $dllName (keys %$entries) {
916 2         3 my $entry = $entries->{$dllName};
917 2         2 $dlls{$dllName} = [@{$entry->{_entries}}];
  2         8  
918             }
919            
920 1         2 return @{$self->{SecData}{'.idata'}{ByName}};
  1         7  
921             }
922            
923            
924             sub haveImportEntry {
925 2     2   4 my ($self, $entryPath) = @_;
926            
927 2 50       6 return if !exists $self->{DataDir}{'.idata'};
928            
929 2 100       11 $self->_parse_idata() if !exists $self->{SecData}{'.idata'};
930 2 50       5 return if !exists $self->{SecData}{'.idata'};
931            
932 2         6 my ($dll, $routine) = split '/', $entryPath;
933            
934 2 50       6 return if !exists $self->{SecData}{'.idata'}{Entries}{$dll};
935 54         54 return !!grep {$routine eq $_}
  2         4  
936 2         3 @{$self->{SecData}{'.idata'}{Entries}{$dll}{_entries}};
937             }
938            
939            
940             sub _parse_idata {
941 1     1   2 my ($self) = @_;
942 1         2 my $idataHdr = $self->{DataDir}{'.idata'};
943            
944 1 50       4 return if ! exists $idataHdr->{filePos};
945            
946 1 50       28 open my $peFile, '<', $self->{'-file'}
947             or die "unable to open file - $!\n";
948 1         2 binmode $peFile;
949 1         3 seek $peFile, $idataHdr->{filePos}, 0;
950 1         16 read $peFile, (my $idata), $idataHdr->{size};
951            
952 1         1 my @dirTable;
953             my @nameArray;
954            
955 1         4 $self->{SecData}{'.idata'}{dirTable} = \@dirTable;
956 1     1   7 open my $dirFile, '<', \$idata;
  1         1  
  1         7  
  1         30  
957 1         920 binmode $dirFile;
958            
959 1         2 while (1) {
960 3         3 my %dirEntry;
961            
962 3 50       10 read $dirFile, (my $entryData), 20 or last;
963             @dirEntry{
964 3         14 qw(ImportLUTableRVA TimeDate ForwarderChain dllNameRVA ThunkTableRVA)
965             } = unpack ('VVVVV', $entryData);
966            
967 3 100       7 last if !$dirEntry{dllNameRVA};
968            
969 2         5 my $dllNameFileAddr = $dirEntry{dllNameRVA} - $idataHdr->{fileBias};
970 2         9 my $dllName = $self->_readNameStr($peFile, $dllNameFileAddr);
971 2   100     13 my $entries = $self->{SecData}{'.idata'}{Entries} ||= {};
972            
973 2         2 push @nameArray, $dllName;
974 2         5 $entries->{$dllName} = \%dirEntry;
975 2         13 $entries->{$dllName}{_entries} =
976             $self->_parseImportLUTable($peFile, $dllName);
977             }
978            
979 1         3 $self->{SecData}{'.idata'}{ByName} = \@nameArray;
980 1         9 close $peFile;
981             }
982            
983            
984             sub _parseImportLUTable {
985 2     2   2 my ($self, $peFile, $dllName) = @_;
986 2         3 my $oldPos = tell $peFile;
987 2   50     5 my $dirEntry = $self->{SecData}{'.idata'}{Entries}{$dllName} ||= {};
988 2         3 my $idataHdr = $self->{DataDir}{'.idata'};
989 2         4 my $luAddr = $dirEntry->{ImportLUTableRVA} - $idataHdr->{fileBias};
990 2         3 my $isPEPlus = $self->{is32Plus};
991 2         4 my @entries;
992            
993 2         3 seek $peFile, $luAddr, 0;
994            
995 2         2 while (1) {
996 42 50       105 read $peFile, my $lutEntry, ($isPEPlus ? 8 : 4);
997            
998 42         30 my $index;
999             my $isOrdinal;
1000            
1001 42 50       41 if ($isPEPlus) {
1002 0         0 ($index, $isOrdinal) = unpack ('VV', $lutEntry);
1003             } else {
1004 42         47 $index = unpack ('V', $lutEntry);
1005 42         32 $isOrdinal = $index & 0x80000000;
1006 42         34 $index &= ~0x80000000;
1007             }
1008            
1009 42 50 66     51 last if !$index && !$isOrdinal;
1010            
1011 40 50       47 if ($isOrdinal) {
1012 0         0 push @entries, $index;
1013 0         0 next;
1014             }
1015            
1016 40         34 my $hintAddr = $index - $idataHdr->{fileBias};
1017 40         36 my $oldPos = tell $peFile;
1018            
1019 40         49 seek $peFile, $hintAddr, 0;
1020 40         77 read $peFile, my $hint, 2;
1021 40         35 $hint = unpack ('v', $hint);
1022            
1023 40         56 my $entryName = $self->_readNameStr($peFile);
1024            
1025 40         37 push @entries, $entryName;
1026 40         64 seek $peFile, $oldPos, 0;
1027             }
1028            
1029 2         8 seek $peFile, $oldPos, 0;
1030 2         7 return \@entries;
1031             }
1032            
1033            
1034             sub _assemble_idata {
1035 0     0   0 my ($self) = @_;
1036            
1037 0 0       0 return 1 if !exists $self->{SecData}{'.idata'}{added};
1038 0         0 die "'" . ref ($self) . "'->_assemble_idata not implemented\n";
1039             }
1040            
1041            
1042             sub _calcSize_idata {
1043 0     0   0 my ($self, $sectionStart) = @_;
1044            
1045 0 0       0 return if !exists $self->{SecData}{'.idata'};
1046            
1047 0 0       0 if (exists $self->{SecData}{'.idata'}{added}) {
1048 0         0 die "'" . ref ($self) . "'->_calcSize_idata not implemented\n";
1049             }
1050            
1051 0         0 return $self->{DataDir}{'.idata'}{size};
1052             }
1053            
1054            
1055             package Win32::PEFile::certHandler;
1056            
1057 1     1   5 use Win32::PEFile::PEConstants;
  1         1  
  1         359  
1058            
1059             push @Win32::PEFile::certHandler::ISA, 'Win32::PEFile::SectionHandlers';
1060             Win32::PEFile::certHandler->register('certTable',
1061             qw'addCertificate getCertificate');
1062            
1063            
1064             sub uiName {
1065 1     1   1 return 'Certificate';
1066             }
1067            
1068            
1069             sub addCertificate {
1070 0     0     my ($self) = @_;
1071            
1072             }
1073            
1074            
1075             sub getCertificate {
1076 0     0     my ($self) = @_;
1077            
1078             }
1079            
1080            
1081             sub _parse_cert {
1082 0     0     my ($self, $sectionStart) = @_;
1083            
1084 0           die "'" . ref ($self) . "'->_parse_cert not implemented\n";
1085             }
1086            
1087            
1088             sub _assemble_cert {
1089 0     0     my ($self) = @_;
1090            
1091 0 0         return 1 if !exists $self->{SecData}{certTable}{added};
1092 0           die "'" . ref ($self) . "'->_assemble_cert not implemented\n";
1093             }
1094            
1095            
1096             sub _calcSize_cert {
1097 0     0     my ($self, $sectionStart) = @_;
1098            
1099 0 0         return if !exists $self->{SecData}{certTable};
1100            
1101 0 0         if (exists $self->{SecData}{certTable}{added}) {
1102 0           die "'" . ref ($self) . "'->_calcSize_cert not implemented\n";
1103             }
1104            
1105 0           return $self->{DataDir}{certTable}{size};
1106             }
1107            
1108            
1109             1;
1110            
1111             =head1 NAME
1112            
1113             Win32::PEFile::SectionHandlers - PEFile section handlers
1114            
1115             =head1 OVERVIEW
1116            
1117             A PEFile contains a number of sections that provide the "interesting"
1118             information and data in the file. This module provides code to handle various
1119             section types. C is a base class that provides
1120             common construction code and hooks the derived section handler classes into the
1121             PEFile parser. This allows entry points provided by section handlers to be made
1122             available from PEFile objects as object methods.
1123            
1124             Section handlers call Win32::PEFile::SectionHandlers::register() passing their
1125             class, section tag ('.rsrc' for example) and the list of public methods
1126             provided by the section handler.
1127            
1128             Win32::PEFile uses AUTOLOAD to hook up section handlers to the Win32::PEFile
1129             class on demand.
1130            
1131             =cut