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
12package Image::ExifTool::LNK;
13
14use strict;
15use vars qw($VERSION);
16use Image::ExifTool qw(:DataAccess :Utils);
17
18$VERSION = '1.07';
19
20sub ProcessItemID($$$);
21sub 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    VolumeLabel => { },
278    LocalBasePath => { },
279    CommonNetworkRelLink => { },
280    CommonPathSuffix => { },
281    NetName => { },
282    DeviceName => { },
283    NetProviderType => {
284        PrintHex => 1,
285        PrintConv => {
286            0x1a0000 => 'AVID',
287            0x1b0000 => 'DOCUSPACE',
288            0x1c0000 => 'MANGOSOFT',
289            0x1d0000 => 'SERNET',
290            0x1e0000 => 'RIVERFRONT1',
291            0x1f0000 => 'RIVERFRONT2',
292            0x200000 => 'DECORB',
293            0x210000 => 'PROTSTOR',
294            0x220000 => 'FJ_REDIR',
295            0x230000 => 'DISTINCT',
296            0x240000 => 'TWINS',
297            0x250000 => 'RDR2SAMPLE',
298            0x260000 => 'CSC',
299            0x270000 => '3IN1',
300            0x290000 => 'EXTENDNET',
301            0x2a0000 => 'STAC',
302            0x2b0000 => 'FOXBAT',
303            0x2c0000 => 'YAHOO',
304            0x2d0000 => 'EXIFS',
305            0x2e0000 => 'DAV',
306            0x2f0000 => 'KNOWARE',
307            0x300000 => 'OBJECT_DIRE',
308            0x310000 => 'MASFAX',
309            0x320000 => 'HOB_NFS',
310            0x330000 => 'SHIVA',
311            0x340000 => 'IBMAL',
312            0x350000 => 'LOCK',
313            0x360000 => 'TERMSRV',
314            0x370000 => 'SRT',
315            0x380000 => 'QUINCY',
316            0x390000 => 'OPENAFS',
317            0x3a0000 => 'AVID1',
318            0x3b0000 => 'DFS',
319        },
320    },
321);
322
323%Image::ExifTool::LNK::UnknownData = (
324    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
325    GROUPS => { 2 => 'Other' },
326);
327
328%Image::ExifTool::LNK::ConsoleData = (
329    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
330    GROUPS => { 2 => 'Other' },
331    0x08 => {
332        Name => 'FillAttributes',
333        Format => 'int16u',
334        PrintConv => 'sprintf("0x%.2x", $val)',
335    },
336    0x0a => {
337        Name => 'PopupFillAttributes',
338        Format => 'int16u',
339        PrintConv => 'sprintf("0x%.2x", $val)',
340    },
341    0x0c => {
342        Name => 'ScreenBufferSize',
343        Format => 'int16u[2]',
344        PrintConv => '$val=~s/ / x /; $val',
345    },
346    0x10 => {
347        Name => 'WindowSize',
348        Format => 'int16u[2]',
349        PrintConv => '$val=~s/ / x /; $val',
350    },
351    0x14 => {
352        Name => 'WindowOrigin',
353        Format => 'int16u[2]',
354        PrintConv => '$val=~s/ / x /; $val',
355    },
356    0x20 => {
357        Name => 'FontSize',
358        Format => 'int16u[2]',
359        PrintConv => '$val=~s/ / x /; $val',
360    },
361    0x24 => {
362        Name => 'FontFamily',
363        Format => 'int32u',
364        PrintHex => 1,
365        PrintConv => {
366            0 => "Don't Care",
367            0x10 => 'Roman',
368            0x20 => 'Swiss',
369            0x30 => 'Modern',
370            0x40 => 'Script',
371            0x50 => 'Decorative',
372        },
373    },
374    0x28 => {
375        Name => 'FontWeight',
376        Format => 'int32u',
377    },
378    0x2c => {
379        Name => 'FontName',
380        Format => 'undef[64]',
381        RawConv => q{
382            $val = $self->Decode($val, 'UCS2');
383            $val =~ s/\0.*//s;
384            return length($val) ? $val : undef;
385        },
386    },
387    0x6c => {
388        Name => 'CursorSize',
389        Format => 'int32u',
390    },
391    0x70 => {
392        Name => 'FullScreen',
393        Format => 'int32u',
394        PrintConv => '$val ? "Yes" : "No"',
395    },
396    0x74 => { #PH (MISSING FROM MS DOCUMENTATION! -- screws up subsequent offsets)
397        Name => 'QuickEdit',
398        Format => 'int32u',
399        PrintConv => '$val ? "Yes" : "No"',
400    },
401    0x78 => {
402        Name => 'InsertMode',
403        Format => 'int32u',
404        PrintConv => '$val ? "Yes" : "No"',
405    },
406    0x7c => {
407        Name => 'WindowOriginAuto',
408        Format => 'int32u',
409        PrintConv => '$val ? "Yes" : "No"',
410    },
411    0x80 => {
412        Name => 'HistoryBufferSize',
413        Format => 'int32u',
414    },
415    0x84 => {
416        Name => 'NumHistoryBuffers',
417        Format => 'int32u',
418    },
419    0x88 => {
420        Name => 'RemoveHistoryDuplicates',
421        Format => 'int32u',
422        PrintConv => '$val ? "Yes" : "No"',
423    },
424);
425
426%Image::ExifTool::LNK::TrackerData = (
427    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
428    GROUPS => { 2 => 'Other' },
429    0x10 => {
430        Name => 'MachineID',
431        Format => 'var_string',
432    },
433);
434
435%Image::ExifTool::LNK::ConsoleFEData = (
436    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
437    GROUPS => { 2 => 'Other' },
438    0x08 => {
439        Name => 'CodePage',
440        Format => 'int32u',
441    },
442);
443
444#------------------------------------------------------------------------------
445# Extract null-terminated ASCII or Unicode string from buffer
446# Inputs: 0) buffer ref, 1) start position, 2) flag for unicode string
447# Return: string or undef if start position is outside bounds
448sub GetString($$;$)
449{
450    my ($dataPt, $pos, $unicode) = @_;
451    return undef if $pos >= length($$dataPt);
452    pos($$dataPt) = $pos;
453    return $1 if ($unicode ? $$dataPt=~/\G((?:..)*?)\0\0/sg : $$dataPt=~/\G(.*?)\0/sg);
454    return substr($$dataPt, $pos);
455}
456
457#------------------------------------------------------------------------------
458# Process item ID data
459# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
460# Returns: 1 on success
461sub ProcessItemID($$$)
462{
463    my ($et, $dirInfo, $tagTablePtr) = @_;
464    my $dataPt = $$dirInfo{DataPt};
465    my $dataLen = length $$dataPt;
466    my $pos = 0;
467    my %opts = (
468        DataPt  => $dataPt,
469        DataPos => $$dirInfo{DataPos},
470    );
471    $et->VerboseDir('ItemID', undef, $dataLen);
472    for (;;) {
473        last if $pos + 4 >= $dataLen;
474        my $size = Get16u($dataPt, $pos);
475        last if $size < 2 or $pos + $size > $dataLen;
476        my $tag = Get16u($dataPt, $pos+2); # (just a guess -- may not be a tag at all)
477        AddTagToTable($tagTablePtr, $tag, {
478            Name => sprintf('Item%.4x', $tag),
479            SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
480        }) unless $$tagTablePtr{$tag};
481        $et->HandleTag($tagTablePtr, $tag, undef, %opts, Start => $pos, Size => $size);
482        $pos += $size;
483    }
484}
485
486#------------------------------------------------------------------------------
487# Process link information data
488# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
489# Returns: 1 on success
490sub ProcessLinkInfo($$$)
491{
492    my ($et, $dirInfo, $tagTablePtr) = @_;
493    my $dataPt = $$dirInfo{DataPt};
494    my $dataLen = length $$dataPt;
495    return 0 if $dataLen < 0x20;
496    my $hdrLen = Get32u($dataPt, 4);
497    my $lif = Get32u($dataPt, 8);   # link info flags
498    my %opts = (
499        DataPt  => $dataPt,
500        DataPos => $$dirInfo{DataPos},
501        Size    => 4, # (typical value size)
502    );
503    my ($off, $unicode, $pos, $val, $size);
504    $et->VerboseDir('LinkInfo', undef, $dataLen);
505    if ($lif & 0x01) {
506        # read Volume ID
507        $off = Get32u($dataPt, 0x0c);
508        if ($off + 0x20 <= $dataLen) {
509            # my $len = Get32u($dataPt, $off);
510            $et->HandleTag($tagTablePtr, 'DriveType', undef, %opts, Start=>$off+4);
511            $pos = Get32u($dataPt, $off + 0x0c);
512            if ($pos == 0x14) {
513                # use VolumeLabelOffsetUnicode instead
514                $pos = Get32u($dataPt, $off + 0x10);
515                $unicode = 1;
516            }
517            $pos += $off;
518            $val = GetString($dataPt, $pos, $unicode);
519            if (defined $val) {
520                $size = length $val;
521                $val = $et->Decode($val, 'UCS2') if $unicode;
522                $et->HandleTag($tagTablePtr, 'VolumeLabel', $val, %opts, Start=>$pos, Size=>$size);
523            }
524        }
525        # read local base path
526        if ($hdrLen >= 0x24) {
527            $pos = Get32u($dataPt, 0x1c);
528            $unicode = 1;
529        } else {
530            $pos = Get32u($dataPt, 0x10);
531            undef $unicode;
532        }
533        $val = GetString($dataPt, $pos, $unicode);
534        if (defined $val) {
535            $size = length $val;
536            $val = $et->Decode($val, 'UCS2') if $unicode;
537            $et->HandleTag($tagTablePtr, 'LocalBasePath', $val, %opts, Start=>$pos, Size=>$size);
538        }
539    }
540    if ($lif & 0x02) {
541        # read common network relative link
542        $off = Get32u($dataPt, 0x14);
543        if ($off and $off + 0x14 <= $dataLen) {
544            my $siz = Get32u($dataPt, $off);
545            $pos = Get32u($dataPt, $off + 0x08);
546            if ($pos > 0x14 and $siz >= 0x18) {
547                $pos = Get32u($dataPt, $off + 0x14);
548                $unicode = 1;
549            } else {
550                undef $unicode;
551            }
552            $val = GetString($dataPt, $pos, $unicode);
553            if (defined $val) {
554                $size = length $val;
555                $val = $et->Decode($val, 'UCS2') if $unicode;
556                $et->HandleTag($tagTablePtr, 'NetName', $val, %opts, Start=>$pos, Size=>$size);
557            }
558            my $flg = Get32u($dataPt, $off + 0x04);
559            if ($flg & 0x01) {
560                $pos = Get32u($dataPt, $off + 0x0c);
561                if ($pos > 0x14 and $siz >= 0x1c) {
562                    $pos = Get32u($dataPt, $off + 0x18);
563                    $unicode = 1;
564                } else {
565                    undef $unicode;
566                }
567                $val = GetString($dataPt, $pos, $unicode);
568                if (defined $val) {
569                    $size = length $val;
570                    $val = $et->Decode($val, 'UCS2') if $unicode;
571                    $et->HandleTag($tagTablePtr, 'DeviceName', $val, %opts, Start=>$pos, Size=>$size);
572                }
573            }
574            if ($flg & 0x02) {
575                $val = Get32u($dataPt, $off + 0x10);
576                $et->HandleTag($tagTablePtr, 'NetProviderType', $val, %opts, Start=>$off + 0x10);
577            }
578        }
579    }
580    return 1;
581}
582
583#------------------------------------------------------------------------------
584# Extract information from a MS Shell Link (Windows shortcut) file
585# Inputs: 0) ExifTool object reference, 1) dirInfo reference
586# Returns: 1 on success, 0 if this wasn't a valid LNK file
587sub ProcessLNK($$)
588{
589    my ($et, $dirInfo) = @_;
590    my $raf = $$dirInfo{RAF};
591    my ($buff, $buf2, $len, $i);
592
593    # read LNK file header
594    $raf->Read($buff, 0x4c) == 0x4c or return 0;
595    $buff =~ /^.{4}\x01\x14\x02\0{5}\xc0\0{6}\x46/s or return 0;
596    $len = unpack('V', $buff);
597    $len >= 0x4c or return 0;
598    if ($len > 0x4c) {
599        $raf->Read($buf2, $len - 0x4c) == $len - 0x4c or return 0;
600        $buff .= $buf2;
601    }
602    $et->SetFileType();
603    SetByteOrder('II');
604
605    my $tagTablePtr = GetTagTable('Image::ExifTool::LNK::Main');
606    my %dirInfo = (
607        DataPt => \$buff,
608        DataPos => 0,
609        DataLen => length $buff,
610        DirLen => length $buff,
611    );
612    $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
613
614    my $flags = Get32u(\$buff, 0x14);
615
616    # read link target ID list
617    if ($flags & 0x01) {
618        $raf->Read($buff, 2) or return 1;
619        $len = unpack('v', $buff);
620        $raf->Read($buff, $len) == $len or return 1;
621        $et->HandleTag($tagTablePtr, 0x10000, undef,
622            DataPt  => \$buff,
623            DataPos => $raf->Tell() - $len,
624            Size    => $len,
625        );
626    }
627
628    # read link information
629    if ($flags & 0x02) {
630        $raf->Read($buff, 4) or return 1;
631        $len = unpack('V', $buff);
632        return 1 if $len < 4;
633        $raf->Read($buf2, $len - 4) == $len - 4 or return 1;
634        $buff .= $buf2;
635        $et->HandleTag($tagTablePtr, 0x20000, undef,
636            DataPt  => \$buff,
637            DataPos => $raf->Tell() - $len,
638            Size    => $len,
639        );
640    }
641
642    # read string data
643    my @strings = qw(Description RelativePath WorkingDirectory
644                     CommandLineArguments IconFileName);
645    for ($i=0; $i<@strings; ++$i) {
646        my $mask = 0x04 << $i;
647        next unless $flags & $mask;
648        $raf->Read($buff, 2) or return 1;
649        $len = unpack('v', $buff);
650        $len *= 2 if $flags & 0x80;  # characters are 2 bytes if Unicode flag is set
651        $raf->Read($buff, $len) or return 1;
652        my $val;
653        $val = $et->Decode($buff, 'UCS2') if $flags & 0x80;
654        $et->HandleTag($tagTablePtr, 0x30000 | $mask, $val,
655            DataPt  => \$buff,
656            DataPos => $raf->Tell() - $len,
657            Size    => $len,
658        );
659    }
660
661    # read extra data
662    while ($raf->Read($buff, 4) == 4) {
663        $len = unpack('V', $buff);
664        last if $len < 4;
665        $len -= 4;
666        $raf->Read($buf2, $len) == $len or last;
667        next unless $len > 4;
668        $buff .= $buf2;
669        my $tag = Get32u(\$buff, 4);
670        my $tagInfo = $$tagTablePtr{$tag};
671        unless (ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}) {
672            $tagInfo = $$tagTablePtr{0xa0000000};
673        }
674        $et->HandleTag($tagTablePtr, $tag, undef,
675            DataPt  => \$buff,
676            DataPos => $raf->Tell() - $len - 4,
677            TagInfo => $tagInfo,
678        );
679    }
680    return 1;
681}
682
6831;  # end
684
685__END__
686
687=head1 NAME
688
689Image::ExifTool::LNK - Read MS Shell Link (.LNK) meta information
690
691=head1 SYNOPSIS
692
693This module is used by Image::ExifTool
694
695=head1 DESCRIPTION
696
697This module contains definitions required by Image::ExifTool to extract meta
698information MS Shell Link (Windows shortcut) files.
699
700=head1 AUTHOR
701
702Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
703
704This library is free software; you can redistribute it and/or modify it
705under the same terms as Perl itself.
706
707=head1 REFERENCES
708
709=over 4
710
711=item L<http://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx>
712
713=item L<http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf>
714
715=back
716
717=head1 SEE ALSO
718
719L<Image::ExifTool::TagNames/LNK Tags>,
720L<Image::ExifTool(3pm)|Image::ExifTool>
721
722=cut
723
724