Coverage Report

Created: 2026-04-29 07:00

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
169k
constexpr bool inRange(int lo, int value, int hi) {
88
169k
  return lo <= value && value <= hi;
89
169k
}
90
91
86.9k
constexpr bool inRange2(int value, int lo1, int hi1, int lo2, int hi2) {
92
86.9k
  return inRange(lo1, value, hi1) || inRange(lo2, value, hi2);
93
86.9k
}
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
135k
bool markerHasLength(byte m) {
99
135k
  bool markerWithoutLength = m >= rst1_ && m <= eoi_;
100
135k
  return !markerWithoutLength;
101
135k
}
102
103
135k
std::pair<std::array<byte, 2>, uint16_t> readSegmentSize(const byte marker, BasicIo& io) {
104
135k
  std::array<byte, 2> buf{0, 0};  // 2-byte buffer for reading the size.
105
135k
  uint16_t size{0};               // Size of the segment, including the 2-byte size field
106
135k
  if (markerHasLength(marker)) {
107
122k
    io.readOrThrow(buf.data(), buf.size(), ErrorCode::kerFailedToReadImageData);
108
122k
    size = getUShort(buf.data(), bigEndian);
109
122k
    enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
110
122k
  }
111
135k
  return {buf, size};
112
135k
}
113
}  // namespace
114
115
JpegBase::JpegBase(ImageType type, BasicIo::UniquePtr io, bool create, const byte initData[], size_t dataSize) :
116
14.8k
    Image(type, mdExif | mdIptc | mdXmp | mdComment, std::move(io)) {
117
14.8k
  if (create) {
118
0
    initImage(initData, dataSize);
119
0
  }
120
14.8k
}
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
144k
byte JpegBase::advanceToMarker(ErrorCode err) const {
134
144k
  int c = -1;
135
  // Skips potential padding between markers
136
10.3M
  while ((c = io_->getb()) != 0xff) {
137
10.2M
    if (c == EOF)
138
3.40k
      throw Error(err);
139
10.2M
  }
140
141
  // Markers can start with any number of 0xff
142
206k
  while ((c = io_->getb()) == 0xff) {
143
65.3k
  }
144
140k
  if (c == EOF)
145
256
    throw Error(err);
146
147
140k
  return static_cast<byte>(c);
148
140k
}
149
150
14.8k
void JpegBase::readMetadata() {
151
14.8k
  int rc = 0;  // Todo: this should be the return value
152
153
14.8k
  if (io_->open() != 0)
154
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
155
14.8k
  IoCloser closer(*io_);
156
  // Ensure that this is the correct image type
157
14.8k
  if (!isThisType(*io_, true)) {
158
29
    if (io_->error() || io_->eof())
159
0
      throw Error(ErrorCode::kerFailedToReadImageData);
160
29
    throw Error(ErrorCode::kerNotAJpeg);
161
29
  }
162
14.8k
  clearMetadata();
163
14.8k
  int search = 6;  // Exif, ICC, XMP, Comment, IPTC, SOF
164
14.8k
  Blob psBlob;
165
14.8k
  bool foundCompletePsData = false;
166
14.8k
  bool foundExifData = false;
167
14.8k
  bool foundXmpData = false;
168
14.8k
  bool foundIccData = false;
169
170
  // Read section marker
171
14.8k
  byte marker = advanceToMarker(ErrorCode::kerNotAJpeg);
172
173
140k
  while (marker != sos_ && marker != eoi_ && search > 0) {
174
135k
    const auto [sizebuf, size] = readSegmentSize(marker, *io_);
175
176
    // Read the rest of the segment.
177
135k
    DataBuf buf(size);
178
    // check if the segment is not empty
179
135k
    if (size > 2) {
180
118k
      io_->readOrThrow(buf.data(2), size - 2, ErrorCode::kerFailedToReadImageData);
181
118k
      std::copy(sizebuf.begin(), sizebuf.end(), buf.begin());
182
118k
    }
183
184
135k
    if (auto itSofMarker = Exiv2::find(jpegProcessMarkerTags, marker)) {
185
10.7k
      sof_encoding_process_ = itSofMarker->label_;
186
10.7k
      if (size >= 7 && buf.c_data(7)) {
187
10.6k
        num_color_components_ = *buf.c_data(7);
188
10.6k
      }
189
10.7k
    }
190
191
135k
    if (!foundExifData && marker == app1_ && size >= 8  // prevent out-of-bounds read in memcmp on next line
192
31.2k
        && buf.cmpBytes(2, exifId_.data(), 6) == 0) {
193
2.75k
      ByteOrder bo = ExifParser::decode(exifData_, buf.c_data(8), size - 8);
194
2.75k
      setByteOrder(bo);
195
2.75k
      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
2.75k
      --search;
202
2.75k
      foundExifData = true;
203
133k
    } else if (!foundXmpData && marker == app1_ && size >= 31  // prevent out-of-bounds read in memcmp on next line
204
10.1k
               && buf.cmpBytes(2, xmpId_.data(), 29) == 0) {
205
7.85k
      xmpPacket_.assign(buf.c_str(31), size - 31);
206
7.85k
      if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) {
207
7.51k
#ifndef SUPPRESS_WARNINGS
208
7.51k
        EXV_WARNING << "Failed to decode XMP metadata.\n";
209
7.51k
#endif
210
7.51k
      }
211
7.85k
      --search;
212
7.85k
      foundXmpData = true;
213
125k
    } else if (!foundCompletePsData && marker == app13_ &&
214
1.65k
               size >= 16  // prevent out-of-bounds read in memcmp on next line
215
1.52k
               && buf.cmpBytes(2, Photoshop::ps3Id_, 14) == 0) {
216
#ifdef EXIV2_DEBUG_MESSAGES
217
      std::cerr << "Found app13 segment, size = " << size << "\n";
218
#endif
219
1.43k
      if (buf.size() > 16) {  // Append to psBlob
220
1.43k
        append(psBlob, buf.c_data(16), size - 16);
221
1.43k
      }
222
      // Check whether psBlob is complete
223
1.43k
      if (!psBlob.empty() && Photoshop::valid(psBlob.data(), psBlob.size())) {
224
328
        --search;
225
328
        foundCompletePsData = true;
226
328
      }
227
123k
    } 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
3.34k
      comment_.assign(buf.c_str(2), size - 2);
232
7.23k
      while (!comment_.empty() && comment_.back() == '\0') {
233
3.88k
        comment_.pop_back();
234
3.88k
      }
235
3.34k
      --search;
236
120k
    } else if (marker == app2_ && size >= 13  // prevent out-of-bounds read in memcmp on next line
237
2.34k
               && buf.cmpBytes(2, iccId_, 11) == 0) {
238
1.82k
      if (size < 2 + 14 + 4) {
239
15
        rc = 8;
240
15
        break;
241
15
      }
242
      // ICC profile
243
1.81k
      if (!foundIccData) {
244
1.48k
        foundIccData = true;
245
1.48k
        --search;
246
1.48k
      }
247
1.81k
      auto chunk = static_cast<int>(buf.read_uint8(2 + 12));
248
1.81k
      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
1.81k
      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
1.81k
      size_t icc_size = size - 2 - 14;
260
1.81k
      if (chunk == 1 && chunks == 1) {
261
1.41k
        enforce(s <= static_cast<uint32_t>(icc_size), ErrorCode::kerInvalidIccProfile);
262
1.41k
        icc_size = s;
263
1.41k
      }
264
265
1.81k
      appendIccProfile(buf.c_data(2 + 14), icc_size, chunk == chunks);
266
118k
    } else if (pixelHeight_ == 0 && inRange2(marker, sof0_, sof3_, sof5_, sof15_)) {
267
      // We hit a SOFn (start-of-frame) marker
268
6.40k
      if (size < 8) {
269
9
        rc = 7;
270
9
        break;
271
9
      }
272
6.39k
      pixelHeight_ = buf.read_uint16(3, bigEndian);
273
6.39k
      pixelWidth_ = buf.read_uint16(5, bigEndian);
274
6.39k
      if (pixelHeight_ != 0)
275
5.55k
        --search;
276
6.39k
    }
277
278
    // Read the beginning of the next segment
279
135k
    try {
280
135k
      marker = advanceToMarker(ErrorCode::kerFailedToReadImageData);
281
135k
    } catch (const Error&) {
282
3.65k
      rc = 5;
283
3.65k
      break;
284
3.65k
    }
285
135k
  }  // while there are segments to process
286
287
8.45k
  if (!psBlob.empty()) {
288
    // Find actual IPTC data within the psBlob
289
1.02k
    Blob iptcBlob;
290
1.02k
    const byte* record = nullptr;
291
1.02k
    uint32_t sizeIptc = 0;
292
1.02k
    uint32_t sizeHdr = 0;
293
1.02k
    const byte* pCur = psBlob.data();
294
1.02k
    const byte* pEnd = pCur + psBlob.size();
295
2.02k
    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
997
      if (sizeIptc) {
300
994
        append(iptcBlob, record + sizeHdr, sizeIptc);
301
994
      }
302
997
      pCur = record + sizeHdr + sizeIptc + (sizeIptc & 1);
303
997
    }
304
1.02k
    if (!iptcBlob.empty() && IptcParser::decode(iptcData_, iptcBlob.data(), iptcBlob.size())) {
305
791
#ifndef SUPPRESS_WARNINGS
306
791
      EXV_WARNING << "Failed to decode IPTC metadata.\n";
307
791
#endif
308
791
      iptcData_.clear();
309
791
    }
310
1.02k
  }
311
312
8.45k
  if (rc != 0) {
313
3.68k
#ifndef SUPPRESS_WARNINGS
314
3.68k
    EXV_WARNING << "JPEG format error, rc = " << rc << "\n";
315
3.68k
#endif
316
3.68k
  }
317
8.45k
}  // 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
0
void JpegBase::writeMetadata() {
576
0
  if (io_->open() != 0) {
577
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
578
0
  }
579
0
  IoCloser closer(*io_);
580
0
  MemIo tempIo;
581
582
0
  doWriteMetadata(tempIo);  // may throw
583
0
  io_->close();
584
0
  io_->transfer(tempIo);  // may throw
585
0
}
586
587
0
DataBuf JpegBase::readNextSegment(byte marker) {
588
0
  const auto [sizebuf, size] = readSegmentSize(marker, *io_);
589
590
  // Read the rest of the segment if not empty.
591
0
  DataBuf buf(size);
592
0
  if (size > 0) {
593
0
    std::copy(sizebuf.begin(), sizebuf.end(), buf.begin());
594
0
    if (size > 2) {
595
0
      io_->readOrThrow(buf.data(2), size - 2, ErrorCode::kerFailedToReadImageData);
596
0
    }
597
0
  }
598
0
  return buf;
599
0
}
600
601
0
void JpegBase::doWriteMetadata(BasicIo& outIo) {
602
0
  if (!io_->isopen())
603
0
    throw Error(ErrorCode::kerInputDataReadFailed);
604
0
  if (!outIo.isopen())
605
0
    throw Error(ErrorCode::kerImageWriteFailed);
606
607
  // Ensure that this is the correct image type
608
0
  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
0
  static const size_t notfound = std::numeric_limits<size_t>::max();
616
617
0
  const size_t seek = io_->tell();
618
0
  size_t count = 0;
619
0
  size_t search = 0;
620
0
  size_t insertPos = 0;
621
0
  size_t comPos = 0;
622
0
  size_t skipApp1Exif = notfound;
623
0
  size_t skipApp1Xmp = notfound;
624
0
  bool foundCompletePsData = false;
625
0
  bool foundIccData = false;
626
0
  std::vector<size_t> skipApp13Ps3;
627
0
  std::vector<size_t> skipApp2Icc;
628
0
  size_t skipCom = notfound;
629
0
  Blob psBlob;
630
0
  DataBuf rawExif;
631
0
  xmpData().usePacket(writeXmpFromPacket());
632
633
  // Write image header
634
0
  if (writeHeader(outIo))
635
0
    throw Error(ErrorCode::kerImageWriteFailed);
636
637
  // Read section marker
638
0
  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
0
  while (marker != sos_ && marker != eoi_ && search < 6) {
644
0
    DataBuf buf = readNextSegment(marker);
645
646
0
    if (marker == app0_) {
647
0
      insertPos = count + 1;
648
0
    } else if (skipApp1Exif == notfound && marker == app1_ &&
649
0
               buf.size() >= 8 &&  // prevent out-of-bounds read in memcmp on next line
650
0
               buf.cmpBytes(2, exifId_.data(), 6) == 0) {
651
0
      skipApp1Exif = count;
652
0
      ++search;
653
0
      if (buf.size() > 8) {
654
0
        rawExif.alloc(buf.size() - 8);
655
0
        std::copy_n(buf.begin() + 8, rawExif.size(), rawExif.begin());
656
0
      }
657
0
    } else if (skipApp1Xmp == notfound && marker == app1_ &&
658
0
               buf.size() >= 31 &&  // prevent out-of-bounds read in memcmp on next line
659
0
               buf.cmpBytes(2, xmpId_.data(), 29) == 0) {
660
0
      skipApp1Xmp = count;
661
0
      ++search;
662
0
    } else if (marker == app2_ && buf.size() >= 13 &&  // prevent out-of-bounds read in memcmp on next line
663
0
               buf.cmpBytes(2, iccId_, 11) == 0) {
664
0
      skipApp2Icc.push_back(count);
665
0
      if (!foundIccData) {
666
0
        ++search;
667
0
        foundIccData = true;
668
0
      }
669
0
    } else if (!foundCompletePsData && marker == app13_ &&
670
0
               buf.size() >= 16 &&  // prevent out-of-bounds read in memcmp on next line
671
0
               buf.cmpBytes(2, Photoshop::ps3Id_, 14) == 0) {
672
#ifdef EXIV2_DEBUG_MESSAGES
673
      std::cerr << "Found APP13 Photoshop PS3 segment\n";
674
#endif
675
0
      skipApp13Ps3.push_back(count);
676
      // Append to psBlob
677
0
      append(psBlob, buf.c_data(16), buf.size() - 16);
678
      // Check whether psBlob is complete
679
0
      if (!psBlob.empty() && Photoshop::valid(psBlob.data(), psBlob.size())) {
680
0
        foundCompletePsData = true;
681
0
      }
682
0
    } 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
0
      skipCom = count;
686
0
      ++search;
687
0
    }
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
0
    if (comPos == 0 && inRange2(marker, sof0_, sof3_, sof5_, sof15_)) {
695
0
      comPos = count;
696
0
      ++search;
697
0
    }
698
0
    marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
699
0
    ++count;
700
0
  }
701
702
0
  if (!foundCompletePsData && !psBlob.empty())
703
0
    throw Error(ErrorCode::kerNoImageInInputData);
704
0
  search += skipApp13Ps3.size() + skipApp2Icc.size();
705
706
0
  if (comPos == 0) {
707
0
    if (marker == eoi_)
708
0
      comPos = count;
709
0
    else
710
0
      comPos = insertPos;
711
0
    ++search;
712
0
  }
713
0
  if (!exifData_.empty())
714
0
    ++search;
715
0
  if (!writeXmpFromPacket() && !xmpData_.empty())
716
0
    ++search;
717
0
  if (writeXmpFromPacket() && !xmpPacket_.empty())
718
0
    ++search;
719
0
  if (foundCompletePsData || !iptcData_.empty())
720
0
    ++search;
721
0
  if (!comment_.empty())
722
0
    ++search;
723
724
0
  io_->seekOrThrow(seek, BasicIo::beg, ErrorCode::kerNoImageInInputData);
725
0
  count = 0;
726
0
  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
0
  while (marker != sos_ && search > 0) {
733
0
    DataBuf buf = readNextSegment(marker);
734
735
0
    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
0
      if (!exifData_.empty()) {
739
0
        Blob blob;
740
0
        ByteOrder bo = byteOrder();
741
0
        if (bo == invalidByteOrder) {
742
0
          bo = littleEndian;
743
0
          setByteOrder(bo);
744
0
        }
745
0
        const byte* pExifData = rawExif.c_data();
746
0
        size_t exifSize = rawExif.size();
747
0
        if (ExifParser::encode(blob, pExifData, exifSize, bo, exifData_) == wmIntrusive) {
748
0
          pExifData = !blob.empty() ? blob.data() : nullptr;
749
0
          exifSize = blob.size();
750
0
        }
751
0
        if (exifSize > 0) {
752
0
          std::array<byte, 10> tmpBuf;
753
          // Write APP1 marker, size of APP1 field, Exif id and Exif data
754
0
          tmpBuf[0] = 0xff;
755
0
          tmpBuf[1] = app1_;
756
757
0
          if (exifSize > 0xffff - 8)
758
0
            throw Error(ErrorCode::kerTooLargeJpegSegment, "Exif");
759
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(exifSize + 8), bigEndian);
760
0
          std::copy(exifId_.begin(), exifId_.end(), tmpBuf.begin() + 4);
761
0
          if (outIo.write(tmpBuf.data(), 10) != 10)
762
0
            throw Error(ErrorCode::kerImageWriteFailed);
763
764
          // Write new Exif data buffer
765
0
          if (outIo.write(pExifData, exifSize) != exifSize)
766
0
            throw Error(ErrorCode::kerImageWriteFailed);
767
0
          if (outIo.error())
768
0
            throw Error(ErrorCode::kerImageWriteFailed);
769
0
          --search;
770
0
        }
771
0
      }
772
0
      if (!writeXmpFromPacket() &&
773
0
          XmpParser::encode(xmpPacket_, xmpData_, XmpParser::useCompactFormat | XmpParser::omitAllFormatting) > 1) {
774
0
#ifndef SUPPRESS_WARNINGS
775
0
        EXV_ERROR << "Failed to encode XMP metadata.\n";
776
0
#endif
777
0
      }
778
0
      if (!xmpPacket_.empty()) {
779
0
        std::array<byte, 33> tmpBuf;
780
        // Write APP1 marker, size of APP1 field, XMP id and XMP packet
781
0
        tmpBuf[0] = 0xff;
782
0
        tmpBuf[1] = app1_;
783
784
0
        if (xmpPacket_.size() > 0xffff - 31)
785
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "XMP");
786
0
        us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(xmpPacket_.size() + 31), bigEndian);
787
0
        std::copy(xmpId_.begin(), xmpId_.end(), tmpBuf.begin() + 4);
788
0
        if (outIo.write(tmpBuf.data(), 33) != 33)
789
0
          throw Error(ErrorCode::kerImageWriteFailed);
790
791
        // Write new XMP packet
792
0
        if (outIo.write(reinterpret_cast<const byte*>(xmpPacket_.data()), xmpPacket_.size()) != xmpPacket_.size())
793
0
          throw Error(ErrorCode::kerImageWriteFailed);
794
0
        if (outIo.error())
795
0
          throw Error(ErrorCode::kerImageWriteFailed);
796
0
        --search;
797
0
      }
798
799
0
      if (iccProfileDefined()) {
800
0
        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
0
        tmpBuf[0] = 0xff;
804
0
        tmpBuf[1] = app2_;
805
806
0
        const size_t chunk_size = (256 * 256) - 40;  // leave bytes for marker, header and padding
807
0
        size_t size = iccProfile_.size();
808
0
        if (size >= 255 * chunk_size)
809
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "IccProfile");
810
0
        const size_t chunks = 1 + ((size - 1) / chunk_size);
811
0
        for (size_t chunk = 0; chunk < chunks; chunk++) {
812
0
          auto bytes = std::min<size_t>(size, chunk_size);  // bytes to write
813
0
          size -= bytes;
814
815
          // write JPEG marker (2 bytes)
816
0
          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
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(2 + 14 + bytes), bigEndian);
820
0
          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
0
          uint8_t pad[2];
825
0
          pad[0] = static_cast<uint8_t>(chunk + 1);
826
0
          pad[1] = static_cast<uint8_t>(chunks);
827
0
          outIo.write(reinterpret_cast<const byte*>(iccId_), 12);
828
0
          outIo.write(reinterpret_cast<const byte*>(pad), 2);
829
0
          if (outIo.write(iccProfile_.c_data(chunk * chunk_size), bytes) != bytes)
830
0
            throw Error(ErrorCode::kerImageWriteFailed);
831
0
          if (outIo.error())
832
0
            throw Error(ErrorCode::kerImageWriteFailed);
833
0
        }
834
0
        --search;
835
0
      }
836
837
0
      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
0
        DataBuf newPsData = Photoshop::setIptcIrb(psBlob.data(), psBlob.size(), iptcData_);
841
0
        const size_t maxChunkSize = 0xffff - 16;
842
0
        const byte* chunkStart = newPsData.empty() ? nullptr : newPsData.c_data();
843
0
        const byte* chunkEnd = newPsData.empty() ? nullptr : newPsData.c_data(newPsData.size() - 1);
844
0
        while (chunkStart < chunkEnd) {
845
          // Determine size of next chunk
846
0
          size_t chunkSize = (chunkEnd + 1 - chunkStart);
847
0
          if (chunkSize > maxChunkSize) {
848
0
            chunkSize = maxChunkSize;
849
            // Don't break at a valid IRB boundary
850
0
            const auto writtenSize = chunkStart - newPsData.c_data();
851
0
            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
0
              chunkSize -= 8;
855
0
            }
856
0
          }
857
858
          // Write APP13 marker, chunk size, and ps3Id
859
0
          std::array<byte, 18> tmpBuf;
860
0
          tmpBuf[0] = 0xff;
861
0
          tmpBuf[1] = app13_;
862
0
          us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(chunkSize + 16), bigEndian);
863
0
          std::copy_n(Photoshop::ps3Id_, 14, tmpBuf.begin() + 4);
864
0
          if (outIo.write(tmpBuf.data(), 18) != 18)
865
0
            throw Error(ErrorCode::kerImageWriteFailed);
866
0
          if (outIo.error())
867
0
            throw Error(ErrorCode::kerImageWriteFailed);
868
869
          // Write next chunk of the Photoshop IRB data buffer
870
0
          if (outIo.write(chunkStart, chunkSize) != chunkSize)
871
0
            throw Error(ErrorCode::kerImageWriteFailed);
872
0
          if (outIo.error())
873
0
            throw Error(ErrorCode::kerImageWriteFailed);
874
875
0
          chunkStart += chunkSize;
876
0
        }
877
0
        --search;
878
0
      }
879
0
    }
880
0
    if (comPos == count) {
881
0
      if (!comment_.empty()) {
882
0
        std::array<byte, 4> tmpBuf;
883
        // Write COM marker, size of comment, and string
884
0
        tmpBuf[0] = 0xff;
885
0
        tmpBuf[1] = com_;
886
887
0
        if (comment_.length() > 0xffff - 3)
888
0
          throw Error(ErrorCode::kerTooLargeJpegSegment, "JPEG comment");
889
0
        us2Data(tmpBuf.data() + 2, static_cast<uint16_t>(comment_.length() + 3), bigEndian);
890
891
0
        if (outIo.write(tmpBuf.data(), 4) != 4)
892
0
          throw Error(ErrorCode::kerImageWriteFailed);
893
0
        if (outIo.write(reinterpret_cast<byte*>(comment_.data()), comment_.length()) != comment_.length())
894
0
          throw Error(ErrorCode::kerImageWriteFailed);
895
0
        if (outIo.putb(0) == EOF)
896
0
          throw Error(ErrorCode::kerImageWriteFailed);
897
0
        if (outIo.error())
898
0
          throw Error(ErrorCode::kerImageWriteFailed);
899
0
        --search;
900
0
      }
901
0
      --search;
902
0
    }
903
0
    if (marker == eoi_) {
904
0
      break;
905
0
    }
906
0
    if (skipApp1Exif == count || skipApp1Xmp == count ||
907
0
        std::find(skipApp13Ps3.begin(), skipApp13Ps3.end(), count) != skipApp13Ps3.end() ||
908
0
        std::find(skipApp2Icc.begin(), skipApp2Icc.end(), count) != skipApp2Icc.end() || skipCom == count) {
909
0
      --search;
910
0
    } else {
911
0
      std::array<byte, 2> tmpBuf;
912
      // Write marker and a copy of the segment.
913
0
      tmpBuf[0] = 0xff;
914
0
      tmpBuf[1] = marker;
915
0
      if (outIo.write(tmpBuf.data(), 2) != 2)
916
0
        throw Error(ErrorCode::kerImageWriteFailed);
917
0
      if (outIo.write(buf.c_data(), buf.size()) != buf.size())
918
0
        throw Error(ErrorCode::kerImageWriteFailed);
919
0
      if (outIo.error())
920
0
        throw Error(ErrorCode::kerImageWriteFailed);
921
0
    }
922
923
    // Next marker
924
0
    marker = advanceToMarker(ErrorCode::kerNoImageInInputData);
925
0
    ++count;
926
0
  }
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
0
  io_->populateFakeData();
931
932
  // Write the final marker, then copy rest of the Io.
933
0
  byte tmpBuf[2];
934
0
  tmpBuf[0] = 0xff;
935
0
  tmpBuf[1] = marker;
936
0
  if (outIo.write(tmpBuf, 2) != 2)
937
0
    throw Error(ErrorCode::kerImageWriteFailed);
938
939
0
  DataBuf buf(4096);
940
0
  size_t readSize = io_->read(buf.data(), buf.size());
941
0
  while (readSize != 0) {
942
0
    if (outIo.write(buf.c_data(), readSize) != readSize)
943
0
      throw Error(ErrorCode::kerImageWriteFailed);
944
0
    readSize = io_->read(buf.data(), buf.size());
945
0
  }
946
0
  if (outIo.error())
947
0
    throw Error(ErrorCode::kerImageWriteFailed);
948
949
0
}  // 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
14.8k
    JpegBase(ImageType::jpeg, std::move(io), create, blank_, sizeof(blank_)) {
969
14.8k
}
970
971
8.43k
std::string JpegImage::mimeType() const {
972
8.43k
  return "image/jpeg";
973
8.43k
}
974
975
0
int JpegImage::writeHeader(BasicIo& outIo) const {
976
  // Jpeg header
977
0
  byte tmpBuf[2];
978
0
  tmpBuf[0] = 0xff;
979
0
  tmpBuf[1] = soi_;
980
0
  if (outIo.write(tmpBuf, 2) != 2)
981
0
    return 4;
982
0
  if (outIo.error())
983
0
    return 4;
984
0
  return 0;
985
0
}
986
987
14.8k
bool JpegImage::isThisType(BasicIo& iIo, bool advance) const {
988
14.8k
  return isJpegType(iIo, advance);
989
14.8k
}
990
991
14.8k
Image::UniquePtr newJpegInstance(BasicIo::UniquePtr io, bool create) {
992
14.8k
  auto image = std::make_unique<JpegImage>(std::move(io), create);
993
14.8k
  if (!image->good()) {
994
0
    return nullptr;
995
0
  }
996
14.8k
  return image;
997
14.8k
}
998
999
57.6k
bool isJpegType(BasicIo& iIo, bool advance) {
1000
57.6k
  bool result = true;
1001
57.6k
  byte tmpBuf[2];
1002
57.6k
  iIo.read(tmpBuf, 2);
1003
57.6k
  if (iIo.error() || iIo.eof())
1004
0
    return false;
1005
1006
57.6k
  if (0xff != tmpBuf[0] || soi_ != tmpBuf[1]) {
1007
13.1k
    result = false;
1008
13.1k
  }
1009
57.6k
  if (!advance || !result)
1010
42.7k
    iIo.seek(-2, BasicIo::cur);
1011
57.6k
  return result;
1012
57.6k
}
1013
1014
ExvImage::ExvImage(BasicIo::UniquePtr io, bool create) :
1015
6
    JpegBase(ImageType::exv, std::move(io), create, blank_, sizeof(blank_)) {
1016
6
}
1017
1018
2
std::string ExvImage::mimeType() const {
1019
2
  return "image/x-exv";
1020
2
}
1021
1022
0
int ExvImage::writeHeader(BasicIo& outIo) const {
1023
  // Exv header
1024
0
  auto tmpBuf = std::array<byte, 7>{0xff, 0x01};
1025
0
  std::copy_n(exiv2Id_, 5, tmpBuf.begin() + 2);
1026
0
  if (outIo.write(tmpBuf.data(), 7) != 7)
1027
0
    return 4;
1028
0
  if (outIo.error())
1029
0
    return 4;
1030
0
  return 0;
1031
0
}
1032
1033
6
bool ExvImage::isThisType(BasicIo& iIo, bool advance) const {
1034
6
  return isExvType(iIo, advance);
1035
6
}
1036
1037
6
Image::UniquePtr newExvInstance(BasicIo::UniquePtr io, bool create) {
1038
6
  auto image = std::make_unique<ExvImage>(std::move(io), create);
1039
6
  if (!image->good())
1040
0
    return nullptr;
1041
6
  return image;
1042
6
}
1043
1044
13.1k
bool isExvType(BasicIo& iIo, bool advance) {
1045
13.1k
  bool result = true;
1046
13.1k
  byte tmpBuf[7];
1047
13.1k
  iIo.read(tmpBuf, 7);
1048
13.1k
  if (iIo.error() || iIo.eof())
1049
0
    return false;
1050
1051
13.1k
  if (0xff != tmpBuf[0] || 0x01 != tmpBuf[1] || memcmp(tmpBuf + 2, ExvImage::exiv2Id_, 5) != 0) {
1052
13.1k
    result = false;
1053
13.1k
  }
1054
13.1k
  if (!advance || !result)
1055
13.1k
    iIo.seek(-7, BasicIo::cur);
1056
13.1k
  return result;
1057
13.1k
}
1058
1059
}  // namespace Exiv2