/src/exiv2/src/bmffimage.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "bmffimage.hpp" |
5 | | |
6 | | #include "basicio.hpp" |
7 | | #include "config.h" |
8 | | #include "enforce.hpp" |
9 | | #include "error.hpp" |
10 | | #include "futils.hpp" |
11 | | #include "image.hpp" |
12 | | #include "image_int.hpp" |
13 | | #include "tags.hpp" |
14 | | #include "tiffcomposite_int.hpp" |
15 | | #include "tiffimage_int.hpp" |
16 | | #include "types.hpp" |
17 | | #include "utils.hpp" |
18 | | |
19 | | #ifdef EXV_HAVE_BROTLI |
20 | | #include <brotli/decode.h> // for JXL brob |
21 | | #include "safe_op.hpp" |
22 | | #endif |
23 | | |
24 | | // + standard includes |
25 | | #include <cstdio> |
26 | | #include <cstring> |
27 | | #include <iostream> |
28 | | #include <string> |
29 | | |
30 | | enum TAG { |
31 | | ftyp = 0x66747970U, //!< "ftyp" File type box */ |
32 | | avci = 0x61766369U, //!< "avci" AVC */ |
33 | | avcs = 0x61766373U, //!< "avcs" AVC */ |
34 | | avif = 0x61766966U, //!< "avif" AVIF */ |
35 | | avio = 0x6176696fU, //!< "avio" AVIF */ |
36 | | avis = 0x61766973U, //!< "avis" AVIF */ |
37 | | heic = 0x68656963U, //!< "heic" HEIC */ |
38 | | heif = 0x68656966U, //!< "heif" HEIF */ |
39 | | heim = 0x6865696dU, //!< "heim" HEIC */ |
40 | | heis = 0x68656973U, //!< "heis" HEIC */ |
41 | | heix = 0x68656978U, //!< "heix" HEIC */ |
42 | | j2is = 0x6a326973U, //!< "j2is" HEJ2K */ |
43 | | j2ki = 0x6a326b69U, //!< "j2ki" HEJ2K */ |
44 | | mif1 = 0x6d696631U, //!< "mif1" HEIF */ |
45 | | crx = 0x63727820U, //!< "crx " Canon CR3 */ |
46 | | jxl = 0x6a786c20U, //!< "jxl " JPEG XL file type */ |
47 | | moov = 0x6d6f6f76U, //!< "moov" Movie */ |
48 | | meta = 0x6d657461U, //!< "meta" Metadata */ |
49 | | mdat = 0x6d646174U, //!< "mdat" Media data */ |
50 | | uuid = 0x75756964U, //!< "uuid" UUID */ |
51 | | dinf = 0x64696e66U, //!< "dinf" Data information */ |
52 | | iprp = 0x69707270U, //!< "iprp" Item properties */ |
53 | | ipco = 0x6970636fU, //!< "ipco" Item property container */ |
54 | | iinf = 0x69696e66U, //!< "iinf" Item info */ |
55 | | iloc = 0x696c6f63U, //!< "iloc" Item location */ |
56 | | ispe = 0x69737065U, //!< "ispe" Image spatial extents */ |
57 | | infe = 0x696e6665U, //!< "infe" Item Info Extension */ |
58 | | ipma = 0x69706d61U, //!< "ipma" Item Property Association */ |
59 | | cmt1 = 0x434d5431U, //!< "CMT1" ifd0Id */ |
60 | | cmt2 = 0x434D5432U, //!< "CMD2" exifID */ |
61 | | cmt3 = 0x434D5433U, //!< "CMT3" canonID */ |
62 | | cmt4 = 0x434D5434U, //!< "CMT4" gpsID */ |
63 | | colr = 0x636f6c72U, //!< "colr" Colour information */ |
64 | | exif = 0x45786966U, //!< "Exif" Used by JXL */ |
65 | | xml = 0x786d6c20U, //!< "xml " Used by JXL */ |
66 | | brob = 0x62726f62U, //!< "brob" Used by JXL (brotli box) */ |
67 | | thmb = 0x54484d42U, //!< "THMB" Canon thumbnail */ |
68 | | prvw = 0x50525657U, //!< "PRVW" Canon preview image */ |
69 | | }; |
70 | | |
71 | | // ***************************************************************************** |
72 | | // class member definitions |
73 | | namespace Exiv2 { |
74 | 0 | bool enableBMFF(bool) { |
75 | | #ifndef EXV_ENABLE_BMFF |
76 | | return false; |
77 | | } |
78 | | #else |
79 | 0 | return true; |
80 | 0 | } |
81 | | |
82 | 0 | std::string Iloc::toString() const { |
83 | 0 | return stringFormat("ID = {} from,length = {},{}", ID_, start_, length_); |
84 | 0 | } |
85 | | |
86 | | BmffImage::BmffImage(BasicIo::UniquePtr io, bool /* create */, size_t max_box_depth) : |
87 | 2.63k | Image(ImageType::bmff, mdExif | mdIptc | mdXmp, std::move(io)), max_box_depth_(max_box_depth) { |
88 | 2.63k | } // BmffImage::BmffImage |
89 | | |
90 | 0 | std::string BmffImage::toAscii(uint32_t n) { |
91 | 0 | std::string result(sizeof(uint32_t), '\0'); |
92 | 0 | for (size_t i = 0; i < result.size(); ++i) { |
93 | 0 | auto c = static_cast<unsigned char>(n >> (8 * (3 - i))); |
94 | 0 | if (c == 0) |
95 | 0 | result[i] = '_'; |
96 | 0 | else if (c < 32 || c > 126) |
97 | 0 | result[i] = '.'; |
98 | 0 | else |
99 | 0 | result[i] = static_cast<char>(c); |
100 | 0 | } |
101 | 0 | return result; |
102 | 0 | } |
103 | | |
104 | 0 | bool BmffImage::superBox(uint32_t box) { |
105 | 0 | return box == TAG::moov || box == TAG::dinf || box == TAG::iprp || box == TAG::ipco || box == TAG::meta || |
106 | 0 | box == TAG::iinf || box == TAG::iloc; |
107 | 0 | } |
108 | | |
109 | 33.8k | bool BmffImage::fullBox(uint32_t box) { |
110 | 33.8k | return box == TAG::meta || box == TAG::iinf || box == TAG::iloc || box == TAG::thmb || box == TAG::prvw; |
111 | 33.8k | } |
112 | | |
113 | 33.9k | static bool skipBox(uint32_t box) { |
114 | | // Allows boxHandler() to optimise the reading of files by identifying |
115 | | // box types that we're not interested in. Box types listed here must |
116 | | // not appear in the cases in switch (box_type) in boxHandler(). |
117 | 33.9k | return box == 0 || box == TAG::mdat; // mdat is where the main image lives and can be huge |
118 | 33.9k | } |
119 | | |
120 | 0 | std::string BmffImage::mimeType() const { |
121 | 0 | switch (fileType_) { |
122 | 0 | case TAG::avci: |
123 | 0 | return "image/avci"; |
124 | 0 | case TAG::avcs: |
125 | 0 | return "image/avcs"; |
126 | 0 | case TAG::avif: |
127 | 0 | case TAG::avio: |
128 | 0 | case TAG::avis: |
129 | 0 | return "image/avif"; |
130 | 0 | case TAG::heic: |
131 | 0 | case TAG::heim: |
132 | 0 | case TAG::heis: |
133 | 0 | case TAG::heix: |
134 | 0 | return "image/heic"; |
135 | 0 | case TAG::heif: |
136 | 0 | case TAG::mif1: |
137 | 0 | return "image/heif"; |
138 | 0 | case TAG::j2is: |
139 | 0 | return "image/j2is"; |
140 | 0 | case TAG::j2ki: |
141 | 0 | return "image/hej2k"; |
142 | 0 | case TAG::crx: |
143 | 0 | return "image/x-canon-cr3"; |
144 | 0 | case TAG::jxl: |
145 | 0 | return "image/jxl"; // https://github.com/novomesk/qt-jpegxl-image-plugin/issues/1 |
146 | 0 | default: |
147 | 0 | return "image/generic"; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | 45 | uint32_t BmffImage::pixelWidth() const { |
152 | 45 | auto imageWidth = exifData_.findKey(Exiv2::ExifKey("Exif.Photo.PixelXDimension")); |
153 | 45 | if (imageWidth == exifData_.end() || imageWidth->count() == 0) |
154 | 45 | return pixelWidth_; |
155 | 0 | return imageWidth->toUint32(); |
156 | 45 | } |
157 | | |
158 | 45 | uint32_t BmffImage::pixelHeight() const { |
159 | 45 | auto imageHeight = exifData_.findKey(Exiv2::ExifKey("Exif.Photo.PixelYDimension")); |
160 | 45 | if (imageHeight == exifData_.end() || imageHeight->count() == 0) |
161 | 45 | return pixelHeight_; |
162 | 0 | return imageHeight->toUint32(); |
163 | 45 | } |
164 | | |
165 | 354 | std::string BmffImage::uuidName(const Exiv2::DataBuf& uuid) { |
166 | 354 | const char* uuidCano = "\x85\xC0\xB6\x87\x82\xF\x11\xE0\x81\x11\xF4\xCE\x46\x2B\x6A\x48"; |
167 | 354 | const char* uuidXmp = "\xBE\x7A\xCF\xCB\x97\xA9\x42\xE8\x9C\x71\x99\x94\x91\xE3\xAF\xAC"; |
168 | 354 | const char* uuidCanp = "\xEA\xF4\x2B\x5E\x1C\x98\x4B\x88\xB9\xFB\xB7\xDC\x40\x6E\x4D\x16"; |
169 | 354 | if (uuid.cmpBytes(0, uuidCano, 16) == 0) |
170 | 4 | return "cano"; |
171 | 350 | if (uuid.cmpBytes(0, uuidXmp, 16) == 0) |
172 | 72 | return "xmp"; |
173 | 278 | if (uuid.cmpBytes(0, uuidCanp, 16) == 0) |
174 | 222 | return "canp"; |
175 | 56 | return ""; |
176 | 278 | } |
177 | | |
178 | | #ifdef EXV_HAVE_BROTLI |
179 | | |
180 | | // Wrapper class for BrotliDecoderState that automatically calls |
181 | | // BrotliDecoderDestroyInstance in its destructor. |
182 | | using BrotliDecoder = std::unique_ptr<BrotliDecoderState, decltype(&BrotliDecoderDestroyInstance)>; |
183 | | |
184 | 137 | void BmffImage::brotliUncompress(const byte* compressedBuf, size_t compressedBufSize, DataBuf& arr) { |
185 | 137 | auto decoder = BrotliDecoder(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr), BrotliDecoderDestroyInstance); |
186 | 137 | size_t uncompressedLen = compressedBufSize * 2; // just a starting point |
187 | 137 | BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; |
188 | 137 | int dos = 0; |
189 | 137 | size_t available_in = compressedBufSize; |
190 | 137 | const byte* next_in = compressedBuf; |
191 | 137 | size_t available_out; |
192 | 137 | byte* next_out; |
193 | 137 | size_t total_out = 0; |
194 | | |
195 | 505 | while (result != BROTLI_DECODER_RESULT_SUCCESS) { |
196 | 440 | arr.alloc(uncompressedLen); |
197 | 440 | available_out = uncompressedLen - total_out; |
198 | 440 | next_out = arr.data() + total_out; |
199 | 440 | result = |
200 | 440 | BrotliDecoderDecompressStream(decoder.get(), &available_in, &next_in, &available_out, &next_out, &total_out); |
201 | 440 | if (result == BROTLI_DECODER_RESULT_SUCCESS) { |
202 | 65 | arr.resize(total_out); |
203 | 375 | } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { |
204 | 334 | uncompressedLen *= 2; |
205 | | // DoS protection - can't be bigger than 128k |
206 | 334 | if (uncompressedLen > 131072) { |
207 | 74 | if (++dos > 1 || total_out > 131072) |
208 | 31 | break; |
209 | 43 | uncompressedLen = 131072; |
210 | 43 | } |
211 | 334 | } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { |
212 | | // compressed input buffer in incomplete |
213 | 10 | throw Error(ErrorCode::kerFailedToReadImageData); |
214 | 31 | } else { |
215 | | // something bad happened |
216 | 31 | throw Error(ErrorCode::kerErrorMessage, BrotliDecoderErrorString(BrotliDecoderGetErrorCode(decoder.get()))); |
217 | 31 | } |
218 | 440 | } |
219 | | |
220 | 96 | if (result != BROTLI_DECODER_RESULT_SUCCESS) { |
221 | 31 | throw Error(ErrorCode::kerFailedToReadImageData); |
222 | 31 | } |
223 | 96 | } |
224 | | #endif |
225 | | |
226 | | uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintStructureOption option /* = kpsNone */, |
227 | 34.7k | uint64_t pbox_end, size_t depth) { |
228 | 34.7k | const size_t address = io_->tell(); |
229 | | // never visit a box twice! |
230 | 34.7k | if (depth == 0) |
231 | 10.8k | visits_.clear(); |
232 | 34.7k | if (visits_.contains(address) || visits_.size() > visits_max_ || depth >= max_box_depth_) { |
233 | 46 | throw Error(ErrorCode::kerCorruptedMetadata); |
234 | 46 | } |
235 | 34.7k | visits_.insert(address); |
236 | | |
237 | | #ifdef EXIV2_DEBUG_MESSAGES |
238 | | bool bTrace = true; |
239 | | #else |
240 | 34.7k | bool bTrace = option == kpsBasic || option == kpsRecursive; |
241 | 34.7k | #endif |
242 | | |
243 | | // 8-byte buffer for parsing the box length and type. |
244 | 34.7k | byte hdrbuf[2 * sizeof(uint32_t)]; |
245 | | |
246 | 34.7k | size_t hdrsize = sizeof(hdrbuf); |
247 | 34.7k | Internal::enforce(hdrsize <= static_cast<size_t>(pbox_end - address), Exiv2::ErrorCode::kerCorruptedMetadata); |
248 | 34.7k | if (io_->read(hdrbuf, sizeof(hdrbuf)) != sizeof(hdrbuf)) |
249 | 0 | return pbox_end; |
250 | | |
251 | | // The box length is encoded as a uint32_t by default, but the special value 1 means |
252 | | // that it's a uint64_t. |
253 | 34.7k | uint64_t box_length = getULong(&hdrbuf[0], endian_); |
254 | 34.7k | uint32_t box_type = getULong(&hdrbuf[sizeof(uint32_t)], endian_); |
255 | 34.7k | bool bLF = true; |
256 | | |
257 | 34.7k | if (bTrace) { |
258 | 0 | bLF = true; |
259 | 0 | out << Internal::indent(depth) << "Exiv2::BmffImage::boxHandler: " << toAscii(box_type) |
260 | 0 | << stringFormat(" {:8}->{} ", address, box_length); |
261 | 0 | } |
262 | | |
263 | 34.7k | if (box_length == 1) { |
264 | | // The box size is encoded as a uint64_t, so we need to read another 8 bytes. |
265 | 209 | hdrsize += 8; |
266 | 209 | Internal::enforce(hdrsize <= static_cast<size_t>(pbox_end - address), Exiv2::ErrorCode::kerCorruptedMetadata); |
267 | 209 | DataBuf data(8); |
268 | 209 | io_->read(data.data(), data.size()); |
269 | 209 | box_length = data.read_uint64(0, endian_); |
270 | 209 | } |
271 | | |
272 | 34.7k | if (box_length == 0) { |
273 | | // Zero length is also valid and indicates box extends to the end of file. |
274 | 21.3k | box_length = pbox_end - address; |
275 | 21.3k | } |
276 | | |
277 | | // read data in box and restore file position |
278 | 34.7k | const size_t restore = io_->tell(); |
279 | 34.7k | Internal::enforce(box_length >= hdrsize, Exiv2::ErrorCode::kerCorruptedMetadata); |
280 | 34.7k | Internal::enforce(box_length - hdrsize <= pbox_end - restore, Exiv2::ErrorCode::kerCorruptedMetadata); |
281 | | |
282 | 34.7k | const auto buffer_size = box_length - hdrsize; |
283 | 34.7k | if (skipBox(box_type)) { |
284 | 75 | if (bTrace) { |
285 | 0 | out << '\n'; |
286 | 0 | } |
287 | | // The enforce() above checks that restore + buffer_size won't |
288 | | // exceed pbox_end, and by implication, won't exceed LONG_MAX |
289 | 75 | return restore + buffer_size; |
290 | 75 | } |
291 | | |
292 | 34.6k | DataBuf data(static_cast<size_t>(buffer_size)); |
293 | 34.6k | const size_t box_end = restore + data.size(); |
294 | 34.6k | io_->read(data.data(), data.size()); |
295 | 34.6k | io_->seek(restore, BasicIo::beg); |
296 | | |
297 | 34.6k | size_t skip = 0; // read position in data.pData_ |
298 | 34.6k | uint8_t version = 0; |
299 | 34.6k | uint32_t flags = 0; |
300 | | |
301 | 34.6k | if (fullBox(box_type)) { |
302 | 3.82k | Internal::enforce(data.size() - skip >= 4, Exiv2::ErrorCode::kerCorruptedMetadata); |
303 | 3.82k | flags = data.read_uint32(skip, endian_); // version/flags |
304 | 3.82k | version = static_cast<uint8_t>(flags >> 24); |
305 | 3.82k | flags &= 0x00ffffff; |
306 | 3.82k | skip += 4; |
307 | 3.82k | } |
308 | | |
309 | 34.6k | switch (box_type) { |
310 | | // See notes in skipBox() |
311 | 4.67k | case TAG::ftyp: { |
312 | 4.67k | Internal::enforce(data.size() >= 4, Exiv2::ErrorCode::kerCorruptedMetadata); |
313 | 4.67k | fileType_ = data.read_uint32(0, endian_); |
314 | 4.67k | if (bTrace) { |
315 | 0 | out << "brand: " << toAscii(fileType_); |
316 | 0 | } |
317 | 4.67k | } break; |
318 | | |
319 | | // 8.11.6.1 |
320 | 316 | case TAG::iinf: { |
321 | 316 | if (bTrace) { |
322 | 0 | out << '\n'; |
323 | 0 | bLF = false; |
324 | 0 | } |
325 | | |
326 | 316 | Internal::enforce(data.size() - skip >= 2, Exiv2::ErrorCode::kerCorruptedMetadata); |
327 | 316 | uint16_t n = data.read_uint16(skip, endian_); |
328 | 316 | skip += 2; |
329 | | |
330 | 316 | io_->seek(skip, BasicIo::cur); |
331 | 945 | while (n-- > 0) { |
332 | 629 | io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg); |
333 | 629 | } |
334 | 316 | } break; |
335 | | |
336 | | // 8.11.6.2 |
337 | 924 | case TAG::infe: { // .__._.__hvc1_ 2 0 0 1 0 1 0 0 104 118 99 49 0 |
338 | 924 | Internal::enforce(data.size() - skip >= 8, Exiv2::ErrorCode::kerCorruptedMetadata); |
339 | 924 | /* getULong (data.pData_+skip,endian_) ; */ skip += 4; |
340 | 924 | uint16_t ID = data.read_uint16(skip, endian_); |
341 | 924 | skip += 2; |
342 | 924 | /* getShort(data.pData_+skip,endian_) ; */ skip += 2; // protection |
343 | 924 | std::string id; |
344 | | // Check that the string has a '\0' terminator. |
345 | 924 | const char* str = data.c_str(skip); |
346 | 924 | const size_t maxlen = data.size() - skip; |
347 | 924 | Internal::enforce(maxlen > 0 && strnlen(str, maxlen) < maxlen, Exiv2::ErrorCode::kerCorruptedMetadata); |
348 | 924 | std::string name(str); |
349 | 924 | if (Internal::contains(name, "Exif")) { // "Exif" or "ExifExif" |
350 | 349 | exifID_ = ID; |
351 | 349 | id = " *** Exif ***"; |
352 | 575 | } else if (Internal::contains(name, "mime\0xmp") || Internal::contains(name, "mime\0application/rdf+xml")) { |
353 | 272 | xmpID_ = ID; |
354 | 272 | id = " *** XMP ***"; |
355 | 272 | } |
356 | 924 | if (bTrace) { |
357 | 0 | out << stringFormat("ID = {:3} {} {}", ID, name, id); |
358 | 0 | } |
359 | 924 | } break; |
360 | | |
361 | 18 | case TAG::moov: |
362 | 36 | case TAG::iprp: |
363 | 17.9k | case TAG::ipco: |
364 | 20.0k | case TAG::meta: { |
365 | 20.0k | if (bTrace) { |
366 | 0 | out << '\n'; |
367 | 0 | bLF = false; |
368 | 0 | } |
369 | 20.0k | io_->seek(skip, BasicIo::cur); |
370 | 43.0k | while (io_->tell() < box_end) { |
371 | 23.0k | io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg); |
372 | 23.0k | } |
373 | | // post-process meta box to recover Exif and XMP |
374 | 20.0k | if (box_type == TAG::meta) { |
375 | 1.46k | auto ilo = ilocs_.find(exifID_); |
376 | 1.46k | if (ilo != ilocs_.end()) { |
377 | 41 | const Iloc& iloc = ilo->second; |
378 | 41 | if (bTrace) { |
379 | 0 | out << Internal::indent(depth) << "Exiv2::BMFF Exif: " << iloc.toString() << '\n'; |
380 | 0 | } |
381 | 41 | parseTiff(Internal::Tag::root, iloc.length_, iloc.start_); |
382 | 41 | } |
383 | 1.46k | ilo = ilocs_.find(xmpID_); |
384 | 1.46k | if (ilo != ilocs_.end()) { |
385 | 107 | const Iloc& iloc = ilo->second; |
386 | 107 | if (bTrace) { |
387 | 0 | out << Internal::indent(depth) << "Exiv2::BMFF XMP: " << iloc.toString() << '\n'; |
388 | 0 | } |
389 | 107 | parseXmp(iloc.length_, iloc.start_); |
390 | 107 | } |
391 | 1.46k | ilocs_.clear(); |
392 | 1.46k | } |
393 | 20.0k | } break; |
394 | | |
395 | | // 8.11.3.1 |
396 | 1.09k | case TAG::iloc: { |
397 | 1.09k | Internal::enforce(data.size() - skip >= 2, Exiv2::ErrorCode::kerCorruptedMetadata); |
398 | 1.09k | uint8_t u = data.read_uint8(skip++); |
399 | 1.09k | uint16_t offsetSize = u >> 4; |
400 | 1.09k | uint16_t lengthSize = u & 0xF; |
401 | | #if 0 |
402 | | uint16_t indexSize = 0 ; |
403 | | u = data.read_uint8(skip++); |
404 | | if ( version == 1 || version == 2 ) { |
405 | | indexSize = u & 0xF ; |
406 | | } |
407 | | #else |
408 | 1.09k | skip++; |
409 | 1.09k | #endif |
410 | 1.09k | Internal::enforce(data.size() - skip >= (version < 2u ? 2u : 4u), Exiv2::ErrorCode::kerCorruptedMetadata); |
411 | 1.09k | uint32_t itemCount = version < 2 ? data.read_uint16(skip, endian_) : data.read_uint32(skip, endian_); |
412 | 1.09k | skip += version < 2 ? 2 : 4; |
413 | 1.09k | if (itemCount && itemCount < box_length / 14 && offsetSize == 4 && lengthSize == 4 && |
414 | 776 | ((box_length - 16) % itemCount) == 0) { |
415 | 766 | if (bTrace) { |
416 | 0 | out << '\n'; |
417 | 0 | bLF = false; |
418 | 0 | } |
419 | 766 | auto step = (static_cast<size_t>(box_length) - 16) / itemCount; // length of data per item. |
420 | 766 | size_t base = skip; |
421 | 8.04k | for (uint32_t i = 0; i < itemCount; i++) { |
422 | 7.27k | skip = base + (i * step); // move in 14, 16 or 18 byte steps |
423 | 7.27k | Internal::enforce(data.size() - skip >= (version > 2u ? 4u : 2u), Exiv2::ErrorCode::kerCorruptedMetadata); |
424 | 7.27k | Internal::enforce(data.size() - skip >= step, Exiv2::ErrorCode::kerCorruptedMetadata); |
425 | 7.27k | uint32_t ID = version > 2 ? data.read_uint32(skip, endian_) : data.read_uint16(skip, endian_); |
426 | 7.27k | auto offset = [&data, skip, step] { |
427 | 7.24k | if (step == 14 || step == 16) |
428 | 5.94k | return data.read_uint32(skip + step - 8, endian_); |
429 | 1.29k | if (step == 18) |
430 | 786 | return data.read_uint32(skip + 4, endian_); |
431 | 513 | return 0u; |
432 | 1.29k | }(); |
433 | | |
434 | 7.27k | uint32_t ldata = data.read_uint32(skip + step - 4, endian_); |
435 | 7.27k | if (bTrace) { |
436 | 0 | out << Internal::indent(depth) |
437 | 0 | << stringFormat("{:8} | {:8} | ID | {:4} | {:6},{:6}\n", address + skip, step, ID, offset, ldata); |
438 | 0 | } |
439 | | // save data for post-processing in meta box |
440 | 7.27k | if (offset && ldata && ID != unknownID_) { |
441 | 5.22k | ilocs_[ID] = Iloc{ID, offset, ldata}; |
442 | 5.22k | } |
443 | 7.27k | } |
444 | 766 | } |
445 | 1.09k | } break; |
446 | | |
447 | 144 | case TAG::ispe: { |
448 | 144 | Internal::enforce(data.size() - skip >= 12, Exiv2::ErrorCode::kerCorruptedMetadata); |
449 | 144 | skip += 4; |
450 | 144 | uint32_t width = data.read_uint32(skip, endian_); |
451 | 144 | skip += 4; |
452 | 144 | uint32_t height = data.read_uint32(skip, endian_); |
453 | 144 | skip += 4; |
454 | 144 | if (bTrace) { |
455 | 0 | out << stringFormat("pixelWidth_, pixelHeight_ = {}, {}", width, height); |
456 | 0 | } |
457 | | // HEIC files can have multiple ispe records |
458 | | // Store largest width/height |
459 | 144 | if (width > pixelWidth_ && height > pixelHeight_) { |
460 | 34 | pixelWidth_ = width; |
461 | 34 | pixelHeight_ = height; |
462 | 34 | } |
463 | 144 | } break; |
464 | | |
465 | | // 12.1.5.2 |
466 | 194 | case TAG::colr: { |
467 | 194 | if (data.size() >= (skip + 4 + 8)) { // .____.HLino..__mntrR 2 0 0 0 0 12 72 76 105 110 111 2 16 ... |
468 | | // https://www.ics.uci.edu/~dan/class/267/papers/jpeg2000.pdf |
469 | 147 | uint8_t meth = data.read_uint8(skip + 0); |
470 | 147 | uint8_t prec = data.read_uint8(skip + 1); |
471 | 147 | uint8_t approx = data.read_uint8(skip + 2); |
472 | 147 | auto colour_type = std::string(data.c_str(), 4); |
473 | 147 | skip += 4; |
474 | 147 | if (colour_type == "rICC" || colour_type == "prof") { |
475 | 37 | DataBuf profile(data.c_data(skip), data.size() - skip); |
476 | 37 | setIccProfile(std::move(profile)); |
477 | 110 | } else if (meth == 2 && prec == 0 && approx == 0) { |
478 | | // JP2000 files have a 3 byte head // 2 0 0 icc...... |
479 | 16 | skip -= 1; |
480 | 16 | DataBuf profile(data.c_data(skip), data.size() - skip); |
481 | 16 | setIccProfile(std::move(profile)); |
482 | 16 | } |
483 | 147 | } |
484 | 194 | } break; |
485 | | |
486 | 354 | case TAG::uuid: { |
487 | 354 | DataBuf uuid(16); |
488 | 354 | io_->read(uuid.data(), uuid.size()); |
489 | 354 | std::string name = uuidName(uuid); |
490 | 354 | if (bTrace) { |
491 | 0 | out << " uuidName " << name << '\n'; |
492 | 0 | bLF = false; |
493 | 0 | } |
494 | 354 | if (name == "cano" || name == "canp") { |
495 | 226 | if (name == "canp") { |
496 | | // based on |
497 | | // https://github.com/lclevy/canon_cr3/blob/7be75d6/parse_cr3.py#L271 |
498 | 222 | io_->seek(8, BasicIo::cur); |
499 | 222 | } |
500 | 494 | while (io_->tell() < box_end) { |
501 | 268 | io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg); |
502 | 268 | } |
503 | 226 | } else if (name == "xmp") { |
504 | 72 | parseXmp(box_length, io_->tell()); |
505 | 72 | } |
506 | 354 | } break; |
507 | | |
508 | 273 | case TAG::cmt1: |
509 | 273 | parseTiff(Internal::Tag::root, box_length); |
510 | 273 | break; |
511 | 592 | case TAG::cmt2: |
512 | 592 | parseTiff(Internal::Tag::cmt2, box_length); |
513 | 592 | break; |
514 | 687 | case TAG::cmt3: |
515 | 687 | parseTiff(Internal::Tag::cmt3, box_length); |
516 | 687 | break; |
517 | 275 | case TAG::cmt4: |
518 | 275 | parseTiff(Internal::Tag::cmt4, box_length); |
519 | 275 | break; |
520 | 1.10k | case TAG::exif: |
521 | 1.10k | parseTiff(Internal::Tag::root, buffer_size, io_->tell()); |
522 | 1.10k | break; |
523 | 236 | case TAG::xml: |
524 | 236 | parseXmp(buffer_size, io_->tell()); |
525 | 236 | break; |
526 | 137 | case TAG::brob: { |
527 | 137 | Internal::enforce(data.size() >= 4, Exiv2::ErrorCode::kerCorruptedMetadata); |
528 | 137 | uint32_t realType = data.read_uint32(0, endian_); |
529 | 137 | if (bTrace) { |
530 | 0 | out << "type: " << toAscii(realType); |
531 | 0 | } |
532 | 137 | #ifdef EXV_HAVE_BROTLI |
533 | 137 | DataBuf arr; |
534 | 137 | brotliUncompress(data.c_data(4), data.size() - 4, arr); |
535 | 137 | if (realType == TAG::exif) { |
536 | 1 | uint32_t offset = Safe::add(arr.read_uint32(0, endian_), 4u); |
537 | 1 | Internal::enforce(Safe::add(offset, 4u) < arr.size(), Exiv2::ErrorCode::kerCorruptedMetadata); |
538 | 1 | Internal::TiffParserWorker::decode(exifData(), iptcData(), xmpData(), arr.c_data(offset), arr.size() - offset, |
539 | 1 | Internal::Tag::root, Internal::TiffMapping::findDecoder); |
540 | 136 | } else if (realType == TAG::xml) { |
541 | 12 | try { |
542 | 12 | Exiv2::XmpParser::decode(xmpData(), std::string(arr.c_str(), arr.size())); |
543 | 12 | } catch (...) { |
544 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
545 | 0 | } |
546 | 12 | } |
547 | 137 | #endif |
548 | 137 | } break; |
549 | 219 | case TAG::thmb: |
550 | 219 | switch (version) { |
551 | 137 | case 0: // JPEG |
552 | 137 | parseCr3Preview(data, out, bTrace, version, skip, skip + 2, skip + 4, skip + 12); |
553 | 137 | break; |
554 | 27 | case 1: // HDR |
555 | 27 | parseCr3Preview(data, out, bTrace, version, skip + 2, skip + 4, skip + 8, skip + 12); |
556 | 27 | break; |
557 | 55 | default: |
558 | 55 | break; |
559 | 219 | } |
560 | 219 | break; |
561 | 219 | case TAG::prvw: |
562 | 76 | switch (version) { |
563 | 64 | case 0: // JPEG |
564 | 64 | case 1: // HDR |
565 | 64 | parseCr3Preview(data, out, bTrace, version, skip + 2, skip + 4, skip + 8, skip + 12); |
566 | 64 | break; |
567 | 12 | default: |
568 | 12 | break; |
569 | 76 | } |
570 | 73 | break; |
571 | | |
572 | 2.50k | default: |
573 | 2.50k | break; /* do nothing */ |
574 | 34.6k | } |
575 | 17.1k | if (bLF && bTrace) |
576 | 0 | out << '\n'; |
577 | | |
578 | | // return address of next box |
579 | 17.1k | return box_end; |
580 | 34.6k | } |
581 | | |
582 | 1.14k | void BmffImage::parseTiff(uint32_t root_tag, uint64_t length, uint64_t start) { |
583 | 1.14k | Internal::enforce(start <= io_->size(), ErrorCode::kerCorruptedMetadata); |
584 | 1.14k | Internal::enforce(length <= io_->size() - start, ErrorCode::kerCorruptedMetadata); |
585 | 1.14k | Internal::enforce(start <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max()), |
586 | 1.14k | ErrorCode::kerCorruptedMetadata); |
587 | 1.14k | Internal::enforce(length <= std::numeric_limits<size_t>::max(), ErrorCode::kerCorruptedMetadata); |
588 | | |
589 | | // read and parse exif data |
590 | 1.14k | const size_t restore = io_->tell(); |
591 | 1.14k | DataBuf exif(static_cast<size_t>(length)); |
592 | 1.14k | io_->seek(static_cast<int64_t>(start), BasicIo::beg); |
593 | 1.14k | if (exif.size() > 8 && io_->read(exif.data(), exif.size()) == exif.size()) { |
594 | | // hunt for "II" or "MM" |
595 | 1.06k | const size_t eof = std::numeric_limits<size_t>::max(); // impossible value for punt |
596 | 1.06k | size_t punt = eof; |
597 | 2.50M | for (size_t i = 0; i < exif.size() - 9 && punt == eof; ++i) { |
598 | 2.50M | auto charCurrent = exif.read_uint8(i); |
599 | 2.50M | auto charNext = exif.read_uint8(i + 1); |
600 | 2.50M | if (charCurrent == charNext && (charCurrent == 'I' || charCurrent == 'M')) |
601 | 876 | punt = i; |
602 | 2.50M | } |
603 | 1.06k | if (punt != eof) { |
604 | 876 | Internal::TiffParserWorker::decode(exifData(), iptcData(), xmpData(), exif.c_data(punt), exif.size() - punt, |
605 | 876 | root_tag, Internal::TiffMapping::findDecoder); |
606 | 876 | } |
607 | 1.06k | } |
608 | 1.14k | io_->seek(restore, BasicIo::beg); |
609 | 1.14k | } |
610 | | |
611 | 1.82k | void BmffImage::parseTiff(uint32_t root_tag, uint64_t length) { |
612 | 1.82k | if (length > 8) { |
613 | 713 | Internal::enforce(length - 8 <= io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); |
614 | 713 | Internal::enforce(length - 8 <= std::numeric_limits<size_t>::max(), ErrorCode::kerCorruptedMetadata); |
615 | 713 | DataBuf data(static_cast<size_t>(length - 8u)); |
616 | 713 | const size_t bufRead = io_->read(data.data(), data.size()); |
617 | | |
618 | 713 | if (io_->error()) |
619 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
620 | 713 | if (bufRead != data.size()) |
621 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
622 | | |
623 | 713 | Internal::TiffParserWorker::decode(exifData(), iptcData(), xmpData(), data.c_data(), data.size(), root_tag, |
624 | 713 | Internal::TiffMapping::findDecoder); |
625 | 713 | } |
626 | 1.82k | } |
627 | | |
628 | 415 | void BmffImage::parseXmp(uint64_t length, uint64_t start) { |
629 | 415 | Internal::enforce(start <= io_->size(), ErrorCode::kerCorruptedMetadata); |
630 | 415 | Internal::enforce(length <= io_->size() - start, ErrorCode::kerCorruptedMetadata); |
631 | | |
632 | 415 | const size_t restore = io_->tell(); |
633 | 415 | io_->seek(static_cast<int64_t>(start), BasicIo::beg); |
634 | | |
635 | 415 | auto lengthSizeT = static_cast<size_t>(length); |
636 | 415 | DataBuf xmp(lengthSizeT + 1); |
637 | 415 | xmp.write_uint8(lengthSizeT, 0); // ensure xmp is null terminated! |
638 | 415 | if (io_->read(xmp.data(), lengthSizeT) != lengthSizeT) |
639 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
640 | 415 | if (io_->error()) |
641 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
642 | 415 | try { |
643 | 415 | Exiv2::XmpParser::decode(xmpData(), std::string(xmp.c_str())); |
644 | 415 | } catch (...) { |
645 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
646 | 0 | } |
647 | | |
648 | 369 | io_->seek(restore, BasicIo::beg); |
649 | 369 | } |
650 | | |
651 | | /// \todo instead of passing the last 4 parameters, pass just one and build the different offsets inside |
652 | | void BmffImage::parseCr3Preview(const DataBuf& data, std::ostream& out, bool bTrace, uint8_t version, |
653 | | size_t width_offset, size_t height_offset, size_t size_offset, |
654 | 228 | size_t relative_position) { |
655 | | // Derived from https://github.com/lclevy/canon_cr3 |
656 | 228 | const size_t here = io_->tell(); |
657 | 228 | Internal::enforce(here <= std::numeric_limits<size_t>::max() - relative_position, ErrorCode::kerCorruptedMetadata); |
658 | 228 | NativePreview nativePreview; |
659 | 228 | nativePreview.position_ = here + relative_position; |
660 | 228 | nativePreview.width_ = data.read_uint16(width_offset, endian_); |
661 | 228 | nativePreview.height_ = data.read_uint16(height_offset, endian_); |
662 | 228 | nativePreview.size_ = data.read_uint32(size_offset, endian_); |
663 | 228 | nativePreview.filter_ = ""; |
664 | 228 | nativePreview.mimeType_ = [version] { |
665 | 225 | if (version == 0) |
666 | 198 | return "image/jpeg"; |
667 | 27 | return "application/octet-stream"; |
668 | 225 | }(); |
669 | 228 | if (bTrace) { |
670 | 0 | out << stringFormat("width,height,size = {},{},{}", nativePreview.width_, nativePreview.height_, |
671 | 0 | nativePreview.size_); |
672 | 0 | } |
673 | 228 | nativePreviews_.push_back(std::move(nativePreview)); |
674 | 228 | } |
675 | | |
676 | 0 | void BmffImage::setExifData(const ExifData& /*exifData*/) { |
677 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "Exif metadata", "BMFF")); |
678 | 0 | } |
679 | | |
680 | 0 | void BmffImage::setIptcData(const IptcData& /*iptcData*/) { |
681 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "IPTC metadata", "BMFF")); |
682 | 0 | } |
683 | | |
684 | 0 | void BmffImage::setXmpData(const XmpData& /*xmpData*/) { |
685 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "XMP metadata", "BMFF")); |
686 | 0 | } |
687 | | |
688 | 0 | void BmffImage::setComment(const std::string&) { |
689 | | // bmff files are read-only |
690 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "BMFF")); |
691 | 0 | } |
692 | | |
693 | 2.63k | void BmffImage::openOrThrow() const { |
694 | 2.63k | if (io_->open() != 0) { |
695 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
696 | 0 | } |
697 | | // Ensure that this is the correct image type |
698 | 2.63k | if (!isBmffType(*io_, false)) { |
699 | 0 | if (io_->error() || io_->eof()) |
700 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
701 | 0 | throw Error(ErrorCode::kerNotAnImage, "BMFF"); |
702 | 0 | } |
703 | 2.63k | } |
704 | | |
705 | 2.63k | void BmffImage::readMetadata() { |
706 | 2.63k | openOrThrow(); |
707 | 2.63k | IoCloser closer(*io_); |
708 | | |
709 | 2.63k | clearMetadata(); |
710 | 2.63k | ilocs_.clear(); |
711 | 2.63k | visits_max_ = io_->size() / 16; |
712 | 2.63k | unknownID_ = 0xffff; |
713 | 2.63k | exifID_ = unknownID_; |
714 | 2.63k | xmpID_ = unknownID_; |
715 | | |
716 | 2.63k | uint64_t address = 0; |
717 | 2.63k | const auto file_end = io_->size(); |
718 | 13.5k | while (address < file_end) { |
719 | 10.8k | io_->seek(address, BasicIo::beg); |
720 | 10.8k | address = boxHandler(std::cout, kpsNone, file_end, 0); |
721 | 10.8k | } |
722 | 2.63k | bReadMetadata_ = true; |
723 | 2.63k | } // BmffImage::readMetadata |
724 | | |
725 | 0 | void BmffImage::printStructure(std::ostream& out, Exiv2::PrintStructureOption option, size_t depth) { |
726 | 0 | if (!bReadMetadata_) |
727 | 0 | readMetadata(); |
728 | |
|
729 | 0 | switch (option) { |
730 | 0 | default: |
731 | 0 | break; // do nothing |
732 | | |
733 | 0 | case kpsIccProfile: { |
734 | 0 | out.write(iccProfile_.c_str(), iccProfile_.size()); |
735 | 0 | } break; |
736 | | |
737 | 0 | #ifdef EXV_HAVE_XMP_TOOLKIT |
738 | 0 | case kpsXMP: { |
739 | 0 | std::string xmp; |
740 | 0 | if (Exiv2::XmpParser::encode(xmp, xmpData())) { |
741 | 0 | throw Exiv2::Error(Exiv2::ErrorCode::kerErrorMessage, "Failed to serialize XMP data"); |
742 | 0 | } |
743 | 0 | out << xmp; |
744 | 0 | } break; |
745 | 0 | #endif |
746 | 0 | case kpsBasic: // drop |
747 | 0 | case kpsRecursive: { |
748 | 0 | openOrThrow(); |
749 | 0 | IoCloser closer(*io_); |
750 | |
|
751 | 0 | uint64_t address = 0; |
752 | 0 | const auto file_end = io_->size(); |
753 | 0 | while (address < file_end) { |
754 | 0 | io_->seek(address, BasicIo::beg); |
755 | 0 | address = boxHandler(out, option, file_end, depth); |
756 | 0 | } |
757 | 0 | } break; |
758 | 0 | } |
759 | 0 | } |
760 | | |
761 | 1.22k | void BmffImage::writeMetadata() { |
762 | | // bmff files are read-only |
763 | 1.22k | throw(Error(ErrorCode::kerWritingImageFormatUnsupported, "BMFF")); |
764 | 1.22k | } // BmffImage::writeMetadata |
765 | | |
766 | | // ************************************************************************* |
767 | | // free functions |
768 | 2.63k | Image::UniquePtr newBmffInstance(BasicIo::UniquePtr io, bool create) { |
769 | 2.63k | auto image = std::make_unique<BmffImage>(std::move(io), create); |
770 | 2.63k | if (!image->good()) { |
771 | 0 | return nullptr; |
772 | 0 | } |
773 | 2.63k | return image; |
774 | 2.63k | } |
775 | | |
776 | 8.74k | bool isBmffType(BasicIo& iIo, bool advance) { |
777 | 8.74k | const int32_t len = 12; |
778 | 8.74k | byte buf[len]; |
779 | 8.74k | iIo.read(buf, len); |
780 | 8.74k | if (iIo.error() || iIo.eof()) { |
781 | 367 | return false; |
782 | 367 | } |
783 | | |
784 | | // bmff should start with "ftyp" |
785 | 8.38k | bool const is_ftyp = (buf[4] == 'f' && buf[5] == 't' && buf[6] == 'y' && buf[7] == 'p'); |
786 | | // jxl files have a special start indicator of "JXL " |
787 | 8.38k | bool const is_jxl = (buf[4] == 'J' && buf[5] == 'X' && buf[6] == 'L' && buf[7] == ' '); |
788 | | |
789 | 8.38k | bool matched = is_jxl || is_ftyp; |
790 | 8.38k | if (!advance || !matched) { |
791 | 8.38k | iIo.seek(0, BasicIo::beg); |
792 | 8.38k | } |
793 | 8.38k | return matched; |
794 | 8.74k | } |
795 | | #endif // EXV_ENABLE_BMFF |
796 | | } // namespace Exiv2 |