Coverage Report

Created: 2025-12-14 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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