LNK.pm 22 KB

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