Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/preview.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "preview.hpp"
5
#include "basicio.hpp"
6
#include "config.h"
7
#include "enforce.hpp"
8
#include "futils.hpp"
9
#include "image.hpp"
10
#include "photoshop.hpp"
11
#include "properties.hpp"
12
#include "safe_op.hpp"
13
#include "tags.hpp"
14
#include "tiffimage.hpp"
15
#include "tiffimage_int.hpp"
16
#include "value.hpp"
17
18
#include <algorithm>
19
#include <array>
20
#include <climits>
21
#include <cstring>
22
23
namespace {
24
using namespace Exiv2;
25
using Exiv2::byte;
26
27
/// @brief Decode a Hex string.
28
DataBuf decodeHex(const byte* src, size_t srcSize);
29
30
/// @brief Decode a Base64 string.
31
DataBuf decodeBase64(const std::string& src);
32
33
/// @brief Decode an Illustrator thumbnail that follows after %AI7_Thumbnail.
34
DataBuf decodeAi7Thumbnail(const DataBuf& src);
35
36
/// @brief Create a PNM image from raw RGB data.
37
DataBuf makePnm(size_t width, size_t height, const DataBuf& rgb);
38
39
/*!
40
  Base class for image loaders. Provides virtual methods for reading properties
41
  and DataBuf.
42
 */
43
class Loader {
44
 public:
45
  //! Virtual destructor.
46
0
  virtual ~Loader() = default;
47
48
  Loader(const Loader&) = delete;
49
  Loader& operator=(const Loader&) = delete;
50
51
  //! Loader auto pointer
52
  using UniquePtr = std::unique_ptr<Loader>;
53
54
  //! Create a Loader subclass for requested id
55
  static UniquePtr create(PreviewId id, const Image& image);
56
57
  //! Check if a preview image with given params exists in the image
58
0
  [[nodiscard]] virtual bool valid() const {
59
0
    return valid_;
60
0
  }
61
62
  //! Get properties of a preview image with given params
63
  [[nodiscard]] virtual PreviewProperties getProperties() const;
64
65
  //! Get a buffer that contains the preview image
66
  [[nodiscard]] virtual DataBuf getData() const = 0;
67
68
  //! Read preview image dimensions when they are not available directly
69
0
  virtual bool readDimensions() {
70
0
    return true;
71
0
  }
72
73
  //! A number of image loaders configured in the loaderList_ table
74
  static PreviewId getNumLoaders();
75
76
 protected:
77
  //! Constructor. Sets all image properties to unknown.
78
  Loader(PreviewId id, const Image& image);
79
80
  //! Functions that creates a loader from given parameters
81
  using CreateFunc = UniquePtr (*)(PreviewId, const Image&, int);
82
83
  //! Structure to list possible loaders
84
  struct LoaderList {
85
    const char* imageMimeType_;  //!< Image type for which the loader is valid, 0 matches all images
86
    CreateFunc create_;          //!< Function that creates particular loader instance
87
    int parIdx_;                 //!< Parameter that is passed into CreateFunc
88
  };
89
90
  //! Table that lists possible loaders.  PreviewId is an index to this table.
91
  static const LoaderList loaderList_[];
92
93
  //! Identifies preview image type
94
  PreviewId id_;
95
96
  //! Source image reference
97
  const Image& image_;
98
99
  //! Preview image width
100
  size_t width_{0};
101
102
  //! Preview image length
103
  size_t height_{0};
104
105
  //! Preview image size in bytes
106
  size_t size_{0};
107
108
  //! True if the source image contains a preview image of given type
109
  bool valid_{false};
110
};
111
112
//! Loader for native previews
113
class LoaderNative : public Loader {
114
 public:
115
  //! Constructor
116
  LoaderNative(PreviewId id, const Image& image, int parIdx);
117
118
  //! Get properties of a preview image with given params
119
  [[nodiscard]] PreviewProperties getProperties() const override;
120
121
  //! Get a buffer that contains the preview image
122
  [[nodiscard]] DataBuf getData() const override;
123
124
  //! Read preview image dimensions
125
  bool readDimensions() override;
126
127
 protected:
128
  //! Native preview information
129
  NativePreview nativePreview_;
130
};
131
132
//! Function to create new LoaderNative
133
Loader::UniquePtr createLoaderNative(PreviewId id, const Image& image, int parIdx);
134
135
//! Loader for Jpeg previews that are not read into ExifData directly
136
class LoaderExifJpeg : public Loader {
137
 public:
138
  //! Constructor
139
  LoaderExifJpeg(PreviewId id, const Image& image, int parIdx);
140
141
  //! Get properties of a preview image with given params
142
  [[nodiscard]] PreviewProperties getProperties() const override;
143
144
  //! Get a buffer that contains the preview image
145
  [[nodiscard]] DataBuf getData() const override;
146
147
  //! Read preview image dimensions
148
  bool readDimensions() override;
149
150
 protected:
151
  //! Structure that lists offset/size tag pairs
152
  struct Param {
153
    const char* offsetKey_;      //!< Offset tag
154
    const char* sizeKey_;        //!< Size tag
155
    const char* baseOffsetKey_;  //!< Tag that holds base offset or 0
156
  };
157
158
  //! Table that holds all possible offset/size pairs. parIdx is an index to this table
159
  static const Param param_[];
160
161
  //! Offset value
162
  size_t offset_{0};
163
};
164
165
//! Function to create new LoaderExifJpeg
166
Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image& image, int parIdx);
167
168
//! Loader for Jpeg previews that are read into ExifData
169
class LoaderExifDataJpeg : public Loader {
170
 public:
171
  //! Constructor
172
  LoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx);
173
174
  //! Get properties of a preview image with given params
175
  [[nodiscard]] PreviewProperties getProperties() const override;
176
177
  //! Get a buffer that contains the preview image
178
  [[nodiscard]] DataBuf getData() const override;
179
180
  //! Read preview image dimensions
181
  bool readDimensions() override;
182
183
 protected:
184
  //! Structure that lists data/size tag pairs
185
  struct Param {
186
    const char* dataKey_;  //!< Data tag
187
    const char* sizeKey_;  //!< Size tag
188
  };
189
190
  //! Table that holds all possible data/size pairs. parIdx is an index to this table
191
  static const Param param_[];
192
193
  //! Key that points to the Value that contains the JPEG preview in data area
194
  ExifKey dataKey_;
195
};
196
197
//! Function to create new LoaderExifDataJpeg
198
Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx);
199
200
//! Loader for Tiff previews - it can get image data from ExifData or image_.io() as needed
201
class LoaderTiff : public Loader {
202
 public:
203
  //! Constructor
204
  LoaderTiff(PreviewId id, const Image& image, int parIdx);
205
206
  //! Get properties of a preview image with given params
207
  [[nodiscard]] PreviewProperties getProperties() const override;
208
209
  //! Get a buffer that contains the preview image
210
  [[nodiscard]] DataBuf getData() const override;
211
212
 protected:
213
  //! Name of the group that contains the preview image
214
  const char* group_;
215
216
  //! Tag that contains image data. Possible values are "StripOffsets" or "TileOffsets"
217
  std::string offsetTag_;
218
219
  //! Tag that contains data sizes. Possible values are "StripByteCounts" or "TileByteCounts"
220
  std::string sizeTag_;
221
222
  //! Structure that lists preview groups
223
  struct Param {
224
    const char* group_;       //!< Group name
225
    const char* checkTag_;    //!< Tag to check or NULL
226
    const char* checkValue_;  //!< The preview image is valid only if the checkTag_ has this value
227
  };
228
229
  //! Table that holds all possible groups. parIdx is an index to this table.
230
  static const Param param_[];
231
};
232
233
//! Function to create new LoaderTiff
234
Loader::UniquePtr createLoaderTiff(PreviewId id, const Image& image, int parIdx);
235
236
//! Loader for JPEG previews stored in the XMP metadata
237
class LoaderXmpJpeg : public Loader {
238
 public:
239
  //! Constructor
240
  LoaderXmpJpeg(PreviewId id, const Image& image, int parIdx);
241
242
  //! Get properties of a preview image with given params
243
  [[nodiscard]] PreviewProperties getProperties() const override;
244
245
  //! Get a buffer that contains the preview image
246
  [[nodiscard]] DataBuf getData() const override;
247
248
  //! Read preview image dimensions
249
  bool readDimensions() override;
250
251
 protected:
252
  //! Preview image data
253
  DataBuf preview_;
254
};
255
256
//! Function to create new LoaderXmpJpeg
257
Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image& image, int parIdx);
258
259
// *****************************************************************************
260
// class member definitions
261
262
const Loader::LoaderList Loader::loaderList_[] = {
263
    {nullptr, createLoaderNative, 0},        {nullptr, createLoaderNative, 1},
264
    {nullptr, createLoaderNative, 2},        {nullptr, createLoaderNative, 3},
265
    {nullptr, createLoaderExifDataJpeg, 0},  {nullptr, createLoaderExifDataJpeg, 1},
266
    {nullptr, createLoaderExifDataJpeg, 2},  {nullptr, createLoaderExifDataJpeg, 3},
267
    {nullptr, createLoaderExifDataJpeg, 4},  {nullptr, createLoaderExifDataJpeg, 5},
268
    {nullptr, createLoaderExifDataJpeg, 6},  {nullptr, createLoaderExifDataJpeg, 7},
269
    {nullptr, createLoaderExifDataJpeg, 8},  {"image/x-panasonic-rw2", createLoaderExifDataJpeg, 9},
270
    {nullptr, createLoaderExifDataJpeg, 10}, {nullptr, createLoaderExifDataJpeg, 11},
271
    {nullptr, createLoaderTiff, 0},          {nullptr, createLoaderTiff, 1},
272
    {nullptr, createLoaderTiff, 2},          {nullptr, createLoaderTiff, 3},
273
    {nullptr, createLoaderTiff, 4},          {nullptr, createLoaderTiff, 5},
274
    {nullptr, createLoaderTiff, 6},          {"image/x-canon-cr2", createLoaderTiff, 7},
275
    {nullptr, createLoaderExifJpeg, 0},      {nullptr, createLoaderExifJpeg, 1},
276
    {nullptr, createLoaderExifJpeg, 2},      {nullptr, createLoaderExifJpeg, 3},
277
    {nullptr, createLoaderExifJpeg, 4},      {nullptr, createLoaderExifJpeg, 5},
278
    {nullptr, createLoaderExifJpeg, 6},      {"image/x-canon-cr2", createLoaderExifJpeg, 7},
279
    {nullptr, createLoaderExifJpeg, 8},      {nullptr, createLoaderXmpJpeg, 0},
280
};
281
282
const LoaderExifJpeg::Param LoaderExifJpeg::param_[] = {
283
    {"Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", nullptr},            // 0
284
    {"Exif.SubImage1.JPEGInterchangeFormat", "Exif.SubImage1.JPEGInterchangeFormatLength", nullptr},    // 1
285
    {"Exif.SubImage2.JPEGInterchangeFormat", "Exif.SubImage2.JPEGInterchangeFormatLength", nullptr},    // 2
286
    {"Exif.SubImage3.JPEGInterchangeFormat", "Exif.SubImage3.JPEGInterchangeFormatLength", nullptr},    // 3
287
    {"Exif.SubImage4.JPEGInterchangeFormat", "Exif.SubImage4.JPEGInterchangeFormatLength", nullptr},    // 4
288
    {"Exif.SubThumb1.JPEGInterchangeFormat", "Exif.SubThumb1.JPEGInterchangeFormatLength", nullptr},    // 5
289
    {"Exif.Image2.JPEGInterchangeFormat", "Exif.Image2.JPEGInterchangeFormatLength", nullptr},          // 6
290
    {"Exif.Image.StripOffsets", "Exif.Image.StripByteCounts", nullptr},                                 // 7
291
    {"Exif.OlympusCs.PreviewImageStart", "Exif.OlympusCs.PreviewImageLength", "Exif.MakerNote.Offset"}  // 8
292
};
293
294
const LoaderExifDataJpeg::Param LoaderExifDataJpeg::param_[] = {
295
    {"Exif.Thumbnail.JPEGInterchangeFormat", "Exif.Thumbnail.JPEGInterchangeFormatLength"},            //  0
296
    {"Exif.NikonPreview.JPEGInterchangeFormat", "Exif.NikonPreview.JPEGInterchangeFormatLength"},      //  1
297
    {"Exif.Pentax.PreviewOffset", "Exif.Pentax.PreviewLength"},                                        //  2
298
    {"Exif.PentaxDng.PreviewOffset", "Exif.PentaxDng.PreviewLength"},                                  //  3
299
    {"Exif.Minolta.ThumbnailOffset", "Exif.Minolta.ThumbnailLength"},                                  //  4
300
    {"Exif.SonyMinolta.ThumbnailOffset", "Exif.SonyMinolta.ThumbnailLength"},                          //  5
301
    {"Exif.Olympus.ThumbnailImage", nullptr},                                                          //  6
302
    {"Exif.Olympus2.ThumbnailImage", nullptr},                                                         //  7
303
    {"Exif.Minolta.Thumbnail", nullptr},                                                               //  8
304
    {"Exif.PanasonicRaw.PreviewImage", nullptr},                                                       //  9
305
    {"Exif.SamsungPreview.JPEGInterchangeFormat", "Exif.SamsungPreview.JPEGInterchangeFormatLength"},  // 10
306
    {"Exif.Casio2.PreviewImage", nullptr}                                                              // 11
307
};
308
309
const LoaderTiff::Param LoaderTiff::param_[] = {
310
    {"Image", "Exif.Image.NewSubfileType", "1"},          // 0
311
    {"SubImage1", "Exif.SubImage1.NewSubfileType", "1"},  // 1
312
    {"SubImage2", "Exif.SubImage2.NewSubfileType", "1"},  // 2
313
    {"SubImage3", "Exif.SubImage3.NewSubfileType", "1"},  // 3
314
    {"SubImage4", "Exif.SubImage4.NewSubfileType", "1"},  // 4
315
    {"SubThumb1", "Exif.SubThumb1.NewSubfileType", "1"},  // 5
316
    {"Thumbnail", nullptr, nullptr},                      // 6
317
    {"Image2", nullptr, nullptr}                          // 7
318
};
319
320
0
Loader::UniquePtr Loader::create(PreviewId id, const Image& image) {
321
0
  Loader::UniquePtr loader;
322
0
  if (id < 0 || id >= Loader::getNumLoaders())
323
0
    return loader;
324
325
0
  if (loaderList_[id].imageMimeType_ && std::string(loaderList_[id].imageMimeType_) != image.mimeType())
326
0
    return loader;
327
328
0
  loader = loaderList_[id].create_(id, image, loaderList_[id].parIdx_);
329
0
  if (!loader->valid())
330
0
    loader = nullptr;
331
332
0
  return loader;
333
0
}
334
335
0
Loader::Loader(PreviewId id, const Image& image) : id_(id), image_(image) {
336
0
}
337
338
0
PreviewProperties Loader::getProperties() const {
339
0
  return {"", "", size_, width_, height_, id_};
340
0
}
341
342
0
PreviewId Loader::getNumLoaders() {
343
0
  return PreviewId{std::size(loaderList_)};
344
0
}
345
346
0
LoaderNative::LoaderNative(PreviewId id, const Image& image, int parIdx) : Loader(id, image) {
347
0
  if (0 > parIdx || static_cast<size_t>(parIdx) >= image.nativePreviews().size())
348
0
    return;
349
0
  nativePreview_ = image.nativePreviews()[parIdx];
350
0
  width_ = nativePreview_.width_;
351
0
  height_ = nativePreview_.height_;
352
0
  valid_ = true;
353
0
  if (nativePreview_.filter_.empty()) {
354
0
    size_ = nativePreview_.size_;
355
0
  } else {
356
0
    size_ = getData().size();
357
0
  }
358
0
}
359
360
0
Loader::UniquePtr createLoaderNative(PreviewId id, const Image& image, int parIdx) {
361
0
  return std::make_unique<LoaderNative>(id, image, parIdx);
362
0
}
363
364
0
PreviewProperties LoaderNative::getProperties() const {
365
0
  PreviewProperties prop = Loader::getProperties();
366
0
  prop.mimeType_ = nativePreview_.mimeType_;
367
0
  if (nativePreview_.mimeType_ == "image/jpeg") {
368
0
    prop.extension_ = ".jpg";
369
0
  } else if (nativePreview_.mimeType_ == "image/tiff") {
370
0
    prop.extension_ = ".tif";
371
0
  } else if (nativePreview_.mimeType_ == "image/x-wmf") {
372
0
    prop.extension_ = ".wmf";
373
0
  } else if (nativePreview_.mimeType_ == "image/x-portable-anymap") {
374
0
    prop.extension_ = ".pnm";
375
0
  } else {
376
0
#ifndef SUPPRESS_WARNINGS
377
0
    EXV_WARNING << "Unknown native preview format: " << nativePreview_.mimeType_ << "\n";
378
0
#endif
379
0
    prop.extension_ = ".dat";
380
0
  }
381
0
  return prop;
382
0
}
383
384
0
DataBuf LoaderNative::getData() const {
385
0
  if (!valid())
386
0
    return {};
387
388
0
  BasicIo& io = image_.io();
389
0
  if (io.open() != 0) {
390
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError());
391
0
  }
392
0
  IoCloser closer(io);
393
0
  const byte* data = io.mmap();
394
0
  if (io.size() < nativePreview_.position_ + nativePreview_.size_) {
395
0
#ifndef SUPPRESS_WARNINGS
396
0
    EXV_WARNING << "Invalid native preview position or size.\n";
397
0
#endif
398
0
    return {};
399
0
  }
400
0
  if (nativePreview_.filter_.empty()) {
401
0
    return {data + nativePreview_.position_, nativePreview_.size_};
402
0
  }
403
0
  if (nativePreview_.filter_ == "hex-ai7thumbnail-pnm") {
404
0
    const DataBuf ai7thumbnail = decodeHex(data + nativePreview_.position_, nativePreview_.size_);
405
0
    const DataBuf rgb = decodeAi7Thumbnail(ai7thumbnail);
406
0
    return makePnm(width_, height_, rgb);
407
0
  }
408
0
  if (nativePreview_.filter_ == "hex-irb") {
409
0
    const DataBuf psData = decodeHex(data + nativePreview_.position_, nativePreview_.size_);
410
0
    const byte* record;
411
0
    uint32_t sizeHdr = 0;
412
0
    uint32_t sizeData = 0;
413
0
    if (Photoshop::locatePreviewIrb(psData.c_data(), psData.size(), &record, sizeHdr, sizeData) != 0) {
414
0
#ifndef SUPPRESS_WARNINGS
415
0
      EXV_WARNING << "Missing preview IRB in Photoshop EPS preview.\n";
416
0
#endif
417
0
      return {};
418
0
    }
419
0
    Internal::enforce(sizeData >= 28, ErrorCode::kerCorruptedMetadata);
420
0
    return {record + sizeHdr + 28, sizeData - 28};
421
0
  }
422
0
  throw Error(ErrorCode::kerErrorMessage, "Invalid native preview filter: ", nativePreview_.filter_);
423
0
}
424
425
0
bool LoaderNative::readDimensions() {
426
0
  if (!valid())
427
0
    return false;
428
0
  if (width_ != 0 || height_ != 0)
429
0
    return true;
430
431
0
  const DataBuf data = getData();
432
0
  if (data.empty())
433
0
    return false;
434
435
0
  try {
436
0
    auto image = ImageFactory::open(data.c_data(), data.size());
437
0
    if (!image)
438
0
      return false;
439
0
    image->readMetadata();
440
441
0
    width_ = image->pixelWidth();
442
0
    height_ = image->pixelHeight();
443
0
  } catch (const Error& /* error */) {
444
0
#ifndef SUPPRESS_WARNINGS
445
0
    EXV_WARNING << "Invalid native preview image.\n";
446
0
#endif
447
0
    return false;
448
0
  }
449
0
  return true;
450
0
}
451
452
0
LoaderExifJpeg::LoaderExifJpeg(PreviewId id, const Image& image, int parIdx) : Loader(id, image) {
453
0
  const ExifData& exifData = image_.exifData();
454
0
  auto pos = exifData.findKey(ExifKey(param_[parIdx].offsetKey_));
455
0
  if (pos != exifData.end() && pos->count() > 0) {
456
0
    offset_ = pos->toUint32();
457
0
  }
458
459
0
  size_ = 0;
460
0
  pos = exifData.findKey(ExifKey(param_[parIdx].sizeKey_));
461
0
  if (pos != exifData.end() && pos->count() > 0) {
462
0
    size_ = pos->toUint32();
463
0
  }
464
465
0
  if (offset_ == 0 || size_ == 0)
466
0
    return;
467
468
0
  if (param_[parIdx].baseOffsetKey_) {
469
0
    pos = exifData.findKey(ExifKey(param_[parIdx].baseOffsetKey_));
470
0
    if (pos != exifData.end() && pos->count() > 0) {
471
0
      offset_ += pos->toUint32();
472
0
    }
473
0
  }
474
475
0
  if (Safe::add(offset_, size_) > image_.io().size())
476
0
    return;
477
478
0
  valid_ = true;
479
0
}
480
481
0
Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image& image, int parIdx) {
482
0
  return std::make_unique<LoaderExifJpeg>(id, image, parIdx);
483
0
}
484
485
0
PreviewProperties LoaderExifJpeg::getProperties() const {
486
0
  PreviewProperties prop = Loader::getProperties();
487
0
  prop.mimeType_ = "image/jpeg";
488
0
  prop.extension_ = ".jpg";
489
0
  return prop;
490
0
}
491
492
0
DataBuf LoaderExifJpeg::getData() const {
493
0
  if (!valid())
494
0
    return {};
495
0
  BasicIo& io = image_.io();
496
497
0
  if (io.open() != 0) {
498
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError());
499
0
  }
500
0
  IoCloser closer(io);
501
502
0
  const Exiv2::byte* base = io.mmap();
503
504
0
  return {base + offset_, size_};
505
0
}
506
507
0
bool LoaderExifJpeg::readDimensions() {
508
0
  if (!valid())
509
0
    return false;
510
0
  if (width_ || height_)
511
0
    return true;
512
513
0
  BasicIo& io = image_.io();
514
515
0
  if (io.open() != 0) {
516
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError());
517
0
  }
518
0
  IoCloser closer(io);
519
0
  const Exiv2::byte* base = io.mmap();
520
521
0
  try {
522
0
    auto image = ImageFactory::open(base + offset_, size_);
523
0
    if (!image)
524
0
      return false;
525
0
    image->readMetadata();
526
527
0
    width_ = image->pixelWidth();
528
0
    height_ = image->pixelHeight();
529
0
  } catch (const Error& /* error */) {
530
0
#ifndef SUPPRESS_WARNINGS
531
0
    EXV_WARNING << "Invalid JPEG preview image.\n";
532
0
#endif
533
0
    return false;
534
0
  }
535
536
0
  return true;
537
0
}
538
539
LoaderExifDataJpeg::LoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx) :
540
0
    Loader(id, image), dataKey_(param_[parIdx].dataKey_) {
541
0
  if (auto pos = image_.exifData().findKey(dataKey_); pos != image_.exifData().end()) {
542
0
    size_ = pos->sizeDataArea();  // indirect data
543
0
    if (size_ == 0 && pos->typeId() == undefined)
544
0
      size_ = pos->size();  // direct data
545
0
  }
546
547
0
  if (size_ == 0)
548
0
    return;
549
550
0
  valid_ = true;
551
0
}
552
553
0
Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image& image, int parIdx) {
554
0
  return std::make_unique<LoaderExifDataJpeg>(id, image, parIdx);
555
0
}
556
557
0
PreviewProperties LoaderExifDataJpeg::getProperties() const {
558
0
  PreviewProperties prop = Loader::getProperties();
559
0
  prop.mimeType_ = "image/jpeg";
560
0
  prop.extension_ = ".jpg";
561
0
  return prop;
562
0
}
563
564
0
DataBuf LoaderExifDataJpeg::getData() const {
565
0
  DataBuf buf;
566
567
0
  if (!valid())
568
0
    return buf;
569
570
0
  if (auto pos = image_.exifData().findKey(dataKey_); pos != image_.exifData().end()) {
571
0
    buf = pos->dataArea();  // indirect data
572
573
0
    if (buf.empty()) {  // direct data
574
0
      buf = DataBuf(pos->size());
575
0
      pos->copy(buf.data(), invalidByteOrder);
576
0
    }
577
578
0
    buf.write_uint8(0, 0xff);  // fix Minolta thumbnails with invalid jpeg header
579
0
    return buf;
580
0
  }
581
582
0
  return buf;
583
0
}
584
585
0
bool LoaderExifDataJpeg::readDimensions() {
586
0
  if (!valid())
587
0
    return false;
588
589
0
  DataBuf buf = getData();
590
0
  if (buf.empty())
591
0
    return false;
592
593
0
  try {
594
0
    auto image = ImageFactory::open(buf.c_data(), buf.size());
595
0
    if (!image)
596
0
      return false;
597
0
    image->readMetadata();
598
599
0
    width_ = image->pixelWidth();
600
0
    height_ = image->pixelHeight();
601
0
  } catch (const Error& /* error */) {
602
0
    return false;
603
0
  }
604
605
0
  return true;
606
0
}
607
608
LoaderTiff::LoaderTiff(PreviewId id, const Image& image, int parIdx) :
609
0
    Loader(id, image), group_(param_[parIdx].group_) {
610
0
  const ExifData& exifData = image_.exifData();
611
612
0
  size_t offsetCount = 0;
613
0
  ExifData::const_iterator pos;
614
615
  // check if the group_ contains a preview image
616
0
  if (param_[parIdx].checkTag_) {
617
0
    pos = exifData.findKey(ExifKey(param_[parIdx].checkTag_));
618
0
    if (pos == exifData.end())
619
0
      return;
620
0
    if (param_[parIdx].checkValue_ && pos->toString() != param_[parIdx].checkValue_)
621
0
      return;
622
0
  }
623
624
0
  pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".StripOffsets"));
625
0
  if (pos != exifData.end()) {
626
0
    offsetTag_ = "StripOffsets";
627
0
    sizeTag_ = "StripByteCounts";
628
0
    offsetCount = pos->value().count();
629
0
  } else {
630
0
    pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".TileOffsets"));
631
0
    if (pos == exifData.end())
632
0
      return;
633
0
    offsetTag_ = "TileOffsets";
634
0
    sizeTag_ = "TileByteCounts";
635
0
    offsetCount = pos->value().count();
636
0
  }
637
638
0
  pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + '.' + sizeTag_));
639
0
  if (pos == exifData.end())
640
0
    return;
641
0
  if (offsetCount != pos->value().count())
642
0
    return;
643
0
  for (size_t i = 0; i < offsetCount; i++) {
644
0
    size_ += pos->toUint32(i);
645
0
  }
646
647
0
  if (size_ == 0)
648
0
    return;
649
650
0
  pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageWidth"));
651
0
  if (pos != exifData.end() && pos->count() > 0) {
652
0
    width_ = pos->toUint32();
653
0
  }
654
655
0
  pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageLength"));
656
0
  if (pos != exifData.end() && pos->count() > 0) {
657
0
    height_ = pos->toUint32();
658
0
  }
659
660
0
  if (width_ == 0 || height_ == 0)
661
0
    return;
662
663
0
  valid_ = true;
664
0
}
665
666
0
Loader::UniquePtr createLoaderTiff(PreviewId id, const Image& image, int parIdx) {
667
0
  return std::make_unique<LoaderTiff>(id, image, parIdx);
668
0
}
669
670
0
PreviewProperties LoaderTiff::getProperties() const {
671
0
  PreviewProperties prop = Loader::getProperties();
672
0
  prop.mimeType_ = "image/tiff";
673
0
  prop.extension_ = ".tif";
674
0
  return prop;
675
0
}
676
677
0
DataBuf LoaderTiff::getData() const {
678
0
  const ExifData& exifData = image_.exifData();
679
680
0
  ExifData preview;
681
682
  // copy tags
683
0
  for (auto&& pos : exifData) {
684
0
    if (pos.groupName() == group_) {
685
      /*
686
         Write only the necessary TIFF image tags
687
         tags that especially could cause problems are:
688
         "NewSubfileType" - the result is no longer a thumbnail, it is a standalone image
689
         "Orientation" - this tag typically appears only in the "Image" group. Deleting it ensures
690
                         consistent result for all previews, including JPEG
691
      */
692
0
      uint16_t tag = pos.tag();
693
0
      if (tag != 0x00fe && tag != 0x00ff && Internal::isTiffImageTag(tag, IfdId::ifd0Id)) {
694
0
        preview.add(ExifKey(tag, "Image"), &pos.value());
695
0
      }
696
0
    }
697
0
  }
698
699
0
  auto& dataValue = const_cast<Value&>(preview["Exif.Image." + offsetTag_].value());
700
701
0
  if (dataValue.sizeDataArea() == 0) {
702
    // image data are not available via exifData, read them from image_.io()
703
0
    BasicIo& io = image_.io();
704
705
0
    if (io.open() != 0) {
706
0
      throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError());
707
0
    }
708
0
    IoCloser closer(io);
709
710
0
    const Exiv2::byte* base = io.mmap();
711
712
0
    const Value& sizes = preview["Exif.Image." + sizeTag_].value();
713
714
0
    if (sizes.count() == dataValue.count()) {
715
0
      if (sizes.count() == 1) {
716
        // this saves one copying of the buffer
717
0
        uint32_t offset = dataValue.toUint32(0);
718
0
        uint32_t size = sizes.toUint32(0);
719
0
        if (Safe::add(offset, size) <= static_cast<uint32_t>(io.size()))
720
0
          dataValue.setDataArea(base + offset, size);
721
0
      } else {
722
        // FIXME: the buffer is probably copied twice, it should be optimized
723
0
        Internal::enforce(size_ <= io.size(), ErrorCode::kerCorruptedMetadata);
724
0
        DataBuf buf(size_);
725
0
        uint32_t idxBuf = 0;
726
0
        for (size_t i = 0; i < sizes.count(); i++) {
727
0
          uint32_t offset = dataValue.toUint32(i);
728
0
          uint32_t size = sizes.toUint32(i);
729
730
          // the size_ parameter is originally computed by summing all values inside sizes
731
          // see the constructor of LoaderTiff
732
          // But e.g in malicious files some of these values could be negative
733
          // That's why we check again for each step here to really make sure we don't overstep
734
0
          Internal::enforce(Safe::add(idxBuf, size) <= size_, ErrorCode::kerCorruptedMetadata);
735
0
          if (size != 0 && Safe::add(offset, size) <= static_cast<uint32_t>(io.size())) {
736
0
            std::copy_n(base + offset, size, buf.begin() + idxBuf);
737
0
          }
738
739
0
          idxBuf += size;
740
0
        }
741
0
        dataValue.setDataArea(buf.c_data(), buf.size());
742
0
      }
743
0
    }
744
0
  }
745
746
  // Fix compression value in the CR2 IFD2 image
747
0
  if (0 == strcmp(group_, "Image2") && image_.mimeType() == "image/x-canon-cr2") {
748
0
    preview["Exif.Image.Compression"] = std::uint16_t{1};
749
0
  }
750
751
  // write new image
752
0
  MemIo mio;
753
0
  IptcData emptyIptc;
754
0
  XmpData emptyXmp;
755
0
  TiffParser::encode(mio, nullptr, 0, Exiv2::littleEndian, preview, emptyIptc, emptyXmp);
756
0
  return {mio.mmap(), mio.size()};
757
0
}
758
759
0
LoaderXmpJpeg::LoaderXmpJpeg(PreviewId id, const Image& image, int parIdx) : Loader(id, image) {
760
0
  (void)parIdx;
761
762
0
  const XmpData& xmpData = image_.xmpData();
763
764
0
  std::string prefix = "xmpGImg";
765
0
  if (xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/xapGImg:image")) != xmpData.end()) {
766
0
    prefix = "xapGImg";
767
0
  }
768
769
0
  auto imageDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":image"));
770
0
  if (imageDatum == xmpData.end())
771
0
    return;
772
0
  auto formatDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":format"));
773
0
  if (formatDatum == xmpData.end())
774
0
    return;
775
0
  auto widthDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":width"));
776
0
  if (widthDatum == xmpData.end())
777
0
    return;
778
0
  auto heightDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":height"));
779
0
  if (heightDatum == xmpData.end())
780
0
    return;
781
782
0
  if (formatDatum->toString() != "JPEG")
783
0
    return;
784
785
0
  width_ = widthDatum->toUint32();
786
0
  height_ = heightDatum->toUint32();
787
0
  preview_ = decodeBase64(imageDatum->toString());
788
0
  size_ = preview_.size();
789
0
  valid_ = true;
790
0
}
791
792
0
Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image& image, int parIdx) {
793
0
  return std::make_unique<LoaderXmpJpeg>(id, image, parIdx);
794
0
}
795
796
0
PreviewProperties LoaderXmpJpeg::getProperties() const {
797
0
  PreviewProperties prop = Loader::getProperties();
798
0
  prop.mimeType_ = "image/jpeg";
799
0
  prop.extension_ = ".jpg";
800
0
  return prop;
801
0
}
802
803
0
DataBuf LoaderXmpJpeg::getData() const {
804
0
  if (!valid())
805
0
    return {};
806
0
  return {preview_.c_data(), preview_.size()};
807
0
}
808
809
0
bool LoaderXmpJpeg::readDimensions() {
810
0
  return valid();
811
0
}
812
813
0
DataBuf decodeHex(const byte* src, size_t srcSize) {
814
  // create decoding table
815
0
  byte invalid = 16;
816
0
  std::array<byte, 256> decodeHexTable;
817
0
  decodeHexTable.fill(invalid);
818
0
  for (byte i = 0; i < 10; i++)
819
0
    decodeHexTable[static_cast<byte>('0') + i] = i;
820
0
  for (byte i = 0; i < 6; i++)
821
0
    decodeHexTable[static_cast<byte>('A') + i] = i + 10;
822
0
  for (byte i = 0; i < 6; i++)
823
0
    decodeHexTable[static_cast<byte>('a') + i] = i + 10;
824
825
  // calculate dest size
826
0
  long validSrcSize = 0;
827
0
  for (size_t srcPos = 0; srcPos < srcSize; srcPos++) {
828
0
    if (decodeHexTable[src[srcPos]] != invalid)
829
0
      validSrcSize++;
830
0
  }
831
0
  const size_t destSize = validSrcSize / 2;
832
833
  // allocate dest buffer
834
0
  DataBuf dest(destSize);
835
836
  // decode
837
0
  for (size_t srcPos = 0, destPos = 0; destPos < destSize; destPos++) {
838
0
    byte buffer = 0;
839
0
    for (int bufferPos = 1; bufferPos >= 0 && srcPos < srcSize; srcPos++) {
840
0
      byte srcValue = decodeHexTable[src[srcPos]];
841
0
      if (srcValue == invalid)
842
0
        continue;
843
0
      buffer |= srcValue << (bufferPos * 4);
844
0
      bufferPos--;
845
0
    }
846
0
    dest.write_uint8(destPos, buffer);
847
0
  }
848
0
  return dest;
849
0
}
850
851
const char encodeBase64Table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
852
853
0
DataBuf decodeBase64(const std::string& src) {
854
0
  DataBuf dest;
855
  // create decoding table
856
0
  unsigned long invalid = 64;
857
0
  auto decodeBase64Table = std::vector<unsigned long>(256, invalid);
858
0
  for (unsigned long i = 0; i < 64; i++)
859
0
    decodeBase64Table[static_cast<unsigned char>(encodeBase64Table[i])] = i;
860
861
  // calculate dest size
862
0
  auto validSrcSize = static_cast<unsigned long>(
863
0
      std::count_if(src.begin(), src.end(), [&](unsigned char c) { return decodeBase64Table.at(c) != invalid; }));
864
0
  if (validSrcSize > ULONG_MAX / 3)
865
0
    return dest;  // avoid integer overflow
866
0
  const unsigned long destSize = (validSrcSize * 3) / 4;
867
868
  // allocate dest buffer
869
0
  dest = DataBuf(destSize);
870
871
  // decode
872
0
  for (unsigned long srcPos = 0, destPos = 0; destPos < destSize;) {
873
0
    unsigned long buffer = 0;
874
0
    for (int bufferPos = 3; bufferPos >= 0 && srcPos < src.size(); srcPos++) {
875
0
      unsigned long srcValue = decodeBase64Table[static_cast<unsigned char>(src[srcPos])];
876
0
      if (srcValue == invalid)
877
0
        continue;
878
0
      buffer |= srcValue << (bufferPos * 6);
879
0
      bufferPos--;
880
0
    }
881
0
    for (int bufferPos = 2; bufferPos >= 0 && destPos < destSize; bufferPos--, destPos++) {
882
0
      dest.write_uint8(destPos, static_cast<byte>((buffer >> (bufferPos * 8))));
883
0
    }
884
0
  }
885
0
  return dest;
886
0
}
887
888
0
DataBuf decodeAi7Thumbnail(const DataBuf& src) {
889
0
  const byte* colorTable = src.c_data();
890
0
  const size_t colorTableSize = 256 * 3;
891
0
  if (src.size() < colorTableSize) {
892
0
#ifndef SUPPRESS_WARNINGS
893
0
    EXV_WARNING << "Invalid size of AI7 thumbnail: " << src.size() << "\n";
894
0
#endif
895
0
    return {};
896
0
  }
897
0
  const byte* imageData = src.c_data(colorTableSize);
898
0
  const size_t imageDataSize = src.size() - colorTableSize;
899
0
  const bool rle = (imageDataSize >= 3 && imageData[0] == 'R' && imageData[1] == 'L' && imageData[2] == 'E');
900
0
  std::string dest;
901
0
  for (size_t i = rle ? 3 : 0; i < imageDataSize;) {
902
0
    byte num = 1;
903
0
    byte value = imageData[i++];
904
0
    if (rle && value == 0xFD) {
905
0
      if (i >= imageDataSize) {
906
0
#ifndef SUPPRESS_WARNINGS
907
0
        EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n";
908
0
#endif
909
0
        return {};
910
0
      }
911
0
      value = imageData[i++];
912
0
      if (value != 0xFD) {
913
0
        if (i >= imageDataSize) {
914
0
#ifndef SUPPRESS_WARNINGS
915
0
          EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n";
916
0
#endif
917
0
          return {};
918
0
        }
919
0
        num = value;
920
0
        value = imageData[i++];
921
0
      }
922
0
    }
923
0
    for (; num != 0; num--) {
924
0
      dest.append(reinterpret_cast<const char*>(colorTable + (3 * value)), 3);
925
0
    }
926
0
  }
927
0
  return {reinterpret_cast<const byte*>(dest.data()), dest.size()};
928
0
}
929
930
0
DataBuf makePnm(size_t width, size_t height, const DataBuf& rgb) {
931
0
  DataBuf dest;
932
0
  if (size_t expectedSize = width * height * 3UL; rgb.size() != expectedSize) {
933
0
#ifndef SUPPRESS_WARNINGS
934
0
    EXV_WARNING << "Invalid size of preview data. Expected " << expectedSize << " bytes, got " << rgb.size()
935
0
                << " bytes.\n";
936
0
#endif
937
0
    return dest;
938
0
  }
939
940
0
  const std::string header = "P6\n" + std::to_string(width) + " " + std::to_string(height) + "\n255\n";
941
942
0
  dest = DataBuf(header.size() + rgb.size());
943
0
  std::copy(header.begin(), header.end(), dest.begin());
944
0
  std::copy(rgb.begin(), rgb.end(), dest.begin() + header.size());
945
0
  return dest;
946
0
}
947
948
}  // namespace
949
950
// *****************************************************************************
951
// class member definitions
952
namespace Exiv2 {
953
PreviewImage::PreviewImage(PreviewProperties properties, DataBuf&& data) :
954
0
    properties_(std::move(properties)), preview_(std::move(data)) {
955
0
}
956
957
0
PreviewImage::PreviewImage(const PreviewImage& rhs) : properties_(rhs.properties_), preview_(rhs.pData(), rhs.size()) {
958
0
}
959
960
0
PreviewImage& PreviewImage::operator=(const PreviewImage& rhs) {
961
0
  if (this == &rhs)
962
0
    return *this;
963
0
  properties_ = rhs.properties_;
964
0
  preview_ = DataBuf(rhs.pData(), rhs.size());
965
0
  return *this;
966
0
}
967
968
#ifdef EXV_ENABLE_FILESYSTEM
969
0
size_t PreviewImage::writeFile(const std::string& path) const {
970
0
  std::string name = path + extension();
971
  // Todo: Creating a DataBuf here unnecessarily copies the memory
972
0
  DataBuf buf(pData(), size());
973
0
  return Exiv2::writeFile(buf, name);
974
0
}
975
#endif
976
977
0
DataBuf PreviewImage::copy() const {
978
0
  return {pData(), size()};
979
0
}
980
981
0
const byte* PreviewImage::pData() const {
982
0
  return preview_.c_data();
983
0
}
984
985
0
uint32_t PreviewImage::size() const {
986
0
  return static_cast<uint32_t>(preview_.size());
987
0
}
988
989
0
const std::string& PreviewImage::mimeType() const {
990
0
  return properties_.mimeType_;
991
0
}
992
993
0
const std::string& PreviewImage::extension() const {
994
0
  return properties_.extension_;
995
0
}
996
997
0
size_t PreviewImage::width() const {
998
0
  return properties_.width_;
999
0
}
1000
1001
0
size_t PreviewImage::height() const {
1002
0
  return properties_.height_;
1003
0
}
1004
1005
0
PreviewId PreviewImage::id() const {
1006
0
  return properties_.id_;
1007
0
}
1008
1009
0
PreviewManager::PreviewManager(const Image& image) : image_(image) {
1010
0
}
1011
1012
0
PreviewPropertiesList PreviewManager::getPreviewProperties() const {
1013
0
  PreviewPropertiesList list;
1014
  // go through the loader table and store all successfully created loaders in the list
1015
0
  for (PreviewId id = 0; id < Loader::getNumLoaders(); ++id) {
1016
0
    auto loader = Loader::create(id, image_);
1017
0
    if (loader && loader->readDimensions()) {
1018
0
      PreviewProperties props = loader->getProperties();
1019
0
      DataBuf buf = loader->getData();  // #16 getPreviewImage()
1020
0
      props.size_ = buf.size();         //     update the size
1021
0
      list.push_back(std::move(props));
1022
0
    }
1023
0
  }
1024
0
  std::sort(list.begin(), list.end(),
1025
0
            [](const auto& lhs, const auto& rhs) { return lhs.width_ * lhs.height_ < rhs.width_ * rhs.height_; });
1026
1027
0
  return list;
1028
0
}
1029
1030
0
PreviewImage PreviewManager::getPreviewImage(const PreviewProperties& properties) const {
1031
0
  auto loader = Loader::create(properties.id_, image_);
1032
0
  DataBuf buf;
1033
0
  if (loader) {
1034
0
    buf = loader->getData();
1035
0
  }
1036
1037
0
  return {properties, std::move(buf)};
1038
0
}
1039
}  // namespace Exiv2