File Coverage

blib/lib/Image/ExifTool/Validate.pm
Criterion Covered Total %
statement 102 193 52.8
branch 54 164 32.9
condition 59 184 32.0
subroutine 9 10 90.0
pod 0 6 0.0
total 224 557 40.2


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Validate.pm
3             #
4             # Description: Additional metadata validation
5             #
6             # Created: 2017/01/18 - P. Harvey
7             #
8             # Notes: My apologies for the convoluted logic contained herein, but it
9             # is done this way to retro-fit the Validate feature into the
10             # existing ExifTool code while reducing the possibility of
11             # introducing bugs or slowing down processing when this feature
12             # is not used.
13             #------------------------------------------------------------------------------
14              
15             package Image::ExifTool::Validate;
16              
17 1     1   9 use strict;
  1         2  
  1         41  
18 1     1   6 use vars qw($VERSION %exifSpec);
  1         2  
  1         64  
19              
20             $VERSION = '1.19';
21              
22 1     1   8 use Image::ExifTool qw(:Utils);
  1         3  
  1         165  
23 1     1   8 use Image::ExifTool::Exif;
  1         2  
  1         4029  
24              
25             # EXIF table tag ID's which are part of the EXIF 2.32 specification
26             # (with ExifVersion numbers for tags where I can determine the version)
27             # (also used by BuildTagLookup to add underlines in HTML version of EXIF Tag Table)
28             %exifSpec = (
29             0x1 => 210,
30             0x100 => 1, 0x8298 => 1, 0x9207 => 1, 0xa217 => 1,
31             0x101 => 1, 0x829a => 1, 0x9208 => 1, 0xa300 => 1,
32             0x102 => 1, 0x829d => 1, 0x9209 => 1, 0xa301 => 1,
33             0x103 => 1, 0x8769 => 1, 0x920a => 1, 0xa302 => 1,
34             0x106 => 1, 0x8822 => 1, 0x9214 => 220, 0xa401 => 220,
35             0x10e => 1, 0x8824 => 1, 0x927c => 1, 0xa402 => 220,
36             0x10f => 1, 0x8825 => 200, 0x9286 => 1, 0xa403 => 220,
37             0x110 => 1, 0x8827 => 1, 0x9290 => 1, 0xa404 => 220,
38             0x111 => 1, 0x8828 => 1, 0x9291 => 1, 0xa405 => 220,
39             0x112 => 1, 0x8830 => 230, 0x9292 => 1, 0xa406 => 220,
40             0x115 => 1, 0x8831 => 230, 0x9400 => 231, 0xa407 => 220,
41             0x116 => 1, 0x8832 => 230, 0x9401 => 231, 0xa408 => 220,
42             0x117 => 1, 0x8833 => 230, 0x9402 => 231, 0xa409 => 220,
43             0x11a => 1, 0x8834 => 230, 0x9403 => 231, 0xa40a => 220,
44             0x11b => 1, 0x8835 => 230, 0x9404 => 231, 0xa40b => 220,
45             0x11c => 1, 0x9000 => 1, 0x9405 => 231, 0xa40c => 220,
46             0x128 => 1, 0x9003 => 1, 0xa000 => 1, 0xa460 => 232,
47             0x12d => 1, 0x9004 => 1, 0xa001 => 1, 0xa461 => 232,
48             0x131 => 1, 0x9010 => 231, 0xa002 => 1, 0xa462 => 232,
49             0x132 => 1, 0x9011 => 231, 0xa003 => 1, 0xa420 => 220,
50             0x13b => 1, 0x9012 => 231, 0xa004 => 1, 0xa430 => 230,
51             0x13e => 1, 0x9101 => 1, 0xa005 => 210, 0xa431 => 230,
52             0x13f => 1, 0x9102 => 1, 0xa20b => 1, 0xa432 => 230,
53             0x201 => 1, 0x9201 => 1, 0xa20c => 1, 0xa433 => 230,
54             0x202 => 1, 0x9202 => 1, 0xa20e => 1, 0xa434 => 230,
55             0x211 => 1, 0x9203 => 1, 0xa20f => 1, 0xa435 => 230,
56             0x212 => 1, 0x9204 => 1, 0xa210 => 1, 0xa500 => 221,
57             0x213 => 1, 0x9205 => 1, 0xa214 => 1,
58             0x214 => 1, 0x9206 => 1, 0xa215 => 1,
59             );
60              
61             # GPSVersionID numbers when each tag was introduced
62             my %gpsVer = (
63             0x01 => 2000, 0x09 => 2000, 0x11 => 2000, 0x19 => 2000,
64             0x02 => 2000, 0x0a => 2000, 0x12 => 2000, 0x1a => 2000,
65             0x03 => 2000, 0x0b => 2000, 0x13 => 2000, 0x1b => 2200,
66             0x04 => 2000, 0x0c => 2000, 0x14 => 2000, 0x1c => 2200,
67             0x05 => 2000, 0x0d => 2000, 0x15 => 2000, 0x1d => 2200,
68             0x06 => 2000, 0x0e => 2000, 0x16 => 2000, 0x1e => 2200,
69             0x07 => 2000, 0x0f => 2000, 0x17 => 2000, 0x1f => 2300,
70             0x08 => 2000, 0x10 => 2000, 0x18 => 2000,
71             );
72              
73             # lookup to check version numbers
74             my %verCheck = (
75             ExifIFD => { ExifVersion => \%exifSpec },
76             InteropIFD => { ExifVersion => \%exifSpec },
77             GPS => { GPSVersionID => \%gpsVer },
78             );
79              
80             # tags standard in various RAW file formats
81             my %otherSpec = (
82             CR2 => { 0xc5d8 => 1, 0xc5d9 => 1, 0xc5e0 => 1, 0xc640 => 1, 0xc6dc => 1, 0xc6dd => 1 },
83             NEF => { 0x9216 => 1, 0x9217 => 1 },
84             DNG => { 0x882a => 1, 0x9211 => 1, 0x9216 => 1 },
85             ARW => { 0x7000 => 1, 0x7001 => 1, 0x7010 => 1, 0x7011 => 1, 0x7020 => 1, 0x7031 => 1,
86             0x7032 => 1, 0x7034 => 1, 0x7035 => 1, 0x7036 => 1, 0x7037 => 1, 0x7038 => 1,
87             0x7310 => 1, 0x7313 => 1, 0x7316 => 1, 0x74c7 => 1, 0x74c8 => 1, 0xa500 => 1 },
88             RW2 => { All => 1 }, # ignore all unknown tags in RW2
89             RWL => { All => 1 },
90             RAF => { All => 1 }, # (temporary)
91             DCR => { All => 1 },
92             KDC => { All => 1 },
93             JXR => { All => 1 },
94             SRW => { 0xa010 => 1, 0xa011 => 1, 0xa101 => 1, 0xa102 => 1 },
95             NRW => { 0x9216 => 1, 0x9217 => 1 },
96             X3F => { 0xa500 => 1 },
97             );
98              
99             # standard format for tags (not necessary for exifSpec or GPS tags where Writable is defined)
100             my %stdFormat = (
101             ExifIFD => {
102             0xa002 => 'int(16|32)u',
103             0xa003 => 'int(16|32)u',
104             },
105             InteropIFD => {
106             0x01 => 'string',
107             0x02 => 'undef',
108             0x1000 => 'string',
109             0x1001 => 'int(16|32)u',
110             0x1002 => 'int(16|32)u',
111             },
112             IFD => {
113             # TIFF, EXIF, XMP, IPTC, ICC_Profile and PrintIM standard tags:
114             0xfe => 'int32u', 0x11f => 'rational64u', 0x14a => 'int32u', 0x205 => 'int16u',
115             0xff => 'int16u', 0x120 => 'int32u', 0x14c => 'int16u', 0x206 => 'int16u',
116             0x100 => 'int(16|32)u', 0x121 => 'int32u', 0x14d => 'string', 0x207 => 'int32u',
117             0x101 => 'int(16|32)u', 0x122 => 'int16u', 0x14e => 'int16u', 0x208 => 'int32u',
118             0x107 => 'int16u', 0x123 => 'int16u', 0x150 => 'int(8|16)u', 0x209 => 'int32u',
119             0x108 => 'int16u', 0x124 => 'int32u', 0x151 => 'string', 0x211 => 'rational64u',
120             0x109 => 'int16u', 0x125 => 'int32u', 0x152 => 'int16u', 0x212 => 'int16u',
121             0x10a => 'int16u', 0x129 => 'int16u', 0x153 => 'int16u', 0x213 => 'int16u',
122             0x10d => 'string', 0x13c => 'string', 0x154 => '.*', 0x214 => 'rational64u',
123             0x111 => 'int(16|32)u', 0x13d => 'int16u', 0x155 => '.*', 0x2bc => 'int8u',
124             0x116 => 'int(16|32)u', 0x140 => 'int16u', 0x156 => 'int16u', 0x828d => 'int16u',
125             0x117 => 'int(16|32)u', 0x141 => 'int16u', 0x15b => 'undef', 0x828e => 'int8u',
126             0x118 => 'int16u', 0x142 => 'int(16|32)u', 0x200 => 'int16u', 0x83bb => 'int32u',
127             0x119 => 'int16u', 0x143 => 'int(16|32)u', 0x201 => 'int32u', 0x8649 => 'int8u',
128             0x11d => 'string', 0x144 => 'int32u', 0x202 => 'int32u', 0x8773 => 'undef',
129             0x11e => 'rational64u', 0x145 => 'int(16|32)u', 0x203 => 'int16u', 0xc4a5 => 'undef',
130             # Windows Explorer tags:
131             0x9c9b => 'int8u', 0x9c9d => 'int8u', 0x9c9f => 'int8u',
132             0x9c9c => 'int8u', 0x9c9e => 'int8u',
133             # GeoTiff tags:
134             0x830e => 'double', 0x8482 => 'double', 0x87af => 'int16u', 0x87b1 => 'string',
135             0x8480 => 'double', 0x85d8 => 'double', 0x87b0 => 'double',
136             # DNG tags:
137             0xc615 => '(string|int8u)', 0xc6d3 => '',
138             0xc61a => '(int16u|int32u|rational64u)', 0xc6f4 => '(string|int8u)',
139             0xc61d => 'int(16|32)u', 0xc6f6 => '(string|int8u)',
140             0xc61f => '(int16u|int32u|rational64u)', 0xc6f8 => '(string|int8u)',
141             0xc620 => '(int16u|int32u|rational64u)', 0xc6fe => '(string|int8u)',
142             0xc628 => '(int16u|rational64u)', 0xc716 => '(string|int8u)',
143             0xc634 => 'int8u', 0xc717 => '(string|int8u)',
144             0xc640 => '', 0xc718 => '(string|int8u)',
145             0xc660 => '', 0xc71e => 'int(16|32)u',
146             0xc68b => '(string|int8u)', 0xc71f => 'int(16|32)u',
147             0xc68d => 'int(16|32)u', 0xc791 => 'int(16|32)u',
148             0xc68e => 'int(16|32)u', 0xc792 => 'int(16|32)u',
149             0xc6d2 => '', 0xc793 => '(int16u|int32u|rational64u)',
150             },
151             );
152              
153             # generate lookup for any IFD
154             my %stdFormatAnyIFD = map { %{$stdFormat{$_}} } keys %stdFormat;
155              
156             # tag values to validate based on file type (from EXIF specification)
157             # - validation code may access $val and %val, and returns 1 on success,
158             # or error message otherwise ('' for a generic message)
159             # - entry is undef if tag must not exist (same as 'not defined $val' in code)
160             my %validValue = (
161             JPEG => {
162             IFD0 => {
163             0x100 => undef, # ImageWidth
164             0x101 => undef, # ImageLength
165             0x102 => undef, # BitsPerSample
166             0x103 => undef, # Compression
167             0x106 => undef, # PhotometricInterpretation
168             0x111 => undef, # StripOffsets
169             0x115 => undef, # SamplesPerPixel
170             0x116 => undef, # RowsPerStrip
171             0x117 => undef, # StripByteCounts
172             0x11a => 'defined $val', # XResolution
173             0x11b => 'defined $val', # YResolution
174             0x11c => undef, # PlanarConfiguration
175             0x128 => '$val =~ /^[123]$/', # ResolutionUnit
176             0x201 => undef, # JPEGInterchangeFormat
177             0x202 => undef, # JPEGInterchangeFormatLength
178             0x212 => undef, # YCbCrSubSampling
179             0x213 => '$val =~ /^[12]$/', # YCbCrPositioning
180             },
181             IFD1 => {
182             0x100 => undef, # ImageWidth
183             0x101 => undef, # ImageLength
184             0x102 => undef, # BitsPerSample
185             0x103 => '$val == 6', # Compression
186             0x106 => undef, # PhotometricInterpretation
187             0x111 => undef, # StripOffsets
188             0x115 => undef, # SamplesPerPixel
189             0x116 => undef, # RowsPerStrip
190             0x117 => undef, # StripByteCounts
191             0x11a => 'defined $val', # XResolution
192             0x11b => 'defined $val', # YResolution
193             0x11c => undef, # PlanarConfiguration
194             0x128 => '$val =~ /^[123]$/', # ResolutionUnit
195             0x201 => 'defined $val', # JPEGInterchangeFormat
196             0x202 => 'defined $val', # JPEGInterchangeFormatLength
197             0x212 => undef, # YCbCrSubSampling
198             },
199             ExifIFD => {
200             0x9000 => 'defined $val and $val =~ /^\d{4}$/', # ExifVersion
201             0x9101 => 'defined $val', # ComponentsConfiguration
202             0xa000 => 'defined $val', # FlashpixVersion
203             0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace
204             0xa002 => 'defined $val', # PixelXDimension
205             0xa003 => 'defined $val', # PixelYDimension
206             },
207             GPS => {
208             0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID
209             0x1b => 'not defined $val or $val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod
210             },
211             InteropIFD => { }, # (needed for ExifVersion check)
212             },
213             TIFF => {
214             IFD0 => {
215             0x100 => 'defined $val', # ImageWidth
216             0x101 => 'defined $val', # ImageLength
217             # (default is 1) 0x102 => 'defined $val', # BitsPerSample
218             0x103 => q{
219             not defined $val or $val =~ /^(1|5|6|32773)$/ or
220             ($val == 2 and (not defined $val{0x102} or $val{0x102} == 1));
221             }, # Compression
222             0x106 => '$val =~ /^[0123]$/', # PhotometricInterpretation
223             0x111 => 'defined $val', # StripOffsets
224             # SamplesPerPixel
225             0x115 => q{
226             my $pi = $val{0x106} || 0;
227             my $xtra = ($val{0x152} ? scalar(split ' ', $val{0x152}) : 0);
228             if ($pi == 2 or $pi == 6) {
229             return $val == 3 + $xtra;
230             } elsif ($pi == 5) {
231             return $val == 4 + $xtra;
232             } else {
233             return 1;
234             }
235             },
236             0x116 => 'defined $val', # RowsPerStrip
237             0x117 => 'defined $val', # StripByteCounts
238             0x11a => 'defined $val', # XResolution
239             0x11b => 'defined $val', # YResolution
240             0x128 => 'not defined $val or $val =~ /^[123]$/', # ResolutionUnit
241             # ColorMap (must be palette image with correct number of colors)
242             0x140 => q{
243             return '' if defined $val{0x106} and $val{0x106} == 3 xor defined $val;
244             return 1 if not defined $val or length($val) == 6 * 2 ** ($val{0x102} || 0);
245             return 'Invalid count for';
246             },
247             0x201 => undef, # JPEGInterchangeFormat
248             0x202 => undef, # JPEGInterchangeFormatLength
249             },
250             ExifIFD => {
251             0x9000 => 'defined $val', # ExifVersion
252             0x9101 => undef, # ComponentsConfiguration
253             0x9102 => undef, # CompressedBitsPerPixel
254             0xa000 => 'defined $val', # FlashpixVersion
255             0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace
256             0xa002 => undef, # PixelXDimension
257             0xa003 => undef, # PixelYDimension
258             },
259             InteropIFD => {
260             0x0001 => undef, # InteropIndex
261             },
262             GPS => {
263             0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID
264             0x1b => '$val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod
265             },
266             },
267             );
268              
269             # validity ranges for constrained date/time fields
270             my @validDateField = (
271             [ 'Month', 1, 12 ],
272             [ 'Day', 1, 31 ],
273             [ 'Hour', 0, 23 ],
274             [ 'Minutes', 0, 59 ],
275             [ 'Seconds', 0, 59 ],
276             [ 'TZhr', 0, 14 ],
277             [ 'TZmin', 0, 59 ],
278             );
279              
280             # "Validate" tag information
281             my %validateInfo = (
282             Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'ExifTool' },
283             Notes => q{
284             generated only if specifically requested. Requesting this tag automatically
285             enables the API L option, imposing
286             additional validation checks when extracting metadata. Returns the number
287             of errors, warnings and minor warnings encountered. Note that the Validate
288             feature focuses mainly on validation of EXIF/TIFF metadata
289             },
290             PrintConv => {
291             '0 0 0' => 'OK',
292             OTHER => sub {
293             my @val = split ' ', shift;
294             my @rtn;
295             push @rtn, sprintf('%d Error%s', $val[0], $val[0] == 1 ? '' : 's') if $val[0];
296             push @rtn, sprintf('%d Warning%s', $val[1], $val[1] == 1 ? '' : 's') if $val[1];
297             if ($val[2]) {
298             my $str = ($val[1] == $val[2] ? ($val[1] == 1 ? '' : 'all ') : "$val[2] ");
299             $rtn[-1] .= " (${str}minor)";
300             }
301             return join(' and ', @rtn);
302             },
303             },
304             );
305              
306             # add "Validate" tag to Extra table
307             AddTagToTable(\%Image::ExifTool::Extra, Validate => \%validateInfo, 1);
308              
309             #------------------------------------------------------------------------------
310             # Validate the raw value of a tag
311             # Inputs: 0) ExifTool ref, 1) tag key, 2) raw tag value
312             # Returns: nothing, but issues a minor Warning if a problem was detected
313             sub ValidateRaw($$$)
314             {
315 213     213 0 451 my ($self, $tag, $val) = @_;
316 213         407 my $tagInfo = $$self{TAG_INFO}{$tag};
317 213         275 my $wrn;
318              
319             # evaluate Validate code if specified
320 213 100       499 if ($$tagInfo{Validate}) {
321 3         22 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
322 3         7 undef $Image::ExifTool::evalWarning;
323             #### eval Validate ($self, $val, $tagInfo)
324 3         185 my $wrn = eval $$tagInfo{Validate};
325 3   33     19 my $err = $Image::ExifTool::evalWarning || $@;
326 3 50 33     21 if ($wrn or $err) {
327 0         0 my $name = $$tagInfo{Table}{GROUPS}{0} . ':' . Image::ExifTool::GetTagName($tag);
328 0 0       0 $self->Warn("Validate $name: $err", 1) if $err;
329 0 0       0 $self->Warn("$wrn for $name", 1) if $wrn;
330             }
331             }
332             # check for unknown values in PrintConv lookup for all standard EXIF tags
333 213 100 66     960 if (ref $$tagInfo{PrintConv} eq 'HASH' and ($$tagInfo{Table}{SHORT_NAME} eq 'GPS::Main' or
      100        
334             ($$tagInfo{Table} eq \%Image::ExifTool::Exif::Main and $exifSpec{$$tagInfo{TagID}})))
335             {
336 18         65 my $prt = $self->GetValue($tag, 'PrintConv');
337 18 50 33     119 $wrn = 'Unknown value for' if $prt and $prt =~ /^Unknown \(/;
338             }
339 213 50       465 $wrn = 'Undefined value for' if $val eq 'undef';
340 213 50       642 if ($wrn) {
341 0         0 my $name = $$self{DIR_NAME} . ':' . Image::ExifTool::GetTagName($tag);
342 0         0 $self->Warn("$wrn $name", 1);
343             }
344             }
345              
346             #------------------------------------------------------------------------------
347             # Validate raw EXIF date/time value
348             # Inputs: 0) date/time value
349             # Returns: error string
350             sub ValidateExifDate($)
351             {
352 3     3 0 7 my $val = shift;
353 3 50 0     22 if ($val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
    0          
354 3         22 my @a = ($1,$2,$3,$4,$5);
355 3         5 my ($i, @bad);
356 3         14 for ($i=0; $i<@a; ++$i) {
357 15 50 33     95 next if $a[$i] eq ' ' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]);
      33        
358 0         0 push @bad, $validDateField[$i][0];
359             }
360 3 50       12 return join('+', @bad) . ' out of range' if @bad;
361             # the EXIF specification allows blank fields or an entire blank value
362             } elsif ($val ne ' : : : : ' and $val ne ' ') {
363 0         0 return 'Invalid date/time format';
364             }
365 3         23 return undef; # OK!
366             }
367              
368             #------------------------------------------------------------------------------
369             # Validate EXIF-reformatted XMP date/time value
370             # Inputs: 0) date/time value
371             # Returns: error string
372             sub ValidateXMPDate($)
373             {
374 0     0 0 0 my $val = shift;
375 0 0 0     0 if ($val =~ /^\d{4}$/ or
      0        
      0        
      0        
      0        
376             $val =~ /^\d{4}:(\d{2})$/ or
377             $val =~ /^\d{4}:(\d{2}):(\d{2})$/ or
378             $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2})()(Z|[-+](\d{2}):(\d{2}))?$/ or
379             $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})(Z|[-+](\d{2}):(\d{2}))?$/ or
380             $val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})\.?\d*(Z|[-+](\d{2}):(\d{2}))?$/)
381             {
382 0         0 my @a = ($1,$2,$3,$4,$5,$7,$8);
383 0         0 my ($i, @bad);
384 0         0 for ($i=0; $i<@a; ++$i) {
385 0 0       0 last unless defined $a[$i];
386 0 0 0     0 next if $a[$i] eq '' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]);
      0        
387 0         0 push @bad, $validDateField[$i][0];
388             }
389 0 0       0 return join('+', @bad) . ' out of range' if @bad;
390             } else {
391 0         0 return 'Invalid date/time format';
392             }
393 0         0 return undef; # OK!
394             }
395              
396             #------------------------------------------------------------------------------
397             # Validate EXIF tag
398             # Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tagInfo ref,
399             # 4) previous tag ID, 5) IFD name, 6) number of values, 7) value format string
400             # Returns: Nothing, but sets Warning tags if any problems are found
401             sub ValidateExif($$$$$$$$)
402             {
403 63     63 0 204 my ($et, $tagTablePtr, $tag, $tagInfo, $lastTag, $ifd, $count, $formatStr) = @_;
404              
405 63 50       152 $et->WarnOnce("Entries in $ifd are out of order") if $tag <= $lastTag;
406              
407             # (get tagInfo for unknown tags if Unknown option not used)
408 63 50 66     164 if (not defined $tagInfo and $$tagTablePtr{$tag} and ref $$tagTablePtr{$tag} eq 'HASH') {
      33        
409 0         0 $tagInfo = $$tagTablePtr{$tag};
410             }
411 63 100 33     172 if (defined $tagInfo) {
    50          
412 61   66     136 my $ti = $tagInfo || $$tagTablePtr{$tag};
413 61 100       137 $ti = $$ti[-1] if ref $ti eq 'ARRAY';
414 61   66     191 my $stdFmt = $stdFormat{$ifd} || $stdFormat{IFD};
415 61 100 66     661 if (defined $$stdFmt{All} or ($tagTablePtr eq \%Image::ExifTool::Exif::Main and
    50 33        
    50 33        
      33        
      33        
416             ($exifSpec{$tag} or $$stdFmt{$tag} or
417             ($tag >= 0xc612 and $tag <= 0xc7b5 and not defined $$stdFmt{$tag}))) or # (DNG tags)
418             $$tagTablePtr{SHORT_NAME} eq 'GPS::Main')
419             {
420 59   66     217 my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP};
421 59 0 66     333 if ($wgp and $wgp ne $ifd and $wgp ne 'All' and not $$ti{OffsetPair} and
      66        
      100        
      25        
      66        
      0        
      33        
      0        
422             ($ifd =~ /^(Sub|Profile)?IFD\d*$/ xor $wgp =~ /^(Sub)?IFD\d*$/) and
423             ($$ti{Writable} or $$ti{WriteGroup}) and $ifd !~ /^SRF\d+$/)
424             {
425 0         0 $et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd));
426             }
427 59   100     233 my $fmt = $$stdFmt{$tag} || $$ti{Writable};
428 59 0 66     890 if ($fmt and $formatStr !~ /^$fmt$/ and (not $tagInfo or
      0        
      33        
429             not $$tagInfo{IsOffset} or $Image::ExifTool::Exif::intFormat{$formatStr}))
430             {
431 0         0 $et->Warn(sprintf('Non-standard format (%s) for %s 0x%.4x %s', $formatStr, $ifd, $tag, $$ti{Name}))
432             }
433             } elsif ($stdFormatAnyIFD{$tag}) {
434 0 0 0     0 if ($$ti{Writable} || $$ti{WriteGroup}) {
435 0   0     0 my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP};
436 0 0 0     0 if ($wgp and $wgp ne $ifd) {
437 0         0 $et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd));
438             }
439             }
440             } elsif (not $otherSpec{$$et{VALUE}{FileType}} or
441             (not $otherSpec{$$et{VALUE}{FileType}}{$tag} and not $otherSpec{$$et{VALUE}{FileType}}{All}))
442             {
443 0 0 0     0 if ($tagTablePtr eq \%Image::ExifTool::Exif::Main or $$tagInfo{Unknown}) {
444 0         0 $et->Warn(sprintf('Non-standard %s tag 0x%.4x %s', $ifd, $tag, $$ti{Name}), 1);
445             }
446             }
447             # change expected count from read Format to Writable size
448 61         189 my $tiCount = $$ti{Count};
449 61 100       196 if ($tiCount) {
450 4 50 66     36 if ($$ti{Format} and $$ti{Writable} and
      33        
      33        
451             $Image::ExifTool::Exif::formatNumber{$$ti{Format}} and
452             $Image::ExifTool::Exif::formatNumber{$$ti{Writable}})
453             {
454 1         3 my $s1 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Format}}];
455 1         3 my $s2 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Writable}}];
456 1         4 $tiCount = int($tiCount * $s1 / $s2);
457             }
458 4 50 66     22 if ($tiCount > 0 and $count != $tiCount) {
459 0         0 $et->Warn(sprintf('Non-standard count (%d) for %s 0x%.4x %s', $count, $ifd, $tag, $$ti{Name}));
460             }
461             }
462             } elsif (not $otherSpec{$$et{VALUE}{FileType}} or
463             (not $otherSpec{$$et{VALUE}{FileType}}{$tag} and not $otherSpec{$$et{VALUE}{FileType}}{All}))
464             {
465 0         0 $et->Warn(sprintf('Unknown %s tag 0x%.4x', $ifd, $tag), 1);
466             }
467             }
468              
469             #------------------------------------------------------------------------------
470             # Validate image data offsets/sizes
471             # Inputs: 0) ExifTool ref, 1) offset info hash ref (arrays of tagInfo/value pairs, keyed by tagID)
472             # 2) directory name, 3) optional flag for minor warning
473             sub ValidateOffsetInfo($$$;$)
474             {
475 5     5 0 9 local $_;
476 5         15 my ($et, $offsetInfo, $dirName, $minor) = @_;
477              
478 5 50       19 my $fileSize = $$et{VALUE}{FileSize} or return;
479              
480             # (don't test RWZ files and some other file types)
481 5 50       16 return if $$et{DontValidateImageData};
482             # (Minolta A200 uses wrong byte order for these)
483 5 0 33     15 return if $$et{TIFF_TYPE} eq 'MRW' and $dirName eq 'IFD0' and $$et{Model} =~ /^DiMAGE A200/;
      33        
484             # (don't test 3FR, RWL or RW2 files)
485 5 50       25 return if $$et{TIFF_TYPE} =~ /^(3FR|RWL|RW2)$/;
486              
487 5         28 Image::ExifTool::Exif::ValidateImageData($et, $offsetInfo, $dirName);
488              
489             # loop through all offsets
490 5         17 while (%$offsetInfo) {
491 16         56 my ($id1) = sort keys %$offsetInfo;
492 16         36 my $offsets = $$offsetInfo{$id1};
493 16         28 delete $$offsetInfo{$id1};
494 16 100       46 next unless ref $offsets eq 'ARRAY';
495 6         15 my $id2 = $$offsets[0]{OffsetPair};
496 6 100 66     40 unless (defined $id2 and $$offsetInfo{$id2}) {
497 2 50 33     17 unless ($$offsets[0]{NotRealPair} or (defined $id2 and $id2 == -1)) {
      33        
498 2 100       8 my $corr = $$offsets[0]{IsOffset} ? 'size' : 'offset';
499 2 50       6 $et->Warn("$dirName:$$offsets[0]{Name} is missing the corresponding $corr tag") unless $minor;
500             }
501 2         11 next;
502             }
503 4         10 my $sizes = $$offsetInfo{$id2};
504 4         8 delete $$offsetInfo{$id2};
505 4 50       13 ($sizes, $offsets) = ($offsets, $sizes) if $$sizes[0]{IsOffset};
506 4         14 my @offsets = split ' ', $$offsets[1];
507 4         12 my @sizes = split ' ', $$sizes[1];
508 4 50       11 if (@sizes != @offsets) {
509             $et->Warn(sprintf('Wrong number of values in %s 0x%.4x %s',
510 0         0 $dirName, $$offsets[0]{TagID}, $$offsets[0]{Name}), $minor);
511 0         0 next;
512             }
513 4         10 while (@offsets) {
514 4         8 my $start = pop @offsets;
515 4         11 my $end = $start + pop @sizes;
516 4 50       12 $et->WarnOnce("$dirName:$$offsets[0]{Name} is zero", $minor) if $start == 0;
517 4 50       10 $et->WarnOnce("$dirName:$$sizes[0]{Name} is zero", $minor) if $start == $end;
518 4 50       25 next unless $end > $fileSize;
519 0 0       0 if ($start >= $fileSize) {
520 0 0       0 if ($start == 0xffffffff) {
521 0         0 $et->Warn("$dirName:$$offsets[0]{Name} is invalid (0xffffffff)", $minor);
522             } else {
523 0         0 $et->Warn("$dirName:$$offsets[0]{Name} is past end of file", $minor);
524             }
525             } else {
526 0         0 $et->Warn("$dirName:$$offsets[0]{Name}+$$sizes[0]{Name} runs past end of file", $minor);
527             }
528 0         0 last;
529             }
530             }
531             }
532              
533             #------------------------------------------------------------------------------
534             # Finish Validating tags
535             # Inputs: 0) ExifTool ref, 1) True to generate Validate tag
536             sub FinishValidate($$)
537             {
538 1     1 0 3 local $_;
539 1         4 my ($et, $mkTag) = @_;
540              
541 1   50     7 my $fileType = $$et{FILE_TYPE} || '';
542 1 50       7 $fileType = $$et{TIFF_TYPE} if $fileType eq 'TIFF';
543              
544 1 50       5 if ($validValue{$fileType}) {
545 0         0 my ($grp, $tag, %val);
546 0         0 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
547 0         0 foreach $grp (sort keys %{$validValue{$fileType}}) {
  0         0  
548 0 0       0 next unless $$et{FOUND_DIR}{$grp};
549 0         0 my ($key, %val, %info, $minor, $verTag, $ver, $vstr);
550 0         0 my $verCheck = $verCheck{$grp};
551 0 0       0 if ($verCheck) {
552 0         0 ($verTag) = keys %$verCheck;
553 0         0 ($ver = $$et{VALUE}{$verTag}) =~ tr/0-9//dc; # (remove non-digits)
554 0 0       0 undef $ver unless $ver =~ /^\d{4}$/; # (already warned if invalid version)
555             }
556             # get all tags in this group
557 0         0 foreach $key (sort keys %{$$et{VALUE}}) {
  0         0  
558 0 0       0 next unless $et->GetGroup($key, 1) eq $grp;
559 0 0 0     0 next if $$et{TAG_EXTRA}{$key} and $$et{TAG_EXTRA}{$key}{G3}; # ignore sub-documents
560             # fill in %val lookup with values based on tag ID
561 0         0 my $tag = $$et{TAG_INFO}{$key}{TagID};
562 0         0 $val{$tag} = $$et{VALUE}{$key};
563             # save TagInfo ref for later
564 0         0 $info{$tag} = $$et{TAG_INFO}{$key};
565 0 0       0 next unless defined $ver;
566 0         0 my $chk = $$verCheck{$verTag};
567 0 0 0     0 next if not defined $$chk{$tag} or $$chk{$tag} == 1 or $ver >= $$chk{$tag};
      0        
568 0 0       0 if ($verTag eq 'GPSVersionID') {
569 0         0 ($vstr = $$chk{$tag}) =~ s/^(\d)(\d)(\d)/$1.$2.$3./;
570             } else {
571 0         0 $vstr = sprintf('%.4d', $$chk{$tag});
572             }
573             $et->Warn(sprintf('%s tag 0x%.4x %s requires %s %s or higher',
574 0         0 $grp, $tag, $$et{TAG_INFO}{$key}{Name}, $verTag, $vstr));
575             }
576             # make quick lookup for values based on tag ID
577 0         0 my $validValue = $validValue{$fileType}{$grp};
578 0         0 foreach $tag (sort { $a <=> $b } keys %$validValue) {
  0         0  
579 0         0 my $val = $val{$tag};
580 0         0 my ($pre, $post);
581 0 0       0 if (defined $$validValue{$tag}) {
582             #### eval ($val, %val)
583 0         0 my $result = eval $$validValue{$tag};
584 0 0       0 if (not defined $result) {
    0          
585 0         0 $pre = 'Internal error validating';
586             } elsif ($result eq '') {
587 0 0       0 $pre = defined $val ? 'Invalid value for' : "Missing required $fileType";
588             } else {
589 0 0       0 next if $result eq '1';
590 0         0 $pre = $result;
591             }
592             } else {
593 0 0       0 next unless defined $val;
594 0         0 $post = "is not allowed in $fileType";
595 0         0 $minor = 1;
596             }
597 0         0 my $name;
598 0 0       0 if ($info{$tag}) {
599 0         0 $name = $info{$tag}{Name};
600             } else {
601 0 0       0 my $table = 'Image::ExifTool::'.($grp eq 'GPS' ? 'GPS' : 'Exif').'::Main';
602 0         0 my $tagInfo = GetTagTable($table)->{$tag};
603 0 0       0 $tagInfo = $$tagInfo[0] if ref $tagInfo eq 'ARRAY';
604 0 0       0 $name = $tagInfo ? $$tagInfo{Name} : '';
605             }
606 0 0 0     0 next if $$et{WrongFormat} and $$et{WrongFormat}{"$grp:$name"};
607 0 0       0 $pre ? ($pre .= ' ') : ($pre = '');
608 0 0       0 $post ? ($post = ' '.$post) : ($post = '');
609 0         0 $et->Warn(sprintf('%s%s tag 0x%.4x %s%s', $pre, $grp, $tag, $name, $post), $minor);
610             }
611             }
612             }
613             # validate file extension
614 1 50       6 if ($$et{FILENAME} ne '') {
615 1 50       13 my $fileExt = ($$et{FILENAME} =~ /^.*\.([^.]+)$/s) ? uc($1) : '';
616 1         6 my $extFileType = Image::ExifTool::GetFileType($fileExt);
617 1 50 33     10 if ($extFileType and $extFileType ne $fileType) {
618 1         5 my $normExt = $$et{VALUE}{FileTypeExtension};
619 1 50 33     9 if ($normExt and $normExt ne $fileExt) {
620 0         0 my $lkup = $Image::ExifTool::fileTypeLookup{$fileExt};
621 0 0 0     0 if (ref $lkup or $lkup ne $normExt) {
622 0         0 $et->Warn("File has wrong extension (should be $normExt, not $fileExt)");
623             }
624             }
625             }
626             }
627             # issue warning if FastScan option used
628 1 50       5 $et->Warn('Validation incomplete because FastScan option used') if $et->Options('FastScan');
629              
630             # generate Validate tag if necessary
631 1 50       4 if ($mkTag) {
632 1         4 my (@num, $key);
633             push @num, $$et{VALUE}{Error} ? ($$et{DUPL_TAG}{Error} || 0) + 1 : 0,
634 1 50 0     9 $$et{VALUE}{Warning} ? ($$et{DUPL_TAG}{Warning} || 0) + 1 : 0, 0;
    50 0        
635 1         3 for ($key = 'Warning'; ; ) {
636 1 50 33     6 ++$num[2] if $$et{VALUE}{$key} and $$et{VALUE}{$key} =~ /^\[minor\]/i;
637 1 50       7 $key = $et->NextTagKey($key) or last;
638             }
639 1         9 $et->FoundTag(Validate => "@num");
640             }
641             }
642              
643             1; # end
644              
645             __END__