File Coverage

blib/lib/Image/ExifTool/LNK.pm
Criterion Covered Total %
statement 108 145 74.4
branch 39 94 41.4
condition 3 18 16.6
subroutine 7 7 100.0
pod 0 4 0.0
total 157 268 58.5


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: LNK.pm
3             #
4             # Description: Read meta information from MS Shell Link files
5             #
6             # Revisions: 2009/09/19 - P. Harvey Created
7             #
8             # References: 1) http://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx
9             # 2) http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf
10             #------------------------------------------------------------------------------
11              
12             package Image::ExifTool::LNK;
13              
14 1     1   4407 use strict;
  1         3  
  1         33  
15 1     1   5 use vars qw($VERSION);
  1         3  
  1         38  
16 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         2519  
17              
18             $VERSION = '1.09';
19              
20             sub ProcessItemID($$$);
21             sub ProcessLinkInfo($$$);
22              
23             # Information extracted from LNK (Windows Shortcut) files
24             %Image::ExifTool::LNK::Main = (
25             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
26             GROUPS => { 2 => 'Other' },
27             VARS => { HEX_ID => 1 }, # print hex ID's in documentation
28             NOTES => 'Information extracted from MS Shell Link (Windows shortcut) files.',
29             # maybe the Flags aren't very useful to the user (since they are
30             # mainly structural), but extract them anyway for completeness
31             0x14 => {
32             Name => 'Flags',
33             Format => 'int32u',
34             PrintConv => { BITMASK => {
35             0 => 'IDList',
36             1 => 'LinkInfo',
37             2 => 'Description',
38             3 => 'RelativePath',
39             4 => 'WorkingDir',
40             5 => 'CommandArgs',
41             6 => 'IconFile',
42             7 => 'Unicode',
43             8 => 'NoLinkInfo',
44             9 => 'ExpString',
45             10 => 'SeparateProc',
46             12 => 'DarwinID',
47             13 => 'RunAsUser',
48             14 => 'ExpIcon',
49             15 => 'NoPidAlias',
50             17 => 'RunWithShim',
51             18 => 'NoLinkTrack',
52             19 => 'TargetMetadata',
53             20 => 'NoLinkPathTracking',
54             21 => 'NoKnownFolderTracking',
55             22 => 'NoKnownFolderAlias',
56             23 => 'LinkToLink',
57             24 => 'UnaliasOnSave',
58             25 => 'PreferEnvPath',
59             26 => 'KeepLocalIDList',
60             }},
61             },
62             0x18 => {
63             Name => 'FileAttributes',
64             Format => 'int32u',
65             PrintConv => { BITMASK => {
66             0 => 'Read-only',
67             1 => 'Hidden',
68             2 => 'System',
69             3 => 'Volume', #(not used)
70             4 => 'Directory',
71             5 => 'Archive',
72             6 => 'Encrypted?', #(ref 2, not used in XP)
73             7 => 'Normal',
74             8 => 'Temporary',
75             9 => 'Sparse',
76             10 => 'Reparse point',
77             11 => 'Compressed',
78             12 => 'Offline',
79             13 => 'Not indexed',
80             14 => 'Encrypted',
81             }},
82             },
83             0x1c => {
84             Name => 'CreateDate',
85             Format => 'int64u',
86             Groups => { 2 => 'Time' },
87             # convert time from 100-ns intervals since Jan 1, 1601
88             RawConv => '$val ? $val : undef',
89             ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)',
90             PrintConv => '$self->ConvertDateTime($val)',
91             },
92             0x24 => {
93             Name => 'AccessDate',
94             Format => 'int64u',
95             Groups => { 2 => 'Time' },
96             RawConv => '$val ? $val : undef',
97             ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)',
98             PrintConv => '$self->ConvertDateTime($val)',
99             },
100             0x2c => {
101             Name => 'ModifyDate',
102             Format => 'int64u',
103             Groups => { 2 => 'Time' },
104             RawConv => '$val ? $val : undef',
105             ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)',
106             PrintConv => '$self->ConvertDateTime($val)',
107             },
108             0x34 => {
109             Name => 'TargetFileSize',
110             Format => 'int32u',
111             },
112             0x38 => {
113             Name => 'IconIndex',
114             Format => 'int32u',
115             PrintConv => '$val ? $val : "(none)"',
116             },
117             0x3c => {
118             Name => 'RunWindow',
119             Format => 'int32u',
120             PrintConv => {
121             0 => 'Hide',
122             1 => 'Normal',
123             2 => 'Show Minimized',
124             3 => 'Show Maximized',
125             4 => 'Show No Activate',
126             5 => 'Show',
127             6 => 'Minimized',
128             7 => 'Show Minimized No Activate',
129             8 => 'Show NA',
130             9 => 'Restore',
131             10 => 'Show Default',
132             },
133             },
134             0x40 => {
135             Name => 'HotKey',
136             Format => 'int32u',
137             PrintHex => 1,
138             PrintConv => {
139             OTHER => sub {
140             my $val = shift;
141             my $ch = $val & 0xff;
142             if (chr $ch =~ /^[A-Z0-9]$/) {
143             $ch = chr $ch;
144             } elsif ($ch >= 0x70 and $ch <= 0x87) {
145             $ch = 'F' . ($ch - 0x6f);
146             } elsif ($ch == 0x90) {
147             $ch = 'Num Lock';
148             } elsif ($ch == 0x91) {
149             $ch = 'Scroll Lock';
150             } else {
151             $ch = sprintf('Unknown (0x%x)', $ch);
152             }
153             $ch = "Alt-$ch" if $val & 0x400;
154             $ch = "Control-$ch" if $val & 0x200;
155             $ch = "Shift-$ch" if $val & 0x100;
156             return $ch;
157             },
158             0x00 => '(none)',
159             # these entries really only for documentation
160             0x90 => 'Num Lock',
161             0x91 => 'Scroll Lock',
162             "0x30'-'0x39" => "0-9",
163             "0x41'-'0x5a" => "A-Z",
164             "0x70'-'0x87" => "F1-F24",
165             0x100 => 'Shift',
166             0x200 => 'Control',
167             0x400 => 'Alt',
168             },
169             },
170             # note: tags 0x10xx are synthesized tag ID's
171             0x10000 => {
172             Name => 'ItemID',
173             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ItemID' },
174             },
175             0x20000 => {
176             Name => 'LinkInfo',
177             SubDirectory => { TagTable => 'Image::ExifTool::LNK::LinkInfo' },
178             },
179             0x30004 => 'Description',
180             0x30008 => 'RelativePath',
181             0x30010 => 'WorkingDirectory',
182             0x30020 => 'CommandLineArguments',
183             0x30040 => 'IconFileName',
184             # note: tags 0xa000000x are actually ID's (not indices)
185             0xa0000000 => {
186             Name => 'UnknownData',
187             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
188             },
189             0xa0000001 => {
190             Name => 'EnvVarData',
191             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
192             },
193             0xa0000002 => {
194             Name => 'ConsoleData',
195             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleData' },
196             },
197             0xa0000003 => {
198             Name => 'TrackerData',
199             SubDirectory => { TagTable => 'Image::ExifTool::LNK::TrackerData' },
200             },
201             0xa0000004 => {
202             Name => 'ConsoleFEData',
203             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleFEData' },
204             },
205             0xa0000005 => {
206             Name => 'SpecialFolderData',
207             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
208             },
209             0xa0000006 => {
210             Name => 'DarwinData',
211             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
212             },
213             0xa0000007 => {
214             Name => 'IconEnvData',
215             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
216             },
217             0xa0000008 => {
218             Name => 'ShimData',
219             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
220             },
221             0xa0000009 => {
222             Name => 'PropertyStoreData',
223             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
224             },
225             0xa000000b => {
226             Name => 'KnownFolderData',
227             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
228             },
229             0xa000000c => {
230             Name => 'VistaIDListData',
231             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
232             },
233             );
234              
235             %Image::ExifTool::LNK::ItemID = (
236             GROUPS => { 2 => 'Other' },
237             PROCESS_PROC => \&ProcessItemID,
238             # (can't find any documentation on these items)
239             0x0032 => {
240             Name => 'Item0032',
241             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Item0032' },
242             },
243             );
244              
245             %Image::ExifTool::LNK::Item0032 = (
246             GROUPS => { 2 => 'Other' },
247             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
248             0x0e => {
249             Name => 'TargetFileDOSName',
250             Format => 'var_string',
251             },
252             #not at a fixed offset -- offset is given by last 2 bytes of the item + 0x14
253             #0x22 => {
254             # Name => 'TargetFileName',
255             # Format => 'var_ustring',
256             #},
257             );
258              
259             %Image::ExifTool::LNK::LinkInfo = (
260             GROUPS => { 2 => 'Other' },
261             PROCESS_PROC => \&ProcessLinkInfo,
262             FORMAT => 'int32u',
263             VARS => { NO_ID => 1 },
264             VolumeID => { },
265             DriveType => {
266             PrintConv => {
267             0 => 'Unknown',
268             1 => 'Invalid Root Path',
269             2 => 'Removable Media',
270             3 => 'Fixed Disk',
271             4 => 'Remote Drive',
272             5 => 'CD-ROM',
273             6 => 'Ram Disk',
274             },
275             },
276             DriveSerialNumber => {
277             PrintConv => 'join("-", unpack("A4 A4", sprintf("%08X", $val)))',
278             },
279             VolumeLabel => { },
280             LocalBasePath => { },
281             CommonNetworkRelLink => { },
282             CommonPathSuffix => { },
283             NetName => { },
284             DeviceName => { },
285             NetProviderType => {
286             PrintHex => 1,
287             PrintConv => {
288             0x1a0000 => 'AVID',
289             0x1b0000 => 'DOCUSPACE',
290             0x1c0000 => 'MANGOSOFT',
291             0x1d0000 => 'SERNET',
292             0x1e0000 => 'RIVERFRONT1',
293             0x1f0000 => 'RIVERFRONT2',
294             0x200000 => 'DECORB',
295             0x210000 => 'PROTSTOR',
296             0x220000 => 'FJ_REDIR',
297             0x230000 => 'DISTINCT',
298             0x240000 => 'TWINS',
299             0x250000 => 'RDR2SAMPLE',
300             0x260000 => 'CSC',
301             0x270000 => '3IN1',
302             0x290000 => 'EXTENDNET',
303             0x2a0000 => 'STAC',
304             0x2b0000 => 'FOXBAT',
305             0x2c0000 => 'YAHOO',
306             0x2d0000 => 'EXIFS',
307             0x2e0000 => 'DAV',
308             0x2f0000 => 'KNOWARE',
309             0x300000 => 'OBJECT_DIRE',
310             0x310000 => 'MASFAX',
311             0x320000 => 'HOB_NFS',
312             0x330000 => 'SHIVA',
313             0x340000 => 'IBMAL',
314             0x350000 => 'LOCK',
315             0x360000 => 'TERMSRV',
316             0x370000 => 'SRT',
317             0x380000 => 'QUINCY',
318             0x390000 => 'OPENAFS',
319             0x3a0000 => 'AVID1',
320             0x3b0000 => 'DFS',
321             },
322             },
323             );
324              
325             %Image::ExifTool::LNK::UnknownData = (
326             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
327             GROUPS => { 2 => 'Other' },
328             );
329              
330             %Image::ExifTool::LNK::ConsoleData = (
331             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
332             GROUPS => { 2 => 'Other' },
333             0x08 => {
334             Name => 'FillAttributes',
335             Format => 'int16u',
336             PrintConv => 'sprintf("0x%.2x", $val)',
337             },
338             0x0a => {
339             Name => 'PopupFillAttributes',
340             Format => 'int16u',
341             PrintConv => 'sprintf("0x%.2x", $val)',
342             },
343             0x0c => {
344             Name => 'ScreenBufferSize',
345             Format => 'int16u[2]',
346             PrintConv => '$val=~s/ / x /; $val',
347             },
348             0x10 => {
349             Name => 'WindowSize',
350             Format => 'int16u[2]',
351             PrintConv => '$val=~s/ / x /; $val',
352             },
353             0x14 => {
354             Name => 'WindowOrigin',
355             Format => 'int16u[2]',
356             PrintConv => '$val=~s/ / x /; $val',
357             },
358             0x20 => {
359             Name => 'FontSize',
360             Format => 'int16u[2]',
361             PrintConv => '$val=~s/ / x /; $val',
362             },
363             0x24 => {
364             Name => 'FontFamily',
365             Format => 'int32u',
366             PrintHex => 1,
367             PrintConv => {
368             0 => "Don't Care",
369             0x10 => 'Roman',
370             0x20 => 'Swiss',
371             0x30 => 'Modern',
372             0x40 => 'Script',
373             0x50 => 'Decorative',
374             },
375             },
376             0x28 => {
377             Name => 'FontWeight',
378             Format => 'int32u',
379             },
380             0x2c => {
381             Name => 'FontName',
382             Format => 'undef[64]',
383             RawConv => q{
384             $val = $self->Decode($val, 'UCS2');
385             $val =~ s/\0.*//s;
386             return length($val) ? $val : undef;
387             },
388             },
389             0x6c => {
390             Name => 'CursorSize',
391             Format => 'int32u',
392             },
393             0x70 => {
394             Name => 'FullScreen',
395             Format => 'int32u',
396             PrintConv => '$val ? "Yes" : "No"',
397             },
398             0x74 => { #PH (MISSING FROM MS DOCUMENTATION! -- screws up subsequent offsets)
399             Name => 'QuickEdit',
400             Format => 'int32u',
401             PrintConv => '$val ? "Yes" : "No"',
402             },
403             0x78 => {
404             Name => 'InsertMode',
405             Format => 'int32u',
406             PrintConv => '$val ? "Yes" : "No"',
407             },
408             0x7c => {
409             Name => 'WindowOriginAuto',
410             Format => 'int32u',
411             PrintConv => '$val ? "Yes" : "No"',
412             },
413             0x80 => {
414             Name => 'HistoryBufferSize',
415             Format => 'int32u',
416             },
417             0x84 => {
418             Name => 'NumHistoryBuffers',
419             Format => 'int32u',
420             },
421             0x88 => {
422             Name => 'RemoveHistoryDuplicates',
423             Format => 'int32u',
424             PrintConv => '$val ? "Yes" : "No"',
425             },
426             );
427              
428             %Image::ExifTool::LNK::TrackerData = (
429             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
430             GROUPS => { 2 => 'Other' },
431             0x10 => {
432             Name => 'MachineID',
433             Format => 'var_string',
434             },
435             );
436              
437             %Image::ExifTool::LNK::ConsoleFEData = (
438             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
439             GROUPS => { 2 => 'Other' },
440             0x08 => {
441             Name => 'CodePage',
442             Format => 'int32u',
443             },
444             );
445              
446             #------------------------------------------------------------------------------
447             # Extract null-terminated ASCII or Unicode string from buffer
448             # Inputs: 0) buffer ref, 1) start position, 2) flag for unicode string
449             # Return: string or undef if start position is outside bounds
450             sub GetString($$;$)
451             {
452 2     2 0 5 my ($dataPt, $pos, $unicode) = @_;
453 2 50       8 return undef if $pos >= length($$dataPt);
454 2         7 pos($$dataPt) = $pos;
455 2 50       21 return $1 if ($unicode ? $$dataPt=~/\G((?:..)*?)\0\0/sg : $$dataPt=~/\G(.*?)\0/sg);
    50          
456 0         0 return substr($$dataPt, $pos);
457             }
458              
459             #------------------------------------------------------------------------------
460             # Process item ID data
461             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
462             # Returns: 1 on success
463             sub ProcessItemID($$$)
464             {
465 1     1 0 3 my ($et, $dirInfo, $tagTablePtr) = @_;
466 1         3 my $dataPt = $$dirInfo{DataPt};
467 1         3 my $dataLen = length $$dataPt;
468 1         2 my $pos = 0;
469             my %opts = (
470             DataPt => $dataPt,
471             DataPos => $$dirInfo{DataPos},
472 1         5 );
473 1         8 $et->VerboseDir('ItemID', undef, $dataLen);
474 1         1 for (;;) {
475 2 100       18 last if $pos + 4 >= $dataLen;
476 1         7 my $size = Get16u($dataPt, $pos);
477 1 50 33     7 last if $size < 2 or $pos + $size > $dataLen;
478 1         4 my $tag = Get16u($dataPt, $pos+2); # (just a guess -- may not be a tag at all)
479             AddTagToTable($tagTablePtr, $tag, {
480             Name => sprintf('Item%.4x', $tag),
481             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
482 1 50       6 }) unless $$tagTablePtr{$tag};
483 1         11 $et->HandleTag($tagTablePtr, $tag, undef, %opts, Start => $pos, Size => $size);
484 1         2 $pos += $size;
485             }
486             }
487              
488             #------------------------------------------------------------------------------
489             # Process link information data
490             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
491             # Returns: 1 on success
492             sub ProcessLinkInfo($$$)
493             {
494 1     1 0 4 my ($et, $dirInfo, $tagTablePtr) = @_;
495 1         2 my $dataPt = $$dirInfo{DataPt};
496 1         3 my $dataLen = length $$dataPt;
497 1 50       3 return 0 if $dataLen < 0x20;
498 1         5 my $hdrLen = Get32u($dataPt, 4);
499 1         2 my $lif = Get32u($dataPt, 8); # link info flags
500             my %opts = (
501             DataPt => $dataPt,
502             DataPos => $$dirInfo{DataPos},
503 1         5 Size => 4, # (typical value size)
504             );
505 1         3 my ($off, $unicode, $pos, $val, $size);
506 1         5 $et->VerboseDir('LinkInfo', undef, $dataLen);
507 1 50       12 if ($lif & 0x01) {
508             # read Volume ID
509 1         5 $off = Get32u($dataPt, 0x0c);
510 1 50 33     8 if ($off and $off + 0x20 <= $dataLen) {
511             # my $len = Get32u($dataPt, $off);
512 1         8 $et->HandleTag($tagTablePtr, 'DriveType', undef, %opts, Start=>$off+4);
513 1         7 $et->HandleTag($tagTablePtr, 'DriveSerialNumber', undef, %opts, Start=>$off+8);
514 1         5 $pos = Get32u($dataPt, $off + 0x0c);
515 1 50       5 if ($pos == 0x14) {
516             # use VolumeLabelOffsetUnicode instead
517 0         0 $pos = Get32u($dataPt, $off + 0x10);
518 0         0 $unicode = 1;
519             }
520 1         2 $pos += $off;
521 1         17 $val = GetString($dataPt, $pos, $unicode);
522 1 50       5 if (defined $val) {
523 1         2 $size = length $val;
524 1 50       4 $val = $et->Decode($val, 'UCS2') if $unicode;
525 1         7 $et->HandleTag($tagTablePtr, 'VolumeLabel', $val, %opts, Start=>$pos, Size=>$size);
526             }
527             }
528             # read local base path
529 1 50       4 if ($hdrLen >= 0x24) {
530 0         0 $pos = Get32u($dataPt, 0x1c);
531 0         0 $unicode = 1;
532             } else {
533 1         3 $pos = Get32u($dataPt, 0x10);
534 1         2 undef $unicode;
535             }
536 1         3 $val = GetString($dataPt, $pos, $unicode);
537 1 50       4 if (defined $val) {
538 1         3 $size = length $val;
539 1 50       5 $val = $et->Decode($val, 'UCS2') if $unicode;
540 1         6 $et->HandleTag($tagTablePtr, 'LocalBasePath', $val, %opts, Start=>$pos, Size=>$size);
541             }
542             }
543 1 50       6 if ($lif & 0x02) {
544             # read common network relative link
545 0         0 $off = Get32u($dataPt, 0x14);
546 0 0 0     0 if ($off and $off + 0x14 <= $dataLen) {
547 0         0 my $siz = Get32u($dataPt, $off);
548 0 0       0 return 0 if $off + $siz > $dataLen;
549 0         0 $pos = Get32u($dataPt, $off + 0x08);
550 0 0 0     0 if ($pos > 0x14 and $siz >= 0x18) {
551 0         0 $pos = Get32u($dataPt, $off + 0x14);
552 0         0 $unicode = 1;
553             } else {
554 0         0 undef $unicode;
555             }
556 0         0 $val = GetString($dataPt, $off + $pos, $unicode);
557 0 0       0 if (defined $val) {
558 0         0 $size = length $val;
559 0 0       0 $val = $et->Decode($val, 'UCS2') if $unicode;
560 0         0 $et->HandleTag($tagTablePtr, 'NetName', $val, %opts, Start=>$pos, Size=>$size);
561             }
562 0         0 my $flg = Get32u($dataPt, $off + 0x04);
563 0 0       0 if ($flg & 0x01) {
564 0         0 $pos = Get32u($dataPt, $off + 0x0c);
565 0 0 0     0 if ($pos > 0x14 and $siz >= 0x1c) {
566 0         0 $pos = Get32u($dataPt, $off + 0x18);
567 0         0 $unicode = 1;
568             } else {
569 0         0 undef $unicode;
570             }
571 0         0 $val = GetString($dataPt, $off + $pos, $unicode);
572 0 0       0 if (defined $val) {
573 0         0 $size = length $val;
574 0 0       0 $val = $et->Decode($val, 'UCS2') if $unicode;
575 0         0 $et->HandleTag($tagTablePtr, 'DeviceName', $val, %opts, Start=>$pos, Size=>$size);
576             }
577             }
578 0 0       0 if ($flg & 0x02) {
579 0         0 $val = Get32u($dataPt, $off + 0x10);
580 0         0 $et->HandleTag($tagTablePtr, 'NetProviderType', $val, %opts, Start=>$off + 0x10);
581             }
582             }
583             }
584 1         3 return 1;
585             }
586              
587             #------------------------------------------------------------------------------
588             # Extract information from a MS Shell Link (Windows shortcut) file
589             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
590             # Returns: 1 on success, 0 if this wasn't a valid LNK file
591             sub ProcessLNK($$)
592             {
593 1     1 0 5 my ($et, $dirInfo) = @_;
594 1         3 my $raf = $$dirInfo{RAF};
595 1         3 my ($buff, $buf2, $len, $i);
596              
597             # read LNK file header
598 1 50       4 $raf->Read($buff, 0x4c) == 0x4c or return 0;
599 1 50       7 $buff =~ /^.{4}\x01\x14\x02\0{5}\xc0\0{6}\x46/s or return 0;
600 1         8 $len = unpack('V', $buff);
601 1 50       3 $len >= 0x4c or return 0;
602 1 50       5 if ($len > 0x4c) {
603 0 0       0 $raf->Read($buf2, $len - 0x4c) == $len - 0x4c or return 0;
604 0         0 $buff .= $buf2;
605             }
606 1         6 $et->SetFileType();
607 1         5 SetByteOrder('II');
608              
609 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::LNK::Main');
610 1         7 my %dirInfo = (
611             DataPt => \$buff,
612             DataPos => 0,
613             DataLen => length $buff,
614             DirLen => length $buff,
615             );
616 1         6 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
617              
618 1         6 my $flags = Get32u(\$buff, 0x14);
619              
620             # read link target ID list
621 1 50       7 if ($flags & 0x01) {
622 1 50       8 $raf->Read($buff, 2) or return 1;
623 1         4 $len = unpack('v', $buff);
624 1 50       16 $raf->Read($buff, $len) == $len or return 1;
625 1         6 $et->HandleTag($tagTablePtr, 0x10000, undef,
626             DataPt => \$buff,
627             DataPos => $raf->Tell() - $len,
628             Size => $len,
629             );
630             }
631              
632             # read link information
633 1 50       6 if ($flags & 0x02) {
634 1 50       5 $raf->Read($buff, 4) or return 1;
635 1         4 $len = unpack('V', $buff);
636 1 50       5 return 1 if $len < 4;
637 1 50       5 $raf->Read($buf2, $len - 4) == $len - 4 or return 1;
638 1         4 $buff .= $buf2;
639 1         6 $et->HandleTag($tagTablePtr, 0x20000, undef,
640             DataPt => \$buff,
641             DataPos => $raf->Tell() - $len,
642             Size => $len,
643             );
644             }
645              
646             # read string data
647 1         5 my @strings = qw(Description RelativePath WorkingDirectory
648             CommandLineArguments IconFileName);
649 1         5 for ($i=0; $i<@strings; ++$i) {
650 5         8 my $mask = 0x04 << $i;
651 5 100       14 next unless $flags & $mask;
652 4 50       14 $raf->Read($buff, 2) or return 1;
653 4         12 $len = unpack('v', $buff);
654 4 50       11 $len *= 2 if $flags & 0x80; # characters are 2 bytes if Unicode flag is set
655 4 50       10 $raf->Read($buff, $len) or return 1;
656 4         7 my $val;
657 4 50       15 $val = $et->Decode($buff, 'UCS2') if $flags & 0x80;
658 4         16 $et->HandleTag($tagTablePtr, 0x30000 | $mask, $val,
659             DataPt => \$buff,
660             DataPos => $raf->Tell() - $len,
661             Size => $len,
662             );
663             }
664              
665             # read extra data
666 1         9 while ($raf->Read($buff, 4) == 4) {
667 3         8 $len = unpack('V', $buff);
668 3 100       9 last if $len < 4;
669 2         3 $len -= 4;
670 2 50       7 $raf->Read($buf2, $len) == $len or last;
671 2 50       8 next unless $len > 4;
672 2         6 $buff .= $buf2;
673 2         9 my $tag = Get32u(\$buff, 4);
674 2         8 my $tagInfo = $$tagTablePtr{$tag};
675 2 50 33     15 unless (ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}) {
676 0         0 $tagInfo = $$tagTablePtr{0xa0000000};
677             }
678 2         8 $et->HandleTag($tagTablePtr, $tag, undef,
679             DataPt => \$buff,
680             DataPos => $raf->Tell() - $len - 4,
681             TagInfo => $tagInfo,
682             );
683             }
684 1         10 return 1;
685             }
686              
687             1; # end
688              
689             __END__