File Coverage

blib/lib/Image/ExifTool/LNK.pm
Criterion Covered Total %
statement 179 259 69.1
branch 70 166 42.1
condition 7 35 20.0
subroutine 11 12 91.6
pod 0 7 0.0
total 267 479 55.7


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             # 2025/10/20 - PH Added .URL file support
8             #
9             # References: 1) http://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx ([MS-SHLLINK].pdf)
10             # 2) http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf
11             # 3) https://harfanglab.io/insidethelab/sadfuture-xdspy-latest-evolution/#tid_specifications_ignored
12             # 4) https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc
13             # 5) https://github.com/EricZimmerman/Lnk/blob/master/Lnk/ShellItems/ShellBag0x00.cs
14             #------------------------------------------------------------------------------
15              
16             package Image::ExifTool::LNK;
17              
18 1     1   5076 use strict;
  1         1  
  1         34  
19 1     1   4 use vars qw($VERSION);
  1         1  
  1         59  
20 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         192  
21 1     1   541 use Image::ExifTool::Microsoft;
  1         4  
  1         78  
22 1     1   598 use Image::ExifTool::ASF; # (for GetGUID)
  1         4  
  1         5073  
23              
24             $VERSION = '1.18';
25              
26             sub ProcessItemID($$$);
27             sub ProcessLinkInfo($$$);
28             sub ProcessURI($$$);
29              
30             my %fileAttributes = (
31             BITMASK => {
32             0 => 'Read-only',
33             1 => 'Hidden',
34             2 => 'System',
35             3 => 'Volume', #(not used)
36             4 => 'Directory',
37             5 => 'Archive',
38             6 => 'Encrypted?', #(ref 2, not used in XP)
39             7 => 'Normal',
40             8 => 'Temporary',
41             9 => 'Sparse',
42             10 => 'Reparse point',
43             11 => 'Compressed',
44             12 => 'Offline',
45             13 => 'Not indexed',
46             14 => 'Encrypted',
47             },
48             );
49              
50             my %fileTime = (
51             Format => 'int64u',
52             # convert time from 100-ns intervals since Jan 1, 1601
53             RawConv => '$val ? $val : undef',
54             ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)',
55             PrintConv => '$self->ConvertDateTime($val)',
56             );
57              
58             my %guidLookup = (
59             # ref https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
60             '008CA0B1-55B4-4C56-B8A8-4DE4B299D3BE' => 'Account Pictures (per-user)',
61             'DE61D971-5EBC-4F02-A3A9-6C82895E5C04' => 'Get Programs (virtual)',
62             '724EF170-A42D-4FEF-9F26-B60E846FBA4F' => 'Administrative Tools (per-user)',
63             'B2C5E279-7ADD-439F-B28C-C41FE1BBF672' => 'AppDataDesktop (per-user)',
64             '7BE16610-1F7F-44AC-BFF0-83E15F2FFCA1' => 'AppDataDocuments (per-user)',
65             '7CFBEFBC-DE1F-45AA-B843-A542AC536CC9' => 'AppDataFavorites (per-user)',
66             '559D40A3-A036-40FA-AF61-84CB430A4D34' => 'AppDataProgramData (per-user)',
67             'A3918781-E5F2-4890-B3D9-A7E54332328C' => 'Application Shortcuts (per-user)',
68             '1E87508D-89C2-42F0-8A7E-645A0F50CA58' => 'Applications (virtual)',
69             'A305CE99-F527-492B-8B1A-7E76FA98D6E4' => 'Installed Updates (virtual)',
70             'AB5FB87B-7CE2-4F83-915D-550846C9537B' => 'Camera Roll (per-user)',
71             '9E52AB10-F80D-49DF-ACB8-4330F5687855' => 'Temporary Burn Folder (per-user)',
72             'DF7266AC-9274-4867-8D55-3BD661DE872D' => 'Programs and Features (virtual)',
73             'D0384E7D-BAC3-4797-8F14-CBA229B392B5' => 'Administrative Tools (common)',
74             'C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D' => 'OEM Links (common)',
75             '0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8' => 'Programs (common)',
76             'A4115719-D62E-491D-AA7C-E74B8BE3B067' => 'Start Menu (common)',
77             '82A5EA35-D9CD-47C5-9629-E15D2F714E6E' => 'Startup (common)',
78             'B94237E7-57AC-4347-9151-B08C6C32D1F7' => 'Templates (common)',
79             '0AC0837C-BBF8-452A-850D-79D08E667CA7' => 'Computer (virtual)',
80             '4BFEFB45-347D-4006-A5BE-AC0CB0567192' => 'Conflicts (virtual)',
81             '6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD' => 'Network Connections (virtual)',
82             '56784854-C6CB-462B-8169-88E350ACB882' => 'Contacts (per-user)',
83             '82A74AEB-AEB4-465C-A014-D097EE346D63' => 'Control Panel (virtual)',
84             '2B0F765D-C0E9-4171-908E-08A611B84FF6' => 'Cookies (per-user)',
85             'B4BFCC3A-DB2C-424C-B029-7FE99A87C641' => 'Desktop (per-user)',
86             '5CE4A5E9-E4EB-479D-B89F-130C02886155' => 'DeviceMetadataStore (common)',
87             'FDD39AD0-238F-46AF-ADB4-6C85480369C7' => 'Documents (per-user)',
88             '7B0DB17D-9CD2-4A93-9733-46CC89022E7C' => 'Documents (per-user)',
89             '374DE290-123F-4565-9164-39C4925E467B' => 'Downloads (per-user)',
90             '1777F761-68AD-4D8A-87BD-30B759FA33DD' => 'Favorites (per-user)',
91             'FD228CB7-AE11-4AE3-864C-16F3910AB8FE' => 'Fonts (fixed)',
92             'CAC52C1A-B53D-4EDC-92D7-6B2E8AC19434' => 'Games (virtual)',
93             '054FAE61-4DD8-4787-80B6-090220C4B700' => 'GameExplorer (per-user)',
94             'D9DC8A3B-B784-432E-A781-5A1130A75963' => 'History (per-user)',
95             '52528A6B-B9E3-4ADD-B60D-588C2DBA842D' => 'Homegroup (virtual)',
96             '9B74B6A3-0DFD-4F11-9E78-5F7800F2E772' => 'User name (%USERNAME%) (virtual)',
97             'BCB5256F-79F6-4CEE-B725-DC34E402FD46' => 'ImplicitAppShortcuts (per-user)',
98             '352481E8-33BE-4251-BA85-6007CAEDCF9D' => 'Temporary Internet Files (per-user)',
99             '4D9F7874-4E0C-4904-967B-40B0D20C3E4B' => 'The Internet (virtual)',
100             '1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE' => 'Libraries (per-user)',
101             'BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968' => 'Links (per-user)',
102             'F1B32785-6FBA-4FCF-9D55-7B8E7F157091' => 'Local (per-user)',
103             'A520A1A4-1780-4FF6-BD18-167343C5AF16' => 'LocalLow (per-user)',
104             '2A00375E-224C-49DE-B8D1-440DF7EF3DDC' => 'None (fixed)',
105             '4BD8D571-6D19-48D3-BE97-422220080E43' => 'Music (per-user)',
106             '2112AB0A-C86A-4FFE-A368-0DE96E47012E' => 'Music (per-user)',
107             'C5ABBF53-E17F-4121-8900-86626FC2C973' => 'Network Shortcuts (per-user)',
108             'D20BEEC4-5CA8-4905-AE3B-BF251EA09B53' => 'Network (virtual)',
109             '31C0DD25-9439-4F12-BF41-7FF4EDA38722' => '3D Objects (per-user)',
110             '2C36C0AA-5812-4B87-BFD0-4CD0DFB19B39' => 'Original Images (per-user)',
111             '69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C' => 'Slide Shows (per-user)',
112             'A990AE9F-A03B-4E80-94BC-9912D7504104' => 'Pictures (per-user)',
113             '33E28130-4E1E-4676-835A-98395C3BC3BB' => 'Pictures (per-user)',
114             'DE92C1C7-837F-4F69-A3BB-86E631204A23' => 'Playlists (per-user)',
115             '76FC4E2D-D6AD-4519-A663-37BD56068185' => 'Printers (virtual)',
116             '9274BD8D-CFD1-41C3-B35E-B13F55A758F4' => 'Printer Shortcuts (per-user)',
117             '5E6C858F-0E22-4760-9AFE-EA3317B67173' => 'User Name (%USERNAME%) (fixed)',
118             '62AB5D82-FDC1-4DC3-A9DD-070D1D495D97' => 'ProgramData (fixed)',
119             '905E63B6-C1BF-494E-B29C-65B732D3D21A' => 'Program Files (fixed)',
120             '6D809377-6AF0-444B-8957-A3773F02200E' => 'Program Files (fixed)',
121             '7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E' => 'Program Files (fixed)',
122             'F7F1ED05-9F6D-47A2-AAAE-29D317C6F066' => 'Common Files (fixed)',
123             '6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D' => 'Common Files (fixed)',
124             'DE974D24-D9C6-4D3E-BF91-F4455120B917' => 'Common Files (fixed)',
125             'A77F5D77-2E2B-44C3-A6A2-ABA601054A51' => 'Programs (per-user)',
126             'DFDF76A2-C82A-4D63-906A-5644AC457385' => 'Public (fixed)',
127             'C4AA340D-F20F-4863-AFEF-F87EF2E6BA25' => 'Public Desktop (common)',
128             'ED4824AF-DCE4-45A8-81E2-FC7965083634' => 'Public Documents (common)',
129             '3D644C9B-1FB8-4F30-9B45-F670235F79C0' => 'Public Downloads (common)',
130             'DEBF2536-E1A8-4C59-B6A2-414586476AEA' => 'GameExplorer (common)',
131             '48DAF80B-E6CF-4F4E-B800-0E69D84EE384' => 'Libraries (common)',
132             '3214FAB5-9757-4298-BB61-92A9DEAA44FF' => 'Public Music (common)',
133             'B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5' => 'Public Pictures (common)',
134             'E555AB60-153B-4D17-9F04-A5FE99FC15EC' => 'Ringtones (common)',
135             '0482AF6C-08F1-4C34-8C90-E17EC98B1E17' => 'Public Account Pictures (common)',
136             '2400183A-6185-49FB-A2D8-4A392A602BA3' => 'Public Videos (common)',
137             '52A4F021-7B75-48A9-9F6B-4B87A210BC8F' => 'Quick Launch (per-user)',
138             'AE50C081-EBD2-438A-8655-8A092E34987A' => 'Recent Items (per-user)',
139             '1A6FDBA2-F42D-4358-A798-B74D745926C5' => 'Recorded TV (common)',
140             'B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC' => 'Recycle Bin (virtual)',
141             '8AD10C31-2ADB-4296-A8F7-E4701232C972' => 'Resources (fixed)',
142             'C870044B-F49E-4126-A9C3-B52A1FF411E8' => 'Ringtones (per-user)',
143             '3EB685DB-65F9-4CF6-A03A-E3EF65729F3D' => 'Roaming (per-user)',
144             'AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E' => 'RoamedTileImages (per-user)',
145             '00BCFC5A-ED94-4E48-96A1-3F6217F21990' => 'RoamingTiles (per-user)',
146             'B250C668-F57D-4EE1-A63C-290EE7D1AA1F' => 'Sample Music (common)',
147             'C4900540-2379-4C75-844B-64E6FAF8716B' => 'Sample Pictures (common)',
148             '15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5' => 'Sample Playlists (common)',
149             '859EAD94-2E85-48AD-A71A-0969CB56A6CD' => 'Sample Videos (common)',
150             '4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4' => 'Saved Games (per-user)',
151             '3B193882-D3AD-4EAB-965A-69829D1FB59F' => 'Saved Pictures (per-user)',
152             'E25B5812-BE88-4BD9-94B0-29233477B6C3' => 'Saved Pictures Library (per-user)',
153             '7D1D3A04-DEBB-4115-95CF-2F29DA2920DA' => 'Searches (per-user)',
154             'B7BEDE81-DF94-4682-A7D8-57A52620B86F' => 'Screenshots (per-user)',
155             'EE32E446-31CA-4ABA-814F-A5EBD2FD6D5E' => 'Offline Files (virtual)',
156             '0D4C3DB6-03A3-462F-A0E6-08924C41B5D4' => 'History (per-user)',
157             '190337D1-B8CA-4121-A639-6D472D16972A' => 'Search Results (virtual)',
158             '98EC0E18-2098-4D44-8644-66979315A281' => 'Microsoft Office Outlook (virtual)',
159             '7E636BFE-DFA9-4D5E-B456-D7B39851D8A9' => 'Templates (per-user)',
160             '8983036C-27C0-404B-8F08-102D10DCFD74' => 'SendTo (per-user)',
161             '7B396E54-9EC5-4300-BE0A-2482EBAE1A26' => 'Gadgets (common)',
162             'A75D362E-50FC-4FB7-AC2C-A8BEAA314493' => 'Gadgets (per-user)',
163             'A52BBA46-E9E1-435F-B3D9-28DAA648C0F6' => 'OneDrive (per-user)',
164             '767E6811-49CB-4273-87C2-20F355E1085B' => 'Camera Roll (per-user)',
165             '24D89E24-2F19-4534-9DDE-6A6671FBB8FE' => 'Documents (per-user)',
166             '339719B5-8C47-4894-94C2-D8F77ADD44A6' => 'Pictures (per-user)',
167             '625B53C3-AB48-4EC1-BA1F-A1EF4146FC19' => 'Start Menu (per-user)',
168             'B97D20BB-F46A-4C97-BA10-5E3608430854' => 'Startup (per-user)',
169             '43668BF8-C14E-49B2-97C9-747784D784B7' => 'Sync Center (virtual)',
170             '289A9A43-BE44-4057-A41B-587A76D7E7F9' => 'Sync Results (virtual)',
171             '0F214138-B1D3-4A90-BBA9-27CBC0C5389A' => 'Sync Setup (virtual)',
172             '1AC14E77-02E7-4E5D-B744-2EB1AE5198B7' => 'System32 (fixed)',
173             'D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27' => 'System32 (fixed)',
174             'A63293E8-664E-48DB-A079-DF759E0509F7' => 'Templates (per-user)',
175             '9E3995AB-1F9C-4F13-B827-48B24B6C7174' => 'User Pinned (per-user)',
176             '0762D272-C50A-4BB0-A382-697DCD729B80' => 'Users (fixed)',
177             '5CD7AEE2-2219-4A67-B85D-6C9CE15660CB' => 'Programs (per-user)',
178             'BCBD3057-CA5C-4622-B42D-BC56DB0AE516' => 'Programs (per-user)',
179             'F3CE0F7C-4901-4ACC-8648-D5D44B04EF8F' => 'Users Full Name (virtual)',
180             'A302545D-DEFF-464B-ABE8-61C8648D939B' => 'Libraries (virtual)',
181             '18989B1D-99B5-455B-841C-AB7C74E4DDFC' => 'MyVideos (per-user)',
182             '491E922F-5643-4AF4-A7EB-4E7A138D8174' => 'Videos (per-user)',
183             # ref Google AI and from my samples
184             '00021401-0000-0000-C000-000000000046' => 'Shell Link Class Identifier',
185             '20D04FE0-3AEA-1069-A2D8-08002B30309D' => 'My Computer',
186             '450D8FBA-AD25-11D0-A2A8-0800361B3003' => 'My Documents',
187             'B4BFCC3A-DB2C-424C-B029-7FE99A87C641' => 'Desktop',
188             'F3364BA0-65B9-11CE-A9BA-00AA004AE661' => 'Search Results Folder',
189             '04731B67-D933-450A-90E6-4ACD2E9408FE' => 'CLSID_SearchFolder (Windows Search)',
190             '53F5630D-B6BF-11D0-94F2-00A0C91EFB8B' => 'Device Class GUID for a volume',
191             'F42EE2D3-909F-4907-8871-4C22FC0BF756' => 'Documents',
192             '17789161-0268-45B3-8557-013009765873' => 'Local AppData',
193             '9E395ED8-512D-4315-9960-9110B74616C8' => 'Recent Items',
194             '21EC2020-3AEA-1069-A2DD-08002B30309D' => 'Control Panel Items',
195             '7007ACC7-3202-11D1-AAD2-00805FC1270E' => 'Network Connections',
196             '26EE0668-A00A-44D7-9371-BEB064C98683' => 'Control Panel',
197             '2559A1F1-21D7-11D4-BDAF-00C04F60B9F0' => 'Windows Help and Support',
198             '031E4825-7B94-4DC3-B131-E946B44C8DD5' => 'Libraries',
199             '22877A6D-37A1-461A-91B0-DBDA5AAEBC99' => 'Recent Items',
200             '2559A1F3-21D7-11D4-BDAF-00C04F60B9F0' => 'Run Dialog Box',
201             '3080F90D-D7AD-11D9-BD98-0000947B0257' => 'Desktop',
202             '3080F90E-D7AD-11D9-BD98-0000947B0257' => 'Task View',
203             '4336A54D-038B-4685-AB02-99BB52D3FB8B' => 'Public User Root Folder',
204             '5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0' => 'Control Panel',
205             '59031A47-3F72-44A7-89C5-5595FE6B30EE' => 'User Profile',
206             '871C5380-42A0-1069-A2EA-08002B30309D' => 'Internet',
207             'ED228FDF-9EA8-4870-83B1-96B02CFE0D52' => 'Game Explorer',
208             'A8CDFF1C-4878-43BE-B5FD-F8091C1C60D0' => 'Documents',
209             '3ADD1653-EB32-4CB0-BBD7-DFA0ABB5ACCA' => 'My Pictures',
210             '52205FD8-5DFB-447D-801A-D0B52F2E83E1' => "User Files",
211             'D3162B92-9365-467A-956B-92703ACA08AF' => 'User Documents',
212             '4234D49B-0245-4DF3-B780-3893943456E1' => 'User Music',
213             # ref https://github.com/EricZimmerman/GuidMapping/blob/master/Resources/GuidToName.txt
214             '0C39A5CF-1A7A-40C8-BA74-8900E6DF5FCD' => 'Recent Items',
215             # ref https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc
216             '5E591A74-DF96-48D3-8D67-1733BCEE28BA' => 'Delegate GUID',
217             '04731b67-d933-450a-90e6-4acd2e9408fe' => 'Search Folder',
218             'DFFACDC5-679F-4156-8947-C5C76BC0B67F' => 'Users Files',
219             '289af617-1cc3-42a6-926c-e6a863f0e3ba' => 'My Computer',
220             '3134ef9c-6b18-4996-ad04-ed5912e00eb5' => 'Recent Files',
221             '35786d3c-b075-49b9-88dd-029876e11c01' => 'Portable Devices',
222             '3936e9e4-d92c-4eee-a85a-bc16d5ea0819' => 'Frequent Places',
223             '59031a47-3f72-44a7-89c5-5595fe6b30ee' => 'Shared Documents',
224             '640167b4-59b0-47a6-b335-a6b3c0695aea' => 'Portable Media Devices',
225             '896664f7-12e1-490f-8782-c0835afd98fc' => 'Libraries',
226             '9113a02d-00a3-46b9-bc5f-9c04daddd5d7' => 'Enhanced Storage Data Source',
227             '9db7a13c-f208-4981-8353-73cc61ae2783' => 'Previous Versions',
228             'b155bdf8-02f0-451e-9a26-ae317cfd7779' => 'NetHood',
229             'c2b136e2-d50e-405c-8784-363c582bf43e' => 'Wireless Devices',
230             'd34a6ca6-62c2-4c34-8a7c-14709c1ad938' => 'Common Places',
231             'dffacdc5-679f-4156-8947-c5c76bc0b67f' => 'Profile',
232             'ed50fc29-b964-48a9-afb3-15ebb9b97f36' => 'PrintHood',
233             'f5fb2c77-0e2f-4a16-a381-3e560c68bc83' => 'Removable Drives',
234             # ref https://learn.microsoft.com/en-us/windows/win32/wpd_sdk/supporting-autoplay
235             '80E170D2-1055-4A3E-B952-82CC4F8A8689' => 'Content Type All',
236             '0FED060E-8793-4B1E-90C9-48AC389AC631' => 'Content Type Appointment',
237             '4AD2C85E-5E2D-45E5-8864-4F229E3C6CF0' => 'Content Type Audio',
238             'AA18737E-5009-48FA-AE21-85F24383B4E6' => 'Content Type Audio Album',
239             'A1FD5967-6023-49A0-9DF1-F8060BE751B0' => 'Content Type Calendar',
240             'DC3876E8-A948-4060-9050-CBD77E8A3D87' => 'Content Type Certificate',
241             'EABA8313-4525-4707-9F0E-87C6808E9435' => 'Content Type Contact',
242             '346B8932-4C36-40D8-9415-1828291F9DE9' => 'Content Type Contact Group',
243             '680ADF52-950A-4041-9B41-65E393648155' => 'Content Type Document',
244             '8038044A-7E51-4F8F-883D-1D0623D14533' => 'Content Type Email',
245             '27E2E392-A111-48E0-AB0C-E17705A05F85' => 'Content Type Folder',
246             '99ED0160-17FF-4C44-9D98-1D7A6F941921' => 'Content Type Functional Object',
247             '0085E0A6-8D34-45D7-BC5C-447E59C73D48' => 'Content Type Generic File',
248             'E80EAAF8-B2DB-4133-B67E-1BEF4B4A6E5F' => 'Content Type Generic Message',
249             'EF2107D5-A52A-4243-A26B-62D4176D7603' => 'Content Type Image',
250             '75793148-15F5-4A30-A813-54ED8A37E226' => 'Content Type Image Album',
251             '5E88B3CC-3E65-4E62-BFFF-229495253AB0' => 'Content Type Media Cast',
252             '9CD20ECF-3B50-414F-A641-E473FFE45751' => 'Content Type Memo',
253             '00F0C3AC-A593-49AC-9219-24ABCA5A2563' => 'Content Type Mixed Content Album',
254             '031DA7EE-18C8-4205-847E-89A11261D0F3' => 'Content Type Network Association',
255             '1A33F7E4-AF13-48F5-994E-77369DFE04A3' => 'Content Type Playlist',
256             'D269F96A-247C-4BFF-98FB-97F3C49220E6' => 'Content Type Program',
257             '821089F5-1D91-4DC9-BE3C-BBB1B35B18CE' => 'Content Type Section',
258             '63252F2C-887F-4CB6-B1AC-D29855DCEF6C' => 'Content Type Task',
259             '60A169CF-F2AE-4E21-9375-9677F11C1C6E' => 'Content Type Television',
260             '28D8D31E-249C-454E-AABC-34883168E634' => 'Content Type Unspecified',
261             '9261B03C-3D78-4519-85E3-02C5E1F50BB9' => 'Content Type Video',
262             '012B0DB7-D4C1-45D6-B081-94B87779614F' => 'Content Type Video Album',
263             '0BAC070A-9F5F-4DA4-A8F6-3DE44D68FD6C' => 'Content Type Wireless Profile',
264             );
265              
266             # common tag information for MTP GUID tags
267             my %mtpGUID = (
268             Format => 'undef[72]',
269             ValueConv => '$self->Decode($val, "UTF16")',
270             SeparateTable => 'GUID',
271             PrintConv => \%guidLookup,
272             );
273              
274             # Information extracted from LNK (Windows Shortcut) files
275             %Image::ExifTool::LNK::Main = (
276             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
277             GROUPS => { 2 => 'Other' },
278             VARS => { ID_FMT => 'hex' }, # print hex ID's in documentation
279             NOTES => 'Information extracted from MS Shell Link (Windows shortcut) files.',
280             # maybe the Flags aren't very useful to the user (since they are
281             # mainly structural), but extract them anyway for completeness
282             0x14 => {
283             Name => 'Flags',
284             Format => 'int32u',
285             PrintConv => { BITMASK => {
286             0 => 'IDList',
287             1 => 'LinkInfo',
288             2 => 'Description',
289             3 => 'RelativePath',
290             4 => 'WorkingDir',
291             5 => 'CommandArgs',
292             6 => 'IconFile',
293             7 => 'Unicode',
294             8 => 'NoLinkInfo',
295             9 => 'ExpString',
296             10 => 'SeparateProc',
297             12 => 'DarwinID',
298             13 => 'RunAsUser',
299             14 => 'ExpIcon',
300             15 => 'NoPidAlias',
301             17 => 'RunWithShim',
302             18 => 'NoLinkTrack',
303             19 => 'TargetMetadata',
304             20 => 'NoLinkPathTracking',
305             21 => 'NoKnownFolderTracking',
306             22 => 'NoKnownFolderAlias',
307             23 => 'LinkToLink',
308             24 => 'UnaliasOnSave',
309             25 => 'PreferEnvPath',
310             26 => 'KeepLocalIDList',
311             }},
312             },
313             0x18 => {
314             Name => 'FileAttributes',
315             Format => 'int32u',
316             SeparateTable => 'FileAttributes',
317             PrintConv => \%fileAttributes,
318             },
319             0x1c => {
320             Name => 'CreateDate',
321             Groups => { 2 => 'Time' },
322             %fileTime,
323             },
324             0x24 => {
325             Name => 'AccessDate',
326             Groups => { 2 => 'Time' },
327             %fileTime,
328             },
329             0x2c => {
330             Name => 'ModifyDate',
331             Groups => { 2 => 'Time' },
332             %fileTime,
333             },
334             0x34 => {
335             Name => 'TargetFileSize',
336             Format => 'int32u',
337             },
338             0x38 => {
339             Name => 'IconIndex',
340             Format => 'int32u',
341             PrintConv => '$val ? $val : "(none)"',
342             # (seen 0xffffffff)
343             },
344             0x3c => {
345             Name => 'RunWindow',
346             Format => 'int32u',
347             PrintConv => {
348             0 => 'Hide',
349             1 => 'Normal',
350             2 => 'Show Minimized',
351             3 => 'Show Maximized',
352             4 => 'Show No Activate',
353             5 => 'Show',
354             6 => 'Minimized',
355             7 => 'Show Minimized No Activate',
356             8 => 'Show NA',
357             9 => 'Restore',
358             10 => 'Show Default',
359             },
360             },
361             0x40 => {
362             Name => 'HotKey',
363             Format => 'int32u',
364             PrintHex => 1,
365             PrintConv => {
366             OTHER => sub {
367             my $val = shift;
368             my $ch = $val & 0xff;
369             if (chr $ch =~ /^[A-Z0-9]$/) {
370             $ch = chr $ch;
371             } elsif ($ch >= 0x70 and $ch <= 0x87) {
372             $ch = 'F' . ($ch - 0x6f);
373             } elsif ($ch == 0x90) {
374             $ch = 'Num Lock';
375             } elsif ($ch == 0x91) {
376             $ch = 'Scroll Lock';
377             } else {
378             $ch = sprintf('Unknown (0x%x)', $ch);
379             }
380             $ch = "Alt-$ch" if $val & 0x400;
381             $ch = "Control-$ch" if $val & 0x200;
382             $ch = "Shift-$ch" if $val & 0x100;
383             return $ch;
384             },
385             0x00 => '(none)',
386             # these entries really only for documentation
387             0x90 => 'Num Lock',
388             0x91 => 'Scroll Lock',
389             "0x30'-'0x39" => "0-9",
390             "0x41'-'0x5a" => "A-Z",
391             "0x70'-'0x87" => "F1-F24",
392             0x100 => 'Shift',
393             0x200 => 'Control',
394             0x400 => 'Alt',
395             },
396             },
397             # note: tags 0x100xx-0x300xx are synthesized tag ID's
398             0x10000 => {
399             Name => 'ItemID',
400             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ItemID' },
401             },
402             0x20000 => {
403             Name => 'LinkInfo',
404             SubDirectory => { TagTable => 'Image::ExifTool::LNK::LinkInfo' },
405             },
406             0x30004 => 'Description',
407             0x30008 => 'RelativePath',
408             0x30010 => 'WorkingDirectory',
409             0x30020 => 'CommandLineArguments',
410             0x30040 => 'IconFileName',
411             # note: tags 0xa000000x are actually ID's (not indices)
412             0xa0000000 => {
413             Name => 'UnknownData',
414             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
415             },
416             0xa0000001 => {
417             Name => 'EnvVarData',
418             SubDirectory => { TagTable => 'Image::ExifTool::LNK::EnvVarData' },
419             },
420             0xa0000002 => {
421             Name => 'ConsoleData',
422             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleData' },
423             },
424             0xa0000003 => {
425             Name => 'TrackerData',
426             SubDirectory => { TagTable => 'Image::ExifTool::LNK::TrackerData' },
427             },
428             0xa0000004 => {
429             Name => 'ConsoleFEData',
430             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ConsoleFEData' },
431             },
432             0xa0000005 => {
433             Name => 'SpecialFolderData',
434             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
435             },
436             0xa0000006 => {
437             Name => 'DarwinData',
438             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
439             },
440             0xa0000007 => {
441             Name => 'IconEnvData',
442             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
443             },
444             0xa0000008 => {
445             Name => 'ShimData',
446             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
447             },
448             0xa0000009 => {
449             Name => 'PropertyStoreData',
450             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
451             },
452             0xa000000b => {
453             Name => 'KnownFolderData',
454             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
455             },
456             0xa000000c => {
457             Name => 'VistaIDListData',
458             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
459             },
460             );
461              
462             # ref: https://helgeklein.com/blog/dissecting-a-shortcut/
463             # ref: https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target_factory.py
464             # Note: The ItemID raw data includes the leading 2-byte size word
465             %Image::ExifTool::LNK::ItemID = (
466             GROUPS => { 2 => 'Other' },
467             PROCESS_PROC => \&ProcessItemID,
468             0x00 => [{
469             Name => 'ControlPanelCPL', # special ID 0xffffffxx
470             Condition => '$$valPt =~ /^.{5}\xff{3}/s',
471             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ControlPanelCPL' },
472             },{
473             Name => 'GameFolderInfo', # special ID 0x49534647
474             Condition => '$$valPt =~ /^.{4}GFSI/s',
475             SubDirectory => { TagTable => 'Image::ExifTool::LNK::GameFolderInfo' },
476             },{
477             Name => 'PropertyStore', # ID 0x23febbee
478             Condition => '$$valPt =~ /^.{6}\xee\xbb\xfe\x23/',
479             SubDirectory => { TagTable => 'Image::ExifTool::LNK::PropertyStore' },
480             },{
481             Name => 'MTPType2', # ID 0x10312005
482             Condition => '$$valPt =~ /^.{6}\\x05\x20\x31\x10/',
483             SubDirectory => { TagTable => 'Image::ExifTool::LNK::MTPType2' },
484             },{
485             Name => 'Item00Info',
486             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Item00Info' },
487             }],
488             0x01 => {
489             Name => 'ControlPanelInfo',
490             SubDirectory => { TagTable => 'Image::ExifTool::LNK::ControlPanelInfo' },
491             },
492             0x1e => { # (seems to be decoding the same as 0x1f)
493             Name => 'RootFolder',
494             SubDirectory => { TagTable => 'Image::ExifTool::LNK::RootFolder' },
495             },
496             0x1f => {
497             Name => 'RootFolder',
498             SubDirectory => { TagTable => 'Image::ExifTool::LNK::RootFolder' },
499             },
500             # 0x20-0x2f MyComputer (even ID's have GUID, odd have name)
501             0x2e => { # (have only seen 0x2e so far)
502             Name => 'VolumeGUID',
503             RawConv => 'length($val) >= 20 ? $val : undef',
504             # seen 0x14 and 0x32 bytes long, but GUID was last 16 bytes in both cases
505             ValueConv => 'Image::ExifTool::ASF::GetGUID(substr($val,-16))',
506             SeparateTable => 'GUID',
507             PrintConv => \%guidLookup,
508             },
509             0x2f => {
510             Name => 'VolumeName',
511             ValueConv => '$_ = substr($val, 3); s/\0+$//; $_',
512             },
513             # 0x30-0x3f ShellFSFolder
514             0x31 => {
515             Name => 'TargetInfo',
516             SubDirectory => { TagTable => 'Image::ExifTool::LNK::TargetInfo' },
517             },
518             # 0x40-0x4f NetworkLocation
519             0x40 => {
520             Name => 'NetworkLocation',
521             # extract ASCII strings (Location + optional Description and Comments)
522             ValueConv => q{
523             $val = substr($val, 6);
524             my @strs = $val =~ /([\x20-\x7f]+)/g;
525             return @strs ? (@strs == 1 ? $strs[0] : \@strs) : \$val;
526             },
527             },
528             0x52 => {
529             Name => 'CompressedFolder',
530             Binary => 1, # (not implemented)
531             },
532             0x61 => {
533             Name => 'URI',
534             SubDirectory => { TagTable => 'Image::ExifTool::LNK::URI' },
535             },
536             0x70 => {
537             Name => 'ControlPanelShellItem',
538             ValueConv => 'Image::ExifTool::ASF::GetGUID(substr($val,14))',
539             SeparateTable => 'GUID',
540             PrintConv => \%guidLookup,
541             },
542             0x71 => {
543             Name => 'ControlPanelShellItem',
544             ValueConv => 'Image::ExifTool::ASF::GetGUID(substr($val,14))',
545             SeparateTable => 'GUID',
546             PrintConv => \%guidLookup,
547             },
548             0x72 => { Name => 'Printers', Binary => 1 },
549             0x73 => { Name => 'CommonPlacesFolder', Binary => 1 },
550             0x74 => {
551             Name => 'UsersFilesFolder',
552             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UsersFilesFolder' },
553             },
554             0xff => { #PH
555             Name => 'VendorData',
556             # extract Unicode and ASCII strings from vendor data (min length 3 chars, null terminated)
557             # or return binary data if no strings
558             ValueConv => q{
559             my @strs = $val =~ /([\x21-\x7f]\0[\x20-\x7f]\0(?:[\x20-\x7f]\0)+\0|[\x21-\x7f][\x20-\x7f][\x20-\x7f]+)\0/g;
560             tr/\0//d foreach @strs; # convert all to ASCII
561             return @strs ? (@strs == 1 ? $strs[0] : \@strs) : \$val;
562             },
563             },
564             #
565             # extension blocks
566             #
567             0xbeef0003 => {
568             Name => 'Beef0003',
569             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Beef0003' },
570             },
571             0xbeef0004 => {
572             Name => 'Beef0004',
573             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Beef0004' },
574             },
575             0xbeef0014 => {
576             Name => 'Beef0014',
577             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Beef0014' },
578             },
579             0xbeef0025 => {
580             Name => 'Beef0025',
581             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Beef0025' },
582             },
583             0xbeef0026 => [{
584             Name => 'Beef0026a',
585             Condition => '$$valPt =~ /^.{8}[\x11\x10\x12\x34\x31]/',
586             SubDirectory => { TagTable => 'Image::ExifTool::LNK::Beef0026a' },
587             },{
588             Name => 'Unknown_beef0026',
589             Binary => 1,
590             }],
591             );
592              
593             %Image::ExifTool::LNK::RootFolder = (
594             GROUPS => { 2 => 'Other' },
595             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
596             # (is this useful?) 0x03 => 'FolderID',
597             0x03 => {
598             Name => 'SortIndex',
599             PrintHex => 1,
600             PrintConv => { #https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target/root_folder.py
601             0x00 => 'Internet Explorer',
602             0x42 => 'Libraries',
603             0x44 => 'Users',
604             0x48 => 'My Documents',
605             0x4c => 'Public Folder',
606             0x50 => 'My Computer',
607             0x54 => 'Users Libraries',
608             0x58 => 'My Network Places/Network',
609             # 0x5b - ChatGPT thinks this is for the user home folder
610             0x60 => 'Recycle Bin',
611             0x68 => 'Internet Explorer',
612             0x70 => 'Control Panel',
613             0x78 => 'Recycle Bin',
614             0x80 => 'My Games',
615             },
616             },
617             0x04 => {
618             Name => 'RootFolderGUID',
619             Format => 'undef[16]',
620             ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
621             SeparateTable => 'GUID',
622             PrintConv => \%guidLookup,
623             },
624             );
625              
626             # ref https://helgeklein.com/blog/dissecting-a-shortcut/
627             %Image::ExifTool::LNK::TargetInfo = (
628             GROUPS => { 2 => 'Other' },
629             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
630             # (always 0?) 3 => 'TargetFileFlags',
631             # (duplicate tag name) 4 => { Name => 'TargetFileSize', Format => 'int32u' },
632             8 => {
633             Name => 'TargetFileModifyDate',
634             Groups => { 2 => 'Time' },
635             Format => 'int32u',
636             RawConv => '$val || undef', # (ignore zero dates)
637             ValueConv => 'Image::ExifTool::LNK::DOSTime($val)',
638             PrintConv => '$self->ConvertDateTime($val)',
639             },
640             12 => {
641             Name => 'TargetFileAttributes',
642             SeparateTable => 'FileAttributes',
643             PrintConv => \%fileAttributes,
644             },
645             14 => {
646             Name => 'TargetFileDOSName',
647             Format => 'undef[$size-14]',
648             RawConv => q{
649             # allow for the possibility of Unicode here
650             if ($val =~ /^[\x20-\x7f]\0[\x20-\x7f]/) {
651             $val = $1 if $val =~ /^((.{2})*?)\0\0/s;
652             $val = $self->Decode($val, 'UTF16', 'II');
653             } else {
654             $val = $1 if $val =~ /^(.*?)\0/s;
655             }
656             },
657             },
658             );
659              
660             # https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target/control_panel_cpl.py
661             %Image::ExifTool::LNK::ControlPanelCPL = (
662             GROUPS => { 2 => 'Other' },
663             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
664             4 => {
665             Name => 'Item00SpecialType',
666             Format => 'int32u',
667             PrintConv => 'sprintf("0x%.8x (ControlPanelCPL)", $val)',
668             # seen 0xffffff37
669             },
670             12 => {
671             Name => 'CPLFilePath',
672             Condition => '$$valPt !~ /^\0/',
673             Format => 'undef[$size-12]',
674             # pull out all valid ASCII strings
675             RawConv => q{
676             $$self{CPLIsASCII} = 1;
677             my @strs = $val =~ /[^\0]+/g;
678             return(@strs <= 1 ? $strs[0] : \@strs);
679             },
680             },
681             24 => {
682             Name => 'CPLFilePath',
683             Condition => 'not $$self{CPLIsASCII}',
684             Format => 'undef[$size-24]',
685             # pull out all valid Unicode strings
686             RawConv => q{
687             my @strs;
688             $val .= "\0\0"; # allow for missing terminator
689             while ($val =~ /^((?:..)*?)\0\0/s) {
690             my $uni = $1;
691             $val = substr($val, length($uni) + 2);
692             if ($uni =~ /^[\x20-\x7f]+$/) {
693             # also extract random ASCII data
694             push @strs, $uni;
695             } else {
696             my $str = $self->Decode($uni, 'UTF16');
697             push @strs, $str if length $str;
698             }
699             }
700             return(@strs <= 1 ? $strs[0] : \@strs);
701             },
702             },
703             );
704              
705             # ref 5
706             %Image::ExifTool::LNK::GameFolderInfo = (
707             GROUPS => { 2 => 'Other' },
708             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
709             4 => {
710             Name => 'Item00SpecialType',
711             Format => 'int32u',
712             PrintConv => 'sprintf("0x%.8x (GameFolder)", $val)',
713             # val = 0x49534647
714             },
715             # (nothing else known)
716             );
717              
718             # ref 5
719             %Image::ExifTool::LNK::PropertyStore = (
720             GROUPS => { 2 => 'Other' },
721             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
722             12 => {
723             Name => 'PropertyStoreGUID',
724             Format => 'undef[18]',
725             ValueConv => 'Image::ExifTool::ASF::GetGUID(substr($val,2))',
726             SeparateTable => 'GUID',
727             PrintConv => \%guidLookup,
728             },
729             );
730              
731             # ref 5
732             %Image::ExifTool::LNK::MTPType2 = (
733             GROUPS => { 2 => 'Other' },
734             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
735             38 => {
736             Name => 'StorageNameLen',
737             Format => 'int32u',
738             RawConv => '$$self{StorageNameLen} = $val; undef',
739             Hidden => 1,
740             },
741             42 => {
742             Name => 'StorageIDLen',
743             Format => 'int32u',
744             RawConv => '$$self{StorageIDLen} = $val; undef',
745             Hidden => 1,
746             },
747             46 => {
748             Name => 'FileSystemNameLen',
749             Format => 'int32u',
750             RawConv => '$$self{FileSystemNameLen} = $val; undef',
751             Hidden => 1,
752             },
753             50 => {
754             Name => 'NumGUIDs',
755             Format => 'int32u',
756             RawConv => '$$self{NumGUIDs} = $val; undef',
757             Hidden => 1,
758             },
759             54 => {
760             Name => 'MTPStorageName',
761             Format => 'undef[$$self{StorageNameLen} * 2]',
762             ValueConv => '$self->Decode($val, "UTF16")',
763             Hook => '$varSize += $$self{StorageNameLen} * 2',
764             },
765             54.1 => {
766             Name => 'MTPStorageID',
767             Format => 'undef[$$self{StorageIDLen} * 2]',
768             ValueConv => '$self->Decode($val, "UTF16")',
769             Hook => '$varSize += $$self{StorageIDLen} * 2',
770             },
771             54.2 => {
772             Name => 'MTPFileSystem',
773             Format => 'undef[$$self{FileSystemNameLen} * 2]',
774             ValueConv => '$self->Decode($val, "UTF16")',
775             Hook => '$varSize += $$self{FileSystemNameLen} * 2',
776             },
777             56 => {
778             Name => 'MTP_GUID1',
779             Condition => '$$self{NumGUIDs} >= 1',
780             %mtpGUID,
781             },
782             134 => {
783             Name => 'MTP_GUID2',
784             Condition => '$$self{NumGUIDs} >= 2',
785             %mtpGUID,
786             },
787             212 => {
788             Name => 'MTP_GUID3',
789             Condition => '$$self{NumGUIDs} >= 3',
790             %mtpGUID,
791             },
792             290 => {
793             Name => 'MTP_GUID4',
794             Condition => '$$self{NumGUIDs} >= 4',
795             %mtpGUID,
796             },
797             368 => {
798             Name => 'MTP_GUID5',
799             Condition => '$$self{NumGUIDs} >= 5',
800             %mtpGUID,
801             },
802             446 => {
803             Name => 'MTP_GUID6',
804             Condition => '$$self{NumGUIDs} >= 6',
805             %mtpGUID,
806             },
807             524 => {
808             Name => 'MTP_GUID7',
809             Condition => '$$self{NumGUIDs} >= 7',
810             %mtpGUID,
811             },
812             602 => {
813             Name => 'MTP_GUID8',
814             Condition => '$$self{NumGUIDs} >= 8',
815             %mtpGUID,
816             },
817             # (arbitrarily decode only the first 8 MTP GUID's)
818             );
819              
820             # ref PH
821             %Image::ExifTool::LNK::Item00Info = (
822             GROUPS => { 2 => 'Other' },
823             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
824             # 4 - int16u data size
825             6 => {
826             Name => 'Item00Type',
827             Format => 'int32u',
828             PrintHex => 1,
829             PrintConv => {
830             # (never used because these are handled separately,
831             # but included here for documentation)
832             0x23febbee => 'PropertyStoreGUID',
833             0x10312005 => 'MTPType2',
834             # 0x00000000 ?
835             # 0x07192006 ?
836             },
837             },
838             # extract property strings (seen with Item00Type == 0x0)
839             20 => {
840             Name => 'Prop1Len',
841             Format => 'int16u',
842             RawConv => '$$self{Prop1Len} = $val; undef',
843             Hidden => 1,
844             },
845             22 => {
846             Name => 'Prop2Len',
847             Format => 'int16u',
848             RawConv => '$$self{Prop2Len} = $val; undef',
849             Hidden => 1,
850             },
851             23 => {
852             Name => 'Dummy',
853             RawConv => 'undef',
854             Hidden => 1,
855             # decide whether or not this looks like property strings
856             Hook => q{
857             if (24 + 2 * ($$self{Prop1Len} + $$self{Prop2Len}) != $size) {
858             # doesn't look like property strings
859             delete $$self{Prop1Len};
860             delete $$self{Prop2Len};
861             }
862             },
863             },
864             24 => {
865             Name => 'PropertyString1',
866             Condition => '$$self{Prop1Len}',
867             Format => 'undef[$$self{Prop1Len} * 2]',
868             RawConv => '$self->Decode($val, "UTF16")',
869             Hook => '$varSize += $$self{Prop1Len} * 2',
870             },
871             24.1 => {
872             Name => 'PropertyString2',
873             Condition => '$$self{Prop2Len}',
874             Format => 'undef[$$self{Prop2Len} * 2]',
875             RawConv => '$self->Decode($val, "UTF16")',
876             },
877             );
878              
879             # ref https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target/control_panel_category.py
880             %Image::ExifTool::LNK::ControlPanelInfo = (
881             GROUPS => { 2 => 'Other' },
882             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
883             8 => {
884             Name => 'ControlPanelCategory',
885             Format => 'int32u',
886             PrintConv => {
887             0 => 'All Control Panel Items',
888             1 => 'Appearance and Personalization',
889             2 => 'Hardware and Sound',
890             3 => 'Network and Internet',
891             4 => 'Sounds, Speech, and Audio Devices',
892             5 => 'System and Security',
893             6 => 'Clock, Language, and Region',
894             7 => 'Ease of Access',
895             8 => 'Programs',
896             9 => 'User Accounts',
897             10 => 'Security Center',
898             11 => 'Mobile PC',
899             },
900             },
901             );
902              
903             %Image::ExifTool::LNK::URI = (
904             GROUPS => { 2 => 'Other' },
905             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
906             # 3 - flags: 0x80 set if URI is in Unicode
907             3 => {
908             Name => 'URIFlags',
909             Hidden => 1,
910             RawConv => '$$self{URIFlags} = $val; undef',
911             },
912             4 => {
913             Name => 'URIDataSize',
914             Format => 'int16u',
915             Hidden => 1,
916             RawConv => '$$self{URIDataSize} = $val; undef',
917             },
918             8 => {
919             Name => 'URI',
920             Condition => '$$self{URIDataSize} == 0',
921             Format => 'undef[$size-8]',
922             RawConv => '$self->Decode($val, ($$self{URIFlags} & 0x80) ? "UTF16" : "Latin")',
923             },
924             # all of the following tags are NC because a sample wasn't available
925             14 => {
926             Name => 'TimeStamp',
927             Condition => '$$self{URIDataSize} > 0',
928             Groups => { 2 => 'Time' },
929             %fileTime,
930             },
931             42 => {
932             Name => 'FTPHostLen',
933             Condition => '$$self{URIDataSize} > 0',
934             Hidden => 1,
935             Format => 'int32u',
936             RawConv => '$$self{FTPHostLen} = $val; undef',
937             },
938             46 => {
939             Name => 'FTPHost',
940             Condition => '$$self{URIDataSize} > 0',
941             Format => 'undef[$$self{FTPHostLen}]',
942             RawConv => '$self->Decode($val, ($$self{URIFlags} & 0x80) ? "UTF16" : "Latin"',
943             Hook => '$varSize += $$self{FTPHostLen} - 1', # (subtract 1 so offset 47 becomes 46)
944             },
945             47 => {
946             Name => 'FTPUserNameLen',
947             Condition => '$$self{URIDataSize} > 0',
948             Hidden => 1,
949             Format => 'int32u',
950             RawConv => '$$self{FTPUserNameLen} = $val; undef',
951             },
952             51 => {
953             Name => 'FTPUserName',
954             Condition => '$$self{URIDataSize} > 0',
955             Format => 'undef[$$self{FTPUserNameLen}]',
956             RawConv => '$self->Decode($val, ($$self{URIFlags} & 0x80) ? "UTF16" : "Latin"',
957             Hook => '$varSize += $$self{URIUserNameLen} - 1',
958             },
959             52 => {
960             Name => 'FTPPasswordLen',
961             Condition => '$$self{URIDataSize} > 0',
962             Hidden => 1,
963             Format => 'int32u',
964             RawConv => '$$self{FTPPasswordLen} = $val; undef',
965             },
966             56 => {
967             Name => 'FTPPassword',
968             Condition => '$$self{URIDataSize} > 0',
969             Format => 'undef[$$self{FTPPasswordLen}]',
970             RawConv => '$self->Decode($val, ($$self{URIFlags} & 0x80) ? "UTF16" : "Latin"',
971             Hook => '$varSize += $$self{FTPPasswordLen} - 1',
972             },
973             57 => {
974             Name => 'URI',
975             Condition => '$$self{URIDataSize} > 0',
976             Format => 'undef[$size-57-$varSize]',
977             RawConv => '$self->Decode($val, ($$self{URIFlags} & 0x80) ? "UTF16" : "Latin"',
978             },
979             );
980              
981             # ref https://github.com/EricZimmerman/ExtensionBlocks/blob/master/ExtensionBlocks/Beef0003.cs
982             %Image::ExifTool::LNK::Beef0003 = (
983             GROUPS => { 2 => 'Other' },
984             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
985             8 => {
986             Name => 'UnknownGUID',
987             Format => 'undef[16]',
988             ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
989             SeparateTable => 'GUID',
990             PrintConv => \%guidLookup,
991             },
992             );
993              
994             # ref https://github.com/EricZimmerman/ExtensionBlocks/blob/master/ExtensionBlocks/Beef0004.cs
995             %Image::ExifTool::LNK::Beef0004 = (
996             GROUPS => { 2 => 'Other' },
997             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
998             NOTES => 'TargetInfo extensions.',
999             # 0 - int16u ExtensionSize
1000             2 => {
1001             Name => 'Beef0004Ver',
1002             Format => 'int16u',
1003             Hidden => 1,
1004             RawConv => '$$self{Beef0004Ver} = $val; undef',
1005             },
1006             # 2 - int16u ExtensionVersion
1007             # 4 - int16u ExtensionType = 0x0004
1008             # 6 - int16u ExtensionMagic = 0xbeef
1009             8 => {
1010             Name => 'TargetFileCreateDate',
1011             Groups => { 2 => 'Time' },
1012             Format => 'int32u',
1013             ValueConv => 'Image::ExifTool::LNK::DOSTime($val)',
1014             PrintConv => '$self->ConvertDateTime($val)',
1015             },
1016             12 => {
1017             Name => 'TargetFileAccessDate',
1018             Groups => { 2 => 'Time' },
1019             Format => 'int32u',
1020             ValueConv => 'Image::ExifTool::LNK::DOSTime($val)',
1021             PrintConv => '$self->ConvertDateTime($val)',
1022             },
1023             16 => {
1024             Name => 'OperatingSystem',
1025             Format => 'int16u',
1026             PrintHex => 1,
1027             PrintConv => {
1028             0x14 => 'Windows XP, 2003',
1029             0x26 => 'Windows Vista',
1030             0x2a => 'Windows 2008, 7, 8',
1031             0x2e => 'Windows 8.1, 10',
1032             },
1033             Hook => q{
1034             $varSize += 18 if $$self{Beef0004Ver} >= 7;
1035             $varSize += 2 if $$self{Beef0004Ver} >= 3;
1036             $varSize += 4 if $$self{Beef0004Ver} >= 9;
1037             $varSize += 4 if $$self{Beef0004Ver} >= 8;
1038             },
1039             },
1040             18 => {
1041             Name => 'TargetFileName',
1042             Format => 'undef[$size - 20 - $varSize]', # (drop offset word after name)
1043             # extract two strings if they exist
1044             RawConv => q{
1045             my @a = $val =~ /((?:..)*?)\0\0/sg;
1046             $_ = $self->Decode($_, "UTF16") foreach @a;
1047             return @a > 1 ? \@a : $a[0];
1048             },
1049             },
1050             );
1051              
1052             # ref 4
1053             %Image::ExifTool::LNK::Beef0014 = (
1054             GROUPS => { 2 => 'Other' },
1055             PROCESS_PROC => \&ProcessURI,
1056             NOTES => 'URI extensions.',
1057             0 => 'AbsoluteURI',
1058             1 => 'URIAuthority',
1059             2 => 'DisplayURI',
1060             3 => 'URIDomain',
1061             4 => 'URIExtension',
1062             5 => 'URIFragment',
1063             6 => 'URIHost',
1064             7 => 'URIPassword',
1065             8 => 'URIPath',
1066             9 => 'URIPathAndQuery',
1067             10 => 'URIQuery',
1068             11 => 'RawURI',
1069             12 => 'URISchemeName',
1070             13 => 'URIUserInfo',
1071             14 => 'URIUserName',
1072             15 => 'URIHostType',
1073             16 => 'URIPort',
1074             17 => 'URIScheme',
1075             18 => 'URIZone',
1076             );
1077              
1078             # ref https://github.com/EricZimmerman/ExtensionBlocks/blob/master/ExtensionBlocks/Beef0025.cs
1079             %Image::ExifTool::LNK::Beef0025 = (
1080             GROUPS => { 2 => 'Other' },
1081             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1082             0x0c => {
1083             Name => 'FileTime1',
1084             Groups => { 2 => 'Time' },
1085             %fileTime,
1086             },
1087             0x14 => {
1088             Name => 'FileTime2',
1089             Groups => { 2 => 'Time' },
1090             %fileTime,
1091             },
1092             );
1093              
1094             # ref https://github.com/EricZimmerman/ExtensionBlocks/blob/master/ExtensionBlocks/Beef0026.cs
1095             %Image::ExifTool::LNK::Beef0026a = (
1096             GROUPS => { 2 => 'Other' },
1097             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1098             0x0c => {
1099             Name => 'CreateDate',
1100             Groups => { 2 => 'Time' },
1101             %fileTime,
1102             },
1103             0x14 => {
1104             Name => 'ModifyDate',
1105             Groups => { 2 => 'Time' },
1106             %fileTime,
1107             },
1108             0x1c => {
1109             Name => 'LastAccessDate',
1110             Groups => { 2 => 'Time' },
1111             %fileTime,
1112             },
1113             );
1114              
1115             # ref https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target/users_files_folder.py
1116             %Image::ExifTool::LNK::UsersFilesFolder = (
1117             GROUPS => { 2 => 'Other' },
1118             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1119             0x04 => {
1120             Name => 'IgnoreThis',
1121             Hidden => 1,
1122             Format => 'int16u',
1123             RawConv => 'undef',
1124             Hook => '$varSize += Get16u($dataPt, $pos)', # skip inner data
1125             },
1126             0x06 => {
1127             Name => 'DelegateClassGUID',
1128             Format => 'undef[16]',
1129             ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
1130             SeparateTable => 'GUID',
1131             PrintConv => \%guidLookup,
1132             },
1133             0x16 => {
1134             Name => 'DelegateFolderGUID',
1135             Format => 'undef[16]',
1136             ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
1137             SeparateTable => 'GUID',
1138             PrintConv => \%guidLookup,
1139             },
1140             );
1141              
1142             %Image::ExifTool::LNK::LinkInfo = (
1143             GROUPS => { 2 => 'Other' },
1144             PROCESS_PROC => \&ProcessLinkInfo,
1145             FORMAT => 'int32u',
1146             VARS => { ID_FMT => 'none' },
1147             VolumeID => { },
1148             DriveType => {
1149             PrintConv => {
1150             0 => 'Unknown',
1151             1 => 'Invalid Root Path',
1152             2 => 'Removable Media',
1153             3 => 'Fixed Disk',
1154             4 => 'Remote Drive',
1155             5 => 'CD-ROM',
1156             6 => 'Ram Disk',
1157             },
1158             },
1159             DriveSerialNumber => {
1160             PrintConv => 'join("-", unpack("A4 A4", sprintf("%08X", $val)))',
1161             },
1162             VolumeLabel => { },
1163             LocalBasePath => { },
1164             CommonNetworkRelLink => { },
1165             CommonPathSuffix => { },
1166             CommonPathSuffixUnicode => { },
1167             NetName => { },
1168             DeviceName => { },
1169             NetProviderType => {
1170             PrintHex => 1,
1171             PrintConv => {
1172             # https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/wnnc.h
1173             0x010000 => 'MSNET',
1174             0x020000 => 'SMB',
1175             0x030000 => 'NETWARE',
1176             0x040000 => 'VINES',
1177             0x050000 => '10NET',
1178             0x060000 => 'LOCUS',
1179             0x070000 => 'SUN_PC_NFS',
1180             0x080000 => 'LANSTEP',
1181             0x090000 => '9TILES',
1182             0x0A0000 => 'LANTASTIC',
1183             0x0B0000 => 'AS400',
1184             0x0C0000 => 'FTP_NFS',
1185             0x0D0000 => 'PATHWORKS',
1186             0x0E0000 => 'LIFENET',
1187             0x0F0000 => 'POWERLAN',
1188             0x100000 => 'BWNFS',
1189             0x110000 => 'COGENT',
1190             0x120000 => 'FARALLON',
1191             0x130000 => 'APPLETALK',
1192             0x140000 => 'INTERGRAPH',
1193             0x150000 => 'SYMFONET',
1194             0x160000 => 'CLEARCASE',
1195             0x170000 => 'FRONTIER',
1196             0x180000 => 'BMC',
1197             0x190000 => 'DCE',
1198             0x1a0000 => 'AVID',
1199             0x1b0000 => 'DOCUSPACE',
1200             0x1c0000 => 'MANGOSOFT',
1201             0x1d0000 => 'SERNET',
1202             0x1e0000 => 'RIVERFRONT1',
1203             0x1f0000 => 'RIVERFRONT2',
1204             0x200000 => 'DECORB',
1205             0x210000 => 'PROTSTOR',
1206             0x220000 => 'FJ_REDIR',
1207             0x230000 => 'DISTINCT',
1208             0x240000 => 'TWINS',
1209             0x250000 => 'RDR2SAMPLE',
1210             0x260000 => 'CSC',
1211             0x270000 => '3IN1',
1212             0x290000 => 'EXTENDNET',
1213             0x2a0000 => 'STAC',
1214             0x2b0000 => 'FOXBAT',
1215             0x2c0000 => 'YAHOO',
1216             0x2d0000 => 'EXIFS',
1217             0x2e0000 => 'DAV',
1218             0x2f0000 => 'KNOWARE',
1219             0x300000 => 'OBJECT_DIRE',
1220             0x310000 => 'MASFAX',
1221             0x320000 => 'HOB_NFS',
1222             0x330000 => 'SHIVA',
1223             0x340000 => 'IBMAL',
1224             0x350000 => 'LOCK',
1225             0x360000 => 'TERMSRV',
1226             0x370000 => 'SRT',
1227             0x380000 => 'QUINCY',
1228             0x390000 => 'OPENAFS',
1229             0x3a0000 => 'AVID1',
1230             0x3b0000 => 'DFS',
1231             0x3c0000 => 'KWNP',
1232             0x3d0000 => 'ZENWORKS',
1233             0x3e0000 => 'DRIVEONWEB',
1234             0x3f0000 => 'VMWARE',
1235             0x400000 => 'RSFX',
1236             0x410000 => 'MFILES',
1237             0x420000 => 'MS_NFS',
1238             0x430000 => 'GOOGLE',
1239             0x440000 => 'NDFS',
1240             0x450000 => 'DOCUSHARE',
1241             },
1242             },
1243             );
1244              
1245             %Image::ExifTool::LNK::UnknownData = (
1246             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1247             GROUPS => { 2 => 'Other' },
1248             );
1249              
1250             %Image::ExifTool::LNK::ConsoleData = (
1251             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1252             GROUPS => { 2 => 'Other' },
1253             0x08 => {
1254             Name => 'FillAttributes',
1255             Format => 'int16u',
1256             PrintConv => 'sprintf("0x%.2x", $val)',
1257             },
1258             0x0a => {
1259             Name => 'PopupFillAttributes',
1260             Format => 'int16u',
1261             PrintConv => 'sprintf("0x%.2x", $val)',
1262             },
1263             0x0c => {
1264             Name => 'ScreenBufferSize',
1265             Format => 'int16u[2]',
1266             PrintConv => '$val=~s/ / x /; $val',
1267             },
1268             0x10 => {
1269             Name => 'WindowSize',
1270             Format => 'int16u[2]',
1271             PrintConv => '$val=~s/ / x /; $val',
1272             },
1273             0x14 => {
1274             Name => 'WindowOrigin',
1275             Format => 'int16u[2]',
1276             PrintConv => '$val=~s/ / x /; $val',
1277             },
1278             0x20 => {
1279             Name => 'FontSize',
1280             Format => 'int16u[2]',
1281             PrintConv => '$val=~s/ / x /; $val',
1282             },
1283             0x24 => {
1284             Name => 'FontFamily',
1285             Format => 'int32u',
1286             PrintHex => 1,
1287             Mask => 0xf0,
1288             PrintConv => {
1289             0 => "Don't Care",
1290             0x1 => 'Roman',
1291             0x2 => 'Swiss',
1292             0x3 => 'Modern',
1293             0x4 => 'Script',
1294             0x5 => 'Decorative',
1295             },
1296             },
1297             0x28 => {
1298             Name => 'FontWeight',
1299             Format => 'int32u',
1300             },
1301             0x2c => {
1302             Name => 'FontName',
1303             Format => 'undef[64]',
1304             RawConv => q{
1305             $val = $self->Decode($val, 'UTF16');
1306             $val =~ s/\0.*//s;
1307             return length($val) ? $val : undef;
1308             },
1309             },
1310             0x6c => {
1311             Name => 'CursorSize',
1312             Format => 'int32u',
1313             },
1314             0x70 => {
1315             Name => 'FullScreen',
1316             Format => 'int32u',
1317             PrintConv => '$val ? "Yes" : "No"',
1318             },
1319             0x74 => { #PH (MISSING FROM MS DOCUMENTATION! -- screws up subsequent offsets)
1320             Name => 'QuickEdit',
1321             Format => 'int32u',
1322             PrintConv => '$val ? "Yes" : "No"',
1323             },
1324             0x78 => {
1325             Name => 'InsertMode',
1326             Format => 'int32u',
1327             PrintConv => '$val ? "Yes" : "No"',
1328             },
1329             0x7c => {
1330             Name => 'WindowOriginAuto',
1331             Format => 'int32u',
1332             PrintConv => '$val ? "Yes" : "No"',
1333             },
1334             0x80 => {
1335             Name => 'HistoryBufferSize',
1336             Format => 'int32u',
1337             },
1338             0x84 => {
1339             Name => 'NumHistoryBuffers',
1340             Format => 'int32u',
1341             },
1342             0x88 => {
1343             Name => 'RemoveHistoryDuplicates',
1344             Format => 'int32u',
1345             PrintConv => '$val ? "Yes" : "No"',
1346             },
1347             );
1348              
1349             %Image::ExifTool::LNK::TrackerData = (
1350             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1351             GROUPS => { 2 => 'Other' },
1352             0x10 => {
1353             Name => 'MachineID',
1354             Format => 'var_string',
1355             },
1356             );
1357              
1358             %Image::ExifTool::LNK::ConsoleFEData = (
1359             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1360             GROUPS => { 2 => 'Other' },
1361             0x08 => {
1362             Name => 'CodePage',
1363             Format => 'int32u',
1364             SeparateTable => 'Microsoft CodePage',
1365             PrintConv => \%Image::ExifTool::Microsoft::codePage,
1366             },
1367             );
1368              
1369             %Image::ExifTool::LNK::EnvVarData = (
1370             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1371             GROUPS => { 2 => 'Other' },
1372             8 => {
1373             Name => 'EnvironmentTarget',
1374             Format => 'string[260]',
1375             },
1376             268 => {
1377             Name => 'EnvironmentTargetUnicode',
1378             Format => 'unicode[260]',
1379             ValueConv => '$self->Decode($val, "UTF16", "II")',
1380             },
1381             );
1382              
1383             %Image::ExifTool::LNK::INI = (
1384             GROUPS => { 2 => 'Document' },
1385             VARS => { ID_FMT => 'none' },
1386             NOTES => 'Tags found in INI-format Windows .URL files.',
1387             URL => { },
1388             IconFile => { },
1389             IconIndex => { },
1390             WorkingDirectory => { },
1391             HotKey => { },
1392             ShowCommand => { PrintConv => { 1 => 'Normal', 2 => 'Minimized', 3 => 'Maximized' } },
1393             Modified => {
1394             Groups => { 2 => 'Time' },
1395             Format => 'int64u',
1396             # convert time from hex 100-ns intervals since Jan 1, 1601 (NC)
1397             RawConv => q{
1398             my $dat = pack('H*', $val);
1399             return undef if length $dat < 8;
1400             my ($lo, $hi) = unpack('V2', $dat);
1401             return undef unless $lo or $hi;
1402             return $hi * 4294967296 + $lo;
1403             },
1404             ValueConv => '$val=$val/1e7-11644473600; ConvertUnixTime($val,1)',
1405             PrintConv => '$self->ConvertDateTime($val)',
1406             },
1407             Author => { Groups => { 2 => 'Author' } },
1408             WhatsNew => { },
1409             Comment => { },
1410             Desc => { },
1411             Roamed => { Notes => '1 if synced across multiple devices' },
1412             IDList => { },
1413             );
1414              
1415             #------------------------------------------------------------------------------
1416             # Get DOS date/time
1417             # Inputs: 0) date/time integer value (date in low word)
1418             # Returns: EXIF-format date/time string
1419             sub DOSTime($)
1420             {
1421 3     3 0 5 my $val = shift;
1422 3         29 return sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d',
1423             (($val >> 9) & 0x7f) + 1980, # year
1424             ($val >> 5) & 0x0f, # month
1425             ($val >> 0) & 0x1f, # day
1426             ($val >> 27) & 0x1f, # hour
1427             ($val >> 21) & 0x3f, # minute
1428             ($val >> 15) & 0x3e # second (2 sec resolution)
1429             );
1430             }
1431              
1432             #------------------------------------------------------------------------------
1433             # Extract null-terminated ASCII or Unicode string from buffer
1434             # Inputs: 0) buffer ref, 1) start position, 2) flag for unicode string
1435             # Return: string or undef if start position is outside bounds
1436             sub GetString($$;$)
1437             {
1438 3     3 0 4 my ($dataPt, $pos, $unicode) = @_;
1439 3 50       4 return undef if $pos >= length($$dataPt);
1440 3         7 pos($$dataPt) = $pos;
1441 3 50       36 return $1 if ($unicode ? $$dataPt=~/\G((?:..)*?)\0\0/sg : $$dataPt=~/\G(.*?)\0/sg);
    50          
1442 0         0 return substr($$dataPt, $pos);
1443             }
1444              
1445             #------------------------------------------------------------------------------
1446             # Process item ID data (ref 4)
1447             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
1448             # Returns: 1 on success
1449             sub ProcessItemID($$$)
1450             {
1451 1     1 0 2 my ($et, $dirInfo, $tagTablePtr) = @_;
1452 1         4 my $verbose = $et->Options('Verbose');
1453 1         2 my $dataPt = $$dirInfo{DataPt};
1454 1         3 my $dataLen = length $$dataPt;
1455 1         1 my $pos = 0;
1456             my %opts = (
1457             DataPt => $dataPt,
1458             DataPos => $$dirInfo{DataPos},
1459 1         4 );
1460 1         7 $et->VerboseDir('ItemID', undef, $dataLen);
1461 1         2 for (;;) {
1462 2 50       5 $pos + 2 > $dataLen and $et->Warn('Missing item ID list terminator'), last;
1463 2         5 my $size = Get16u($dataPt, $pos);
1464 2 100       7 if ($size == 0) {
    50          
1465 1         2 my $more = $dataLen - $pos - 2;
1466 1 50       2 $more and $et->Warn("Unknown $more bytes after item ID list");
1467 1         2 last;
1468             } elsif ($size < 4) {
1469 0         0 $et->Warn('Invalid item ID entry size');
1470 0         0 last;
1471             }
1472 1         4 my $tag = Get8u($dataPt, $pos+2); # get item ID
1473 1 50       3 if ($pos + $size > $dataLen) {
1474 0         0 $et->Warn(sprintf('Truncated item with ID 0x%.2x', $tag));
1475 0         0 $size = $dataLen - $pos;
1476             }
1477             # 0x20-0x2f, 0x30-0x3f and 0x40-0x4f are ID ranges for the same tags
1478 1 50       4 if (not $$tagTablePtr{$tag}) {
1479 1         5 my %lkup = ( 0x20=>0x2e, 0x21=>0x2f, 0x30=>0x31, 0x40=>0x40 );
1480 1   33     4 my $srcTag = $lkup{$tag & 0x71} || $lkup{$tag & 0x70}; # (keep bit 0 of MyComputer ID)
1481             # make a clone of the existing tag definition
1482 1 50       4 if (ref $$tagTablePtr{$srcTag} eq 'HASH') {
1483 1         2 my %clone = %{$$tagTablePtr{$srcTag}};
  1         5  
1484 1         3 $clone{TagID} = $tag;
1485 1         3 $$tagTablePtr{$tag} = \%clone;
1486             }
1487             }
1488             AddTagToTable($tagTablePtr, $tag, {
1489             Name => sprintf('Item_%.2x', $tag),
1490             SubDirectory => { TagTable => 'Image::ExifTool::LNK::UnknownData' },
1491 1 50       3 }) unless $$tagTablePtr{$tag};
1492             # isolate this itemID data
1493 1         3 my $dat = substr($$dataPt, $pos, $size);
1494             # look for 0xbeef extension blocks
1495             # (don't know what the first 2 bytes are, but I've seen 0x0003, 0x0007, 0x0008
1496             # and 0x0009 for 0xbeef0004, and 0x0000 for 0xbeef0014 and 0xbeef0025)
1497             # List of observed 0xbeef blocks (0xbeef ID - ItemID):
1498             # 0xbeef0003 - 0xb1
1499             # 0xbeef0004 - 0x31,0x32
1500             # 0xbeef0014 - 0x61
1501             # 0xbeef0025 - 0x2e
1502             # 0xbeef0026 - 0x1f,0x2e
1503 1         1 my $beefStart;
1504 1 50       8 if ($dat =~ /.{5}\0\xef\xbe/sg) {
1505 1         2 my $off = pos($dat) - 8;
1506 1         4 my $off2 = Get16u(\$dat, length($dat) - 2);
1507 1 50       3 if ($off == $off2) {
1508 1         2 $beefStart = $pos + $off;
1509             } else {
1510 0         0 $et->Warn('Unexpected 0xbeef extension offset');
1511             }
1512             }
1513 1 50       12 $et->HandleTag($tagTablePtr, $tag, undef, %opts,
1514             Start => $pos,
1515             Size => $beefStart ? $beefStart - $pos : $size,
1516             );
1517 1 50 33     4 if ($verbose > 1 and $beefStart) {
1518 0         0 my $len = $size - $beefStart + $pos;
1519 0         0 $et->VPrint(1, sprintf("$$et{INDENT}- Tag 0x%.4x Extension (%d bytes)\n",$tag,$len));
1520 0         0 $et->VerboseDump($dataPt, %opts, Start => $beefStart, Len => $len);
1521             }
1522             # process 0xbeef extension blocks
1523 1         3 while ($beefStart) {
1524 2         4 my $end = $pos + $size; # end if itemID entry
1525 2 100       5 if ($beefStart + 8 > $end) {
1526 1 50       2 $et->Warn('Garbage data in 0xbeef extension') unless $beefStart == $end;
1527 1         2 last;
1528             }
1529 1         2 my $len = Get16u($dataPt, $beefStart);
1530 1         20 my $beefID = Get32u($dataPt, $beefStart + 4);
1531 1 50       3 ($beefID & 0xffff0000) == 0xbeef0000 or $et->Warn('Invalid 0xbeef extension'), last;
1532 1 50       4 $beefStart + $len <= $end or $et->Warn('Truncated 0xbeef extension'), last;
1533             AddTagToTable($tagTablePtr, $beefID, {
1534             Name => sprintf('Unknown_beef%.4x', $beefID & 0xffff),
1535             Binary => 1,
1536 1 50       4 }) unless $$tagTablePtr{$beefID};
1537 1         5 $et->HandleTag($tagTablePtr, $beefID, undef, %opts,
1538             Start => $beefStart,
1539             Size => $len,
1540             );
1541 1 50       4 ++$len if $len & 0x01; # assume they start on 2-byte boundaries (NC)
1542 1         3 $beefStart += $len; # step to next extension block
1543             }
1544 1         2 $pos += $size;
1545             }
1546             }
1547              
1548             #------------------------------------------------------------------------------
1549             # Process URI 0xbeef0014 extension (ref 4)
1550             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1551             # Returns: 1 on success
1552             sub ProcessURI($$$)
1553             {
1554 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
1555 0         0 my $dataPt = $$dirInfo{DataPt};
1556 0   0     0 my $pos = $$dirInfo{DirStart} || 0;
1557 0         0 my $dirLen = $$dirInfo{DirLen};
1558 0         0 my $end = $pos + $dirLen;
1559 0 0       0 return 0 if $dirLen < 56;
1560 0         0 $et->VerboseDir('URI', undef, $dirLen);
1561             # offset 8 - GUID df2fce13-25ec-45bb-9d4c-cecd47c2430c
1562 0         0 my $num = Get32u($dataPt, $pos + 52);
1563 0         0 my $i;
1564 0         0 $pos += 56;
1565 0         0 for ($i=0; $i<$num; ++$i) {
1566 0 0       0 $pos + 8 > $end and $et->Warn('Truncated URI extension'), last;
1567 0         0 my $tag = Get32u($dataPt, $pos);
1568 0         0 my $size = Get32u($dataPt, $pos + 4);
1569 0         0 $pos += 8;
1570 0 0       0 next unless $size;
1571 0 0       0 $pos + $size > $end and $et->Warn('Truncated URI string'), last;
1572             # (many tags are untested -- some may not be Unicode strings?)
1573 0         0 my $val = $et->Decode(substr($$dataPt, $pos, $size), 'UTF16');
1574             $et->HandleTag($tagTablePtr, $tag, $val,
1575             DataPt => $dataPt,
1576             DataPos => $$dirInfo{DataPos},
1577 0         0 Start => $pos,
1578             Size => $size,
1579             );
1580 0         0 $pos += $size;
1581             }
1582 0         0 return 1;
1583             }
1584              
1585             #------------------------------------------------------------------------------
1586             # Process link information data
1587             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
1588             # Returns: 1 on success
1589             sub ProcessLinkInfo($$$)
1590             {
1591 1     1 0 3 my ($et, $dirInfo, $tagTablePtr) = @_;
1592 1         2 my $dataPt = $$dirInfo{DataPt};
1593 1         3 my $dataLen = length $$dataPt;
1594 1 50       3 return 0 if $dataLen < 0x24;
1595 1         3 my $hdrLen = Get32u($dataPt, 4);
1596 1         2 my $lif = Get32u($dataPt, 8); # link info flags
1597             my %opts = (
1598             DataPt => $dataPt,
1599             DataPos => $$dirInfo{DataPos},
1600 1         4 Size => 4, # (typical value size)
1601             );
1602 1         2 my ($off, $unicode, $pos, $val, $size);
1603 1         4 $et->VerboseDir('LinkInfo', undef, $dataLen);
1604 1 50       3 if ($lif & 0x01) {
1605             # read Volume ID
1606 1         2 $off = Get32u($dataPt, 0x0c);
1607 1 50 33     5 if ($off and $off + 0x20 <= $dataLen) {
1608             # my $len = Get32u($dataPt, $off);
1609 1         4 $et->HandleTag($tagTablePtr, 'DriveType', undef, %opts, Start=>$off+4);
1610 1         4 $et->HandleTag($tagTablePtr, 'DriveSerialNumber', undef, %opts, Start=>$off+8);
1611 1         4 $pos = Get32u($dataPt, $off + 0x0c);
1612 1 50       3 if ($pos == 0x14) {
1613             # use VolumeLabelOffsetUnicode instead
1614 0         0 $pos = Get32u($dataPt, $off + 0x10);
1615 0         0 $unicode = 1;
1616             }
1617 1         3 $pos += $off;
1618 1         4 $val = GetString($dataPt, $pos, $unicode);
1619 1 50       4 if (defined $val) {
1620 1         1 $size = length $val;
1621 1 50       3 $val = $et->Decode($val, 'UTF16') if $unicode;
1622 1         4 $et->HandleTag($tagTablePtr, 'VolumeLabel', $val, %opts, Start=>$pos, Size=>$size);
1623             }
1624             }
1625             # read local base path
1626 1 50       3 if ($hdrLen >= 0x24) {
1627 0         0 $pos = Get32u($dataPt, 0x1c);
1628 0         0 $unicode = 1;
1629             } else {
1630 1         2 $pos = Get32u($dataPt, 0x10);
1631 1         1 undef $unicode;
1632             }
1633 1         3 $val = GetString($dataPt, $pos, $unicode);
1634 1 50       3 if (defined $val) {
1635 1         1 $size = length $val;
1636 1 50       2 $val = $et->Decode($val, 'UTF16') if $unicode;
1637 1         4 $et->HandleTag($tagTablePtr, 'LocalBasePath', $val, %opts, Start=>$pos, Size=>$size);
1638             }
1639             }
1640 1 50       3 if ($lif & 0x02) {
1641             # read common network relative link
1642 0         0 $off = Get32u($dataPt, 0x14);
1643 0 0 0     0 if ($off and $off + 0x14 <= $dataLen) {
1644 0         0 my $siz = Get32u($dataPt, $off);
1645 0 0       0 return 0 if $off + $siz > $dataLen;
1646 0         0 $pos = Get32u($dataPt, $off + 0x08);
1647 0 0 0     0 if ($pos > 0x14 and $siz >= 0x18) {
1648 0         0 $pos = Get32u($dataPt, $off + 0x14);
1649 0         0 $unicode = 1;
1650             } else {
1651 0         0 undef $unicode;
1652             }
1653 0         0 $val = GetString($dataPt, $off + $pos, $unicode);
1654 0 0       0 if (defined $val) {
1655 0         0 $size = length $val;
1656 0 0       0 $val = $et->Decode($val, 'UTF16') if $unicode;
1657 0         0 $et->HandleTag($tagTablePtr, 'NetName', $val, %opts, Start=>$pos, Size=>$size);
1658             }
1659 0         0 my $flg = Get32u($dataPt, $off + 0x04);
1660 0 0       0 if ($flg & 0x01) {
1661 0         0 $pos = Get32u($dataPt, $off + 0x0c);
1662 0 0 0     0 if ($pos > 0x14 and $siz >= 0x1c) {
1663 0         0 $pos = Get32u($dataPt, $off + 0x18);
1664 0         0 $unicode = 1;
1665             } else {
1666 0         0 undef $unicode;
1667             }
1668 0         0 $val = GetString($dataPt, $off + $pos, $unicode);
1669 0 0       0 if (defined $val) {
1670 0         0 $size = length $val;
1671 0 0       0 $val = $et->Decode($val, 'UTF16') if $unicode;
1672 0         0 $et->HandleTag($tagTablePtr, 'DeviceName', $val, %opts, Start=>$pos, Size=>$size);
1673             }
1674             }
1675 0 0       0 if ($flg & 0x02) {
1676 0         0 $val = Get32u($dataPt, $off + 0x10);
1677 0         0 $et->HandleTag($tagTablePtr, 'NetProviderType', $val, %opts, Start=>$off + 0x10);
1678             }
1679             }
1680             }
1681 1         2 $off = Get32u($dataPt, 0x18);
1682 1 50 33     5 if ($off and $off < $dataLen) {
1683 1         2 $val = GetString($dataPt, $off);
1684 1         4 $et->HandleTag($tagTablePtr, 'CommonPathSuffix', $val, %opts, Start=>$off, Size=>length($val)+1);
1685             }
1686 1 50       3 if ($hdrLen >= 0x24) {
1687 0         0 $off = Get32u($dataPt, 0x20);
1688 0 0 0     0 if ($off and $off < $dataLen) {
1689 0         0 $val = GetString($dataPt, $off, 1);
1690 0         0 $et->HandleTag($tagTablePtr, 'CommonPathSuffixUnicode', $val, %opts, Start=>$off, Size=>length($val)+1);
1691             }
1692             }
1693 1         3 return 1;
1694             }
1695              
1696             #------------------------------------------------------------------------------
1697             # Extract information from a INI-format file
1698             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1699             # Returns: 1 on success, 0 if this wasn't a valid INI file
1700             sub ProcessINI($$)
1701             {
1702 1     1 0 3 my ($et, $dirInfo) = @_;
1703 1         2 my $raf = $$dirInfo{RAF};
1704 1         1 my $buff;
1705 1         5 local $/ = "\x0d\x0a";
1706 1         2 my $tagTablePtr = GetTagTable('Image::ExifTool::LNK::INI');
1707 1         4 while ($raf->ReadLine($buff)) {
1708 14 100       57 if ($buff =~ /^\[(.*?)\]/) {
    50          
1709 1         7 $et->VPrint(0, "$1 section:\n");
1710             } elsif ($buff =~ /^\s*(\w+)=(.*)\x0d\x0a$/) {
1711 13         24 $et->HandleTag($tagTablePtr, $1, $2, MakeTagInfo => 1);
1712             }
1713             }
1714 1         5 return 1;
1715             }
1716              
1717             #------------------------------------------------------------------------------
1718             # Extract information from a MS Shell Link (Windows shortcut) file
1719             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1720             # Returns: 1 on success, 0 if this wasn't a valid LNK file
1721             sub ProcessLNK($$)
1722             {
1723 2     2 0 5 my ($et, $dirInfo) = @_;
1724 2         4 my $raf = $$dirInfo{RAF};
1725 2         2 my ($buff, $buf2, $len, $i, $bad, $isUnicode);
1726              
1727             # read LNK file header
1728 2 50       7 $raf->Read($buff, 0x4c) == 0x4c or return 0;
1729 2 100       9 unless ($buff =~ /^.{4}\x01\x14\x02\0{5}\xc0\0{6}\x46/s) {
1730             # check for INI-format LNK file (eg. .URL file)
1731 1 50       5 return 0 unless $buff =~ /^\[[InternetShortcut\][\x0d\x0a]/;
1732 1 50       4 $raf->Seek(0,0) or return 0;
1733 1         5 $et->SetFileType('URL', 'application/x-mswinurl');
1734 1         4 return ProcessINI($et, $dirInfo);
1735             };
1736 1         3 $len = unpack('V', $buff);
1737 1 50       3 $len >= 0x4c or return 0;
1738 1 50       3 if ($len > 0x4c) {
1739 0 0       0 $raf->Read($buf2, $len - 0x4c) == $len - 0x4c or return 0;
1740 0         0 $buff .= $buf2;
1741             }
1742 1         4 $et->SetFileType();
1743 1         5 SetByteOrder('II');
1744              
1745 1         3 my $tagTablePtr = GetTagTable('Image::ExifTool::LNK::Main');
1746 1         6 my %dirInfo = (
1747             DataPt => \$buff,
1748             DataPos => 0,
1749             DataLen => length $buff,
1750             DirLen => length $buff,
1751             );
1752 1         16 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1753              
1754 1         5 my $flags = Get32u(\$buff, 0x14);
1755              
1756             # save Unicode flag for strings
1757 1 50       7 $isUnicode = 1 if $flags & 0x80;
1758              
1759             # read link target ID list
1760 1 50       6 if ($flags & 0x01) {
1761 1 50       9 $raf->Read($buff, 2) == 2 or $et->Warn('Missing target ID list'), return 1;
1762 1         4 $len = unpack('v', $buff);
1763 1 50       4 $raf->Read($buff, $len) == $len or $et->Warn('Truncated target ID list'), $len = length($buff);
1764 1         7 $et->HandleTag($tagTablePtr, 0x10000, undef,
1765             DataPt => \$buff,
1766             DataPos => $raf->Tell() - $len,
1767             Size => $len,
1768             );
1769             }
1770              
1771             # read link information
1772 1 50       3 if ($flags & 0x02) {
1773 1 50       4 $raf->Read($buff, 4) == 4 or $et->Warn('Missing link information'), return 1;
1774 1         2 $len = unpack('V', $buff);
1775 1 50       3 $len < 4 and $et->Warn('Invalid link information'), return 1;
1776 1 50       2 $raf->Read($buf2, $len-4) == $len-4 or $et->Warn('Truncated link information'), $len = length($buf2);
1777 1         2 $buff .= $buf2;
1778 1         3 $et->HandleTag($tagTablePtr, 0x20000, undef,
1779             DataPt => \$buff,
1780             DataPos => $raf->Tell() - $len,
1781             Size => $len,
1782             );
1783             }
1784            
1785             # read string data
1786 1         4 my @strings = qw(Description RelativePath WorkingDirectory
1787             CommandLineArguments IconFileName);
1788 1         4 for ($i=0; $i<@strings; ++$i) {
1789 5         6 my ($val, $limit);
1790 5         6 my $mask = 0x04 << $i;
1791 5 100       9 next unless $flags & $mask;
1792 4 50       10 $raf->Read($buff, 2) == 2 or $et->Warn("Invalid $strings[$i] string"), return 1;
1793 4         8 my $pos = $raf->Tell();
1794 4 50       8 $len = unpack('v', $buff) or next;
1795             # Windows doesn't follow their own specification and limits the length
1796             # for most of these strings (ref 3)
1797 4 50 66     10 if ($i != 3 and $len >= 260) {
1798 0         0 $limit = 1;
1799 0 0       0 if ($len > 260) {
1800 0         0 $len = 260;
1801 0         0 $et->Warn('LNK string data overrun! Possible security issue');
1802             }
1803             }
1804             # characters are 2 bytes if Unicode flag is set
1805 4 50       8 $len *= 2 if $isUnicode;
1806 4 50       6 $raf->Read($buff, $len) or $et->Warn("Truncated $strings[$i] string"), return 1;
1807             # remove last character if string is at length limit (Windows treats this as a null)
1808 4 50       7 if ($limit) {
1809 0 0       0 $len -= $isUnicode ? 2 : 1;
1810 0         0 $buff = substr($buff, 0, $len);
1811             }
1812 4 50       12 $val = $et->Decode($buff, 'UTF16') if $isUnicode;
1813 4         22 $et->HandleTag($tagTablePtr, 0x30000 | $mask, $val,
1814             DataPt => \$buff,
1815             DataPos => $pos,
1816             Size => $len,
1817             );
1818             }
1819              
1820             # read extra data
1821 1         3 while ($raf->Read($buff, 4) == 4) {
1822 3         17 $len = unpack('V', $buff);
1823 3 100       6 last if $len < 4;
1824 2         3 $len -= 4;
1825 2 50       4 $raf->Read($buf2, $len) == $len or $et->Warn('Truncated extra data'), last;
1826 2 50       4 next unless $len > 4;
1827 2         3 $buff .= $buf2;
1828 2         5 my $tag = Get32u(\$buff, 4);
1829 2         3 my $tagInfo = $$tagTablePtr{$tag};
1830 2 50 33     10 unless (ref $tagInfo eq 'HASH' and $$tagInfo{SubDirectory}) {
1831 0         0 $tagInfo = $$tagTablePtr{0xa0000000};
1832             }
1833 2         6 $et->HandleTag($tagTablePtr, $tag, undef,
1834             DataPt => \$buff,
1835             DataPos => $raf->Tell() - $len - 4,
1836             TagInfo => $tagInfo,
1837             );
1838             }
1839             # check for an overlay
1840 1         3 my $pos = $raf->Tell();
1841 1         2 $len = 0;
1842 1         2 for (;;) {
1843 1         2 $i = $raf->Read($buff, 65536);
1844 1 50       3 last unless $i;
1845 0         0 $len += $i;
1846 0 0 0     0 $bad = 1 if not $bad and $buff =~ /[^\0]/;
1847             }
1848 1 50       4 if ($bad) {
    50          
1849 0         0 $et->Warn(sprintf('Unknown %d-byte overlay at offset 0x%x', $len, $pos));
1850             } elsif ($len) {
1851 0         0 $et->Warn("$len bytes of null padding at end of file");
1852             }
1853 1         7 return 1;
1854             }
1855              
1856             1; # end
1857              
1858             __END__