Coverage Report

Created: 2026-08-11 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/jp2image.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "jp2image.hpp"
5
6
#include "config.h"
7
8
#include "basicio.hpp"
9
#include "enforce.hpp"
10
#include "error.hpp"
11
#include "futils.hpp"
12
#include "image.hpp"
13
#include "image_int.hpp"
14
#include "jp2image_int.hpp"
15
#include "safe_op.hpp"
16
#include "tiffimage.hpp"
17
#include "types.hpp"
18
19
#include <algorithm>
20
#include <array>
21
#include <cstring>
22
#include <iostream>
23
24
#ifdef EXIV2_DEBUG_MESSAGES
25
#include <fstream>
26
#endif
27
28
// JPEG-2000 box types
29
enum kJp2BoxType : uint32_t {
30
  Signature = 0x6a502020,    // signature box, required,
31
  FileTypeBox = 0x66747970,  // File type box, required
32
  Header = 0x6a703268,       // Jp2 Header Box, required, Superbox
33
  ImageHeader = 0x69686472,  // Image Header Box ('ihdr'), required,
34
  ColorSpec = 0x636f6c72,    // Color Specification box ('colr'), required
35
  Uuid = 0x75756964,         // 'uuid'
36
  Close = 0x6a703263,        // 'jp2c'
37
};
38
39
namespace Exiv2 {
40
namespace {
41
// JPEG-2000 UUIDs for embedded metadata
42
//
43
// See http://www.jpeg.org/public/wg1n2600.doc for information about embedding IPTC-NAA data in JPEG-2000 files
44
// See http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf for information about embedding XMP data in JPEG-2000
45
// files
46
47
constexpr auto kJp2UuidExif = std::array<byte, 16>{
48
    0x4A, 0x70, 0x67, 0x54, 0x69, 0x66, 0x66, 0x45, 0x78, 0x69, 0x66, 0x2D, 0x3E, 0x4A, 0x50, 0x32,
49
};
50
constexpr auto kJp2UuidIptc = std::array<byte, 16>{
51
    0x33, 0xc7, 0xa4, 0xd2, 0xb8, 0x1d, 0x47, 0x23, 0xa0, 0xba, 0xf1, 0xa3, 0xe0, 0x97, 0xad, 0x38,
52
};
53
constexpr auto kJp2UuidXmp = std::array<byte, 16>{
54
    0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac,
55
};
56
57
// See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification
58
constexpr std::array<byte, 12> Jp2Signature{
59
    0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a,
60
};
61
62
constexpr std::array<byte, 249> Jp2Blank{
63
    0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x66, 0x74,
64
    0x79, 0x70, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x2d,
65
    0x6a, 0x70, 0x32, 0x68, 0x00, 0x00, 0x00, 0x16, 0x69, 0x68, 0x64, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
66
    0x00, 0x01, 0x00, 0x01, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x63, 0x6f, 0x6c, 0x72, 0x01, 0x00,
67
    0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x63, 0xff, 0x4f, 0xff, 0x51, 0x00,
68
    0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
69
    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
70
    0x01, 0x07, 0x01, 0x01, 0xff, 0x64, 0x00, 0x23, 0x00, 0x01, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3a,
71
    0x20, 0x4a, 0x61, 0x73, 0x50, 0x65, 0x72, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e,
72
    0x39, 0x30, 0x30, 0x2e, 0x31, 0xff, 0x52, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x04, 0x04, 0x00,
73
    0x01, 0xff, 0x5c, 0x00, 0x13, 0x40, 0x40, 0x48, 0x48, 0x50, 0x48, 0x48, 0x50, 0x48, 0x48, 0x50, 0x48, 0x48,
74
    0x50, 0x48, 0x48, 0x50, 0xff, 0x90, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x01, 0xff, 0x5d,
75
    0x00, 0x14, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
76
    0x00, 0x00, 0xff, 0x93, 0xcf, 0xb4, 0x04, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, 0xd9,
77
};
78
79
const size_t boxHSize = sizeof(Internal::Jp2BoxHeader);
80
81
0
void lf(std::ostream& out, bool& bLF) {
82
0
  if (bLF) {
83
0
    out << '\n';
84
0
    out.flush();
85
0
    bLF = false;
86
0
  }
87
0
}
88
89
843
void boxes_check(size_t b, size_t m) {
90
843
  if (b > m) {
91
#ifdef EXIV2_DEBUG_MESSAGES
92
    std::cout << "Exiv2::Jp2Image::readMetadata box maximum exceeded" << '\n';
93
#endif
94
0
    throw Error(ErrorCode::kerCorruptedMetadata);
95
0
  }
96
843
}
97
98
}  // namespace
99
100
Jp2Image::Jp2Image(BasicIo::UniquePtr io, const ImageCtorParams& params) :
101
268
    Image(ImageType::jp2, mdExif | mdIptc | mdXmp, std::move(io), params) {
102
268
  if (params.create() && io_->open() == 0) {
103
#ifdef EXIV2_DEBUG_MESSAGES
104
    std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << '\n';
105
#endif
106
0
    IoCloser closer(*io_);
107
0
    if (io_->write(Jp2Blank.data(), Jp2Blank.size()) != Jp2Blank.size()) {
108
#ifdef EXIV2_DEBUG_MESSAGES
109
      std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << '\n';
110
#endif
111
0
    }
112
0
  }
113
268
}
114
115
// Obtains the ascii version from the box.type
116
0
std::string Jp2Image::toAscii(uint32_t n) {
117
0
  std::string result(sizeof(uint32_t), '\0');
118
0
  for (size_t i = 0; i < result.size(); ++i) {
119
0
    auto c = static_cast<unsigned char>(n >> (8 * (3 - i)));
120
0
    result[i] = static_cast<char>(c);
121
0
  }
122
0
  return result;
123
0
}
124
125
0
std::string Jp2Image::mimeType() const {
126
0
  if (brand_ == Internal::brandJph)
127
0
    return "image/jph";
128
0
  return "image/jp2";
129
0
}
130
131
0
void Jp2Image::setComment(const std::string&) {
132
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2"));
133
0
}
134
135
262
void Jp2Image::readMetadata() {
136
#ifdef EXIV2_DEBUG_MESSAGES
137
  std::cerr << "Exiv2::Jp2Image::readMetadata: Reading JPEG-2000 file " << io_->path() << '\n';
138
#endif
139
262
  if (io_->open() != 0) {
140
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
141
0
  }
142
262
  IoCloser closer(*io_);
143
262
  if (!isJp2Type(*io_, false)) {
144
0
    throw Error(ErrorCode::kerNotAnImage, "JPEG-2000");
145
0
  }
146
147
262
  Internal::Jp2BoxHeader box = {0, 0};
148
262
  Internal::Jp2BoxHeader subBox = {0, 0};
149
262
  Internal::Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0};
150
262
  Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
151
262
  size_t boxesCount = 0;
152
262
  const size_t boxem = 1000;  // boxes max
153
262
  uint32_t lastBoxTypeRead = 0;
154
262
  bool boxSignatureFound = false;
155
262
  bool boxFileTypeFound = false;
156
157
852
  while (io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) {
158
843
    boxes_check(boxesCount++, boxem);
159
843
    const size_t position = io_->tell();
160
843
    box.length = getULong(reinterpret_cast<byte*>(&box.length), bigEndian);
161
843
    box.type = getULong(reinterpret_cast<byte*>(&box.type), bigEndian);
162
#ifdef EXIV2_DEBUG_MESSAGES
163
    std::cout << "Exiv2::Jp2Image::readMetadata: Position: " << position << " box type: " << toAscii(box.type)
164
              << " length: " << box.length << '\n';
165
#endif
166
843
    Internal::enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
167
168
843
    if (box.length == 0)
169
26
      return;
170
171
817
    if (box.length == 1) {
172
      /// \todo In this case, the real box size is given in XLBox (bytes 8-15)
173
33
    }
174
175
817
    switch (box.type) {
176
268
      case kJp2BoxType::Signature: {
177
268
        if (boxSignatureFound)  // Only one is allowed
178
6
          throw Error(ErrorCode::kerCorruptedMetadata);
179
262
        boxSignatureFound = true;
180
262
        break;
181
268
      }
182
67
      case kJp2BoxType::FileTypeBox: {
183
        // This box shall immediately follow the JPEG 2000 Signature box
184
67
        if (boxFileTypeFound || lastBoxTypeRead != kJp2BoxType::Signature) {  // Only one is allowed
185
6
          throw Error(ErrorCode::kerCorruptedMetadata);
186
6
        }
187
61
        boxFileTypeFound = true;
188
61
        Internal::enforce(box.length >= boxHSize, ErrorCode::kerCorruptedMetadata);
189
61
        Blob boxData(box.length - boxHSize);
190
61
        io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata);
191
61
        if (!Internal::isValidBoxFileType(boxData))
192
55
          throw Error(ErrorCode::kerCorruptedMetadata);
193
6
        brand_ = getULong(boxData.data(), bigEndian);
194
6
        break;
195
61
      }
196
0
      case kJp2BoxType::Header: {
197
#ifdef EXIV2_DEBUG_MESSAGES
198
        std::cout << "Exiv2::Jp2Image::readMetadata: JP2Header box found\n";
199
#endif
200
0
        size_t restore = io_->tell();
201
202
0
        while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize && subBox.length) {
203
0
          boxes_check(boxesCount++, boxem);
204
0
          subBox.length = getULong(reinterpret_cast<byte*>(&subBox.length), bigEndian);
205
0
          subBox.type = getULong(reinterpret_cast<byte*>(&subBox.type), bigEndian);
206
0
          if (subBox.length > io_->size()) {
207
0
            throw Error(ErrorCode::kerCorruptedMetadata);
208
0
          }
209
#ifdef EXIV2_DEBUG_MESSAGES
210
          std::cout << "Exiv2::Jp2Image::readMetadata: "
211
                    << "subBox = " << toAscii(subBox.type) << " length = " << subBox.length << '\n';
212
#endif
213
0
          if (subBox.type == kJp2BoxType::ColorSpec && subBox.length != 15) {
214
#ifdef EXIV2_DEBUG_MESSAGES
215
            std::cout << "Exiv2::Jp2Image::readMetadata: "
216
                      << "Color data found" << '\n';
217
#endif
218
219
0
            const size_t pad = 3;  // 3 padding bytes 2 0 0
220
0
            const size_t data_length = Safe::add(subBox.length, 8u);
221
            // data_length makes no sense if it is larger than the rest of the file
222
0
            if (data_length > io_->size() - io_->tell()) {
223
0
              throw Error(ErrorCode::kerCorruptedMetadata);
224
0
            }
225
0
            DataBuf data(data_length);
226
0
            io_->read(data.data(), data.size());
227
0
            const size_t iccLength = data.read_uint32(pad, bigEndian);
228
            // subtracting pad from data.size() is safe:
229
            // data.size() is at least 8 and pad = 3
230
0
            if (iccLength > data.size() - pad) {
231
0
              throw Error(ErrorCode::kerCorruptedMetadata);
232
0
            }
233
0
            DataBuf icc(iccLength);
234
0
            std::copy_n(data.begin() + pad, icc.size(), icc.begin());
235
#ifdef EXIV2_DEBUG_MESSAGES
236
            const char* iccPath = "/tmp/libexiv2_jp2.icc";
237
            if (auto f = std::ofstream(iccPath, std::ios::binary)) {
238
              f.write(reinterpret_cast<const char*>(icc.c_data()), static_cast<std::streamsize>(icc.size()));
239
              f.close();
240
              std::cout << "Exiv2::Jp2Image::readMetadata: wrote iccProfile " << icc.size() << " bytes to " << iccPath
241
                        << '\n';
242
            }
243
#endif
244
0
            setIccProfile(std::move(icc));
245
0
          }
246
247
0
          if (subBox.type == kJp2BoxType::ImageHeader) {
248
0
            io_->read(reinterpret_cast<byte*>(&ihdr), sizeof(ihdr));
249
#ifdef EXIV2_DEBUG_MESSAGES
250
            std::cout << "Exiv2::Jp2Image::readMetadata: Ihdr data found" << '\n';
251
#endif
252
0
            ihdr.imageHeight = getULong(reinterpret_cast<byte*>(&ihdr.imageHeight), bigEndian);
253
0
            ihdr.imageWidth = getULong(reinterpret_cast<byte*>(&ihdr.imageWidth), bigEndian);
254
0
            ihdr.componentCount = getShort(reinterpret_cast<byte*>(&ihdr.componentCount), bigEndian);
255
0
            Internal::enforce(ihdr.c == 7, ErrorCode::kerCorruptedMetadata);
256
257
0
            pixelWidth_ = ihdr.imageWidth;
258
0
            pixelHeight_ = ihdr.imageHeight;
259
0
          }
260
261
0
          io_->seek(restore, BasicIo::beg);
262
0
          if (io_->seek(subBox.length, BasicIo::cur) != 0) {
263
0
            throw Error(ErrorCode::kerCorruptedMetadata);
264
0
          }
265
0
          restore = io_->tell();
266
0
        }
267
0
        break;
268
0
      }
269
270
71
      case kJp2BoxType::Uuid: {
271
#ifdef EXIV2_DEBUG_MESSAGES
272
        std::cout << "Exiv2::Jp2Image::readMetadata: UUID box found" << '\n';
273
#endif
274
275
71
        if (io_->read(reinterpret_cast<byte*>(&uuid), sizeof(uuid)) == sizeof(uuid)) {
276
71
          DataBuf rawData;
277
71
          size_t bufRead;
278
71
          bool bIsExif = uuid.uuid == kJp2UuidExif;
279
71
          bool bIsIPTC = uuid.uuid == kJp2UuidIptc;
280
71
          bool bIsXMP = uuid.uuid == kJp2UuidXmp;
281
282
71
          if (bIsExif) {
283
#ifdef EXIV2_DEBUG_MESSAGES
284
            std::cout << "Exiv2::Jp2Image::readMetadata: Exif data found" << '\n';
285
#endif
286
0
            Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
287
0
            rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
288
0
            bufRead = io_->read(rawData.data(), rawData.size());
289
0
            if (io_->error())
290
0
              throw Error(ErrorCode::kerFailedToReadImageData);
291
0
            if (bufRead != rawData.size())
292
0
              throw Error(ErrorCode::kerInputDataReadFailed);
293
294
0
            if (rawData.size() > 8)  // "II*\0long"
295
0
            {
296
              // Find the position of Exif header in bytes array.
297
0
              const char a = rawData.read_uint8(0);
298
0
              const char b = rawData.read_uint8(1);
299
0
              const size_t notfound = std::numeric_limits<size_t>::max();
300
0
              size_t pos = (a == b && (a == 'I' || a == 'M')) ? 0 : notfound;
301
302
              // #1242  Forgive having Exif\0\0 in rawData.pData_
303
0
              std::array<byte, 6> exifHeader{0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
304
0
              for (size_t i = 0; pos == notfound && i < (rawData.size() - exifHeader.size()); i++) {
305
0
                if (rawData.cmpBytes(i, exifHeader.data(), exifHeader.size()) == 0) {
306
0
                  pos = i + sizeof(exifHeader);
307
0
#ifndef SUPPRESS_WARNINGS
308
0
                  EXV_WARNING << "Reading non-standard UUID-EXIF_bad box in " << io_->path() << '\n';
309
0
#endif
310
0
                }
311
0
              }
312
313
              // If found it, store only these data at from this place.
314
0
              if (pos != notfound) {
315
#ifdef EXIV2_DEBUG_MESSAGES
316
                std::cout << "Exiv2::Jp2Image::readMetadata: Exif header found at position " << pos << '\n';
317
#endif
318
0
                ByteOrder bo =
319
0
                    TiffParser::decode(exifData(), iptcData(), xmpData(), rawData.c_data(pos), rawData.size() - pos);
320
0
                setByteOrder(bo);
321
0
              }
322
0
            } else {
323
0
#ifndef SUPPRESS_WARNINGS
324
0
              EXV_WARNING << "Failed to decode Exif metadata." << '\n';
325
0
#endif
326
0
              exifData_.clear();
327
0
            }
328
0
          }
329
330
71
          if (bIsIPTC) {
331
#ifdef EXIV2_DEBUG_MESSAGES
332
            std::cout << "Exiv2::Jp2Image::readMetadata: Iptc data found" << '\n';
333
#endif
334
16
            Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
335
16
            rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
336
16
            bufRead = io_->read(rawData.data(), rawData.size());
337
16
            if (io_->error())
338
0
              throw Error(ErrorCode::kerFailedToReadImageData);
339
16
            if (bufRead != rawData.size())
340
0
              throw Error(ErrorCode::kerInputDataReadFailed);
341
342
16
            if (IptcParser::decode(iptcData_, rawData.c_data(), rawData.size())) {
343
6
#ifndef SUPPRESS_WARNINGS
344
6
              EXV_WARNING << "Failed to decode IPTC metadata." << '\n';
345
6
#endif
346
6
              iptcData_.clear();
347
6
            }
348
16
          }
349
350
71
          if (bIsXMP) {
351
#ifdef EXIV2_DEBUG_MESSAGES
352
            std::cout << "Exiv2::Jp2Image::readMetadata: Xmp data found" << '\n';
353
#endif
354
39
            Internal::enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
355
39
            rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
356
39
            bufRead = io_->read(rawData.data(), rawData.size());
357
39
            if (io_->error())
358
0
              throw Error(ErrorCode::kerFailedToReadImageData);
359
39
            if (bufRead != rawData.size())
360
0
              throw Error(ErrorCode::kerInputDataReadFailed);
361
39
            xmpPacket_.assign(rawData.c_str(), rawData.size());
362
363
39
            if (auto idx = xmpPacket_.find_first_of('<'); idx != std::string::npos && idx > 0) {
364
24
#ifndef SUPPRESS_WARNINGS
365
24
              EXV_WARNING << "Removing " << static_cast<uint32_t>(idx)
366
24
                          << " characters from the beginning of the XMP packet" << '\n';
367
24
#endif
368
24
              xmpPacket_ = xmpPacket_.substr(idx);
369
24
            }
370
371
39
            if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) {
372
32
#ifndef SUPPRESS_WARNINGS
373
32
              EXV_WARNING << "Failed to decode XMP metadata." << '\n';
374
32
#endif
375
32
            }
376
39
          }
377
71
        }
378
71
        break;
379
71
      }
380
381
261
      default:
382
261
        break;
383
817
    }
384
590
    lastBoxTypeRead = box.type;
385
386
    // Move to the next box.
387
590
    io_->seek(static_cast<int64_t>(position - boxHSize + box.length), BasicIo::beg);
388
590
    if (io_->error())
389
0
      throw Error(ErrorCode::kerFailedToReadImageData);
390
590
  }
391
392
262
}  // Jp2Image::readMetadata
393
394
0
void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
395
0
  if (io_->open() != 0)
396
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
397
398
0
  if (!isJp2Type(*io_, false)) {
399
0
    throw Error(ErrorCode::kerNotAJpeg);
400
0
  }
401
402
  // According to the JP2 standard: The start of the first box shall be the first byte of the file, and the
403
  // last byte of the last box shall be the last byte of the file.
404
405
0
  bool bPrint = option == kpsBasic || option == kpsRecursive;
406
0
  bool bRecursive = option == kpsRecursive;
407
0
  bool bICC = option == kpsIccProfile;
408
0
  bool bXMP = option == kpsXMP;
409
0
  bool bIPTCErase = option == kpsIptcErase;
410
0
  bool boxSignatureFound = false;
411
412
0
  if (bPrint) {
413
0
    out << "STRUCTURE OF JPEG2000 FILE: " << io_->path() << '\n';
414
0
    out << " address |   length | box       | data" << '\n';
415
0
  }
416
417
0
  if (bPrint || bXMP || bICC || bIPTCErase) {
418
0
    Internal::Jp2BoxHeader box = {1, 1};
419
0
    Internal::Jp2BoxHeader subBox = {1, 1};
420
0
    Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
421
0
    bool bLF = false;
422
423
0
    while (box.length && box.type != kJp2BoxType::Close &&
424
0
           io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) {
425
0
      const size_t position = io_->tell();
426
0
      box.length = getULong(reinterpret_cast<byte*>(&box.length), bigEndian);
427
0
      box.type = getULong(reinterpret_cast<byte*>(&box.type), bigEndian);
428
0
      Internal::enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
429
430
0
      if (bPrint) {
431
0
        out << stringFormat("{:8} | {:8} | {}      | ", position - boxHSize, box.length, toAscii(box.type));
432
0
        bLF = true;
433
0
        if (box.type == kJp2BoxType::Close)
434
0
          lf(out, bLF);
435
0
      }
436
0
      if (box.type == kJp2BoxType::Close)
437
0
        break;
438
439
0
      switch (box.type) {
440
0
        case kJp2BoxType::Signature: {
441
0
          if (boxSignatureFound)  // Only one is allowed
442
0
            throw Error(ErrorCode::kerCorruptedMetadata);
443
0
          boxSignatureFound = true;
444
0
          break;
445
0
        }
446
0
        case kJp2BoxType::FileTypeBox: {
447
          // This box shall immediately follow the JPEG 2000 Signature box
448
          /// \todo  All files shall contain one and only one File Type box.
449
0
          Internal::enforce(box.length >= boxHSize, ErrorCode::kerCorruptedMetadata);
450
0
          Blob boxData(box.length - boxHSize);
451
0
          io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata);
452
0
          if (!Internal::isValidBoxFileType(boxData))
453
0
            throw Error(ErrorCode::kerCorruptedMetadata);
454
0
          break;
455
0
        }
456
0
        case kJp2BoxType::Header: {
457
0
          lf(out, bLF);
458
          /// \todo  All files shall contain one and only one Header box.
459
460
0
          while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize &&
461
0
                 io_->tell() < position + box.length)  // don't read beyond the box!
462
0
          {
463
0
            const size_t address = io_->tell() - boxHSize;
464
0
            subBox.length = getULong(reinterpret_cast<byte*>(&subBox.length), bigEndian);
465
0
            subBox.type = getULong(reinterpret_cast<byte*>(&subBox.type), bigEndian);
466
467
0
            if (subBox.length < boxHSize || subBox.length > io_->size() - io_->tell()) {
468
0
              throw Error(ErrorCode::kerCorruptedMetadata);
469
0
            }
470
471
0
            DataBuf data(subBox.length - boxHSize);
472
0
            io_->read(data.data(), data.size());
473
0
            if (bPrint) {
474
0
              out << stringFormat("{:8} | {:8} |  sub:{} | ", address, subBox.length, toAscii(subBox.type))
475
0
                  << Internal::binaryToString(makeSlice(data, 0, std::min<size_t>(30, data.size())));
476
0
              bLF = true;
477
0
            }
478
479
0
            if (subBox.type == kJp2BoxType::ImageHeader) {
480
0
              Internal::enforce(subBox.length == 22, ErrorCode::kerCorruptedMetadata);
481
              // height (4), width (4), componentsCount (2), bpc (1)
482
0
              auto compressionType = data.read_uint8(11);
483
0
              auto unkC = data.read_uint8(12);
484
0
              auto ipr = data.read_uint8(13);
485
0
              if (compressionType != 7 || unkC > 1 || ipr > 1) {
486
0
                throw Error(ErrorCode::kerCorruptedMetadata);
487
0
              }
488
0
            } else if (subBox.type == kJp2BoxType::ColorSpec) {
489
0
              const size_t pad = 3;  // don't know why there are 3 padding bytes
490
491
              // Bounds-check for the `getULong()` below, which reads 4 bytes, starting at `pad`.
492
0
              Internal::enforce(data.size() >= pad + 4, ErrorCode::kerCorruptedMetadata);
493
494
              /// \todo A conforming JP2 reader shall ignore all Colour Specification boxes after the first.
495
0
              auto METH = data.read_uint8(0);
496
              // auto PREC = data.read_uint8(1);
497
              // auto APPROX = data.read_uint8(2);
498
0
              if (METH == 1) {  // Enumerated Colourspace
499
0
                auto enumCS = data.read_uint32(3, bigEndian);
500
0
                if (enumCS != 16 && enumCS != 17) {
501
0
                  throw Error(ErrorCode::kerCorruptedMetadata);
502
0
                }
503
0
              } else {  // Restricted ICC Profile
504
                        // see the ICC Profile Format Specification, version ICC.1:1998-09
505
0
                const size_t iccLength = data.read_uint32(pad, bigEndian);
506
0
                if (bPrint) {
507
0
                  out << " | iccLength:" << iccLength;
508
0
                }
509
0
                Internal::enforce(iccLength <= data.size() - pad, ErrorCode::kerCorruptedMetadata);
510
0
                if (bICC) {
511
0
                  out.write(data.c_str(pad), iccLength);
512
0
                }
513
0
              }
514
0
            }
515
0
            lf(out, bLF);
516
0
          }
517
0
        } break;
518
519
0
        case kJp2BoxType::Uuid: {
520
0
          if (io_->read(reinterpret_cast<byte*>(&uuid), sizeof(uuid)) == sizeof(uuid)) {
521
0
            bool bIsExif = uuid.uuid == kJp2UuidExif;
522
0
            bool bIsIPTC = uuid.uuid == kJp2UuidIptc;
523
0
            bool bIsXMP = uuid.uuid == kJp2UuidXmp;
524
525
0
            bool bUnknown = !(bIsExif || bIsIPTC || bIsXMP);
526
527
0
            if (bPrint) {
528
0
              if (bIsExif)
529
0
                out << "Exif: ";
530
0
              if (bIsIPTC)
531
0
                out << "IPTC: ";
532
0
              if (bIsXMP)
533
0
                out << "XMP : ";
534
0
              if (bUnknown)
535
0
                out << "????: ";
536
0
            }
537
538
0
            DataBuf rawData;
539
0
            Internal::enforce(box.length >= sizeof(uuid) + boxHSize, ErrorCode::kerCorruptedMetadata);
540
0
            rawData.alloc(box.length - sizeof(uuid) - boxHSize);
541
0
            const size_t bufRead = io_->read(rawData.data(), rawData.size());
542
0
            if (io_->error())
543
0
              throw Error(ErrorCode::kerFailedToReadImageData);
544
0
            if (bufRead != rawData.size())
545
0
              throw Error(ErrorCode::kerInputDataReadFailed);
546
547
0
            if (bPrint) {
548
0
              out << Internal::binaryToString(makeSlice(rawData, 0, std::min<size_t>(40, rawData.size())));
549
0
              out.flush();
550
0
            }
551
0
            lf(out, bLF);
552
553
0
            if (bIsExif && bRecursive && rawData.size() > 8) {  // "II*\0long"
554
0
              const char a = rawData.read_uint8(0);
555
0
              const char b = rawData.read_uint8(1);
556
0
              if (a == b && (a == 'I' || a == 'M')) {
557
0
                MemIo p(rawData.c_data(), rawData.size());
558
0
                printTiffStructure(p, out, option, depth + 1);
559
0
              }
560
0
            }
561
562
0
            if (bIsIPTC && bRecursive) {
563
0
              IptcData::printStructure(out, makeSlice(rawData, 0, rawData.size()), depth);
564
0
            }
565
566
0
            if (bIsXMP && bXMP) {
567
0
              out.write(rawData.c_str(), rawData.size());
568
0
            }
569
0
          }
570
0
        } break;
571
572
0
        default:
573
0
          break;
574
0
      }
575
576
      // Move to the next box.
577
0
      io_->seek(static_cast<int64_t>(position - boxHSize + box.length), BasicIo::beg);
578
0
      if (io_->error())
579
0
        throw Error(ErrorCode::kerFailedToReadImageData);
580
0
      if (bPrint)
581
0
        lf(out, bLF);
582
0
    }
583
0
  }
584
0
}
585
586
0
void Jp2Image::writeMetadata() {
587
0
  if (io_->open() != 0) {
588
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
589
0
  }
590
0
  IoCloser closer(*io_);
591
0
  MemIo tempIo;
592
593
0
  doWriteMetadata(tempIo);  // may throw
594
0
  io_->close();
595
0
  io_->transfer(tempIo);  // may throw
596
597
0
}  // Jp2Image::writeMetadata
598
599
0
void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
600
0
  DataBuf output(boxBuf.size() + iccProfile_.size() + 100);  // allocate sufficient space
601
0
  size_t outlen = boxHSize;                                  // now many bytes have we written to output?
602
0
  size_t inlen = boxHSize;                                   // how many bytes have we read from boxBuf?
603
0
  Internal::enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata);
604
0
  uint32_t length = getULong(boxBuf.c_data(0), bigEndian);
605
0
  Internal::enforce(length <= output.size(), ErrorCode::kerCorruptedMetadata);
606
0
  uint32_t count = boxHSize;
607
0
  bool bWroteColor = false;
608
609
0
  while (count < length && !bWroteColor) {
610
0
    Internal::enforce(boxHSize <= length - count, ErrorCode::kerCorruptedMetadata);
611
0
    Internal::Jp2BoxHeader subBox;
612
0
    std::memcpy(&subBox, boxBuf.c_data(count), boxHSize);
613
0
    Internal::Jp2BoxHeader newBox = subBox;
614
615
0
    if (count < length) {
616
0
      subBox.length = getULong(boxBuf.c_data(count), bigEndian);
617
0
      subBox.type = getULong(boxBuf.c_data(count + 4), bigEndian);
618
#ifdef EXIV2_DEBUG_MESSAGES
619
      std::cout << "Jp2Image::encodeJp2Header subbox: " << toAscii(subBox.type) << " length = " << subBox.length
620
                << '\n';
621
#endif
622
0
      Internal::enforce(subBox.length > 0, ErrorCode::kerCorruptedMetadata);
623
0
      Internal::enforce(subBox.length <= length - count, ErrorCode::kerCorruptedMetadata);
624
0
      count += subBox.length;
625
0
      newBox.type = subBox.type;
626
0
    } else {
627
0
      subBox.length = 0;
628
0
      newBox.type = kJp2BoxType::ColorSpec;
629
0
      count = length;
630
0
    }
631
632
0
    size_t newlen = subBox.length;
633
0
    if (newBox.type == kJp2BoxType::ColorSpec) {
634
0
      bWroteColor = true;
635
0
      if (!iccProfileDefined()) {
636
0
        const char* pad = "\x01\x00\x00\x00\x00\x00\x10\x00\x00\x05\x1cuuid";
637
0
        uint32_t psize = 15;
638
0
        Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
639
0
        ul2Data(reinterpret_cast<byte*>(&newBox.length), psize, bigEndian);
640
0
        ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian);
641
0
        std::memcpy(output.data(outlen), &newBox, sizeof(newBox));
642
0
        std::memcpy(output.data(outlen) + sizeof(newBox), pad, psize);
643
0
      } else {
644
0
        const char* pad = "\x02\x00\x00";
645
0
        uint32_t psize = 3;
646
0
        newlen = sizeof(newBox) + psize + iccProfile_.size();
647
0
        Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
648
0
        ul2Data(reinterpret_cast<byte*>(&newBox.length), static_cast<uint32_t>(newlen), bigEndian);
649
0
        ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian);
650
0
        std::memcpy(output.data(outlen), &newBox, sizeof(newBox));
651
0
        std::memcpy(output.data(outlen) + sizeof(newBox), pad, psize);
652
0
        std::copy(iccProfile_.begin(), iccProfile_.end(), output.begin() + outlen + sizeof(newBox) + psize);
653
0
      }
654
0
    } else {
655
0
      Internal::enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
656
0
      std::copy_n(boxBuf.begin() + inlen, subBox.length, output.begin() + outlen);
657
0
    }
658
659
0
    outlen += newlen;
660
0
    inlen += subBox.length;
661
0
  }
662
663
  // allocate the correct number of bytes, copy the data and update the box header
664
0
  outBuf.alloc(outlen);
665
0
  std::copy_n(output.begin(), outlen, outBuf.begin());
666
0
  ul2Data(outBuf.data(0), static_cast<uint32_t>(outlen), bigEndian);
667
0
  ul2Data(outBuf.data(4), kJp2BoxType::Header, bigEndian);
668
0
}
669
670
0
void Jp2Image::doWriteMetadata(BasicIo& outIo) {
671
0
  if (!io_->isopen())
672
0
    throw Error(ErrorCode::kerInputDataReadFailed);
673
0
  if (!outIo.isopen())
674
0
    throw Error(ErrorCode::kerImageWriteFailed);
675
676
#ifdef EXIV2_DEBUG_MESSAGES
677
  std::cout << "Exiv2::Jp2Image::doWriteMetadata: Writing JPEG-2000 file " << io_->path() << '\n';
678
  std::cout << "Exiv2::Jp2Image::doWriteMetadata: tmp file created " << outIo.path() << '\n';
679
#endif
680
681
  // Ensure that this is the correct image type
682
0
  if (!isJp2Type(*io_, true)) {
683
0
    throw Error(ErrorCode::kerNoImageInInputData);
684
0
  }
685
686
  // Write JPEG2000 Signature (This is the 1st box)
687
0
  if (outIo.write(Jp2Signature.data(), Jp2Signature.size()) != 12)
688
0
    throw Error(ErrorCode::kerImageWriteFailed);
689
690
#ifdef EXIV2_DEBUG_MESSAGES
691
  std::cout << "Jp2Image::doWriteMetadata: JPEG 2000 Signature box written" << '\n';
692
#endif
693
694
0
  Internal::Jp2BoxHeader box = {0, 0};
695
696
0
  std::array<byte, 4> boxDataSize;
697
0
  std::array<byte, 4> boxUUIDtype;
698
0
  DataBuf bheaderBuf(8);
699
700
0
  while (io_->tell() < io_->size()) {
701
#ifdef EXIV2_DEBUG_MESSAGES
702
    std::cout << "Exiv2::Jp2Image::doWriteMetadata: Position: " << io_->tell() << " / " << io_->size() << '\n';
703
#endif
704
705
    // Read chunk header.
706
0
    io_->readOrThrow(bheaderBuf.data(), bheaderBuf.size(), ErrorCode::kerInputDataReadFailed);
707
708
    // Decode box header.
709
0
    box.length = bheaderBuf.read_uint32(0, bigEndian);
710
0
    box.type = bheaderBuf.read_uint32(4, bigEndian);
711
712
#ifdef EXIV2_DEBUG_MESSAGES
713
    std::cout << "Exiv2::Jp2Image::doWriteMetadata: box type: " << toAscii(box.type) << " length: " << box.length
714
              << '\n';
715
#endif
716
717
0
    if (box.length == 0) {
718
#ifdef EXIV2_DEBUG_MESSAGES
719
      std::cout << "Exiv2::Jp2Image::doWriteMetadata: Null Box size has been found. "
720
                   "This is the last box of file."
721
                << '\n';
722
#endif
723
0
      box.length = static_cast<uint32_t>(io_->size() - io_->tell() + 8);
724
0
    }
725
0
    if (box.length < 8) {
726
      // box is broken, so there is nothing we can do here
727
0
      throw Error(ErrorCode::kerCorruptedMetadata);
728
0
    }
729
730
    // Prevent a malicious file from causing a large memory allocation.
731
0
    Internal::enforce(box.length - 8 <= io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
732
733
    // Read whole box : Box header + Box data (not fixed size - can be null).
734
0
    DataBuf boxBuf(box.length);                                       // Box header (8 bytes) + box data.
735
0
    std::copy(bheaderBuf.begin(), bheaderBuf.end(), boxBuf.begin());  // Copy header.
736
0
    io_->readOrThrow(boxBuf.data(8), box.length - 8, ErrorCode::kerInputDataReadFailed);  // Extract box data.
737
738
0
    switch (box.type) {
739
0
      case kJp2BoxType::Header: {
740
0
        DataBuf newBuf;
741
0
        encodeJp2Header(boxBuf, newBuf);
742
#ifdef EXIV2_DEBUG_MESSAGES
743
        std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write JP2Header box (length: " << box.length << ")\n";
744
#endif
745
0
        if (outIo.write(newBuf.data(), newBuf.size()) != newBuf.size())
746
0
          throw Error(ErrorCode::kerImageWriteFailed);
747
748
        // Write all updated metadata here, just after JP2Header.
749
750
0
        if (!exifData_.empty()) {
751
          // Update Exif data to a new UUID box
752
753
0
          Blob blob;
754
0
          ExifParser::encode(blob, littleEndian, exifData_);
755
0
          if (!blob.empty()) {
756
0
            DataBuf rawExif(blob.size());
757
0
            std::copy(blob.begin(), blob.end(), rawExif.begin());
758
759
0
            DataBuf boxData(8 + 16 + rawExif.size());
760
0
            ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian);
761
0
            ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian);
762
0
            std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin());
763
0
            std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4);
764
0
            std::copy(kJp2UuidExif.begin(), kJp2UuidExif.end(), boxData.begin() + 8);
765
0
            std::copy(rawExif.begin(), rawExif.end(), boxData.begin() + 8 + 16);
766
767
#ifdef EXIV2_DEBUG_MESSAGES
768
            std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Exif metadata (length: " << boxData.size()
769
                      << '\n';
770
#endif
771
0
            if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size())
772
0
              throw Error(ErrorCode::kerImageWriteFailed);
773
0
          }
774
0
        }
775
776
0
        if (!iptcData_.empty()) {
777
          // Update Iptc data to a new UUID box
778
779
0
          DataBuf rawIptc = IptcParser::encode(iptcData_);
780
0
          if (!rawIptc.empty()) {
781
0
            DataBuf boxData(8 + 16 + rawIptc.size());
782
0
            ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian);
783
0
            ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian);
784
0
            std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin());
785
0
            std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4);
786
0
            std::copy(kJp2UuidIptc.begin(), kJp2UuidIptc.end(), boxData.begin() + 8);
787
0
            std::copy(rawIptc.begin(), rawIptc.end(), boxData.begin() + 8 + 16);
788
789
#ifdef EXIV2_DEBUG_MESSAGES
790
            std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with Iptc metadata (length: " << boxData.size()
791
                      << '\n';
792
#endif
793
0
            if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size())
794
0
              throw Error(ErrorCode::kerImageWriteFailed);
795
0
          }
796
0
        }
797
798
0
        if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) {
799
0
#ifndef SUPPRESS_WARNINGS
800
0
          EXV_ERROR << "Failed to encode XMP metadata." << '\n';
801
0
#endif
802
0
        }
803
0
        if (!xmpPacket_.empty()) {
804
          // Update Xmp data to a new UUID box
805
806
0
          DataBuf xmp(reinterpret_cast<const byte*>(xmpPacket_.data()), xmpPacket_.size());
807
0
          DataBuf boxData(8 + 16 + xmp.size());
808
0
          ul2Data(boxDataSize.data(), static_cast<uint32_t>(boxData.size()), bigEndian);
809
0
          ul2Data(boxUUIDtype.data(), kJp2BoxType::Uuid, bigEndian);
810
0
          std::copy(boxDataSize.begin(), boxDataSize.end(), boxData.begin());
811
0
          std::copy(boxUUIDtype.begin(), boxUUIDtype.end(), boxData.begin() + 4);
812
0
          std::copy(kJp2UuidXmp.begin(), kJp2UuidXmp.end(), boxData.begin() + 8);
813
0
          std::copy(xmp.begin(), xmp.end(), boxData.begin() + 8 + 16);
814
815
#ifdef EXIV2_DEBUG_MESSAGES
816
          std::cout << "Exiv2::Jp2Image::doWriteMetadata: Write box with XMP metadata (length: " << boxData.size()
817
                    << ")" << '\n';
818
#endif
819
0
          if (outIo.write(boxData.c_data(), boxData.size()) != boxData.size())
820
0
            throw Error(ErrorCode::kerImageWriteFailed);
821
0
        }
822
823
0
        break;
824
0
      }
825
826
0
      case kJp2BoxType::Uuid: {
827
0
        Internal::enforce(boxBuf.size() >= 24, ErrorCode::kerCorruptedMetadata);
828
0
        if (boxBuf.cmpBytes(8, kJp2UuidExif.data(), 16) == 0) {
829
#ifdef EXIV2_DEBUG_MESSAGES
830
          std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Exif Uuid box" << '\n';
831
#endif
832
0
        } else if (boxBuf.cmpBytes(8, kJp2UuidIptc.data(), 16) == 0) {
833
#ifdef EXIV2_DEBUG_MESSAGES
834
          std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Iptc Uuid box" << '\n';
835
#endif
836
0
        } else if (boxBuf.cmpBytes(8, kJp2UuidXmp.data(), 16) == 0) {
837
#ifdef EXIV2_DEBUG_MESSAGES
838
          std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Xmp Uuid box" << '\n';
839
#endif
840
0
        } else {
841
#ifdef EXIV2_DEBUG_MESSAGES
842
          std::cout << "Exiv2::Jp2Image::doWriteMetadata: write Uuid box (length: " << box.length << ")" << '\n';
843
#endif
844
0
          if (outIo.write(boxBuf.c_data(), boxBuf.size()) != boxBuf.size())
845
0
            throw Error(ErrorCode::kerImageWriteFailed);
846
0
        }
847
0
        break;
848
0
      }
849
850
0
      default: {
851
#ifdef EXIV2_DEBUG_MESSAGES
852
        std::cout << "Exiv2::Jp2Image::doWriteMetadata: write box (length: " << box.length << ")" << '\n';
853
#endif
854
0
        if (outIo.write(boxBuf.c_data(), boxBuf.size()) != boxBuf.size())
855
0
          throw Error(ErrorCode::kerImageWriteFailed);
856
857
0
        break;
858
0
      }
859
0
    }
860
0
  }
861
862
#ifdef EXIV2_DEBUG_MESSAGES
863
  std::cout << "Exiv2::Jp2Image::doWriteMetadata: EOF" << '\n';
864
#endif
865
866
0
}  // Jp2Image::doWriteMetadata
867
868
// *************************************************************************
869
// free functions
870
268
Image::UniquePtr newJp2Instance(BasicIo::UniquePtr io, const ImageCtorParams& params) {
871
268
  auto image = std::make_unique<Jp2Image>(std::move(io), params);
872
268
  if (!image->good()) {
873
6
    return nullptr;
874
6
  }
875
262
  return image;
876
268
}
877
878
9.71k
bool isJp2Type(BasicIo& iIo, bool advance) {
879
9.71k
  std::array<byte, Jp2Signature.size()> buf;
880
9.71k
  const size_t bytesRead = iIo.read(buf.data(), Jp2Signature.size());
881
9.71k
  if (iIo.error() || iIo.eof() || bytesRead != Jp2Signature.size()) {
882
252
    return false;
883
252
  }
884
9.46k
  bool matched = buf == Jp2Signature;
885
9.46k
  if (!advance || !matched) {
886
9.46k
    iIo.seek(-static_cast<int64_t>(Jp2Signature.size()), BasicIo::cur);  // Return to original position
887
9.46k
  }
888
9.46k
  return matched;
889
9.71k
}
890
}  // namespace Exiv2