| 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 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 39 |  | 
| 18 | 1 |  |  | 1 |  | 5 | use vars qw($VERSION %exifSpec); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 78 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | $VERSION = '1.20'; | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 1 |  |  | 1 |  | 7 | use Image::ExifTool qw(:Utils); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 179 |  | 
| 23 | 1 |  |  | 1 |  | 19 | use Image::ExifTool::Exif; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 4049 |  | 
| 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 | 411 | my ($self, $tag, $val) = @_; | 
| 316 | 213 |  |  |  |  | 484 | my $tagInfo = $$self{TAG_INFO}{$tag}; | 
| 317 | 213 |  |  |  |  | 311 | my $wrn; | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | # evaluate Validate code if specified | 
| 320 | 213 | 100 |  |  |  | 421 | if ($$tagInfo{Validate}) { | 
| 321 | 3 |  |  |  |  | 17 | local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; | 
| 322 | 3 |  |  |  |  | 11 | undef $Image::ExifTool::evalWarning; | 
| 323 |  |  |  |  |  |  | #### eval Validate ($self, $val, $tagInfo) | 
| 324 | 3 |  |  |  |  | 178 | my $wrn = eval $$tagInfo{Validate}; | 
| 325 | 3 |  | 33 |  |  | 20 | my $err = $Image::ExifTool::evalWarning || $@; | 
| 326 | 3 | 50 | 33 |  |  | 18 | 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 |  |  | 844 | 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 |  |  |  |  | 86 | my $prt = $self->GetValue($tag, 'PrintConv'); | 
| 337 | 18 | 50 | 33 |  |  | 83 | $wrn = 'Unknown value for' if $prt and $prt =~ /^Unknown \(/; | 
| 338 |  |  |  |  |  |  | } | 
| 339 | 213 | 50 |  |  |  | 513 | $wrn = 'Undefined value for' if $val eq 'undef'; | 
| 340 | 213 | 50 |  |  |  | 650 | 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 | 10 | my $val = shift; | 
| 353 | 3 | 50 | 0 |  |  | 16 | if ($val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { | 
|  |  | 0 |  |  |  |  |  | 
| 354 | 3 |  |  |  |  | 26 | my @a = ($1,$2,$3,$4,$5); | 
| 355 | 3 |  |  |  |  | 7 | my ($i, @bad); | 
| 356 | 3 |  |  |  |  | 11 | for ($i=0; $i<@a; ++$i) { | 
| 357 | 15 | 50 | 33 |  |  | 86 | 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 |  |  |  |  | 30 | 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 | 191 | my ($et, $tagTablePtr, $tag, $tagInfo, $lastTag, $ifd, $count, $formatStr) = @_; | 
| 404 |  |  |  |  |  |  |  | 
| 405 | 63 | 50 |  |  |  | 144 | $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 |  |  | 147 | 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 |  |  | 162 | if (defined $tagInfo) { | 
|  |  | 50 |  |  |  |  |  | 
| 412 | 61 |  | 66 |  |  | 141 | my $ti = $tagInfo || $$tagTablePtr{$tag}; | 
| 413 | 61 | 100 |  |  |  | 141 | $ti = $$ti[-1] if ref $ti eq 'ARRAY'; | 
| 414 | 61 |  | 66 |  |  | 186 | my $stdFmt = $stdFormat{$ifd} || $stdFormat{IFD}; | 
| 415 | 61 | 100 | 66 |  |  | 517 | 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 |  |  | 244 | my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP}; | 
| 421 | 59 | 0 | 66 |  |  | 386 | 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 |  |  | 255 | my $fmt = $$stdFmt{$tag} || $$ti{Writable}; | 
| 428 | 59 | 0 | 66 |  |  | 926 | 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{FileType}} or | 
| 441 |  |  |  |  |  |  | (not $otherSpec{$$et{FileType}}{$tag} and not $otherSpec{$$et{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 |  |  |  |  | 149 | my $tiCount = $$ti{Count}; | 
| 449 | 61 | 100 |  |  |  | 206 | if ($tiCount) { | 
| 450 | 4 | 50 | 66 |  |  | 37 | 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 |  |  |  |  | 5 | 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 |  |  |  |  | 7 | $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{FileType}} or | 
| 463 |  |  |  |  |  |  | (not $otherSpec{$$et{FileType}}{$tag} and not $otherSpec{$$et{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 | 10 | local $_; | 
| 476 | 5 |  |  |  |  | 12 | my ($et, $offsetInfo, $dirName, $minor) = @_; | 
| 477 |  |  |  |  |  |  |  | 
| 478 | 5 | 50 |  |  |  | 14 | my $fileSize = $$et{VALUE}{FileSize} or return; | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | # (don't test RWZ files and some other file types) | 
| 481 | 5 | 50 |  |  |  | 13 | return if $$et{DontValidateImageData}; | 
| 482 |  |  |  |  |  |  | # (Minolta A200 uses wrong byte order for these) | 
| 483 | 5 | 0 | 33 |  |  | 18 | 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 |  |  |  | 23 | return if $$et{TIFF_TYPE} =~ /^(3FR|RWL|RW2)$/; | 
| 486 |  |  |  |  |  |  |  | 
| 487 | 5 |  |  |  |  | 26 | Image::ExifTool::Exif::ValidateImageData($et, $offsetInfo, $dirName); | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | # loop through all offsets | 
| 490 | 5 |  |  |  |  | 16 | while (%$offsetInfo) { | 
| 491 | 16 |  |  |  |  | 53 | my ($id1) = sort keys %$offsetInfo; | 
| 492 | 16 |  |  |  |  | 32 | my $offsets = $$offsetInfo{$id1}; | 
| 493 | 16 |  |  |  |  | 29 | delete $$offsetInfo{$id1}; | 
| 494 | 16 | 100 |  |  |  | 40 | next unless ref $offsets eq 'ARRAY'; | 
| 495 | 6 |  |  |  |  | 17 | my $id2 = $$offsets[0]{OffsetPair}; | 
| 496 | 6 | 100 | 66 |  |  | 28 | 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 |  |  |  | 11 | my $corr = $$offsets[0]{IsOffset} ? 'size' : 'offset'; | 
| 499 | 2 | 50 |  |  |  | 5 | $et->Warn("$dirName:$$offsets[0]{Name} is missing the corresponding $corr tag") unless $minor; | 
| 500 |  |  |  |  |  |  | } | 
| 501 | 2 |  |  |  |  | 12 | next; | 
| 502 |  |  |  |  |  |  | } | 
| 503 | 4 |  |  |  |  | 10 | my $sizes = $$offsetInfo{$id2}; | 
| 504 | 4 |  |  |  |  | 9 | delete $$offsetInfo{$id2}; | 
| 505 | 4 | 50 |  |  |  | 13 | ($sizes, $offsets) = ($offsets, $sizes) if $$sizes[0]{IsOffset}; | 
| 506 | 4 |  |  |  |  | 17 | my @offsets = split ' ', $$offsets[1]; | 
| 507 | 4 |  |  |  |  | 9 | my @sizes = split ' ', $$sizes[1]; | 
| 508 | 4 | 50 |  |  |  | 13 | 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 |  |  |  |  | 10 | my $start = pop @offsets; | 
| 515 | 4 |  |  |  |  | 13 | my $end = $start + pop @sizes; | 
| 516 | 4 | 50 |  |  |  | 10 | $et->WarnOnce("$dirName:$$offsets[0]{Name} is zero", $minor) if $start == 0; | 
| 517 | 4 | 50 |  |  |  | 15 | $et->WarnOnce("$dirName:$$sizes[0]{Name} is zero", $minor) if $start == $end; | 
| 518 | 4 | 50 |  |  |  | 26 | 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 | 5 | local $_; | 
| 539 | 1 |  |  |  |  | 4 | my ($et, $mkTag) = @_; | 
| 540 |  |  |  |  |  |  |  | 
| 541 | 1 |  | 50 |  |  | 8 | my $fileType = $$et{FILE_TYPE} || ''; | 
| 542 | 1 | 50 |  |  |  | 7 | $fileType = $$et{TIFF_TYPE} if $fileType eq 'TIFF'; | 
| 543 |  |  |  |  |  |  |  | 
| 544 | 1 | 50 |  |  |  | 6 | 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 |  |  |  | 4 | if ($$et{FILENAME} ne '') { | 
| 615 | 1 | 50 |  |  |  | 14 | my $fileExt = ($$et{FILENAME} =~ /^.*\.([^.]+)$/s) ? uc($1) : ''; | 
| 616 | 1 |  |  |  |  | 6 | my $extFileType = Image::ExifTool::GetFileType($fileExt); | 
| 617 | 1 | 50 | 33 |  |  | 9 | if ($extFileType and $extFileType ne $fileType) { | 
| 618 | 1 |  |  |  |  | 4 | my $normExt = $$et{VALUE}{FileTypeExtension}; | 
| 619 | 1 | 50 | 33 |  |  | 11 | 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 |  |  |  | 4 | $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 |  |  |  |  | 5 | my (@num, $key); | 
| 633 |  |  |  |  |  |  | push @num, $$et{VALUE}{Error}   ? ($$et{DUPL_TAG}{Error}   || 0) + 1 : 0, | 
| 634 | 1 | 50 | 0 |  |  | 10 | $$et{VALUE}{Warning} ? ($$et{DUPL_TAG}{Warning} || 0) + 1 : 0, 0; | 
|  |  | 50 | 0 |  |  |  |  | 
| 635 | 1 |  |  |  |  | 5 | for ($key = 'Warning'; ; ) { | 
| 636 | 1 | 50 | 33 |  |  | 9 | ++$num[2] if $$et{VALUE}{$key} and $$et{VALUE}{$key} =~ /^\[minor\]/i; | 
| 637 | 1 | 50 |  |  |  | 8 | $key = $et->NextTagKey($key) or last; | 
| 638 |  |  |  |  |  |  | } | 
| 639 | 1 |  |  |  |  | 10 | $et->FoundTag(Validate => "@num"); | 
| 640 |  |  |  |  |  |  | } | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | 1;  # end | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | __END__ |