exiftool_vendored 13.31.0 → 13.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +38 -1
  3. data/bin/MANIFEST +5 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +47 -46
  7. data/bin/exiftool +81 -56
  8. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +7 -5
  9. data/bin/lib/Image/ExifTool/Canon.pm +10 -2
  10. data/bin/lib/Image/ExifTool/Exif.pm +5 -3
  11. data/bin/lib/Image/ExifTool/FlashPix.pm +4 -159
  12. data/bin/lib/Image/ExifTool/FujiFilm.pm +10 -3
  13. data/bin/lib/Image/ExifTool/Geotag.pm +5 -3
  14. data/bin/lib/Image/ExifTool/GoPro.pm +14 -2
  15. data/bin/lib/Image/ExifTool/LNK.pm +4 -1
  16. data/bin/lib/Image/ExifTool/Lang/cs.pm +0 -1
  17. data/bin/lib/Image/ExifTool/Lang/de.pm +0 -1
  18. data/bin/lib/Image/ExifTool/Lang/fr.pm +0 -1
  19. data/bin/lib/Image/ExifTool/Lang/it.pm +0 -1
  20. data/bin/lib/Image/ExifTool/Lang/ja.pm +0 -1
  21. data/bin/lib/Image/ExifTool/Lang/nl.pm +0 -1
  22. data/bin/lib/Image/ExifTool/Lang/pl.pm +0 -1
  23. data/bin/lib/Image/ExifTool/Lang/zh_cn.pm +0 -1
  24. data/bin/lib/Image/ExifTool/Microsoft.pm +158 -1
  25. data/bin/lib/Image/ExifTool/Minolta.pm +1 -1
  26. data/bin/lib/Image/ExifTool/Nikon.pm +71 -37
  27. data/bin/lib/Image/ExifTool/NikonCustom.pm +40 -10
  28. data/bin/lib/Image/ExifTool/Olympus.pm +240 -35
  29. data/bin/lib/Image/ExifTool/Panasonic.pm +3 -3
  30. data/bin/lib/Image/ExifTool/Pentax.pm +271 -53
  31. data/bin/lib/Image/ExifTool/QuickTime.pm +11 -4
  32. data/bin/lib/Image/ExifTool/Sony.pm +5 -2
  33. data/bin/lib/Image/ExifTool/TNEF.pm +487 -0
  34. data/bin/lib/Image/ExifTool/TagLookup.pm +4373 -4264
  35. data/bin/lib/Image/ExifTool/TagNames.pod +249 -17
  36. data/bin/lib/Image/ExifTool/WriteExif.pl +14 -12
  37. data/bin/lib/Image/ExifTool/Writer.pl +15 -11
  38. data/bin/lib/Image/ExifTool/XMPStruct.pl +1 -1
  39. data/bin/lib/Image/ExifTool.pm +11 -4
  40. data/bin/lib/Image/ExifTool.pod +45 -44
  41. data/bin/perl-Image-ExifTool.spec +46 -45
  42. data/lib/exiftool_vendored/version.rb +1 -1
  43. metadata +3 -2
@@ -0,0 +1,487 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: TNEF.pm
3
+ #
4
+ # Description: Read TNEF meta information
5
+ #
6
+ # Revisions: 2025-07-08 - P. Harvey Created
7
+ #
8
+ # References: 1) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXTNEF/%5bMS-OXTNEF%5d.pdf
9
+ # 2) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCMSG/%5bMS-OXCMSG%5d.pdf
10
+ # 3) https://msopenspecs.azureedge.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
11
+ # 4) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCDATA/%5bMS-OXCDATA%5d.pdf
12
+ # 5) https://github.com/echo-devim/pyjacktrick/blob/main/mapi_constants.py
13
+ #------------------------------------------------------------------------------
14
+
15
+ package Image::ExifTool::TNEF;
16
+
17
+ use strict;
18
+ use vars qw($VERSION);
19
+ use Image::ExifTool qw(:DataAccess :Utils);
20
+ use Image::ExifTool::ASF;
21
+ use Image::ExifTool::Microsoft;
22
+
23
+ $VERSION = '1.00';
24
+
25
+ sub ProcessProps($$$);
26
+
27
+ # TNEF property types
28
+ my %propType = (
29
+ 0x01 => 'null',
30
+ 0x02 => 'int16s',
31
+ 0x03 => 'int32s',
32
+ 0x04 => 'float',
33
+ 0x05 => 'double',
34
+ 0x06 => 'int64s', # (currency / 10000)
35
+ 0x07 => 'double', # (days since Dec 30, 1899)
36
+ 0x0a => 'int32s', # (error code)
37
+ 0x0b => 'int16s', # (boolean)
38
+ 0x0d => 'undef', # (object)
39
+ 0x14 => 'int64s',
40
+ 0x1e => 'string', # (with terminating null)
41
+ 0x1f => 'Unicode',# (with terminating null)
42
+ 0x40 => 'int64u', # (time in 100 ns since 1601)
43
+ 0x48 => 'GUID', # (16 bytes)
44
+ 0x102 => 'undef', # (blob)
45
+ );
46
+
47
+ # byte count for non-integer fixed-size formats
48
+ my %fmtSize = (
49
+ null => 0,
50
+ float => 4,
51
+ double => 8,
52
+ GUID => 16,
53
+ );
54
+
55
+ my %dateInfo = (
56
+ Format => 'date',
57
+ Groups => { 2 => 'Time' },
58
+ PrintConv => '$self->ConvertDateTime($val)',
59
+ );
60
+
61
+ %Image::ExifTool::TNEF::Main = (
62
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
63
+ VARS => { NO_LOOKUP => 1 },
64
+ NOTES => q{
65
+ Information extracted from Transport Neutral Encapsulation Format (TNEF)
66
+ files (eg. winmail.dat). But note that the exiftool application doesn't
67
+ process files with a .DAT extension by default when a directory name is
68
+ given, so in this case either specify the .DAT file(s) by name or add
69
+ C<-ext+ dat> to the command.
70
+ },
71
+ 0x069007 => {
72
+ Name => 'CodePage',
73
+ Format => 'int32u',
74
+ SeparateTable => 'Microsoft CodePage',
75
+ # (ignore secondary code page)
76
+ RawConv => '$val=~s/ .*//;$$self{Charset} = $charsetName{"cp$val"}; $val',
77
+ PrintConv => \%Image::ExifTool::Microsoft::codePage,
78
+ },
79
+ 0x089006 => {
80
+ Name => 'TNEFVersion',
81
+ Format => 'int8u',
82
+ ValueConv => 'my @a = reverse split " ", $val; "@a"',
83
+ PrintConv => '$val =~ tr/ /./; $val',
84
+ },
85
+ 0x078008 => 'MessageClass',
86
+ 0x008000 => 'From',
87
+ 0x018004 => 'Subject',
88
+ 0x038005 => { Name => 'SentDate', %dateInfo },
89
+ 0x038006 => { Name => 'ReceivedDate', %dateInfo },
90
+ 0x068007 => 'MessageStatus',
91
+ 0x018009 => 'MessageID',
92
+ 0x02800C => 'MessageBody',
93
+ 0x04800D => {
94
+ Name => 'Priority',
95
+ Format => 'int16u', # (contrary to documentation which says int32u)
96
+ PrintConv => {
97
+ 0 => 'Low',
98
+ 1 => 'Normal',
99
+ 2 => 'High',
100
+ },
101
+ },
102
+ 0x038020 => { Name => 'MessageModifyDate', %dateInfo }, # (unclear what this really means)
103
+ 0x069003 => {
104
+ Name => 'MessageProps',
105
+ SubDirectory => { TagTable => 'Image::ExifTool::TNEF::MsgProps' },
106
+ },
107
+ 0x069004 => 'RecipientTable',
108
+ 0x070600 => 'OriginalMessageClass',
109
+ 0x060000 => 'Owner',
110
+ 0x060001 => 'SentFor',
111
+ 0x060002 => 'Delegate',
112
+ 0x030006 => { Name => 'StartDate', %dateInfo },
113
+ 0x030007 => { Name => 'EndDate', %dateInfo },
114
+ 0x050008 => 'OwnerAppointmentID',
115
+ 0x040009 => 'ResponseRequested',
116
+ 0x06800F => { Name => 'AttachData', Binary => 1 },
117
+ 0x018010 => 'AttachTitle',
118
+ 0x068011 => { Name => 'AttachMetaFile', Binary => 1 },
119
+ 0x038012 => { Name => 'AttachCreateDate', %dateInfo },
120
+ 0x038013 => { Name => 'AttachModifyDate', %dateInfo },
121
+ 0x069001 => 'AttachTransportFilename',
122
+ 0x069002 => { Name => 'AttachRenderingData', Binary => 1 }, # (start of attachment)
123
+ 0x069005 => { # (end of attachment)
124
+ Name => 'AttachInfo',
125
+ SubDirectory => { TagTable => 'Image::ExifTool::TNEF::AttachInfo' },
126
+ },
127
+ );
128
+
129
+ %Image::ExifTool::TNEF::MsgProps = (
130
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
131
+ PROCESS_PROC => \&ProcessProps,
132
+ TAG_PREFIX => 'MsgProps',
133
+ VARS => { LONG_TAGS => 0, NO_LOOKUP => 1 }, # (suppress "long tags" warning in BuildTagLookup)
134
+ 0x0002 => 'AlternateRecipientAllowed',
135
+ 0x0039 => { Name => 'ClientSubmitTime', %dateInfo },
136
+ 0x0040 => 'ReceivedByName',
137
+ 0x0044 => 'ReceivedRepresentingName',
138
+ 0x004d => { Name => 'OriginalAuthorName', Groups => { 2 => 'Author' } },
139
+ 0x0055 => { Name => 'OriginalDeliveryTime', %dateInfo },
140
+ 0x0070 => 'Subject',
141
+ 0x0075 => 'ReceivedByAddressType',
142
+ 0x0076 => 'ReceivedByEmailAddress',
143
+ 0x0077 => 'ReceivedRepresentingAddressType',
144
+ 0x0078 => 'ReceivedRepresentingEmailAddress',
145
+ 0x007f => { Name => 'CorrelationKey', RawConv => '$$val' },
146
+ 0x0c1a => 'SenderName',
147
+ 0x0c1d => { Name => 'SenderSearchKey', RawConv => 'ref $val ? $$val : $val' },
148
+ 0x0e06 => { Name => 'MessageDeliveryTime', %dateInfo },
149
+ 0x0e1d => 'NormalizedSubject',
150
+ 0x0e28 => 'PrimarySendAccount',
151
+ 0x0e29 => 'NextSendAccount',
152
+ 0x0f02 => { Name => 'DeliveryOrRenewTime', %dateInfo }, #5
153
+ 0x1000 => { Name => 'MessageBodyText', Binary => 1 },
154
+ 0x1007 => 'SyncBodyCount',
155
+ 0x1008 => 'SyncBodyData',
156
+ 0x1009 => {
157
+ Name => 'MessageBodyRTF',
158
+ Notes => 'RTF message body, decompressed if necessary',
159
+ RawConv => '$$val', # (ValueConv won't convert a scalar ref, so convert to scalar here)
160
+ ValueConv => 'my $dat = Image::ExifTool::TNEF::DecompressRTF($self,$val); \$dat',
161
+ },
162
+ 0x1013 => { Name => 'MessageBodyHTML', Binary => 1 },
163
+ 0x1035 => 'InternetMessageID',
164
+ 0x10f4 => 'Hidden',
165
+ 0x10f6 => 'ReadOnly',
166
+ 0x3007 => { Name => 'CreateDate', %dateInfo },
167
+ 0x3008 => { Name => 'ModifyDate', %dateInfo },
168
+ 0x3fde => 'InternetCodePage',
169
+ 0x3ff1 => 'LocalUserID',
170
+ 0x3ff8 => { Name => 'CreatorName', Groups => { 2 => 'Author' } },
171
+ 0x3ffa => 'LastModifierName',
172
+ 0x3ffd => 'MessageCodePage',
173
+ 0x4076 => { Name => 'SpamConfidenceLevel' },
174
+ # named properties that look interesting
175
+ '00020329_Author' => {
176
+ Name => 'Author',
177
+ Groups => { 2 => 'Author' },
178
+ Notes => q{
179
+ tag ID's for named properties are constructed from the property namespace
180
+ GUID with the ending "-0000-0000-C000-000000000046" removed, followed by the
181
+ string or numerical ID in hex, separated by an underscore
182
+ },
183
+ }, # (NC)
184
+ '00020329_LastAuthor' => { Name => 'LastAuthor', Groups => { 2 => 'Author' } }, # (NC)
185
+ '00062004_0000801A' => 'HomeAddress', # (NC)
186
+ '00062004_000080DA' => 'HomeAddressCountryCode', # (NC)
187
+ '00062008_00008554' => 'AppVersion',
188
+ );
189
+
190
+ # ref https://pkg.go.dev/github.com/axigenmessaging/tnef#section-readme
191
+ %Image::ExifTool::TNEF::AttachInfo = (
192
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
193
+ PROCESS_PROC => \&ProcessProps,
194
+ TAG_PREFIX => 'Attach',
195
+ 0x0e20 => 'AttachSize',
196
+ 0x0e21 => 'AttachNum',
197
+ 0x0ff8 => { Name => 'MappingSignature', Unknown => 1 },
198
+ 0x3001 => 'AttachFileName',
199
+ 0x3703 => 'AttachFileExtension',
200
+ 0x3701 => 'AttachBinary',
201
+ 0x3705 => {
202
+ Name => 'AttachMethod',
203
+ PrintConv => {
204
+ 0 => 'Attachment Created',
205
+ 1 => 'AttachData', # (contrary to documentation which says the AttachBinary tag)
206
+ 2 => 'AttachLongPathName (recipients with access)',
207
+ 4 => 'AttachLongPathName',
208
+ 5 => 'Embedded Message',
209
+ 6 => 'AttachBinary (object)',
210
+ 7 => 'AttachLongPathName (using AttachmentProviderType)',
211
+ },
212
+ },
213
+ 0x3707 => 'AttachLongFileName',
214
+ 0x3708 => 'AttachPathName',
215
+ 0x370d => 'AttachLongPathName',
216
+ 0x370e => 'AttachMIMEType',
217
+ 0x7ffb => {
218
+ Name => 'ExceptionStartTime',
219
+ %dateInfo,
220
+ Unknown => 1, # (because these values don't make sense in my samples)
221
+ },
222
+ 0x7ffc => { Name => 'ExceptionEndTime', Unknown => 1, %dateInfo },
223
+ );
224
+
225
+ #------------------------------------------------------------------------------
226
+ # Decompress RTF text (ref https://metacpan.org/pod/Mail::Exchange::Message)
227
+ # Inputs: 0) ExifTool ref, 1) compressed RTF
228
+ # Returns: Decompressed RTF or '' on error
229
+ sub DecompressRTF($$)
230
+ {
231
+ my ($et, $cdat) = @_;
232
+ return '' unless length $cdat > 16;
233
+ my $comp = unpack('x8V', $cdat);
234
+
235
+ if ($comp == 0x414c454D) {
236
+ return substr($cdat, 16);
237
+ } elsif ($comp != 0x75465a4c) {
238
+ $et->Warn(sprintf('Unknown RTF compression 0x%x', $comp));
239
+ return '';
240
+ }
241
+ my $dict = '{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}'.
242
+ '{\f0\fnil \froman \fswiss \fmodern '.
243
+ '\fscript \fdecor MS Sans SerifSymbolArialTimes'.
244
+ ' New RomanCourier{\colortbl\red0\green0\blue0'.
245
+ "\r\n".'\par \pard\plain\f0\fs20\b\i\u\tab\tx';
246
+ my $cpos = 16;
247
+ my $clen = length $cdat;
248
+ my $dpos = length $dict;
249
+ my $rtnVal = '';
250
+ while ($cpos < $clen) {
251
+ my $control = unpack('C', substr($cdat, $cpos++, 1));
252
+ my ($i, $j);
253
+ for ($i=0; $i<8 && $cpos<$clen; ++$i) {
254
+ if ($control & (1<<$i)) {
255
+ return $rtnVal if $cpos + 2 > $clen;
256
+ my $ref = unpack('n', substr($cdat, $cpos, 2));
257
+ $cpos += 2;
258
+ my $off = $ref >> 4;
259
+ my $len = ($ref & 0x0f) + 2;
260
+ return $rtnVal if $off == $dpos % 4096 or $off % 4096 >= length($dict);
261
+ for ($j=0; $j<$len; ++$j) {
262
+ my $ch = substr($dict, ($off++ % 4096), 1);
263
+ substr($dict, ($dpos++ % 4096), 1) = $ch;
264
+ $rtnVal .= $ch;
265
+ }
266
+ } else {
267
+ my $ch = substr($cdat, $cpos++, 1);
268
+ substr($dict, ($dpos++ % 4096), 1) = $ch;
269
+ $rtnVal .= $ch;
270
+ }
271
+ }
272
+ }
273
+ return $rtnVal;
274
+ }
275
+
276
+ #------------------------------------------------------------------------------
277
+ # Process TNEF message properties
278
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
279
+ # Returns: 1 on success
280
+ sub ProcessProps($$$)
281
+ {
282
+ my ($et, $dirInfo, $tagTbl) = @_;
283
+ my $dataPt = $$dirInfo{DataPt};
284
+ my $dataPos = $$dirInfo{DataPos};
285
+ my $dirLen = length $$dataPt;
286
+ return 0 unless $dirLen > 4;
287
+ my $entries = unpack('V', $$dataPt);
288
+ $et->VerboseDir('TNEF Properties', $entries);
289
+ my $pos = 4;
290
+ my $i;
291
+ for ($i=0; $i<$entries; ++$i) {
292
+ last if $pos + 4 > $dirLen;
293
+ my $type = Get16u($dataPt, $pos);
294
+ my $tag = Get16u($dataPt, $pos+2);
295
+ $pos += 4;
296
+ # handle named properties (bit 0x8000 set)
297
+ if ($tag & 0x8000) {
298
+ last if $pos + 24 > $dirLen;
299
+ my $uid = Image::ExifTool::ASF::GetGUID(substr($$dataPt, $pos, 16));
300
+ $uid =~ s/-0000-0000-C000-000000000046$//; # remove common suffix
301
+ my $idtype = Get32u($dataPt, $pos + 16);
302
+ my $num = Get32u($dataPt, $pos + 20);
303
+ $pos += 24;
304
+ if ($idtype == 0) { # number
305
+ $tag = $uid . sprintf('_%.8x', $num);
306
+ } elsif ($idtype == 1) { # string
307
+ last if $pos + $num > $dirLen or $num < 2;
308
+ # decode string (ignoring null terminator)
309
+ my $name = $et->Decode(substr($$dataPt, $pos, $num-2), 'UTF16');
310
+ $tag = "${uid}_$name";
311
+ AddTagToTable($tagTbl, $tag, {
312
+ Name => Image::ExifTool::MakeTagName($name)
313
+ }) unless $$tagTbl{$tag};
314
+ $pos += ($num + 3) & 0xfffffffc; # (padded to an even 4 bytes)
315
+ } else {
316
+ last; # error
317
+ }
318
+ }
319
+ my $count = 1;
320
+ my ($multi, $fmt);
321
+ if ($type & 0x1000) {
322
+ $multi = 1;
323
+ $type &= 0x0fff;
324
+ last if $pos + 4 > $dirLen;
325
+ $count = Get32u($dataPt, $pos);
326
+ $pos += 4;
327
+ }
328
+ $fmt = $propType{$type} or last;
329
+ while ($count) {
330
+ my $size = $fmtSize{$fmt};
331
+ my $val;
332
+ unless ($size) {
333
+ if ($fmt =~ /(\d+)/) {
334
+ $size = $count * $1 / 8;
335
+ } elsif ($fmt eq 'null') {
336
+ $val = ''; # ($size is already 0)
337
+ } else {
338
+ # skip 1 count for "special case" stupidity
339
+ $pos += 4 unless $multi;
340
+ last if $pos + 4 > $dirLen;
341
+ $size = Get32u($dataPt, $pos);
342
+ $pos += 4;
343
+ last if $pos + $size > $dirLen;
344
+ $val = substr($$dataPt, $pos, $size);
345
+ }
346
+ }
347
+ if (not defined $val) {
348
+ $val = ReadValue($dataPt, $pos, $fmt, $count, $size);
349
+ if ($type == 0x06 or $type == 0x07 or $type == 0x0b or $type == 0x40) {
350
+ my @a = split ' ', $val;
351
+ if ($type == 0x06) { # currency
352
+ $_ = $_ / 10000 foreach @a;
353
+ } elsif ($type == 0x07) { # OLE date
354
+ # convert time from days since Dec 30, 1899
355
+ foreach (@a) {
356
+ $_ = ($_ - 25569) * 24 * 3600 if $_ != 0;
357
+ $_ = Image::ExifTool::ConvertUnixTime($_);
358
+ }
359
+ } elsif ($type == 0x0b) { # boolean
360
+ $_ = $_ ? 'True' : 'False' foreach @a;
361
+ } elsif ($type == 0x40) { # SYSTIME
362
+ # convert time from 100-ns intervals since Jan 1, 1601
363
+ $_ = Image::ExifTool::ConvertUnixTime($_/1e7-11644473600,1) foreach @a;
364
+ }
365
+ $val = @a > 1 ? \@a : $a[0];
366
+ }
367
+ $count = 1; # (read them all already)
368
+ } elsif ($fmt eq 'GUID') {
369
+ $val = Image::ExifTool::ASF::GetGUID($val);
370
+ } elsif ($fmt eq 'Unicode') {
371
+ ($val = $et->Decode($val, 'UTF16')) =~ s/\0+$//;
372
+ } elsif ($fmt eq 'string') {
373
+ $val =~ s/\0+$//;
374
+ # convert from specified code page if supported
375
+ $val = $et->Decode($val, $$et{Charset}) if $$et{Charset};
376
+ } elsif ($fmt eq 'undef' and length $val) {
377
+ my $copy = $val;
378
+ $val = \$copy; # change to a binary data reference
379
+ }
380
+ $et->HandleTag($tagTbl, $tag, $val,
381
+ DataPt => $dataPt,
382
+ DataPos => $dataPos,
383
+ Start => $pos,
384
+ Size => $size,
385
+ Format => sprintf('%s, type 0x%.2x', $fmt, $type),
386
+ Index => $i,
387
+ );
388
+ $pos += ($size + 3) & 0xfffffffc;
389
+ --$count;
390
+ }
391
+ }
392
+ $et->Warn('Error parsing message properties') unless $i == $entries;
393
+ return 1;
394
+ }
395
+
396
+ #------------------------------------------------------------------------------
397
+ # Extract EXIF information from a TNEF image
398
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
399
+ # Returns: 1 on success, 0 if this wasn't a valid TNEF file
400
+ sub ProcessTNEF($$)
401
+ {
402
+ my ($et, $dirInfo) = @_;
403
+ my $raf = $$dirInfo{RAF};
404
+ my ($buff, $tagTablePtr);
405
+
406
+ # verify this is a valid TNEF file (read TNEFHeader and TNEFVersion)
407
+ return 0 unless $raf->Read($buff, 0x15) == 0x15 and $raf->Seek(6, 0);
408
+ return 0 unless $buff =~ /^\x78\x9f\x3e\x22..\x01\x06\x90\x08\0/s;
409
+ $et->SetFileType('TNEF');
410
+ SetByteOrder('II');
411
+ my $tagTbl = GetTagTable('Image::ExifTool::TNEF::Main');
412
+ # read through the attributes
413
+ while ($raf->Read($buff, 9) == 9) {
414
+ # (ignore the attrLevel byte: 1 for message, 2 for attachment)
415
+ my ($tag, $len) = unpack('x1VV', $buff);
416
+ # increment document number for each attachment
417
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT} if $tag == 0x069002;
418
+ $raf->Read($buff, $len) == $len or last;
419
+ my $tagInfo = $$tagTbl{$tag};
420
+ my ($val, $fmt);
421
+ if ($tagInfo and $$tagInfo{Format}) {
422
+ $fmt = $$tagInfo{Format};
423
+ if ($fmt eq 'date' and length($buff) >= 12) {
424
+ my @date = unpack('v6', $buff);
425
+ $val = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @date);
426
+ }
427
+ } else {
428
+ $val = $buff;
429
+ }
430
+ $et->HandleTag($tagTbl, $tag, $val,
431
+ DataPt => \$buff,
432
+ DataPos => $raf->Tell() - $len,
433
+ Format => $fmt,
434
+ );
435
+ delete $$et{DOC_NUM} if $tag == 0x069005; # end of attachment
436
+ $raf->Seek(2, 1); # skip checksum
437
+ }
438
+ delete $$et{DOC_NUM};
439
+ return 1;
440
+ }
441
+
442
+ 1; # end
443
+
444
+ __END__
445
+
446
+ =head1 NAME
447
+
448
+ Image::ExifTool::TNEF - Read TNEF meta information
449
+
450
+ =head1 SYNOPSIS
451
+
452
+ This module is used by Image::ExifTool
453
+
454
+ =head1 DESCRIPTION
455
+
456
+ This module contains definitions required by Image::ExifTool to read TNEF
457
+ (Transport Neutral Encapsulation Format) files.
458
+
459
+ =head1 AUTHOR
460
+
461
+ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
462
+
463
+ This library is free software; you can redistribute it and/or modify it
464
+ under the same terms as Perl itself.
465
+
466
+ =head1 REFERENCES
467
+
468
+ =over 4
469
+
470
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXTNEF/%5bMS-OXTNEF%5d.pdf>
471
+
472
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCMSG/%5bMS-OXCMSG%5d.pdf>
473
+
474
+ =item L<https://msopenspecs.azureedge.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf>
475
+
476
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCDATA/%5bMS-OXCDATA%5d.pdf>
477
+
478
+ =item L<https://github.com/echo-devim/pyjacktrick/blob/main/mapi_constants.py>
479
+
480
+ =back
481
+
482
+ =head1 SEE ALSO
483
+
484
+ L<Image::ExifTool::TagNames/TNEF Tags>,
485
+ L<Image::ExifTool(3pm)|Image::ExifTool>
486
+
487
+ =cut