Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/jpgimage.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "jpgimage.hpp"
5
#include "basicio.hpp"
6
#include "config.h"
7
#include "enforce.hpp"
8
#include "error.hpp"
9
#include "futils.hpp"
10
#include "helper_functions.hpp"
11
#include "i18n.h"  // NLS support.
12
#include "image_int.hpp"
13
#include "photoshop.hpp"
14
#include "tags_int.hpp"
15
16
#include <array>
17
#include <iostream>
18
19
// *****************************************************************************
20
// class member definitions
21
22
namespace Exiv2 {
23
24
enum markers : byte {
25
  // JPEG Segment markers (The first byte is always 0xFF, the value of these constants correspond to the 2nd byte)
26
  sos_ = 0xda,    //!< JPEG SOS marker
27
  app0_ = 0xe0,   //!< JPEG APP0 marker
28
  app1_ = 0xe1,   //!< JPEG APP1 marker
29
  app2_ = 0xe2,   //!< JPEG APP2 marker
30
  app13_ = 0xed,  //!< JPEG APP13 marker
31
  com_ = 0xfe,    //!< JPEG Comment marker
32
33
  // Markers without payload
34
  soi_ = 0xd8,   //!< SOI marker
35
  eoi_ = 0xd9,   //!< JPEG EOI marker
36
  rst1_ = 0xd0,  //!< JPEG Restart 0 Marker (from 0xD0 to 0xD7 there might be 8 of these markers)
37
38
  // Start of Frame markers, nondifferential Huffman-coding frames
39
  sof0_ = 0xc0,  //!< JPEG Start-Of-Frame marker
40
  sof1_ = 0xc1,  //!< JPEG Start-Of-Frame marker
41
  sof2_ = 0xc2,  //!< JPEG Start-Of-Frame marker
42
  sof3_ = 0xc3,  //!< JPEG Start-Of-Frame marker
43
44
  // Start of Frame markers, differential Huffman-coding frames
45
  sof5_ = 0xc5,  //!< JPEG Start-Of-Frame marker
46
  sof6_ = 0xc6,  //!< JPEG Start-Of-Frame marker
47
  sof7_ = 0xc6,  //!< JPEG Start-Of-Frame marker
48
49
  // Start of Frame markers, differential arithmetic-coding frames
50
  sof9_ = 0xc9,   //!< JPEG Start-Of-Frame marker
51
  sof10_ = 0xca,  //!< JPEG Start-Of-Frame marker
52
  sof11_ = 0xcb,  //!< JPEG Start-Of-Frame marker
53
  sof13_ = 0xcd,  //!< JPEG Start-Of-Frame marker
54
  sof14_ = 0xce,  //!< JPEG Start-Of-Frame marker
55
  sof15_ = 0xcf,  //!< JPEG Start-Of-Frame marker
56
};
57
58
using Exiv2::Internal::enforce;
59
namespace {
60
// JPEG process SOF markers
61
constexpr Internal::TagDetails jpegProcessMarkerTags[] = {
62
    {sof0_, N_("Baseline DCT, Huffman coding")},
63
    {sof1_, N_("Extended sequential DCT, Huffman coding")},
64
    {sof2_, N_("Progressive DCT, Huffman coding")},
65
    {sof3_, N_("Lossless, Huffman coding")},
66
    {sof5_, N_("Sequential DCT, differential Huffman coding")},
67
    {sof6_, N_("Progressive DCT, differential Huffman coding")},
68
    {sof7_, N_("Lossless, Differential Huffman coding")},
69
    {sof9_, N_("Extended sequential DCT, arithmetic coding")},
70
    {sof10_, N_("Progressive DCT, arithmetic coding")},
71
    {sof11_, N_("Lossless, arithmetic coding")},
72
    {sof13_, N_("Sequential DCT, differential arithmetic coding")},
73
    {sof14_, N_("Progressive DCT, differential arithmetic coding")},
74
    {sof15_, N_("Lossless, differential arithmetic coding")},
75
};
76
77
constexpr std::array<byte, 6> exifId_{
78
    'E', 'x', 'i', 'f', '\0', '\0',
79
};  //!< Exif identifier
80
constexpr std::array<byte, 29> xmpId_{
81
    'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
82
    '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', 0x0,
83
};  //!< XMP packet identifier
84
// constexpr auto jfifId_ = "JFIF";     //!< JFIF identifier
85
constexpr auto iccId_ = "ICC_PROFILE";  //!< ICC profile identifier
86
87
89.6k
constexpr bool inRange(int lo, int value, int hi) {
88
89.6k
  return lo <= value && value <= hi;
89
89.6k
}
90
91
45.3k
constexpr bool inRange2(int value, int lo1, int hi1, int lo2, int hi2) {
92
45.3k
  return inRange(lo1, value, hi1) || inRange(lo2, value, hi2);
93
45.3k
}
94
95
/// @brief has the segment a non-zero payload?
96
/// @param m The marker at the start of a segment
97
/// @return true if the segment has a length field/payload
98
113k
bool markerHasLength(byte m) {
99
113k
  bool markerWithoutLength = m >= rst1_ && m <= eoi_;
100
113k
  return !markerWithoutLength;
101
113k
}
102
103
113k
std::pair<std::array<byte, 2>, uint16_t> readSegmentSize(const byte marker, BasicIo& io) {
104
113k
  std::array<byte, 2> buf{0, 0};  // 2-byte buffer for reading the size.
105
113k
  uint16_t size{0};               // Size of the segment, including the 2-byte size field
106
113k
  if (markerHasLength(marker)) {
107
105k
    io.readOrThrow(buf.data(), buf.size(), ErrorCode::kerFailedToReadImageData);
108
105k
    size = getUShort(buf.data(), bigEndian);
109
105k
    enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
110
105k
  }
111
113k
  return {buf, size};
112
113k
}
113
}  // namespace
114
115
JpegBase::JpegBase(ImageType type, BasicIo::UniquePtr io, const ImageCtorParams& params, const byte initData[],
116
                   size_t dataSize) :
117
36.7k
    Image(type, mdExif | mdIptc | mdXmp | mdComment, std::move(io), params) {
118
36.7k
  if (params.create()) {
119
0
    initImage(initData, dataSize);
120
0
  }
121
36.7k
}
122
123
0
int JpegBase::initImage(const byte initData[], size_t dataSize) {
124
0
  if (io_->open() != 0) {
125
0
    return 4;
126
0
  }
127
0
  IoCloser closer(*io_);
128
0
  if (io_->write(initData, dataSize) != dataSize) {
129
0
    return 4;
130
0
  }
131
0
  return 0;
132
0
}
133
134
136k
byte JpegBase::advanceToMarker(ErrorCode err) const {
135
136k
  int c = -1;
136
  // Skips potential padding between markers
137
4.71M
  while ((c = io_->getb()) != 0xff) {
138
4.60M
    if (c == EOF)
139
22.1k
      throw Error(err);
140
4.60M
  }
141
142
  // Markers can start with any number of 0xff
143
144k
  while ((c = io_->getb()) == 0xff) {
144
30.6k
  }
145
114k
  if (c == EOF)
146
328
    throw Error(err);
147
148
113k
  return static_cast<byte>(c);
149
114k
}
150
151
36.7k
void JpegBase::readMetadata() {
152
36.7k
  int rc = 0;  // Todo: this should be the return value
153
154
36.7k
  if (io_->open() != 0)
155
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
156
36.7k
  IoCloser closer(*io_);
157
  // Ensure that this is the correct image type
158
36.7k
  if (!isThisType(*io_, true)) {
159
101
    if (io_->error() || io_->eof())
160
0
      throw Error(ErrorCode::kerFailedToReadImageData);
161
101
    throw Error(ErrorCode::kerNotAJpeg);
162
101
  }
163
36.6k
  clearMetadata();
164
36.6k
  int search = 6;  // Exif, ICC, XMP, Comment, IPTC, SOF
165
36.6k
  Blob psBlob;
166
36.6k
  bool foundCompletePsData = false;
167
36.6k
  bool foundExifData = false;
168
36.6k
  bool foundXmpData = false;
169
36.6k
  bool foundIccData = false;
170
171
  // Read section marker
172
36.6k
  byte marker = advanceToMarker(ErrorCode::kerNotAJpeg);
173
174
113k
  while (marker != sos_ && marker != eoi_ && search > 0) {
175
113k
    const auto [sizebuf, size] = readSegmentSize(marker, *io_);
176
177
    // Read the rest of the segment.
178
113k
    DataBuf buf(size);
179
    // check if the segment is not empty
180
113k
    if (size > 2) {
181
96.3k
      io_->readOrThrow(buf.data(2), size - 2, ErrorCode::kerFailedToReadImageData);
182
96.3k
      std::copy(sizebuf.begin(), sizebuf.end(), buf.begin());
183
96.3k
    }
184
185
113k
    if (auto itSofMarker = Exiv2::find(jpegProcessMarkerTags, marker)) {
186
4.60k
      sof_encoding_process_ = itSofMarker->label_;
187
4.60k
      if (size >= 7 && buf.c_data(7)) {
188
3.81k
        num_color_components_ = *buf.c_data(7);
189
3.81k
      }
190
4.60k
    }
191
192
113k
    if (!foundExifData && marker == app1_ && size >= 8  // prevent out-of-bounds read in memcmp on next line
193
36.8k
        && buf.cmpBytes(2, exifId_.data(), 6) == 0) {
194
21.1k
      const DecodeParams dp(max_recursion_depth_);
195
21.1k
      ByteOrder bo = ExifParser::decode(exifData_, buf.c_data(8), size - 8, dp);
196
21.1k
      setByteOrder(bo);
197
21.1k
      if (size > 8 && byteOrder() == invalidByteOrder) {
198
0
#ifndef SUPPRESS_WARNINGS
199
0
        EXV_WARNING << "Failed to decode Exif metadata.\n";
200
0
#endif
201
0
        exifData_.clear();
202
0
      }
203
21.1k
      --search;
204
21.1k
      foundExifData = true;
205
92.1k
    } else if (!foundXmpData && marker == app1_ && size >= 31  // prevent out-of-bounds read in memcmp on next line
206
16.5k
               && buf.cmpBytes(2, xmpId_.data(), 29) == 0) {
207
14.5k
      xmpPacket_.assign(buf.c_str(31), size - 31);
208
14.5k
      const DecodeParams dp(max_recursion_depth_);
209
14.5k
      if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_, dp)) {
210
14.2k
#ifndef SUPPRESS_WARNINGS
211
14.2k
        EXV_WARNING << "Failed to decode XMP metadata.\n";
212
14.2k
#endif
213
14.2k
      }
214
14.5k
      --search;
215
14.5k
      foundXmpData = true;
216
77.6k
    } else if (!foundCompletePsData && marker == app13_ &&
217
12.1k
               size >= 16  // prevent out-of-bounds read in memcmp on next line
218
11.2k
               && buf.cmpBytes(2, Photoshop::ps3Id_, 14) == 0) {
219
#ifdef EXIV2_DEBUG_MESSAGES
220
      std::cerr << "Found app13 segment, size = " << size << "\n";
221
#endif
222
8.36k
      if (buf.size() > 16) {  // Append to psBlob
223
7.69k
        append(psBlob, buf.c_data(16), size - 16);
224
7.69k
      }
225
      // Check whether psBlob is complete
226
8.36k
      if (!psBlob.empty() && Photoshop::valid(psBlob.data(), psBlob.size())) {
227
578
        --search;
228
578
        foundCompletePsData = true;
229
578
      }
230
69.2k
    } else if (marker == com_ && comment_.empty()) {
231
      // JPEGs can have multiple comments, but for now only read
232
      // the first one (most jpegs only have one anyway). Comments
233
      // are simple single byte ISO-8859-1 strings.
234
738
      comment_.assign(buf.c_str(2), size - 2);
235
3.79k
      while (!comment_.empty() && comment_.back() == '\0') {
236
3.06k
        comment_.pop_back();
237
3.06k
      }
238
738
      --search;
239
68.5k
    } else if (marker == app2_ && size >= 13  // prevent out-of-bounds read in memcmp on next line
240
1.35k
               && buf.cmpBytes(2, iccId_, 11) == 0) {
241
858
      if (size < 2 + 14 + 4) {
242
10
        rc = 8;
243
10
        break;
244
10
      }
245
      // ICC profile
246
848
      if (!foundIccData) {
247
171
        foundIccData = true;
248
171
        --search;
249
171
      }
250
848
      auto chunk = static_cast<int>(buf.read_uint8(2 + 12));
251
848
      auto chunks = static_cast<int>(buf.read_uint8(2 + 13));
252
      // ICC1v43_2010-12.pdf header is 14 bytes
253
      // header = "ICC_PROFILE\0" (12 bytes)
254
      // chunk/chunks are a single byte
255
      // Spec 7.2 Profile bytes 0-3 size
256
848
      uint32_t s = buf.read_uint32(2 + 14, bigEndian);
257
#ifdef EXIV2_DEBUG_MESSAGES
258
      std::cerr << "Found ICC Profile chunk " << chunk << " of " << chunks << (chunk == 1 ? " size: " : "")
259
                << (chunk == 1 ? s : 0) << '\n';
260
#endif
261
      // #1286 profile can be padded
262
848
      size_t icc_size = size - 2 - 14;
263
848
      if (chunk == 1 && chunks == 1) {
264
136
        enforce(s <= static_cast<uint32_t>(icc_size), ErrorCode::kerInvalidIccProfile);
265
136
        icc_size = s;
266
136
      }
267
268
848
      appendIccProfile(buf.c_data(2 + 14), icc_size, chunk == chunks);
269
67.6k
    } else if (pixelHeight_ == 0 && inRange2(marker, sof0_, sof3_, sof5_, sof15_)) {
270
      // We hit a SOFn (start-of-frame) marker
271
1.63k
      if (size < 8) {
272
53
        rc = 7;
273
53
        break;
274
53
      }
275
1.58k
      pixelHeight_ = buf.read_uint16(3, bigEndian);
276
1.58k
      pixelWidth_ = buf.read_uint16(5, bigEndian);
277
1.58k
      if (pixelHeight_ != 0)
278
990
        --search;
279
1.58k
    }
280
281
    // Read the beginning of the next segment
282
113k
    try {
283
113k
      marker = advanceToMarker(ErrorCode::kerFailedToReadImageData);
284
113k
    } catch (const Error&) {
285
22.3k
      rc = 5;
286
22.3k
      break;
287
22.3k
    }
288
113k
  }  // while there are segments to process
289
290
23.0k
  if (!psBlob.empty()) {
291
    // Find actual IPTC data within the psBlob
292
1.10k
    Blob iptcBlob;
293
1.10k
    const byte* record = nullptr;
294
1.10k
    uint32_t sizeIptc = 0;
295
1.10k
    uint32_t sizeHdr = 0;
296
1.10k
    const byte* pCur = psBlob.data();
297
1.10k
    const byte* pEnd = pCur + psBlob.size();
298
1.88k
    while (pCur < pEnd && 0 == Photoshop::locateIptcIrb(pCur, pEnd - pCur, &record, sizeHdr, sizeIptc)) {
299
#ifdef EXIV2_DEBUG_MESSAGES
300
      std::cerr << "Found IPTC IRB, size = " << sizeIptc << "\n";
301
#endif
302
783
      if (sizeIptc) {
303
762
        append(iptcBlob, record + sizeHdr, sizeIptc);
304
762
      }
305
783
      pCur = record + sizeHdr + sizeIptc + (sizeIptc & 1);
306
783
    }
307
1.10k
    if (!iptcBlob.empty() && IptcParser::decode(iptcData_, iptcBlob.data(), iptcBlob.size())) {
308
180
#ifndef SUPPRESS_WARNINGS
309
180
      EXV_WARNING << "Failed to decode IPTC metadata.\n";
310
180
#endif
311
180
      iptcData_.clear();
312
180
    }
313
1.10k
  }
314
315
23.0k
  if (rc != 0) {
316
22.4k
#ifndef SUPPRESS_WARNINGS
317
22.4k
    EXV_WARNING << "JPEG format error, rc = " << rc << "\n";
318
22.4k
#endif
319
22.4k
  }
320
23.0k
}  // JpegBase::readMetadata
321
322
#define REPORT_MARKER                                 \
323
0
  if ((option == kpsBasic || option == kpsRecursive)) \
324
0
  out << stringFormat("{:8} | 0xff{:02x} {:<5}", io_->tell() - 2, marker, nm[marker].c_str())
325
326
0
void JpegBase::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
327
0
  if (io_->open() != 0)
328
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
329
  // Ensure that this is the correct image type
330
0
  if (!isThisType(*io_, false)) {
331
0
    if (io_->error() || io_->eof())
332
0
      throw Error(ErrorCode::kerFailedToReadImageData);
333
0
    throw Error(ErrorCode::kerNotAJpeg);
334
0
  }
335
336
0
  bool bPrint = option == kpsBasic || option == kpsRecursive;
337
0
  std::vector<std::pair<size_t, size_t>> iptcDataSegs;
338
339
0
  if (bPrint || option == kpsXMP || option == kpsIccProfile || option == kpsIptcErase) {
340
    // mnemonic for markers
341
0
    std::string nm[256];
342
0
    nm[0xd8] = "SOI";
343
0
    nm[0xd9] = "EOI";
344
0
    nm[0xda] = "SOS";
345
0
    nm[0xdb] = "DQT";
346
0
    nm[0xdd] = "DRI";
347
0
    nm[0xfe] = "COM";
348
349
    // 0xe0 .. 0xef are APPn
350
    // 0xc0 .. 0xcf are SOFn (except 4)
351
0
    nm[0xc4] = "DHT";
352
0
    for (int i = 0; i <= 15; i++) {
353
0
      nm[0xe0 + i] = stringFormat("APP{}", i);
354
0
      if (i != 4) {
355
0
        nm[0xc0 + i] = stringFormat("SOF{}", i);
356
0
      }
357
0
    }
358
359
    // Container for the signature
360
0
    bool bExtXMP = false;
361
362
    // Read section marker
363
0
    byte marker = advanceToMarker(ErrorCode::kerNotAJpeg);
364
365
0
    bool done = false;
366
0
    bool first = true;
367
0
    while (!done) {
368
      // print marker bytes
369
0
      if (first && bPrint) {
370
0
        out << "STRUCTURE OF JPEG FILE: " << io_->path() << '\n';
371
0
        out << " address | marker       |  length | data" << '\n';
372
0
        REPORT_MARKER;
373
0
      }
374
0
      first = false;
375
0
      bool bLF = bPrint;
376
377
0
      const auto [sizebuf, size] = readSegmentSize(marker, *io_);
378
379
      // Read the rest of the segment if not empty.
380
0
      DataBuf buf(size);
381
0
      if (size > 2) {
382
0
        io_->readOrThrow(buf.data(2), size - 2, ErrorCode::kerFailedToReadImageData);
383
0
        std::copy(sizebuf.begin(), sizebuf.end(), buf.begin());
384
0
      }
385
386
0
      if (bPrint && markerHasLength(marker))
387
0
        out << stringFormat(" | {:7} ", size);
388
389
      // print signature for APPn
390
0
      if (marker >= app0_ && marker <= (app0_ | 0x0F)) {
391
        // http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf p75
392
0
        const std::string signature = string_from_unterminated(buf.c_str(2), size - 2);
393
394
        // 728 rmills@rmillsmbp:~/gnu/exiv2/ttt $ exiv2 -pS test/data/exiv2-bug922.jpg
395
        // STRUCTURE OF JPEG FILE: test/data/exiv2-bug922.jpg
396
        // address | marker     | length  | data
397
        //       0 | 0xd8 SOI   |       0
398
        //       2 | 0xe1 APP1  |     911 | Exif..MM.*.......%.........#....
399
        //     915 | 0xe1 APP1  |     870 | http://ns.adobe.com/xap/1.0/.<x:
400
        //    1787 | 0xe1 APP1  |   65460 | http://ns.adobe.com/xmp/extensio
401
0
        if (option == kpsXMP && signature.starts_with("http://ns.adobe.com/x")) {
402
          // extract XMP
403
0
          const char* xmp = buf.c_str();
404
0
          size_t start = 2;
405
406
          // http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
407
          // if we find HasExtendedXMP, set the flag and ignore this block
408
          // the first extended block is a copy of the Standard block.
409
          // a robust implementation allows extended blocks to be out of sequence
410
          // we could implement out of sequence with a dictionary of sequence/offset
411
          // and dumping the XMP in a post read operation similar to kpsIptcErase
412
          // for the moment, dumping 'on the fly' is working fine
413
0
          if (!bExtXMP) {
414
0
            while (start < size && xmp[start]) {
415
0
              start++;
416
0
            }
417
0
            start++;
418
0
            if (start < size) {
419
0
              const std::string xmp_from_start = string_from_unterminated(&xmp[start], size - start);
420
0
              if (xmp_from_start.find("HasExtendedXMP", start) != std::string::npos) {
421
0
                start = size;  // ignore this packet, we'll get on the next time around
422
0
                bExtXMP = true;
423
0
              }
424
0
            }
425
0
          } else {
426
0
            start = 2 + 35 + 32 + 4 + 4;  // Adobe Spec, p19
427
0
          }
428
429
0
          enforce(start <= size, ErrorCode::kerInvalidXmpText);
430
0
          out.write(&xmp[start], size - start);
431
0
          done = !bExtXMP;
432
0
        } else if (option == kpsIccProfile && signature == iccId_) {
433
          // extract ICCProfile
434
0
          if (size >= 16) {
435
0
            out.write(buf.c_str(16), size - 16);
436
#ifdef EXIV2_DEBUG_MESSAGES
437
            std::cout << "iccProfile size = " << size - 16 << '\n';
438
#endif
439
0
          }
440
0
        } else if (option == kpsIptcErase && signature == "Photoshop 3.0") {
441
          // delete IPTC data segment from JPEG
442
0
          iptcDataSegs.emplace_back(io_->tell() - size, io_->tell());
443
0
        } else if (bPrint) {
444
0
          const size_t start = 2;
445
0
          const auto end = std::min<size_t>(34, size);
446
0
          out << "| ";
447
0
          if (start < end)
448
0
            out << Internal::binaryToString(makeSlice(buf, start, end));
449
0
          if (signature == iccId_) {
450
            // extract the chunk information from the buffer
451
            //
452
            // the buffer looks like this in this branch
453
            // ICC_PROFILE\0AB
454
            // where A & B are bytes (the variables chunk & chunks)
455
            //
456
            // We cannot extract the variables A and B from the signature string, as they are beyond the
457
            // null termination (and signature ends there).
458
            // => Read the chunk info from the DataBuf directly
459
0
            enforce<std::out_of_range>(size >= 16, "Buffer too small to extract chunk information.");
460
0
            const int chunk = buf.read_uint8(2 + 12);
461
0
            const int chunks = buf.read_uint8(2 + 13);
462
0
            out << stringFormat(" chunk {}/{}", chunk, chunks);
463
0
          }
464
0
        }
465
466
        // for MPF: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/MPF.html
467
        // for FLIR: http://owl.phy.queensu.ca/~phil/exiftool/TagNames/FLIR.html
468
0
        bool bFlir = option == kpsRecursive && marker == (app0_ + 1) && signature == "FLIR";
469
0
        bool bExif = option == kpsRecursive && marker == (app0_ + 1) && signature == "Exif";
470
0
        bool bMPF = option == kpsRecursive && marker == (app0_ + 2) && signature == "MPF";
471
0
        bool bPS = option == kpsRecursive && signature == "Photoshop 3.0";
472
0
        if (bFlir || bExif || bMPF || bPS) {
473
          // extract Exif data block which is tiff formatted
474
0
          out << '\n';
475
476
          //                        const byte* exif = buf.c_data();
477
0
          uint32_t start = signature == "Exif" ? 8 : 6;
478
0
          uint32_t max = static_cast<uint32_t>(size) - 1;
479
480
          // is this an fff block?
481
0
          if (bFlir) {
482
0
            start = 2;
483
0
            bFlir = false;
484
0
            while (start + 3 <= max) {
485
0
              if (std::strcmp(buf.c_str(start), "FFF") == 0) {
486
0
                bFlir = true;
487
0
                break;
488
0
              }
489
0
              start++;
490
0
            }
491
0
          }
492
493
          // there is a header in FLIR, followed by a tiff block
494
          // Hunt down the tiff using brute force
495
0
          if (bFlir) {
496
            // FLIRFILEHEAD* pFFF = (FLIRFILEHEAD*) (exif+start) ;
497
0
            while (start < max) {
498
0
              if (buf.read_uint8(start) == 'I' && buf.read_uint8(start + 1) == 'I')
499
0
                break;
500
0
              if (buf.read_uint8(start) == 'M' && buf.read_uint8(start + 1) == 'M')
501
0
                break;
502
0
              start++;
503
0
            }
504
#ifdef EXIV2_DEBUG_MESSAGES
505
            if (start < max)
506
              std::cout << "  FFF start = " << start << '\n';
507
            // << " index = " << pFFF->dwIndexOff << '\n';
508
#endif
509
0
          }
510
511
0
          if (bPS) {
512
0
            IptcData::printStructure(out, makeSlice(buf, 0, size), depth);
513
0
          } else {
514
0
            if (start < max) {
515
              // create a copy on write memio object with the data, then print the structure
516
0
              MemIo p(buf.c_data(start), size - start);
517
0
              printTiffStructure(p, out, option, depth + 1);
518
0
            }
519
0
          }
520
521
          // restore and clean up
522
0
          bLF = false;
523
0
        }
524
0
      }
525
526
      // print COM marker
527
0
      if (bPrint && marker == com_) {
528
        // size includes 2 for the two bytes for size!
529
0
        const auto n = std::min<size_t>(32, size - 2);
530
        // start after the two bytes
531
0
        out << "| "
532
0
            << Internal::binaryToString(makeSlice(buf, 2, n + 2 /* cannot overflow as n is at most size - 2 */));
533
0
      }
534
535
0
      if (bLF)
536
0
        out << '\n';
537
538
0
      if (marker != sos_) {
539
        // Read the beginning of the next segment
540
0
        marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
541
0
        REPORT_MARKER;
542
0
      }
543
0
      done |= marker == eoi_ || marker == sos_;
544
0
      if (done && bPrint)
545
0
        out << '\n';
546
0
    }
547
0
  }
548
0
  if (option == kpsIptcErase && !iptcDataSegs.empty()) {
549
    // Add a sentinel to the end of iptcDataSegs
550
0
    iptcDataSegs.emplace_back(io_->size(), 0);
551
552
    // $ dd bs=1 skip=$((0)) count=$((13164)) if=ETH0138028.jpg of=E1.jpg
553
    // $ dd bs=1 skip=$((49304)) count=2000000  if=ETH0138028.jpg of=E2.jpg
554
    // cat E1.jpg E2.jpg > E.jpg
555
    // exiv2 -pS E.jpg
556
557
    // binary copy io_ to a temporary file
558
0
    MemIo tempIo;
559
0
    size_t start = 0;
560
0
    for (const auto& [l, s] : iptcDataSegs) {
561
0
      const size_t length = l - start;
562
0
      io_->seekOrThrow(start, BasicIo::beg, ErrorCode::kerFailedToReadImageData);
563
0
      DataBuf buf(length);
564
0
      io_->readOrThrow(buf.data(), buf.size(), ErrorCode::kerFailedToReadImageData);
565
0
      tempIo.write(buf.c_data(), buf.size());
566
0
      start = s + 2;  // skip the 2 byte marker
567
0
    }
568
569
0
    io_->seekOrThrow(0, BasicIo::beg, ErrorCode::kerFailedToReadImageData);
570
0
    io_->transfer(tempIo);  // may throw
571
0
    io_->seekOrThrow(0, BasicIo::beg, ErrorCode::kerFailedToReadImageData);
572
573
    // Check that the result is correctly formatted.
574
0
    readMetadata();
575
0
  }
576
0
}  // JpegBase::printStructure
577
578
0
void JpegBase::writeMetadata() {
579
0
  if (io_->open() != 0) {
580
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
581
0
  }
582
0
  IoCloser closer(*io_);
583
0
  MemIo tempIo;
584
585
0
  doWriteMetadata(tempIo);  // may throw
586
0
  io_->close();
587
0
  io_->transfer(tempIo);  // may throw
588
0
}
589
590
0
DataBuf JpegBase::readNextSegment(byte marker) {
591
0
  const auto [sizebuf, size] = readSegmentSize(marker, *io_);
592
593
  // Read the rest of the segment if not empty.
594
0
  DataBuf buf(size);
595
0
  if (size > 0) {
596
0
    std::copy(sizebuf.begin(), sizebuf.end(), buf.begin());
597
0
    if (size > 2) {
598
0
      io_->readOrThrow(buf.data(2), size - 2, ErrorCode::kerFailedToReadImageData);
599
0
    }
600
0
  }
601
0
  return buf;
602
0
}
603
604
0
void JpegBase::doWriteMetadata(BasicIo& outIo) {
605
0
  if (!io_->isopen())
606
0
    throw Error(ErrorCode::kerInputDataReadFailed);
607
0
  if (!outIo.isopen())
608
0
    throw Error(ErrorCode::kerImageWriteFailed);
609
610
  // Ensure that this is the correct image type
611
0
  if (!isThisType(*io_, true)) {
612
0
    if (io_->error() || io_->eof())
613
0
      throw Error(ErrorCode::kerInputDataReadFailed);
614
0
    throw Error(ErrorCode::kerNoImageInInputData);
615
0
  }
616
617
  // Used to initialize search variables such as skipCom.
618
0
  static const size_t notfound = std::numeric_limits<size_t>::max();
619
620
0
  const size_t seek = io_->tell();
621
0
  size_t count = 0;
622
0
  size_t search = 0;
623
0
  size_t insertPos = 0;
624
0
  size_t comPos = 0;
625
0
  size_t skipApp1Exif = notfound;
626
0
  size_t skipApp1Xmp = notfound;
627
0
  bool foundCompletePsData = false;
628
0
  bool foundIccData = false;
629
0
  std::vector<size_t> skipApp13Ps3;
630
0
  std::vector<size_t> skipApp2Icc;
631
0
  size_t skipCom = notfound;
632
0
  Blob psBlob;
633
0
  DataBuf rawExif;
634
0
  xmpData().usePacket(writeXmpFromPacket());
635
636
  // Write image header
637
0
  if (writeHeader(outIo))
638
0
    throw Error(ErrorCode::kerImageWriteFailed);
639
640
  // Read section marker
641
0
  byte marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
642
643
  // First find segments of interest. Normally app0 is first and we want
644
  // to insert after it. But if app0 comes after com, app1 and app13 then
645
  // don't bother.
646
0
  while (marker != sos_ && marker != eoi_ && search < 6) {
647
0
    DataBuf buf = readNextSegment(marker);
648
649
0
    if (marker == app0_) {
650
0
      insertPos = count + 1;
651
0
    } else if (skipApp1Exif == notfound && marker == app1_ &&
652
0
               buf.size() >= 8 &&  // prevent out-of-bounds read in memcmp on next line
653
0
               buf.cmpBytes(2, exifId_.data(), 6) == 0) {
654
0
      skipApp1Exif = count;
655
0
      ++search;
656
0
      if (buf.size() > 8) {
657
0
        rawExif.alloc(buf.size() - 8);
658
0
        std::copy_n(buf.begin() + 8, rawExif.size(), rawExif.begin());
659
0
      }
660
0
    } else if (skipApp1Xmp == notfound && marker == app1_ &&
661
0
               buf.size() >= 31 &&  // prevent out-of-bounds read in memcmp on next line
662
0
               buf.cmpBytes(2, xmpId_.data(), 29) == 0) {
663
0
      skipApp1Xmp = count;
664
0
      ++search;
665
0
    } else if (marker == app2_ && buf.size() >= 13 &&  // prevent out-of-bounds read in memcmp on next line
666
0
               buf.cmpBytes(2, iccId_, 11) == 0) {
667
0
      skipApp2Icc.push_back(count);
668
0
      if (!foundIccData) {
669
0
        ++search;
670
0
        foundIccData = true;
671
0
      }
672
0
    } else if (!foundCompletePsData && marker == app13_ &&
673
0
               buf.size() >= 16 &&  // prevent out-of-bounds read in memcmp on next line
674
0
               buf.cmpBytes(2, Photoshop::ps3Id_, 14) == 0) {
675
#ifdef EXIV2_DEBUG_MESSAGES
676
      std::cerr << "Found APP13 Photoshop PS3 segment\n";
677
#endif
678
0
      skipApp13Ps3.push_back(count);
679
      // Append to psBlob
680
0
      append(psBlob, buf.c_data(16), buf.size() - 16);
681
      // Check whether psBlob is complete
682
0
      if (!psBlob.empty() && Photoshop::valid(psBlob.data(), psBlob.size())) {
683
0
        foundCompletePsData = true;
684
0
      }
685
0
    } else if (marker == com_ && skipCom == notfound) {
686
      // Jpegs can have multiple comments, but for now only handle
687
      // the first one (most jpegs only have one anyway).
688
0
      skipCom = count;
689
0
      ++search;
690
0
    }
691
692
    // As in jpeg-6b/wrjpgcom.c:
693
    // We will insert the new comment marker just before SOFn.
694
    // This (a) causes the new comment to appear after, rather than before,
695
    // existing comments; and (b) ensures that comments come after any JFIF
696
    // or JFXX markers, as required by the JFIF specification.
697
0
    if (comPos == 0 && inRange2(marker, sof0_, sof3_, sof5_, sof15_)) {
698
0
      comPos = count;
699
0
      ++search;
700
0
    }
701
0
    marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
702
0
    ++count;
703
0
  }
704
705
0
  if (!foundCompletePsData && !psBlob.empty())
706
0
    throw Error(ErrorCode::kerNoImageInInputData);
707
0
  search += skipApp13Ps3.size() + skipApp2Icc.size();
708
709
0
  if (comPos == 0) {
710
0
    if (marker == eoi_)
711
0
      comPos = count;
712
0
    else
713
0
      comPos = insertPos;
714
0
    ++search;
715
0
  }
716
0
  if (!exifData_.empty())
717
0
    ++search;
718
0
  if (!writeXmpFromPacket() && !xmpData_.empty())
719
0
    ++search;
720
0
  if (writeXmpFromPacket() && !xmpPacket_.empty())
721
0
    ++search;
722
0
  if (foundCompletePsData || !iptcData_.empty())
723
0
    ++search;
724
0
  if (!comment_.empty())
725
0
    ++search;
726
727
0
  io_->seekOrThrow(seek, BasicIo::beg, ErrorCode::kerNoImageInInputData);
728
0
  count = 0;
729
0
  marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
730
731
  // To simplify this a bit, new segments are inserts at either the start
732
  // or right after app0. This is standard in most jpegs, but has the
733
  // potential to change segment ordering (which is allowed).
734
  // Segments are erased if there is no assigned metadata.
735
0
  while (marker != sos_ && search > 0) {
736
0
    DataBuf buf = readNextSegment(marker);
737
738
0
    if (insertPos == count) {
739
      // Write Exif data first so that - if there is no app0 - we
740
      // create "Exif images" according to the Exif standard.
741
0
      if (!exifData_.empty()) {
742
0
        Blob blob;
743
0
        ByteOrder bo = byteOrder();
744
0
        if (bo == invalidByteOrder) {
745
0
          bo = littleEndian;
746
0
          setByteOrder(bo);
747
0
        }
748
0
        const byte* pExifData = rawExif.c_data();
749
0
        size_t exifSize = rawExif.size();
750
0
        if (ExifParser::encode(blob, pExifData, exifSize, bo, exifData_) == wmIntrusive) {
751
0
          pExifData = !blob.empty() ? blob.data() : nullptr;
752
0
          exifSize = blob.size();
753
0
        }
754
0
        if (exifSize > 0) {
755
0
          std::array<byte, 10> tmpBuf;
756
          // Write APP1 marker, size of APP1 field, Exif id and Exif data
757
0
          tmpBuf[0] = 0xff;
758
0
          tmpBuf[1] = app1_;
759
760
0
          if (exifSize > 0xffff - 8)
761
0
            throw Error(ErrorCode::kerTooLargeJpegSegment, "Exif");
762
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(exifSize + 8), bigEndian);
763
0
          std::copy(exifId_.begin(), exifId_.end(), tmpBuf.begin() + 4);
764
0
          if (outIo.write(tmpBuf.data(), 10) != 10)
765
0
            throw Error(ErrorCode::kerImageWriteFailed);
766
767
          // Write new Exif data buffer
768
0
          if (outIo.write(pExifData, exifSize) != exifSize)
769
0
            throw Error(ErrorCode::kerImageWriteFailed);
770
0
          if (outIo.error())
771
0
            throw Error(ErrorCode::kerImageWriteFailed);
772
0
          --search;
773
0
        }
774
0
      }
775
0
      if (!writeXmpFromPacket() &&
776
0
          XmpParser::encode(xmpPacket_, xmpData_, XmpParser::useCompactFormat | XmpParser::omitAllFormatting) > 1) {
777
0
#ifndef SUPPRESS_WARNINGS
778
0
        EXV_ERROR << "Failed to encode XMP metadata.\n";
779
0
#endif
780
0
      }
781
0
      if (!xmpPacket_.empty()) {
782
0
        std::array<byte, 33> tmpBuf;
783
        // Write APP1 marker, size of APP1 field, XMP id and XMP packet
784
0
        tmpBuf[0] = 0xff;
785
0
        tmpBuf[1] = app1_;
786
787
0
        if (xmpPacket_.size() > 0xffff - 31)
788
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "XMP");
789
0
        us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(xmpPacket_.size() + 31), bigEndian);
790
0
        std::copy(xmpId_.begin(), xmpId_.end(), tmpBuf.begin() + 4);
791
0
        if (outIo.write(tmpBuf.data(), 33) != 33)
792
0
          throw Error(ErrorCode::kerImageWriteFailed);
793
794
        // Write new XMP packet
795
0
        if (outIo.write(reinterpret_cast<const byte*>(xmpPacket_.data()), xmpPacket_.size()) != xmpPacket_.size())
796
0
          throw Error(ErrorCode::kerImageWriteFailed);
797
0
        if (outIo.error())
798
0
          throw Error(ErrorCode::kerImageWriteFailed);
799
0
        --search;
800
0
      }
801
802
0
      if (iccProfileDefined()) {
803
0
        std::array<byte, 4> tmpBuf;
804
        // Write APP2 marker, size of APP2 field, and IccProfile
805
        // See comments in readMetadata() about the ICC embedding specification
806
0
        tmpBuf[0] = 0xff;
807
0
        tmpBuf[1] = app2_;
808
809
0
        const size_t chunk_size = (256 * 256) - 40;  // leave bytes for marker, header and padding
810
0
        size_t size = iccProfile_.size();
811
0
        if (size >= 255 * chunk_size)
812
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "IccProfile");
813
0
        const size_t chunks = 1 + ((size - 1) / chunk_size);
814
0
        for (size_t chunk = 0; chunk < chunks; chunk++) {
815
0
          auto bytes = std::min<size_t>(size, chunk_size);  // bytes to write
816
0
          size -= bytes;
817
818
          // write JPEG marker (2 bytes)
819
0
          if (outIo.write(tmpBuf.data(), 2) != 2)
820
0
            throw Error(ErrorCode::kerImageWriteFailed);  // JPEG Marker
821
          // write length (2 bytes).  length includes the 2 bytes for the length
822
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(2 + 14 + bytes), bigEndian);
823
0
          if (outIo.write(tmpBuf.data() + 2, 2) != 2)
824
0
            throw Error(ErrorCode::kerImageWriteFailed);  // JPEG Length
825
826
          // write the ICC_PROFILE header (14 bytes)
827
0
          uint8_t pad[2];
828
0
          pad[0] = static_cast<uint8_t>(chunk + 1);
829
0
          pad[1] = static_cast<uint8_t>(chunks);
830
0
          outIo.write(reinterpret_cast<const byte*>(iccId_), 12);
831
0
          outIo.write(reinterpret_cast<const byte*>(pad), 2);
832
0
          if (outIo.write(iccProfile_.c_data(chunk * chunk_size), bytes) != bytes)
833
0
            throw Error(ErrorCode::kerImageWriteFailed);
834
0
          if (outIo.error())
835
0
            throw Error(ErrorCode::kerImageWriteFailed);
836
0
        }
837
0
        --search;
838
0
      }
839
840
0
      if (foundCompletePsData || !iptcData_.empty()) {
841
        // Set the new IPTC IRB, keeps existing IRBs but removes the
842
        // IPTC block if there is no new IPTC data to write
843
0
        DataBuf newPsData = Photoshop::setIptcIrb(psBlob.data(), psBlob.size(), iptcData_);
844
0
        const size_t maxChunkSize = 0xffff - 16;
845
0
        const byte* chunkStart = newPsData.empty() ? nullptr : newPsData.c_data();
846
0
        const byte* chunkEnd = newPsData.empty() ? nullptr : newPsData.c_data(newPsData.size() - 1);
847
0
        while (chunkStart < chunkEnd) {
848
          // Determine size of next chunk
849
0
          size_t chunkSize = (chunkEnd + 1 - chunkStart);
850
0
          if (chunkSize > maxChunkSize) {
851
0
            chunkSize = maxChunkSize;
852
            // Don't break at a valid IRB boundary
853
0
            const auto writtenSize = chunkStart - newPsData.c_data();
854
0
            if (Photoshop::valid(newPsData.c_data(), writtenSize + chunkSize)) {
855
              // Since an IRB has minimum size 12,
856
              // (chunkSize - 8) can't be also a IRB boundary
857
0
              chunkSize -= 8;
858
0
            }
859
0
          }
860
861
          // Write APP13 marker, chunk size, and ps3Id
862
0
          std::array<byte, 18> tmpBuf;
863
0
          tmpBuf[0] = 0xff;
864
0
          tmpBuf[1] = app13_;
865
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(chunkSize + 16), bigEndian);
866
0
          std::copy_n(Photoshop::ps3Id_, 14, tmpBuf.begin() + 4);
867
0
          if (outIo.write(tmpBuf.data(), 18) != 18)
868
0
            throw Error(ErrorCode::kerImageWriteFailed);
869
0
          if (outIo.error())
870
0
            throw Error(ErrorCode::kerImageWriteFailed);
871
872
          // Write next chunk of the Photoshop IRB data buffer
873
0
          if (outIo.write(chunkStart, chunkSize) != chunkSize)
874
0
            throw Error(ErrorCode::kerImageWriteFailed);
875
0
          if (outIo.error())
876
0
            throw Error(ErrorCode::kerImageWriteFailed);
877
878
0
          chunkStart += chunkSize;
879
0
        }
880
0
        --search;
881
0
      }
882
0
    }
883
0
    if (comPos == count) {
884
0
      if (!comment_.empty()) {
885
0
        std::array<byte, 4> tmpBuf;
886
        // Write COM marker, size of comment, and string
887
0
        tmpBuf[0] = 0xff;
888
0
        tmpBuf[1] = com_;
889
890
0
        if (comment_.length() > 0xffff - 3)
891
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "JPEG comment");
892
0
        us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(comment_.length() + 3), bigEndian);
893
894
0
        if (outIo.write(tmpBuf.data(), 4) != 4)
895
0
          throw Error(ErrorCode::kerImageWriteFailed);
896
0
        if (outIo.write(reinterpret_cast<byte*>(comment_.data()), comment_.length()) != comment_.length())
897
0
          throw Error(ErrorCode::kerImageWriteFailed);
898
0
        if (outIo.putb(0) == EOF)
899
0
          throw Error(ErrorCode::kerImageWriteFailed);
900
0
        if (outIo.error())
901
0
          throw Error(ErrorCode::kerImageWriteFailed);
902
0
        --search;
903
0
      }
904
0
      --search;
905
0
    }
906
0
    if (marker == eoi_) {
907
0
      break;
908
0
    }
909
0
    if (skipApp1Exif == count || skipApp1Xmp == count ||
910
0
        std::find(skipApp13Ps3.begin(), skipApp13Ps3.end(), count) != skipApp13Ps3.end() ||
911
0
        std::find(skipApp2Icc.begin(), skipApp2Icc.end(), count) != skipApp2Icc.end() || skipCom == count) {
912
0
      --search;
913
0
    } else {
914
0
      std::array<byte, 2> tmpBuf;
915
      // Write marker and a copy of the segment.
916
0
      tmpBuf[0] = 0xff;
917
0
      tmpBuf[1] = marker;
918
0
      if (outIo.write(tmpBuf.data(), 2) != 2)
919
0
        throw Error(ErrorCode::kerImageWriteFailed);
920
0
      if (outIo.write(buf.c_data(), buf.size()) != buf.size())
921
0
        throw Error(ErrorCode::kerImageWriteFailed);
922
0
      if (outIo.error())
923
0
        throw Error(ErrorCode::kerImageWriteFailed);
924
0
    }
925
926
    // Next marker
927
0
    marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
928
0
    ++count;
929
0
  }
930
931
  // Populate the fake data, only make sense for remoteio, httpio and sshio.
932
  // it avoids allocating memory for parts of the file that contain image-date.
933
0
  io_->populateFakeData();
934
935
  // Write the final marker, then copy rest of the Io.
936
0
  byte tmpBuf[2];
937
0
  tmpBuf[0] = 0xff;
938
0
  tmpBuf[1] = marker;
939
0
  if (outIo.write(tmpBuf, 2) != 2)
940
0
    throw Error(ErrorCode::kerImageWriteFailed);
941
942
0
  DataBuf buf(4096);
943
0
  size_t readSize = io_->read(buf.data(), buf.size());
944
0
  while (readSize != 0) {
945
0
    if (outIo.write(buf.c_data(), readSize) != readSize)
946
0
      throw Error(ErrorCode::kerImageWriteFailed);
947
0
    readSize = io_->read(buf.data(), buf.size());
948
0
  }
949
0
  if (outIo.error())
950
0
    throw Error(ErrorCode::kerImageWriteFailed);
951
952
0
}  // JpegBase::doWriteMetadata
953
954
const byte JpegImage::blank_[] = {
955
    0xFF, 0xD8, 0xFF, 0xDB, 0x00, 0x84, 0x00, 0x10, 0x0B, 0x0B, 0x0B, 0x0C, 0x0B, 0x10, 0x0C, 0x0C, 0x10, 0x17,
956
    0x0F, 0x0D, 0x0F, 0x17, 0x1B, 0x14, 0x10, 0x10, 0x14, 0x1B, 0x1F, 0x17, 0x17, 0x17, 0x17, 0x17, 0x1F, 0x1E,
957
    0x17, 0x1A, 0x1A, 0x1A, 0x1A, 0x17, 0x1E, 0x1E, 0x23, 0x25, 0x27, 0x25, 0x23, 0x1E, 0x2F, 0x2F, 0x33, 0x33,
958
    0x2F, 0x2F, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x01,
959
    0x11, 0x0F, 0x0F, 0x11, 0x13, 0x11, 0x15, 0x12, 0x12, 0x15, 0x14, 0x11, 0x14, 0x11, 0x14, 0x1A, 0x14, 0x16,
960
    0x16, 0x14, 0x1A, 0x26, 0x1A, 0x1A, 0x1C, 0x1A, 0x1A, 0x26, 0x30, 0x23, 0x1E, 0x1E, 0x1E, 0x1E, 0x23, 0x30,
961
    0x2B, 0x2E, 0x27, 0x27, 0x27, 0x2E, 0x2B, 0x35, 0x35, 0x30, 0x30, 0x35, 0x35, 0x40, 0x40, 0x3F, 0x40, 0x40,
962
    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00,
963
    0x01, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x4B, 0x00, 0x01, 0x01,
964
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01,
965
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01,
966
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x01,
967
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA,
968
    0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0xA0, 0x00, 0x0F, 0xFF, 0xD9};
969
970
JpegImage::JpegImage(BasicIo::UniquePtr io, const ImageCtorParams& params) :
971
36.7k
    JpegBase(ImageType::jpeg, std::move(io), params, blank_, sizeof(blank_)) {
972
36.7k
}
973
974
0
std::string JpegImage::mimeType() const {
975
0
  return "image/jpeg";
976
0
}
977
978
0
int JpegImage::writeHeader(BasicIo& outIo) const {
979
  // Jpeg header
980
0
  byte tmpBuf[2];
981
0
  tmpBuf[0] = 0xff;
982
0
  tmpBuf[1] = soi_;
983
0
  if (outIo.write(tmpBuf, 2) != 2)
984
0
    return 4;
985
0
  if (outIo.error())
986
0
    return 4;
987
0
  return 0;
988
0
}
989
990
36.7k
bool JpegImage::isThisType(BasicIo& iIo, bool advance) const {
991
36.7k
  return isJpegType(iIo, advance);
992
36.7k
}
993
994
36.6k
Image::UniquePtr newJpegInstance(BasicIo::UniquePtr io, const ImageCtorParams& params) {
995
36.6k
  auto image = std::make_unique<JpegImage>(std::move(io), params);
996
36.6k
  if (!image->good()) {
997
0
    return nullptr;
998
0
  }
999
36.6k
  return image;
1000
36.6k
}
1001
1002
147k
bool isJpegType(BasicIo& iIo, bool advance) {
1003
147k
  bool result = true;
1004
147k
  byte tmpBuf[2];
1005
147k
  iIo.read(tmpBuf, 2);
1006
147k
  if (iIo.error() || iIo.eof())
1007
33
    return false;
1008
1009
147k
  if (0xff != tmpBuf[0] || soi_ != tmpBuf[1]) {
1010
37.7k
    result = false;
1011
37.7k
  }
1012
147k
  if (!advance || !result)
1013
111k
    iIo.seek(-2, BasicIo::cur);
1014
147k
  return result;
1015
147k
}
1016
1017
ExvImage::ExvImage(BasicIo::UniquePtr io, const ImageCtorParams& params) :
1018
20
    JpegBase(ImageType::exv, std::move(io), params, blank_, sizeof(blank_)) {
1019
20
}
1020
1021
0
std::string ExvImage::mimeType() const {
1022
0
  return "image/x-exv";
1023
0
}
1024
1025
0
int ExvImage::writeHeader(BasicIo& outIo) const {
1026
  // Exv header
1027
0
  auto tmpBuf = std::array<byte, 7>{0xff, 0x01};
1028
0
  std::copy_n(exiv2Id_, 5, tmpBuf.begin() + 2);
1029
0
  if (outIo.write(tmpBuf.data(), 7) != 7)
1030
0
    return 4;
1031
0
  if (outIo.error())
1032
0
    return 4;
1033
0
  return 0;
1034
0
}
1035
1036
20
bool ExvImage::isThisType(BasicIo& iIo, bool advance) const {
1037
20
  return isExvType(iIo, advance);
1038
20
}
1039
1040
20
Image::UniquePtr newExvInstance(BasicIo::UniquePtr io, const ImageCtorParams& params) {
1041
20
  auto image = std::make_unique<ExvImage>(std::move(io), params);
1042
20
  if (!image->good())
1043
0
    return nullptr;
1044
20
  return image;
1045
20
}
1046
1047
37.7k
bool isExvType(BasicIo& iIo, bool advance) {
1048
37.7k
  bool result = true;
1049
37.7k
  byte tmpBuf[7];
1050
37.7k
  iIo.read(tmpBuf, 7);
1051
37.7k
  if (iIo.error() || iIo.eof())
1052
176
    return false;
1053
1054
37.5k
  if (0xff != tmpBuf[0] || 0x01 != tmpBuf[1] || memcmp(tmpBuf + 2, ExvImage::exiv2Id_, 5) != 0) {
1055
37.5k
    result = false;
1056
37.5k
  }
1057
37.5k
  if (!advance || !result)
1058
37.5k
    iIo.seek(-7, BasicIo::cur);
1059
37.5k
  return result;
1060
37.7k
}
1061
1062
}  // namespace Exiv2