Coverage Report

Created: 2025-12-14 06:57

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