Coverage Report

Created: 2026-01-16 06:09

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
45
std::string Iloc::toString() const {
83
45
  return stringFormat("ID = {} from,length = {},{}", ID_, start_, length_);
84
45
}
85
86
BmffImage::BmffImage(BasicIo::UniquePtr io, bool /* create */, size_t max_box_depth) :
87
10.2k
    Image(ImageType::bmff, mdExif | mdIptc | mdXmp, std::move(io)), max_box_depth_(max_box_depth) {
88
10.2k
}  // BmffImage::BmffImage
89
90
79.2k
std::string BmffImage::toAscii(uint32_t n) {
91
79.2k
  std::string result(sizeof(uint32_t), '\0');
92
396k
  for (size_t i = 0; i < result.size(); ++i) {
93
316k
    auto c = static_cast<unsigned char>(n >> (8 * (3 - i)));
94
316k
    if (c == 0)
95
25.4k
      result[i] = '_';
96
291k
    else if (c < 32 || c > 126)
97
41.0k
      result[i] = '.';
98
250k
    else
99
250k
      result[i] = static_cast<char>(c);
100
316k
  }
101
79.2k
  return result;
102
79.2k
}
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
127k
bool BmffImage::fullBox(uint32_t box) {
110
127k
  return box == TAG::meta || box == TAG::iinf || box == TAG::iloc || box == TAG::thmb || box == TAG::prvw;
111
127k
}
112
113
127k
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
127k
  return box == 0 || box == TAG::mdat;  // mdat is where the main image lives and can be huge
118
127k
}
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
62
uint32_t BmffImage::pixelWidth() const {
152
62
  auto imageWidth = exifData_.findKey(Exiv2::ExifKey("Exif.Photo.PixelXDimension"));
153
62
  if (imageWidth == exifData_.end() || imageWidth->count() == 0)
154
62
    return pixelWidth_;
155
0
  return imageWidth->toUint32();
156
62
}
157
158
62
uint32_t BmffImage::pixelHeight() const {
159
62
  auto imageHeight = exifData_.findKey(Exiv2::ExifKey("Exif.Photo.PixelYDimension"));
160
62
  if (imageHeight == exifData_.end() || imageHeight->count() == 0)
161
62
    return pixelHeight_;
162
0
  return imageHeight->toUint32();
163
62
}
164
165
1.13k
std::string BmffImage::uuidName(const Exiv2::DataBuf& uuid) {
166
1.13k
  const char* uuidCano = "\x85\xC0\xB6\x87\x82\xF\x11\xE0\x81\x11\xF4\xCE\x46\x2B\x6A\x48";
167
1.13k
  const char* uuidXmp = "\xBE\x7A\xCF\xCB\x97\xA9\x42\xE8\x9C\x71\x99\x94\x91\xE3\xAF\xAC";
168
1.13k
  const char* uuidCanp = "\xEA\xF4\x2B\x5E\x1C\x98\x4B\x88\xB9\xFB\xB7\xDC\x40\x6E\x4D\x16";
169
1.13k
  if (uuid.cmpBytes(0, uuidCano, 16) == 0)
170
25
    return "cano";
171
1.10k
  if (uuid.cmpBytes(0, uuidXmp, 16) == 0)
172
278
    return "xmp";
173
830
  if (uuid.cmpBytes(0, uuidCanp, 16) == 0)
174
334
    return "canp";
175
496
  return "";
176
830
}
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
660
void BmffImage::brotliUncompress(const byte* compressedBuf, size_t compressedBufSize, DataBuf& arr) {
185
660
  auto decoder = BrotliDecoder(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr), BrotliDecoderDestroyInstance);
186
660
  size_t uncompressedLen = compressedBufSize * 2;  // just a starting point
187
660
  BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
188
660
  int dos = 0;
189
660
  size_t available_in = compressedBufSize;
190
660
  const byte* next_in = compressedBuf;
191
660
  size_t available_out;
192
660
  byte* next_out;
193
660
  size_t total_out = 0;
194
195
1.61k
  while (result != BROTLI_DECODER_RESULT_SUCCESS) {
196
1.06k
    arr.alloc(uncompressedLen);
197
1.06k
    available_out = uncompressedLen - total_out;
198
1.06k
    next_out = arr.data() + total_out;
199
1.06k
    result =
200
1.06k
        BrotliDecoderDecompressStream(decoder.get(), &available_in, &next_in, &available_out, &next_out, &total_out);
201
1.06k
    if (result == BROTLI_DECODER_RESULT_SUCCESS) {
202
547
      arr.resize(total_out);
203
547
    } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
204
448
      uncompressedLen *= 2;
205
      // DoS protection - can't be bigger than 128k
206
448
      if (uncompressedLen > 131072) {
207
94
        if (++dos > 1 || total_out > 131072)
208
40
          break;
209
54
        uncompressedLen = 131072;
210
54
      }
211
448
    } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
212
      // compressed input buffer in incomplete
213
28
      throw Error(ErrorCode::kerFailedToReadImageData);
214
45
    } else {
215
      // something bad happened
216
45
      throw Error(ErrorCode::kerErrorMessage, BrotliDecoderErrorString(BrotliDecoderGetErrorCode(decoder.get())));
217
45
    }
218
1.06k
  }
219
220
587
  if (result != BROTLI_DECODER_RESULT_SUCCESS) {
221
40
    throw Error(ErrorCode::kerFailedToReadImageData);
222
40
  }
223
587
}
224
#endif
225
226
uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintStructureOption option /* = kpsNone */,
227
129k
                               uint64_t pbox_end, size_t depth) {
228
129k
  const size_t address = io_->tell();
229
  // never visit a box twice!
230
129k
  if (depth == 0)
231
100k
    visits_.clear();
232
129k
  if (visits_.contains(address) || visits_.size() > visits_max_ || depth >= max_box_depth_) {
233
59
    throw Error(ErrorCode::kerCorruptedMetadata);
234
59
  }
235
129k
  visits_.insert(address);
236
237
#ifdef EXIV2_DEBUG_MESSAGES
238
  bool bTrace = true;
239
#else
240
129k
  bool bTrace = option == kpsBasic || option == kpsRecursive;
241
129k
#endif
242
243
  // 8-byte buffer for parsing the box length and type.
244
129k
  byte hdrbuf[2 * sizeof(uint32_t)];
245
246
129k
  size_t hdrsize = sizeof(hdrbuf);
247
129k
  Internal::enforce(hdrsize <= static_cast<size_t>(pbox_end - address), Exiv2::ErrorCode::kerCorruptedMetadata);
248
129k
  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
129k
  uint64_t box_length = getULong(&hdrbuf[0], endian_);
254
129k
  uint32_t box_type = getULong(&hdrbuf[sizeof(uint32_t)], endian_);
255
129k
  bool bLF = true;
256
257
129k
  if (bTrace) {
258
57.3k
    bLF = true;
259
57.3k
    out << Internal::indent(depth) << "Exiv2::BmffImage::boxHandler: " << toAscii(box_type)
260
57.3k
        << stringFormat(" {:8}->{} ", address, box_length);
261
57.3k
  }
262
263
129k
  if (box_length == 1) {
264
    // The box size is encoded as a uint64_t, so we need to read another 8 bytes.
265
315
    hdrsize += 8;
266
315
    Internal::enforce(hdrsize <= static_cast<size_t>(pbox_end - address), Exiv2::ErrorCode::kerCorruptedMetadata);
267
315
    DataBuf data(8);
268
315
    io_->read(data.data(), data.size());
269
315
    box_length = data.read_uint64(0, endian_);
270
315
  }
271
272
129k
  if (box_length == 0) {
273
    // Zero length is also valid and indicates box extends to the end of file.
274
44.2k
    box_length = pbox_end - address;
275
44.2k
  }
276
277
  // read data in box and restore file position
278
129k
  const size_t restore = io_->tell();
279
129k
  Internal::enforce(box_length >= hdrsize, Exiv2::ErrorCode::kerCorruptedMetadata);
280
129k
  Internal::enforce(box_length - hdrsize <= pbox_end - restore, Exiv2::ErrorCode::kerCorruptedMetadata);
281
282
129k
  const auto buffer_size = box_length - hdrsize;
283
129k
  if (skipBox(box_type)) {
284
767
    if (bTrace) {
285
414
      out << '\n';
286
414
    }
287
    // The enforce() above checks that restore + buffer_size won't
288
    // exceed pbox_end, and by implication, won't exceed LONG_MAX
289
767
    return restore + buffer_size;
290
767
  }
291
292
128k
  DataBuf data(static_cast<size_t>(buffer_size));
293
128k
  const size_t box_end = restore + data.size();
294
128k
  io_->read(data.data(), data.size());
295
128k
  io_->seek(restore, BasicIo::beg);
296
297
128k
  size_t skip = 0;  // read position in data.pData_
298
128k
  uint8_t version = 0;
299
128k
  uint32_t flags = 0;
300
301
128k
  if (fullBox(box_type)) {
302
8.73k
    Internal::enforce(data.size() - skip >= 4, Exiv2::ErrorCode::kerCorruptedMetadata);
303
8.73k
    flags = data.read_uint32(skip, endian_);  // version/flags
304
8.73k
    version = static_cast<uint8_t>(flags >> 24);
305
8.73k
    flags &= 0x00ffffff;
306
8.73k
    skip += 4;
307
8.73k
  }
308
309
128k
  switch (box_type) {
310
    //  See notes in skipBox()
311
38.2k
    case TAG::ftyp: {
312
38.2k
      Internal::enforce(data.size() >= 4, Exiv2::ErrorCode::kerCorruptedMetadata);
313
38.2k
      fileType_ = data.read_uint32(0, endian_);
314
38.2k
      if (bTrace) {
315
21.7k
        out << "brand: " << toAscii(fileType_);
316
21.7k
      }
317
38.2k
    } break;
318
319
    // 8.11.6.1
320
879
    case TAG::iinf: {
321
879
      if (bTrace) {
322
32
        out << '\n';
323
32
        bLF = false;
324
32
      }
325
326
879
      Internal::enforce(data.size() - skip >= 2, Exiv2::ErrorCode::kerCorruptedMetadata);
327
879
      uint16_t n = data.read_uint16(skip, endian_);
328
879
      skip += 2;
329
330
879
      io_->seek(skip, BasicIo::cur);
331
2.59k
      while (n-- > 0) {
332
1.71k
        io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg);
333
1.71k
      }
334
879
    } break;
335
336
    // 8.11.6.2
337
2.46k
    case TAG::infe: {  // .__._.__hvc1_ 2 0 0 1 0 1 0 0 104 118 99 49 0
338
2.46k
      Internal::enforce(data.size() - skip >= 8, Exiv2::ErrorCode::kerCorruptedMetadata);
339
2.46k
      /* getULong (data.pData_+skip,endian_) ; */ skip += 4;
340
2.46k
      uint16_t ID = data.read_uint16(skip, endian_);
341
2.46k
      skip += 2;
342
2.46k
      /* getShort(data.pData_+skip,endian_) ; */ skip += 2;  // protection
343
2.46k
      std::string id;
344
      // Check that the string has a '\0' terminator.
345
2.46k
      const char* str = data.c_str(skip);
346
2.46k
      const size_t maxlen = data.size() - skip;
347
2.46k
      Internal::enforce(maxlen > 0 && strnlen(str, maxlen) < maxlen, Exiv2::ErrorCode::kerCorruptedMetadata);
348
2.46k
      std::string name(str);
349
2.46k
      if (Internal::contains(name, "Exif")) {  // "Exif" or "ExifExif"
350
1.02k
        exifID_ = ID;
351
1.02k
        id = " *** Exif ***";
352
1.44k
      } else if (Internal::contains(name, "mime\0xmp") || Internal::contains(name, "mime\0application/rdf+xml")) {
353
767
        xmpID_ = ID;
354
767
        id = " *** XMP ***";
355
767
      }
356
2.46k
      if (bTrace) {
357
288
        out << stringFormat("ID = {:3} {} {}", ID, name, id);
358
288
      }
359
2.46k
    } break;
360
361
238
    case TAG::moov:
362
271
    case TAG::iprp:
363
18.9k
    case TAG::ipco:
364
23.3k
    case TAG::meta: {
365
23.3k
      if (bTrace) {
366
1.29k
        out << '\n';
367
1.29k
        bLF = false;
368
1.29k
      }
369
23.3k
      io_->seek(skip, BasicIo::cur);
370
50.1k
      while (io_->tell() < box_end) {
371
26.7k
        io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg);
372
26.7k
      }
373
      // post-process meta box to recover Exif and XMP
374
23.3k
      if (box_type == TAG::meta) {
375
3.55k
        auto ilo = ilocs_.find(exifID_);
376
3.55k
        if (ilo != ilocs_.end()) {
377
86
          const Iloc& iloc = ilo->second;
378
86
          if (bTrace) {
379
34
            out << Internal::indent(depth) << "Exiv2::BMFF Exif: " << iloc.toString() << '\n';
380
34
          }
381
86
          parseTiff(Internal::Tag::root, iloc.length_, iloc.start_);
382
86
        }
383
3.55k
        ilo = ilocs_.find(xmpID_);
384
3.55k
        if (ilo != ilocs_.end()) {
385
212
          const Iloc& iloc = ilo->second;
386
212
          if (bTrace) {
387
11
            out << Internal::indent(depth) << "Exiv2::BMFF XMP: " << iloc.toString() << '\n';
388
11
          }
389
212
          parseXmp(iloc.length_, iloc.start_);
390
212
        }
391
3.55k
        ilocs_.clear();
392
3.55k
      }
393
23.3k
    } break;
394
395
    // 8.11.3.1
396
2.13k
    case TAG::iloc: {
397
2.13k
      Internal::enforce(data.size() - skip >= 2, Exiv2::ErrorCode::kerCorruptedMetadata);
398
2.13k
      uint8_t u = data.read_uint8(skip++);
399
2.13k
      uint16_t offsetSize = u >> 4;
400
2.13k
      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
2.13k
      skip++;
409
2.13k
#endif
410
2.13k
      Internal::enforce(data.size() - skip >= (version < 2u ? 2u : 4u), Exiv2::ErrorCode::kerCorruptedMetadata);
411
2.13k
      uint32_t itemCount = version < 2 ? data.read_uint16(skip, endian_) : data.read_uint32(skip, endian_);
412
2.13k
      skip += version < 2 ? 2 : 4;
413
2.13k
      if (itemCount && itemCount < box_length / 14 && offsetSize == 4 && lengthSize == 4 &&
414
1.58k
          ((box_length - 16) % itemCount) == 0) {
415
1.50k
        if (bTrace) {
416
221
          out << '\n';
417
221
          bLF = false;
418
221
        }
419
1.50k
        auto step = (static_cast<size_t>(box_length) - 16) / itemCount;  // length of data per item.
420
1.50k
        size_t base = skip;
421
20.2k
        for (uint32_t i = 0; i < itemCount; i++) {
422
18.7k
          skip = base + (i * step);  // move in 14, 16 or 18 byte steps
423
18.7k
          Internal::enforce(data.size() - skip >= (version > 2u ? 4u : 2u), Exiv2::ErrorCode::kerCorruptedMetadata);
424
18.7k
          Internal::enforce(data.size() - skip >= step, Exiv2::ErrorCode::kerCorruptedMetadata);
425
18.7k
          uint32_t ID = version > 2 ? data.read_uint32(skip, endian_) : data.read_uint16(skip, endian_);
426
18.7k
          auto offset = [&data, skip, step] {
427
18.7k
            if (step == 14 || step == 16)
428
15.9k
              return data.read_uint32(skip + step - 8, endian_);
429
2.79k
            if (step == 18)
430
2.09k
              return data.read_uint32(skip + 4, endian_);
431
702
            return 0u;
432
2.79k
          }();
433
434
18.7k
          uint32_t ldata = data.read_uint32(skip + step - 4, endian_);
435
18.7k
          if (bTrace) {
436
5.46k
            out << Internal::indent(depth)
437
5.46k
                << stringFormat("{:8} | {:8} |   ID | {:4} | {:6},{:6}\n", address + skip, step, ID, offset, ldata);
438
5.46k
          }
439
          // save data for post-processing in meta box
440
18.7k
          if (offset && ldata && ID != unknownID_) {
441
15.1k
            ilocs_[ID] = Iloc{ID, offset, ldata};
442
15.1k
          }
443
18.7k
        }
444
1.50k
      }
445
2.13k
    } break;
446
447
762
    case TAG::ispe: {
448
762
      Internal::enforce(data.size() - skip >= 12, Exiv2::ErrorCode::kerCorruptedMetadata);
449
762
      skip += 4;
450
762
      uint32_t width = data.read_uint32(skip, endian_);
451
762
      skip += 4;
452
762
      uint32_t height = data.read_uint32(skip, endian_);
453
762
      skip += 4;
454
762
      if (bTrace) {
455
180
        out << stringFormat("pixelWidth_, pixelHeight_ = {}, {}", width, height);
456
180
      }
457
      // HEIC files can have multiple ispe records
458
      // Store largest width/height
459
762
      if (width > pixelWidth_ && height > pixelHeight_) {
460
55
        pixelWidth_ = width;
461
55
        pixelHeight_ = height;
462
55
      }
463
762
    } break;
464
465
    // 12.1.5.2
466
489
    case TAG::colr: {
467
489
      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
361
        uint8_t meth = data.read_uint8(skip + 0);
470
361
        uint8_t prec = data.read_uint8(skip + 1);
471
361
        uint8_t approx = data.read_uint8(skip + 2);
472
361
        auto colour_type = std::string(data.c_str(), 4);
473
361
        skip += 4;
474
361
        if (colour_type == "rICC" || colour_type == "prof") {
475
67
          DataBuf profile(data.c_data(skip), data.size() - skip);
476
67
          setIccProfile(std::move(profile));
477
294
        } else if (meth == 2 && prec == 0 && approx == 0) {
478
          // JP2000 files have a 3 byte head // 2 0 0 icc......
479
186
          skip -= 1;
480
186
          DataBuf profile(data.c_data(skip), data.size() - skip);
481
186
          setIccProfile(std::move(profile));
482
186
        }
483
361
      }
484
489
    } break;
485
486
1.13k
    case TAG::uuid: {
487
1.13k
      DataBuf uuid(16);
488
1.13k
      io_->read(uuid.data(), uuid.size());
489
1.13k
      std::string name = uuidName(uuid);
490
1.13k
      if (bTrace) {
491
320
        out << " uuidName " << name << '\n';
492
320
        bLF = false;
493
320
      }
494
1.13k
      if (name == "cano" || name == "canp") {
495
359
        if (name == "canp") {
496
          // based on
497
          // https://github.com/lclevy/canon_cr3/blob/7be75d6/parse_cr3.py#L271
498
334
          io_->seek(8, BasicIo::cur);
499
334
        }
500
739
        while (io_->tell() < box_end) {
501
380
          io_->seek(boxHandler(out, option, box_end, depth + 1), BasicIo::beg);
502
380
        }
503
774
      } else if (name == "xmp") {
504
278
        parseXmp(box_length, io_->tell());
505
278
      }
506
1.13k
    } break;
507
508
3.08k
    case TAG::cmt1:
509
3.08k
      parseTiff(Internal::Tag::root, box_length);
510
3.08k
      break;
511
709
    case TAG::cmt2:
512
709
      parseTiff(Internal::Tag::cmt2, box_length);
513
709
      break;
514
20.1k
    case TAG::cmt3:
515
20.1k
      parseTiff(Internal::Tag::cmt3, box_length);
516
20.1k
      break;
517
1.72k
    case TAG::cmt4:
518
1.72k
      parseTiff(Internal::Tag::cmt4, box_length);
519
1.72k
      break;
520
13.6k
    case TAG::exif:
521
13.6k
      parseTiff(Internal::Tag::root, buffer_size, io_->tell());
522
13.6k
      break;
523
812
    case TAG::xml:
524
812
      parseXmp(buffer_size, io_->tell());
525
812
      break;
526
661
    case TAG::brob: {
527
661
      Internal::enforce(data.size() >= 4, Exiv2::ErrorCode::kerCorruptedMetadata);
528
661
      uint32_t realType = data.read_uint32(0, endian_);
529
661
      if (bTrace) {
530
154
        out << "type: " << toAscii(realType);
531
154
      }
532
661
#ifdef EXV_HAVE_BROTLI
533
661
      DataBuf arr;
534
661
      brotliUncompress(data.c_data(4), data.size() - 4, arr);
535
661
      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
660
      } else if (realType == TAG::xml) {
541
43
        try {
542
43
          Exiv2::XmpParser::decode(xmpData(), std::string(arr.c_str(), arr.size()));
543
43
        } catch (...) {
544
0
          throw Error(ErrorCode::kerFailedToReadImageData);
545
0
        }
546
43
      }
547
661
#endif
548
661
    } break;
549
959
    case TAG::thmb:
550
959
      switch (version) {
551
343
        case 0:  // JPEG
552
343
          parseCr3Preview(data, out, bTrace, version, skip, skip + 2, skip + 4, skip + 12);
553
343
          break;
554
479
        case 1:  // HDR
555
479
          parseCr3Preview(data, out, bTrace, version, skip + 2, skip + 4, skip + 8, skip + 12);
556
479
          break;
557
137
        default:
558
137
          break;
559
959
      }
560
956
      break;
561
956
    case TAG::prvw:
562
350
      switch (version) {
563
289
        case 0:  // JPEG
564
297
        case 1:  // HDR
565
297
          parseCr3Preview(data, out, bTrace, version, skip + 2, skip + 4, skip + 8, skip + 12);
566
297
          break;
567
53
        default:
568
53
          break;
569
350
      }
570
347
      break;
571
572
15.5k
    default:
573
15.5k
      break; /* do nothing */
574
128k
  }
575
109k
  if (bLF && bTrace)
576
55.0k
    out << '\n';
577
578
  // return address of next box
579
109k
  return box_end;
580
128k
}
581
582
13.7k
void BmffImage::parseTiff(uint32_t root_tag, uint64_t length, uint64_t start) {
583
13.7k
  Internal::enforce(start <= io_->size(), ErrorCode::kerCorruptedMetadata);
584
13.7k
  Internal::enforce(length <= io_->size() - start, ErrorCode::kerCorruptedMetadata);
585
13.7k
  Internal::enforce(start <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max()),
586
13.7k
                    ErrorCode::kerCorruptedMetadata);
587
13.7k
  Internal::enforce(length <= std::numeric_limits<size_t>::max(), ErrorCode::kerCorruptedMetadata);
588
589
  // read and parse exif data
590
13.7k
  const size_t restore = io_->tell();
591
13.7k
  DataBuf exif(static_cast<size_t>(length));
592
13.7k
  io_->seek(static_cast<int64_t>(start), BasicIo::beg);
593
13.7k
  if (exif.size() > 8 && io_->read(exif.data(), exif.size()) == exif.size()) {
594
    // hunt for "II" or "MM"
595
13.4k
    const size_t eof = std::numeric_limits<size_t>::max();  // impossible value for punt
596
13.4k
    size_t punt = eof;
597
5.51M
    for (size_t i = 0; i < exif.size() - 9 && punt == eof; ++i) {
598
5.49M
      auto charCurrent = exif.read_uint8(i);
599
5.49M
      auto charNext = exif.read_uint8(i + 1);
600
5.49M
      if (charCurrent == charNext && (charCurrent == 'I' || charCurrent == 'M'))
601
12.7k
        punt = i;
602
5.49M
    }
603
13.4k
    if (punt != eof) {
604
12.7k
      Internal::TiffParserWorker::decode(exifData(), iptcData(), xmpData(), exif.c_data(punt), exif.size() - punt,
605
12.7k
                                         root_tag, Internal::TiffMapping::findDecoder);
606
12.7k
    }
607
13.4k
  }
608
13.7k
  io_->seek(restore, BasicIo::beg);
609
13.7k
}
610
611
25.7k
void BmffImage::parseTiff(uint32_t root_tag, uint64_t length) {
612
25.7k
  if (length > 8) {
613
25.0k
    Internal::enforce(length - 8 <= io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
614
25.0k
    Internal::enforce(length - 8 <= std::numeric_limits<size_t>::max(), ErrorCode::kerCorruptedMetadata);
615
25.0k
    DataBuf data(static_cast<size_t>(length - 8u));
616
25.0k
    const size_t bufRead = io_->read(data.data(), data.size());
617
618
25.0k
    if (io_->error())
619
0
      throw Error(ErrorCode::kerFailedToReadImageData);
620
25.0k
    if (bufRead != data.size())
621
0
      throw Error(ErrorCode::kerInputDataReadFailed);
622
623
25.0k
    Internal::TiffParserWorker::decode(exifData(), iptcData(), xmpData(), data.c_data(), data.size(), root_tag,
624
25.0k
                                       Internal::TiffMapping::findDecoder);
625
25.0k
  }
626
25.7k
}
627
628
1.30k
void BmffImage::parseXmp(uint64_t length, uint64_t start) {
629
1.30k
  Internal::enforce(start <= io_->size(), ErrorCode::kerCorruptedMetadata);
630
1.30k
  Internal::enforce(length <= io_->size() - start, ErrorCode::kerCorruptedMetadata);
631
632
1.30k
  const size_t restore = io_->tell();
633
1.30k
  io_->seek(static_cast<int64_t>(start), BasicIo::beg);
634
635
1.30k
  auto lengthSizeT = static_cast<size_t>(length);
636
1.30k
  DataBuf xmp(lengthSizeT + 1);
637
1.30k
  xmp.write_uint8(lengthSizeT, 0);  // ensure xmp is null terminated!
638
1.30k
  if (io_->read(xmp.data(), lengthSizeT) != lengthSizeT)
639
0
    throw Error(ErrorCode::kerInputDataReadFailed);
640
1.30k
  if (io_->error())
641
0
    throw Error(ErrorCode::kerFailedToReadImageData);
642
1.30k
  try {
643
1.30k
    Exiv2::XmpParser::decode(xmpData(), std::string(xmp.c_str()));
644
1.30k
  } catch (...) {
645
0
    throw Error(ErrorCode::kerFailedToReadImageData);
646
0
  }
647
648
1.23k
  io_->seek(restore, BasicIo::beg);
649
1.23k
}
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
1.11k
                                size_t relative_position) {
655
  // Derived from https://github.com/lclevy/canon_cr3
656
1.11k
  const size_t here = io_->tell();
657
1.11k
  Internal::enforce(here <= std::numeric_limits<size_t>::max() - relative_position, ErrorCode::kerCorruptedMetadata);
658
1.11k
  NativePreview nativePreview;
659
1.11k
  nativePreview.position_ = here + relative_position;
660
1.11k
  nativePreview.width_ = data.read_uint16(width_offset, endian_);
661
1.11k
  nativePreview.height_ = data.read_uint16(height_offset, endian_);
662
1.11k
  nativePreview.size_ = data.read_uint32(size_offset, endian_);
663
1.11k
  nativePreview.filter_ = "";
664
1.11k
  nativePreview.mimeType_ = [version] {
665
1.11k
    if (version == 0)
666
627
      return "image/jpeg";
667
486
    return "application/octet-stream";
668
1.11k
  }();
669
1.11k
  if (bTrace) {
670
412
    out << stringFormat("width,height,size = {},{},{}", nativePreview.width_, nativePreview.height_,
671
412
                        nativePreview.size_);
672
412
  }
673
1.11k
  nativePreviews_.push_back(std::move(nativePreview));
674
1.11k
}
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
23.4k
void BmffImage::openOrThrow() const {
694
23.4k
  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
23.4k
  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
23.4k
}
704
705
10.2k
void BmffImage::readMetadata() {
706
10.2k
  openOrThrow();
707
10.2k
  IoCloser closer(*io_);
708
709
10.2k
  clearMetadata();
710
10.2k
  ilocs_.clear();
711
10.2k
  visits_max_ = io_->size() / 16;
712
10.2k
  unknownID_ = 0xffff;
713
10.2k
  exifID_ = unknownID_;
714
10.2k
  xmpID_ = unknownID_;
715
716
10.2k
  uint64_t address = 0;
717
10.2k
  const auto file_end = io_->size();
718
54.7k
  while (address < file_end) {
719
44.5k
    io_->seek(address, BasicIo::beg);
720
44.5k
    address = boxHandler(std::cout, kpsNone, file_end, 0);
721
44.5k
  }
722
10.2k
  bReadMetadata_ = true;
723
10.2k
}  // BmffImage::readMetadata
724
725
39.6k
void BmffImage::printStructure(std::ostream& out, Exiv2::PrintStructureOption option, size_t depth) {
726
39.6k
  if (!bReadMetadata_)
727
0
    readMetadata();
728
729
39.6k
  switch (option) {
730
13.2k
    default:
731
13.2k
      break;  // do nothing
732
733
13.2k
    case kpsIccProfile: {
734
6.60k
      out.write(iccProfile_.c_str(), iccProfile_.size());
735
6.60k
    } break;
736
737
0
#ifdef EXV_HAVE_XMP_TOOLKIT
738
6.60k
    case kpsXMP: {
739
6.60k
      std::string xmp;
740
6.60k
      if (Exiv2::XmpParser::encode(xmp, xmpData())) {
741
0
        throw Exiv2::Error(Exiv2::ErrorCode::kerErrorMessage, "Failed to serialize XMP data");
742
0
      }
743
6.60k
      out << xmp;
744
6.60k
    } break;
745
0
#endif
746
6.61k
    case kpsBasic:  // drop
747
13.2k
    case kpsRecursive: {
748
13.2k
      openOrThrow();
749
13.2k
      IoCloser closer(*io_);
750
751
13.2k
      uint64_t address = 0;
752
13.2k
      const auto file_end = io_->size();
753
69.2k
      while (address < file_end) {
754
56.0k
        io_->seek(address, BasicIo::beg);
755
56.0k
        address = boxHandler(out, option, file_end, depth);
756
56.0k
      }
757
13.2k
    } break;
758
39.6k
  }
759
39.6k
}
760
761
7.91k
void BmffImage::writeMetadata() {
762
  // bmff files are read-only
763
7.91k
  throw(Error(ErrorCode::kerWritingImageFormatUnsupported, "BMFF"));
764
7.91k
}  // BmffImage::writeMetadata
765
766
// *************************************************************************
767
// free functions
768
10.2k
Image::UniquePtr newBmffInstance(BasicIo::UniquePtr io, bool create) {
769
10.2k
  auto image = std::make_unique<BmffImage>(std::move(io), create);
770
10.2k
  if (!image->good()) {
771
0
    return nullptr;
772
0
  }
773
10.2k
  return image;
774
10.2k
}
775
776
45.4k
bool isBmffType(BasicIo& iIo, bool advance) {
777
45.4k
  const int32_t len = 12;
778
45.4k
  byte buf[len];
779
45.4k
  iIo.read(buf, len);
780
45.4k
  if (iIo.error() || iIo.eof()) {
781
594
    return false;
782
594
  }
783
784
  // bmff should start with "ftyp"
785
44.8k
  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
44.8k
  bool const is_jxl = (buf[4] == 'J' && buf[5] == 'X' && buf[6] == 'L' && buf[7] == ' ');
788
789
44.8k
  bool matched = is_jxl || is_ftyp;
790
44.8k
  if (!advance || !matched) {
791
44.8k
    iIo.seek(0, BasicIo::beg);
792
44.8k
  }
793
44.8k
  return matched;
794
45.4k
}
795
#endif  // EXV_ENABLE_BMFF
796
}  // namespace Exiv2