// SPDX-License-Identifier: GPL-2.0-or-later // included header files #include "preview.hpp" #include "config.h" #include "enforce.hpp" #include "futils.hpp" #include "image.hpp" #include "jpgimage.hpp" #include "safe_op.hpp" #include "tiffimage.hpp" #include "tiffimage_int.hpp" #include namespace { using namespace Exiv2; using Exiv2::byte; /*! @brief Compare two preview images by number of pixels, if width and height of both lhs and rhs are available or else by size. Return true if lhs is smaller than rhs. */ bool cmpPreviewProperties( const PreviewProperties& lhs, const PreviewProperties& rhs ) { uint32_t l = lhs.width_ * lhs.height_; uint32_t r = rhs.width_ * rhs.height_; return l < r; } /*! @brief Decode a Hex string. */ DataBuf decodeHex(const byte *src, long srcSize); /*! @brief Decode a Base64 string. */ DataBuf decodeBase64(const std::string &src); /*! @brief Decode an Illustrator thumbnail that follows after %AI7_Thumbnail. */ DataBuf decodeAi7Thumbnail(const DataBuf &src); /*! @brief Create a PNM image from raw RGB data. */ DataBuf makePnm(uint32_t width, uint32_t height, const DataBuf &rgb); /*! Base class for image loaders. Provides virtual methods for reading properties and DataBuf. */ class Loader { public: //! Virtual destructor. virtual ~Loader() = default; //! Loader auto pointer using UniquePtr = std::unique_ptr; //! Create a Loader subclass for requested id static UniquePtr create(PreviewId id, const Image &image); //! Check if a preview image with given params exists in the image virtual bool valid() const { return valid_; } //! Get properties of a preview image with given params virtual PreviewProperties getProperties() const; //! Get a buffer that contains the preview image virtual DataBuf getData() const = 0; //! Read preview image dimensions when they are not available directly virtual bool readDimensions() { return true; } //! A number of image loaders configured in the loaderList_ table static PreviewId getNumLoaders(); protected: //! Constructor. Sets all image properties to unknown. Loader(PreviewId id, const Image &image); //! Functions that creates a loader from given parameters using CreateFunc = UniquePtr (*)(PreviewId, const Image &, int); //! Structure to list possible loaders struct LoaderList { const char *imageMimeType_; //!< Image type for which the loader is valid, 0 matches all images CreateFunc create_; //!< Function that creates particular loader instance int parIdx_; //!< Parameter that is passed into CreateFunc }; //! Table that lists possible loaders. PreviewId is an index to this table. static const LoaderList loaderList_[]; //! Identifies preview image type PreviewId id_; //! Source image reference const Image &image_; //! Preview image width uint32_t width_; //! Preview image length uint32_t height_; //! Preview image size in bytes size_t size_; //! True if the source image contains a preview image of given type bool valid_; }; //! Loader for native previews class LoaderNative : public Loader { public: //! Constructor LoaderNative(PreviewId id, const Image &image, int parIdx); //! Get properties of a preview image with given params PreviewProperties getProperties() const override; //! Get a buffer that contains the preview image DataBuf getData() const override; //! Read preview image dimensions bool readDimensions() override; protected: //! Native preview information NativePreview nativePreview_; }; //! Function to create new LoaderNative Loader::UniquePtr createLoaderNative(PreviewId id, const Image &image, int parIdx); //! Loader for Jpeg previews that are not read into ExifData directly class LoaderExifJpeg : public Loader { public: //! Constructor LoaderExifJpeg(PreviewId id, const Image &image, int parIdx); //! Get properties of a preview image with given params PreviewProperties getProperties() const override; //! Get a buffer that contains the preview image DataBuf getData() const override; //! Read preview image dimensions bool readDimensions() override; protected: //! Structure that lists offset/size tag pairs struct Param { const char* offsetKey_; //!< Offset tag const char* sizeKey_; //!< Size tag const char* baseOffsetKey_; //!< Tag that holds base offset or 0 }; //! Table that holds all possible offset/size pairs. parIdx is an index to this table static const Param param_[]; //! Offset value size_t offset_; }; //! Function to create new LoaderExifJpeg Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image &image, int parIdx); //! Loader for Jpeg previews that are read into ExifData class LoaderExifDataJpeg : public Loader { public: //! Constructor LoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx); //! Get properties of a preview image with given params PreviewProperties getProperties() const override; //! Get a buffer that contains the preview image DataBuf getData() const override; //! Read preview image dimensions bool readDimensions() override; protected: //! Structure that lists data/size tag pairs struct Param { const char* dataKey_; //!< Data tag const char* sizeKey_; //!< Size tag }; //! Table that holds all possible data/size pairs. parIdx is an index to this table static const Param param_[]; //! Key that points to the Value that contains the JPEG preview in data area ExifKey dataKey_; }; //! Function to create new LoaderExifDataJpeg Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx); //! Loader for Tiff previews - it can get image data from ExifData or image_.io() as needed class LoaderTiff : public Loader { public: //! Constructor LoaderTiff(PreviewId id, const Image &image, int parIdx); //! Get properties of a preview image with given params PreviewProperties getProperties() const override; //! Get a buffer that contains the preview image DataBuf getData() const override; protected: //! Name of the group that contains the preview image const char *group_; //! Tag that contains image data. Possible values are "StripOffsets" or "TileOffsets" std::string offsetTag_; //! Tag that contains data sizes. Possible values are "StripByteCounts" or "TileByteCounts" std::string sizeTag_; //! Structure that lists preview groups struct Param { const char* group_; //!< Group name const char* checkTag_; //!< Tag to check or NULL const char* checkValue_; //!< The preview image is valid only if the checkTag_ has this value }; //! Table that holds all possible groups. parIdx is an index to this table. static const Param param_[]; }; //! Function to create new LoaderTiff Loader::UniquePtr createLoaderTiff(PreviewId id, const Image &image, int parIdx); //! Loader for JPEG previews stored in the XMP metadata class LoaderXmpJpeg : public Loader { public: //! Constructor LoaderXmpJpeg(PreviewId id, const Image &image, int parIdx); //! Get properties of a preview image with given params PreviewProperties getProperties() const override; //! Get a buffer that contains the preview image DataBuf getData() const override; //! Read preview image dimensions bool readDimensions() override; protected: //! Preview image data DataBuf preview_; }; //! Function to create new LoaderXmpJpeg Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image &image, int parIdx); // ***************************************************************************** // class member definitions const Loader::LoaderList Loader::loaderList_[] = { { nullptr, createLoaderNative, 0 }, { nullptr, createLoaderNative, 1 }, { nullptr, createLoaderNative, 2 }, { nullptr, createLoaderNative, 3 }, { nullptr, createLoaderExifDataJpeg, 0 }, { nullptr, createLoaderExifDataJpeg, 1 }, { nullptr, createLoaderExifDataJpeg, 2 }, { nullptr, createLoaderExifDataJpeg, 3 }, { nullptr, createLoaderExifDataJpeg, 4 }, { nullptr, createLoaderExifDataJpeg, 5 }, { nullptr, createLoaderExifDataJpeg, 6 }, { nullptr, createLoaderExifDataJpeg, 7 }, { nullptr, createLoaderExifDataJpeg, 8 }, { "image/x-panasonic-rw2", createLoaderExifDataJpeg, 9 }, { nullptr, createLoaderExifDataJpeg,10 }, { nullptr, createLoaderExifDataJpeg,11 }, { nullptr, createLoaderTiff, 0 }, { nullptr, createLoaderTiff, 1 }, { nullptr, createLoaderTiff, 2 }, { nullptr, createLoaderTiff, 3 }, { nullptr, createLoaderTiff, 4 }, { nullptr, createLoaderTiff, 5 }, { nullptr, createLoaderTiff, 6 }, { "image/x-canon-cr2", createLoaderTiff, 7 }, { nullptr, createLoaderExifJpeg, 0 }, { nullptr, createLoaderExifJpeg, 1 }, { nullptr, createLoaderExifJpeg, 2 }, { nullptr, createLoaderExifJpeg, 3 }, { nullptr, createLoaderExifJpeg, 4 }, { nullptr, createLoaderExifJpeg, 5 }, { nullptr, createLoaderExifJpeg, 6 }, { "image/x-canon-cr2", createLoaderExifJpeg, 7 }, { nullptr, createLoaderExifJpeg, 8 }, { nullptr, createLoaderXmpJpeg, 0 } }; const LoaderExifJpeg::Param LoaderExifJpeg::param_[] = { { "Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", nullptr }, // 0 { "Exif.SubImage1.JPEGInterchangeFormat", "Exif.SubImage1.JPEGInterchangeFormatLength", nullptr }, // 1 { "Exif.SubImage2.JPEGInterchangeFormat", "Exif.SubImage2.JPEGInterchangeFormatLength", nullptr }, // 2 { "Exif.SubImage3.JPEGInterchangeFormat", "Exif.SubImage3.JPEGInterchangeFormatLength", nullptr }, // 3 { "Exif.SubImage4.JPEGInterchangeFormat", "Exif.SubImage4.JPEGInterchangeFormatLength", nullptr }, // 4 { "Exif.SubThumb1.JPEGInterchangeFormat", "Exif.SubThumb1.JPEGInterchangeFormatLength", nullptr }, // 5 { "Exif.Image2.JPEGInterchangeFormat", "Exif.Image2.JPEGInterchangeFormatLength", nullptr }, // 6 { "Exif.Image.StripOffsets", "Exif.Image.StripByteCounts", nullptr }, // 7 { "Exif.OlympusCs.PreviewImageStart", "Exif.OlympusCs.PreviewImageLength", "Exif.MakerNote.Offset"} // 8 }; const LoaderExifDataJpeg::Param LoaderExifDataJpeg::param_[] = { { "Exif.Thumbnail.JPEGInterchangeFormat", "Exif.Thumbnail.JPEGInterchangeFormatLength" }, // 0 { "Exif.NikonPreview.JPEGInterchangeFormat", "Exif.NikonPreview.JPEGInterchangeFormatLength" }, // 1 { "Exif.Pentax.PreviewOffset", "Exif.Pentax.PreviewLength" }, // 2 { "Exif.PentaxDng.PreviewOffset", "Exif.PentaxDng.PreviewLength" }, // 3 { "Exif.Minolta.ThumbnailOffset", "Exif.Minolta.ThumbnailLength" }, // 4 { "Exif.SonyMinolta.ThumbnailOffset", "Exif.SonyMinolta.ThumbnailLength" }, // 5 { "Exif.Olympus.ThumbnailImage", nullptr }, // 6 { "Exif.Olympus2.ThumbnailImage", nullptr }, // 7 { "Exif.Minolta.Thumbnail", nullptr }, // 8 { "Exif.PanasonicRaw.PreviewImage", nullptr }, // 9 { "Exif.SamsungPreview.JPEGInterchangeFormat", "Exif.SamsungPreview.JPEGInterchangeFormatLength" }, // 10 { "Exif.Casio2.PreviewImage", nullptr } // 11 }; const LoaderTiff::Param LoaderTiff::param_[] = { { "Image", "Exif.Image.NewSubfileType", "1" }, // 0 { "SubImage1", "Exif.SubImage1.NewSubfileType", "1" }, // 1 { "SubImage2", "Exif.SubImage2.NewSubfileType", "1" }, // 2 { "SubImage3", "Exif.SubImage3.NewSubfileType", "1" }, // 3 { "SubImage4", "Exif.SubImage4.NewSubfileType", "1" }, // 4 { "SubThumb1", "Exif.SubThumb1.NewSubfileType", "1" }, // 5 { "Thumbnail", nullptr, nullptr }, // 6 { "Image2", nullptr, nullptr } // 7 }; Loader::UniquePtr Loader::create(PreviewId id, const Image &image) { if (id < 0 || id >= Loader::getNumLoaders()) return nullptr; if (loaderList_[id].imageMimeType_ && std::string(loaderList_[id].imageMimeType_) != image.mimeType()) return nullptr; auto loader = loaderList_[id].create_(id, image, loaderList_[id].parIdx_); if (loader && !loader->valid()) loader.reset(); return loader; } Loader::Loader(PreviewId id, const Image &image) : id_(id), image_(image), width_(0), height_(0), size_(0), valid_(false) { } PreviewProperties Loader::getProperties() const { PreviewProperties prop; prop.id_ = id_; prop.size_ = size_; prop.width_ = width_; prop.height_ = height_; return prop; } PreviewId Loader::getNumLoaders() { return PreviewId(std::size(loaderList_)); } LoaderNative::LoaderNative(PreviewId id, const Image &image, int parIdx) : Loader(id, image) { if (!(0 <= parIdx && static_cast(parIdx) < image.nativePreviews().size())) return; nativePreview_ = image.nativePreviews()[parIdx]; width_ = nativePreview_.width_; height_ = nativePreview_.height_; valid_ = true; if (nativePreview_.filter_.empty()) { size_ = nativePreview_.size_; } else { size_ = static_cast(getData().size()); } } Loader::UniquePtr createLoaderNative(PreviewId id, const Image &image, int parIdx) { return std::make_unique(id, image, parIdx); } PreviewProperties LoaderNative::getProperties() const { PreviewProperties prop = Loader::getProperties(); prop.mimeType_ = nativePreview_.mimeType_; if (nativePreview_.mimeType_ == "image/jpeg") { prop.extension_ = ".jpg"; } else if (nativePreview_.mimeType_ == "image/tiff") { prop.extension_ = ".tif"; } else if (nativePreview_.mimeType_ == "image/x-wmf") { prop.extension_ = ".wmf"; } else if (nativePreview_.mimeType_ == "image/x-portable-anymap") { prop.extension_ = ".pnm"; } else { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unknown native preview format: " << nativePreview_.mimeType_ << "\n"; #endif prop.extension_ = ".dat"; } return prop; } DataBuf LoaderNative::getData() const { if (!valid()) return {}; BasicIo &io = image_.io(); if (io.open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); } IoCloser closer(io); const byte* data = io.mmap(); if (static_cast(io.size()) < nativePreview_.position_ + static_cast(nativePreview_.size_)) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Invalid native preview position or size.\n"; #endif return {}; } if (nativePreview_.filter_.empty()) { return {data + nativePreview_.position_, nativePreview_.size_}; } if (nativePreview_.filter_ == "hex-ai7thumbnail-pnm") { const DataBuf ai7thumbnail = decodeHex(data + nativePreview_.position_, static_cast(nativePreview_.size_)); const DataBuf rgb = decodeAi7Thumbnail(ai7thumbnail); return makePnm(width_, height_, rgb); } if (nativePreview_.filter_ == "hex-irb") { const DataBuf psData = decodeHex(data + nativePreview_.position_, static_cast(nativePreview_.size_)); const byte *record; uint32_t sizeHdr = 0; uint32_t sizeData = 0; if (Photoshop::locatePreviewIrb(psData.c_data(), psData.size(), &record, &sizeHdr, &sizeData) != 0) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Missing preview IRB in Photoshop EPS preview.\n"; #endif return {}; } return {record + sizeHdr + 28, sizeData - 28}; } throw Error(ErrorCode::kerErrorMessage, "Invalid native preview filter: " + nativePreview_.filter_); } bool LoaderNative::readDimensions() { if (!valid()) return false; if (width_ != 0 || height_ != 0) return true; const DataBuf data = getData(); if (data.empty()) return false; try { auto image = ImageFactory::open(data.c_data(), data.size()); if (!image) return false; image->readMetadata(); width_ = image->pixelWidth(); height_ = image->pixelHeight(); } catch (const Error& /* error */) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Invalid native preview image.\n"; #endif return false; } return true; } LoaderExifJpeg::LoaderExifJpeg(PreviewId id, const Image &image, int parIdx) : Loader(id, image), offset_(0) { const ExifData &exifData = image_.exifData(); auto pos = exifData.findKey(ExifKey(param_[parIdx].offsetKey_)); if (pos != exifData.end() && pos->count() > 0) { offset_ = pos->toUint32(); } size_ = 0; pos = exifData.findKey(ExifKey(param_[parIdx].sizeKey_)); if (pos != exifData.end() && pos->count() > 0) { size_ = pos->toUint32(); } if (offset_ == 0 || size_ == 0) return; if (param_[parIdx].baseOffsetKey_) { pos = exifData.findKey(ExifKey(param_[parIdx].baseOffsetKey_)); if (pos != exifData.end() && pos->count() > 0) { offset_ += pos->toUint32(); } } if (Safe::add(offset_, size_) > static_cast(image_.io().size())) return; valid_ = true; } Loader::UniquePtr createLoaderExifJpeg(PreviewId id, const Image &image, int parIdx) { return std::make_unique(id, image, parIdx); } PreviewProperties LoaderExifJpeg::getProperties() const { PreviewProperties prop = Loader::getProperties(); prop.mimeType_ = "image/jpeg"; prop.extension_ = ".jpg"; return prop; } DataBuf LoaderExifJpeg::getData() const { if (!valid()) return {}; BasicIo &io = image_.io(); if (io.open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); } IoCloser closer(io); const Exiv2::byte* base = io.mmap(); return {base + offset_, size_}; } bool LoaderExifJpeg::readDimensions() { if (!valid()) return false; if (width_ || height_) return true; BasicIo &io = image_.io(); if (io.open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); } IoCloser closer(io); const Exiv2::byte* base = io.mmap(); try { auto image = ImageFactory::open(base + offset_, size_); if (!image) return false; image->readMetadata(); width_ = image->pixelWidth(); height_ = image->pixelHeight(); } catch (const Error& /* error */ ) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Invalid JPEG preview image.\n"; #endif return false; } return true; } LoaderExifDataJpeg::LoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx) : Loader(id, image), dataKey_(param_[parIdx].dataKey_) { auto pos = image_.exifData().findKey(dataKey_); if (pos != image_.exifData().end()) { size_ = pos->sizeDataArea(); // indirect data if (size_ == 0 && pos->typeId() == undefined) size_ = pos->size(); // direct data } if (size_ == 0) return; valid_ = true; } Loader::UniquePtr createLoaderExifDataJpeg(PreviewId id, const Image &image, int parIdx) { return std::make_unique(id, image, parIdx); } PreviewProperties LoaderExifDataJpeg::getProperties() const { PreviewProperties prop = Loader::getProperties(); prop.mimeType_ = "image/jpeg"; prop.extension_ = ".jpg"; return prop; } DataBuf LoaderExifDataJpeg::getData() const { if (!valid()) return {}; auto pos = image_.exifData().findKey(dataKey_); if (pos != image_.exifData().end()) { DataBuf buf = pos->dataArea(); // indirect data if (buf.empty()) { // direct data buf = DataBuf(pos->size()); pos->copy(buf.data(), invalidByteOrder); } buf.write_uint8(0, 0xff); // fix Minolta thumbnails with invalid jpeg header return buf; } return {}; } bool LoaderExifDataJpeg::readDimensions() { if (!valid()) return false; DataBuf buf = getData(); if (buf.empty()) return false; try { auto image = ImageFactory::open(buf.c_data(), buf.size()); if (!image) return false; image->readMetadata(); width_ = image->pixelWidth(); height_ = image->pixelHeight(); } catch (const Error& /* error */ ) { return false; } return true; } LoaderTiff::LoaderTiff(PreviewId id, const Image &image, int parIdx) : Loader(id, image), group_(param_[parIdx].group_) { const ExifData &exifData = image_.exifData(); size_t offsetCount = 0; ExifData::const_iterator pos; // check if the group_ contains a preview image if (param_[parIdx].checkTag_) { pos = exifData.findKey(ExifKey(param_[parIdx].checkTag_)); if (pos == exifData.end()) return; if (param_[parIdx].checkValue_ && pos->toString() != param_[parIdx].checkValue_) return; } pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".StripOffsets")); if (pos != exifData.end()) { offsetTag_ = "StripOffsets"; sizeTag_ = "StripByteCounts"; offsetCount = pos->value().count(); } else { pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".TileOffsets")); if (pos == exifData.end()) return; offsetTag_ = "TileOffsets"; sizeTag_ = "TileByteCounts"; offsetCount = pos->value().count(); } pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + '.' + sizeTag_)); if (pos == exifData.end()) return; if (offsetCount != pos->value().count()) return; for (size_t i = 0; i < offsetCount; i++) { size_ += pos->toUint32(static_cast(i)); } if (size_ == 0) return; pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageWidth")); if (pos != exifData.end() && pos->count() > 0) { width_ = pos->toUint32(); } pos = exifData.findKey(ExifKey(std::string("Exif.") + group_ + ".ImageLength")); if (pos != exifData.end() && pos->count() > 0) { height_ = pos->toUint32(); } if (width_ == 0 || height_ == 0) return; valid_ = true; } Loader::UniquePtr createLoaderTiff(PreviewId id, const Image &image, int parIdx) { return std::make_unique(id, image, parIdx); } PreviewProperties LoaderTiff::getProperties() const { PreviewProperties prop = Loader::getProperties(); prop.mimeType_ = "image/tiff"; prop.extension_ = ".tif"; return prop; } DataBuf LoaderTiff::getData() const { const ExifData &exifData = image_.exifData(); ExifData preview; // copy tags for (auto &&pos : exifData) { if (pos.groupName() == group_) { /* Write only the necessary TIFF image tags tags that especially could cause problems are: "NewSubfileType" - the result is no longer a thumbnail, it is a standalone image "Orientation" - this tag typically appears only in the "Image" group. Deleting it ensures consistent result for all previews, including JPEG */ uint16_t tag = pos.tag(); if (tag != 0x00fe && tag != 0x00ff && Internal::isTiffImageTag(tag, Internal::ifd0Id)) { preview.add(ExifKey(tag, "Image"), &pos.value()); } } } Value &dataValue = const_cast(preview["Exif.Image." + offsetTag_].value()); if (dataValue.sizeDataArea() == 0) { // image data are not available via exifData, read them from image_.io() BasicIo &io = image_.io(); if (io.open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); } IoCloser closer(io); const Exiv2::byte* base = io.mmap(); const Value &sizes = preview["Exif.Image." + sizeTag_].value(); if (sizes.count() == dataValue.count()) { if (sizes.count() == 1) { // this saves one copying of the buffer uint32_t offset = dataValue.toUint32(0); uint32_t size = sizes.toUint32(0); if (Safe::add(offset, size) <= static_cast(io.size())) dataValue.setDataArea(base + offset, size); } else { // FIXME: the buffer is probably copied twice, it should be optimized enforce(size_ <= static_cast(io.size()), ErrorCode::kerCorruptedMetadata); DataBuf buf(size_); uint32_t idxBuf = 0; for (size_t i = 0; i < sizes.count(); i++) { uint32_t offset = dataValue.toUint32(i); uint32_t size = sizes.toUint32(i); // the size_ parameter is originally computed by summing all values inside sizes // see the constructor of LoaderTiff // But e.g in malicious files some of these values could be negative // That's why we check again for each step here to really make sure we don't overstep enforce(Safe::add(idxBuf, size) <= size_, ErrorCode::kerCorruptedMetadata); if (size!=0 && Safe::add(offset, size) <= static_cast(io.size())){ buf.copyBytes(idxBuf, base + offset, size); } idxBuf += size; } dataValue.setDataArea(buf.c_data(), buf.size()); } } } // Fix compression value in the CR2 IFD2 image if (0 == strcmp(group_, "Image2") && image_.mimeType() == "image/x-canon-cr2") { preview["Exif.Image.Compression"] = uint16_t(1); } // write new image MemIo mio; IptcData emptyIptc; XmpData emptyXmp; TiffParser::encode(mio, nullptr, 0, Exiv2::littleEndian, preview, emptyIptc, emptyXmp); return {mio.mmap(), mio.size()}; } LoaderXmpJpeg::LoaderXmpJpeg(PreviewId id, const Image &image, int parIdx) : Loader(id, image) { (void)parIdx; const XmpData &xmpData = image_.xmpData(); std::string prefix = "xmpGImg"; if (xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/xapGImg:image")) != xmpData.end()) { prefix = "xapGImg"; } auto imageDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":image")); if (imageDatum == xmpData.end()) return; auto formatDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":format")); if (formatDatum == xmpData.end()) return; auto widthDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":width")); if (widthDatum == xmpData.end()) return; auto heightDatum = xmpData.findKey(XmpKey("Xmp.xmp.Thumbnails[1]/" + prefix + ":height")); if (heightDatum == xmpData.end()) return; if (formatDatum->toString() != "JPEG") return; width_ = widthDatum->toUint32(); height_ = heightDatum->toUint32(); preview_ = decodeBase64(imageDatum->toString()); size_ = static_cast(preview_.size()); valid_ = true; } Loader::UniquePtr createLoaderXmpJpeg(PreviewId id, const Image &image, int parIdx) { return std::make_unique(id, image, parIdx); } PreviewProperties LoaderXmpJpeg::getProperties() const { PreviewProperties prop = Loader::getProperties(); prop.mimeType_ = "image/jpeg"; prop.extension_ = ".jpg"; return prop; } DataBuf LoaderXmpJpeg::getData() const { if (!valid()) return {}; return {preview_.c_data(), preview_.size()}; } bool LoaderXmpJpeg::readDimensions() { return valid(); } DataBuf decodeHex(const byte *src, long srcSize) { // create decoding table byte invalid = 16; std::array decodeHexTable; decodeHexTable.fill(invalid); for (byte i = 0; i < 10; i++) decodeHexTable[static_cast('0') + i] = i; for (byte i = 0; i < 6; i++) decodeHexTable[static_cast('A') + i] = i + 10; for (byte i = 0; i < 6; i++) decodeHexTable[static_cast('a') + i] = i + 10; // calculate dest size long validSrcSize = 0; for (long srcPos = 0; srcPos < srcSize; srcPos++) { if (decodeHexTable[src[srcPos]] != invalid) validSrcSize++; } const long destSize = validSrcSize / 2; // allocate dest buffer DataBuf dest(destSize); // decode for (long srcPos = 0, destPos = 0; destPos < destSize; destPos++) { byte buffer = 0; for (int bufferPos = 1; bufferPos >= 0 && srcPos < srcSize; srcPos++) { byte srcValue = decodeHexTable[src[srcPos]]; if (srcValue == invalid) continue; buffer |= srcValue << (bufferPos * 4); bufferPos--; } dest.write_uint8(destPos, buffer); } return dest; } const char encodeBase64Table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; DataBuf decodeBase64(const std::string& src) { const size_t srcSize = src.size(); // create decoding table unsigned long invalid = 64; std::array decodeBase64Table; decodeBase64Table.fill(invalid); for (unsigned long i = 0; i < 64; i++) decodeBase64Table[static_cast(encodeBase64Table[i])] = i; // calculate dest size unsigned long validSrcSize = 0; for (unsigned long srcPos = 0; srcPos < srcSize; srcPos++) { if (decodeBase64Table[static_cast(src[srcPos])] != invalid) validSrcSize++; } if (validSrcSize > ULONG_MAX / 3) return {}; // avoid integer overflow const unsigned long destSize = (validSrcSize * 3) / 4; // allocate dest buffer if (destSize > LONG_MAX) return {}; // avoid integer overflow DataBuf dest(static_cast(destSize)); // decode for (unsigned long srcPos = 0, destPos = 0; destPos < destSize;) { unsigned long buffer = 0; for (int bufferPos = 3; bufferPos >= 0 && srcPos < srcSize; srcPos++) { unsigned long srcValue = decodeBase64Table[static_cast(src[srcPos])]; if (srcValue == invalid) continue; buffer |= srcValue << (bufferPos * 6); bufferPos--; } for (int bufferPos = 2; bufferPos >= 0 && destPos < destSize; bufferPos--, destPos++) { dest.write_uint8(destPos, static_cast((buffer >> (bufferPos * 8)) & 0xFF)); } } return dest; } DataBuf decodeAi7Thumbnail(const DataBuf &src) { const byte *colorTable = src.c_data(); const long colorTableSize = 256 * 3; if (src.size() < colorTableSize) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Invalid size of AI7 thumbnail: " << src.size() << "\n"; #endif return {}; } const byte *imageData = src.c_data(colorTableSize); const long imageDataSize = static_cast(src.size()) - colorTableSize; const bool rle = (imageDataSize >= 3 && imageData[0] == 'R' && imageData[1] == 'L' && imageData[2] == 'E'); std::string dest; for (long i = rle ? 3 : 0; i < imageDataSize;) { byte num = 1; byte value = imageData[i++]; if (rle && value == 0xFD) { if (i >= imageDataSize) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n"; #endif return {}; } value = imageData[i++]; if (value != 0xFD) { if (i >= imageDataSize) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unexpected end of image data at AI7 thumbnail.\n"; #endif return {}; } num = value; value = imageData[i++]; } } for (; num != 0; num--) { dest.append(reinterpret_cast(colorTable + (3*value)), 3); } } return {reinterpret_cast(dest.data()), dest.size()}; } DataBuf makePnm(uint32_t width, uint32_t height, const DataBuf &rgb) { const size_t expectedSize = width * height * 3UL; if (rgb.size() != expectedSize) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Invalid size of preview data. Expected " << expectedSize << " bytes, got " << rgb.size() << " bytes.\n"; #endif return {}; } const std::string header = "P6\n" + toString(width) + " " + toString(height) + "\n255\n"; const auto headerBytes = reinterpret_cast(header.data()); DataBuf dest(static_cast(header.size() + rgb.size())); dest.copyBytes(0, headerBytes, header.size()); dest.copyBytes(header.size(), rgb.c_data(), rgb.size()); return dest; } } // namespace // ***************************************************************************** // class member definitions namespace Exiv2 { PreviewImage::PreviewImage(PreviewProperties properties, DataBuf&& data) : properties_(std::move(properties)), preview_(std::move(data)) {} PreviewImage::PreviewImage(const PreviewImage &rhs) : properties_(rhs.properties_), preview_(rhs.pData(), rhs.size()) {} PreviewImage& PreviewImage::operator=(const PreviewImage& rhs) { if (this == &rhs) return *this; properties_ = rhs.properties_; preview_ = DataBuf(rhs.pData(), rhs.size()); return *this; } size_t PreviewImage::writeFile(const std::string& path) const { std::string name = path + extension(); // Todo: Creating a DataBuf here unnecessarily copies the memory DataBuf buf(pData(), size()); return Exiv2::writeFile(buf, name); } DataBuf PreviewImage::copy() const { return {pData(), size()}; } const byte* PreviewImage::pData() const { return preview_.c_data(); } uint32_t PreviewImage::size() const { return static_cast(preview_.size()); } std::string PreviewImage::mimeType() const { return properties_.mimeType_; } std::string PreviewImage::extension() const { return properties_.extension_; } uint32_t PreviewImage::width() const { return properties_.width_; } uint32_t PreviewImage::height() const { return properties_.height_; } PreviewId PreviewImage::id() const { return properties_.id_; } PreviewManager::PreviewManager(const Image& image) : image_(image) { } PreviewPropertiesList PreviewManager::getPreviewProperties() const { PreviewPropertiesList list; // go through the loader table and store all successfully created loaders in the list for (PreviewId id = 0; id < Loader::getNumLoaders(); ++id) { auto loader = Loader::create(id, image_); if (loader && loader->readDimensions()) { PreviewProperties props = loader->getProperties(); DataBuf buf = loader->getData(); // #16 getPreviewImage() props.size_ = static_cast(buf.size()); // update the size list.push_back(props) ; } } std::sort(list.begin(), list.end(), cmpPreviewProperties); return list; } PreviewImage PreviewManager::getPreviewImage(const PreviewProperties &properties) const { auto loader = Loader::create(properties.id_, image_); DataBuf buf; if (loader) { buf = loader->getData(); } return {properties, std::move(buf)}; } } // namespace Exiv2