Merge pull request #2197 from Exiv2/mainRefactoringFormats
Refactoring in JpegImage and Photoshop classes
This commit is contained in:
commit
25c47cd1cf
@ -13,6 +13,7 @@
|
||||
#include "xmp_exiv2.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include "exiv2/mrwimage.hpp"
|
||||
#include "exiv2/orfimage.hpp"
|
||||
#include "exiv2/pgfimage.hpp"
|
||||
#include "exiv2/photoshop.hpp"
|
||||
|
||||
#ifdef EXV_HAVE_LIBZ
|
||||
#include "exiv2/pngimage.hpp"
|
||||
|
||||
@ -3,11 +3,8 @@
|
||||
#ifndef JPGIMAGE_HPP_
|
||||
#define JPGIMAGE_HPP_
|
||||
|
||||
// *****************************************************************************
|
||||
#include "exiv2lib_export.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
// included header files
|
||||
#include "error.hpp"
|
||||
#include "image.hpp"
|
||||
@ -18,59 +15,6 @@ namespace Exiv2 {
|
||||
// *****************************************************************************
|
||||
// class definitions
|
||||
|
||||
/// @brief Helper class, has methods to deal with %Photoshop "Information Resource Blocks" (IRBs).
|
||||
struct EXIV2API Photoshop {
|
||||
// Todo: Public for now
|
||||
static constexpr std::array irbId_{"8BIM", "AgHg", "DCSR", "PHUT"}; //!< %Photoshop IRB markers
|
||||
static constexpr auto ps3Id_ = "Photoshop 3.0\0"; //!< %Photoshop marker
|
||||
static constexpr uint16_t iptc_ = 0x0404; //!< %Photoshop IPTC marker
|
||||
static constexpr uint16_t preview_ = 0x040c; //!< %Photoshop preview marker
|
||||
|
||||
/// @brief Checks an IRB
|
||||
/// @param pPsData Existing IRB buffer. It is expected to be of size 4.
|
||||
/// @return true if the IRB marker is known
|
||||
/// @todo This should be an implementation detail and not exposed in the API. An attacker could try to pass
|
||||
/// a smaller buffer or null pointer.
|
||||
static bool isIrb(const byte* pPsData);
|
||||
|
||||
/// @brief Validates all IRBs
|
||||
/// @param pPsData Existing IRB buffer
|
||||
/// @param sizePsData Size of the IRB buffer, may be 0
|
||||
/// @return true if all IRBs are valid;<BR> false otherwise
|
||||
static bool valid(const byte* pPsData, size_t sizePsData);
|
||||
|
||||
/// @brief Locates the data for a %Photoshop tag in a %Photoshop formated memory buffer.
|
||||
/// Operates on raw data to simplify reuse.
|
||||
/// @param pPsData Pointer to buffer containing entire payload of %Photoshop formated data (from APP13 Jpeg segment)
|
||||
/// @param sizePsData Size in bytes of pPsData.
|
||||
/// @param psTag %Tag number of the block to look for.
|
||||
/// @param record Output value that is set to the start of the data block within pPsData (may not be null).
|
||||
/// @param sizeHdr Output value that is set to the size of the header within the data block pointed to by record
|
||||
/// (may not be null).
|
||||
/// @param sizeData Output value that is set to the size of the actual data within the data block pointed to by record
|
||||
/// (may not be null).
|
||||
/// @return 0 if successful;<BR>
|
||||
/// 3 if no data for psTag was found in pPsData;<BR>
|
||||
/// -2 if the pPsData buffer does not contain valid data.
|
||||
static int locateIrb(const byte* pPsData, size_t sizePsData, uint16_t psTag, const byte** record,
|
||||
uint32_t* const sizeHdr, uint32_t* const sizeData);
|
||||
|
||||
/// @brief Forwards to locateIrb() with \em psTag = \em iptc_
|
||||
static int locateIptcIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData);
|
||||
|
||||
/// @brief Forwards to locatePreviewIrb() with \em psTag = \em preview_
|
||||
static int locatePreviewIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData);
|
||||
|
||||
/// @brief Set the new IPTC IRB, keeps existing IRBs but removes the IPTC block if there is no new IPTC data to write.
|
||||
/// @param pPsData Existing IRB buffer
|
||||
/// @param sizePsData Size of the IRB buffer, may be 0
|
||||
/// @param iptcData Iptc data to embed, may be empty
|
||||
/// @return A data buffer containing the new IRB buffer, may have 0 size
|
||||
static DataBuf setIptcIrb(const byte* pPsData, size_t sizePsData, const IptcData& iptcData);
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief Abstract helper base class to access JPEG images.
|
||||
*/
|
||||
@ -151,44 +95,6 @@ class EXIV2API JpegBase : public Image {
|
||||
virtual int writeHeader(BasicIo& oIo) const = 0;
|
||||
//@}
|
||||
|
||||
// Constant Data
|
||||
static constexpr byte dht_ = 0xc4; //!< JPEG DHT marker
|
||||
static constexpr byte dqt_ = 0xdb; //!< JPEG DQT marker
|
||||
static constexpr byte dri_ = 0xdd; //!< JPEG DRI marker
|
||||
static constexpr byte sos_ = 0xda; //!< JPEG SOS marker
|
||||
static constexpr byte eoi_ = 0xd9; //!< JPEG EOI marker
|
||||
static constexpr byte app0_ = 0xe0; //!< JPEG APP0 marker
|
||||
static constexpr byte app1_ = 0xe1; //!< JPEG APP1 marker
|
||||
static constexpr byte app2_ = 0xe2; //!< JPEG APP2 marker
|
||||
static constexpr byte app13_ = 0xed; //!< JPEG APP13 marker
|
||||
static constexpr byte com_ = 0xfe; //!< JPEG Comment marker
|
||||
|
||||
// Start of Frame markers, nondifferential Huffman-coding frames
|
||||
static constexpr byte sof0_ = 0xc0; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof1_ = 0xc1; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof2_ = 0xc2; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof3_ = 0xc3; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
// Start of Frame markers, differential Huffman-coding frames
|
||||
static constexpr byte sof5_ = 0xc5; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof6_ = 0xc6; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof7_ = 0xc7; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
// Start of Frame markers, nondifferential arithmetic-coding frames
|
||||
static constexpr byte sof9_ = 0xc9; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof10_ = 0xca; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof11_ = 0xcb; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
// Start of Frame markers, differential arithmetic-coding frames
|
||||
static constexpr byte sof13_ = 0xcd; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof14_ = 0xce; //!< JPEG Start-Of-Frame marker
|
||||
static constexpr byte sof15_ = 0xcf; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
static constexpr auto exifId_ = "Exif\0\0"; //!< Exif identifier
|
||||
static constexpr auto jfifId_ = "JFIF\0"; //!< JFIF identifier
|
||||
static constexpr auto xmpId_ = "http://ns.adobe.com/xap/1.0/\0"; //!< XMP packet identifier
|
||||
static constexpr auto iccId_ = "ICC_PROFILE\0"; //!< ICC profile identifier
|
||||
|
||||
private:
|
||||
//! @name Manipulators
|
||||
//@{
|
||||
@ -225,14 +131,7 @@ class EXIV2API JpegBase : public Image {
|
||||
//@}
|
||||
|
||||
DataBuf readNextSegment(byte marker);
|
||||
|
||||
/*!
|
||||
@brief Is the marker followed by a non-zero payload?
|
||||
@param marker The marker at the start of a segment
|
||||
@return true if the marker is followed by a non-zero payload
|
||||
*/
|
||||
static bool markerHasLength(byte marker);
|
||||
}; // class JpegBase
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief Class to access JPEG images
|
||||
@ -292,9 +191,8 @@ class EXIV2API JpegImage : public JpegBase {
|
||||
|
||||
private:
|
||||
// Constant data
|
||||
static constexpr byte soi_ = 0xd8; // SOI marker
|
||||
static const byte blank_[]; // Minimal Jpeg image
|
||||
}; // class JpegImage
|
||||
static const byte blank_[]; ///< Minimal Jpeg image
|
||||
};
|
||||
|
||||
//! Helper class to access %Exiv2 files
|
||||
class EXIV2API ExvImage : public JpegBase {
|
||||
|
||||
70
include/exiv2/photoshop.hpp
Normal file
70
include/exiv2/photoshop.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifndef PHOTOSHOP_INT_HPP
|
||||
#define PHOTOSHOP_INT_HPP
|
||||
|
||||
#include "exiv2lib_export.h"
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Exiv2 {
|
||||
// Forward declarations
|
||||
class IptcData;
|
||||
|
||||
/// @brief Helper class, has methods to deal with %Photoshop "Information Resource Blocks" (IRBs).
|
||||
struct EXIV2API Photoshop {
|
||||
// Todo: Public for now
|
||||
static constexpr std::array irbId_{"8BIM", "AgHg", "DCSR", "PHUT"}; //!< %Photoshop IRB markers
|
||||
static constexpr auto ps3Id_ = "Photoshop 3.0\0"; //!< %Photoshop marker
|
||||
static constexpr uint16_t iptc_ = 0x0404; //!< %Photoshop IPTC marker
|
||||
static constexpr uint16_t preview_ = 0x040c; //!< %Photoshop preview marker
|
||||
|
||||
/// @brief Checks an IRB
|
||||
/// @param pPsData Existing IRB buffer. It is expected to be of size 4.
|
||||
/// @return true if the IRB marker is known
|
||||
/// @todo This should be an implementation detail and not exposed in the API. An attacker could try to pass
|
||||
/// a smaller buffer or null pointer.
|
||||
static bool isIrb(const byte* pPsData);
|
||||
|
||||
/// @brief Validates all IRBs
|
||||
/// @param pPsData Existing IRB buffer
|
||||
/// @param sizePsData Size of the IRB buffer, may be 0
|
||||
/// @return true if all IRBs are valid;<BR> false otherwise
|
||||
static bool valid(const byte* pPsData, size_t sizePsData);
|
||||
|
||||
/// @brief Locates the data for a %Photoshop tag in a %Photoshop formated memory buffer.
|
||||
/// Operates on raw data to simplify reuse.
|
||||
/// @param pPsData Pointer to buffer containing entire payload of %Photoshop formated data (from APP13 Jpeg segment)
|
||||
/// @param sizePsData Size in bytes of pPsData.
|
||||
/// @param psTag %Tag number of the block to look for.
|
||||
/// @param record Output value that is set to the start of the data block within pPsData (may not be null).
|
||||
/// @param sizeHdr Output value that is set to the size of the header within the data block pointed to by record
|
||||
/// (may not be null).
|
||||
/// @param sizeData Output value that is set to the size of the actual data within the data block pointed to by record
|
||||
/// (may not be null).
|
||||
/// @return 0 if successful;<BR>
|
||||
/// 3 if no data for psTag was found in pPsData;<BR>
|
||||
/// -2 if the pPsData buffer does not contain valid data.
|
||||
static int locateIrb(const byte* pPsData, size_t sizePsData, uint16_t psTag, const byte** record,
|
||||
uint32_t* const sizeHdr, uint32_t* const sizeData);
|
||||
|
||||
/// @brief Forwards to locateIrb() with \em psTag = \em iptc_
|
||||
static int locateIptcIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData);
|
||||
|
||||
/// @brief Forwards to locatePreviewIrb() with \em psTag = \em preview_
|
||||
static int locatePreviewIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData);
|
||||
|
||||
/// @brief Set the new IPTC IRB, keeps existing IRBs but removes the IPTC block if there is no new IPTC data to write.
|
||||
/// @param pPsData Existing IRB buffer
|
||||
/// @param sizePsData Size of the IRB buffer, may be 0
|
||||
/// @param iptcData Iptc data to embed, may be empty
|
||||
/// @return A data buffer containing the new IRB buffer, may have 0 size
|
||||
static DataBuf setIptcIrb(const byte* pPsData, size_t sizePsData, const IptcData& iptcData);
|
||||
};
|
||||
} // namespace Exiv2
|
||||
|
||||
#endif // PHOTOSHOP_INT_HPP
|
||||
@ -65,6 +65,7 @@ set(PUBLIC_HEADERS
|
||||
../include/exiv2/mrwimage.hpp
|
||||
../include/exiv2/orfimage.hpp
|
||||
../include/exiv2/pgfimage.hpp
|
||||
../include/exiv2/photoshop.hpp
|
||||
../include/exiv2/preview.hpp
|
||||
../include/exiv2/properties.hpp
|
||||
../include/exiv2/psdimage.hpp
|
||||
@ -107,6 +108,7 @@ add_library( exiv2lib
|
||||
mrwimage.cpp
|
||||
orfimage.cpp
|
||||
pgfimage.cpp
|
||||
photoshop.cpp
|
||||
preview.cpp
|
||||
properties.cpp
|
||||
psdimage.cpp
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "xmpsidecar.hpp"
|
||||
|
||||
// + standard includes
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
258
src/jpgimage.cpp
258
src/jpgimage.cpp
@ -9,6 +9,7 @@
|
||||
#include "helper_functions.hpp"
|
||||
#include "image_int.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "photoshop.hpp"
|
||||
#include "safe_op.hpp"
|
||||
|
||||
#ifdef WIN32
|
||||
@ -21,198 +22,70 @@
|
||||
|
||||
#include "fff.h"
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
||||
// *****************************************************************************
|
||||
// class member definitions
|
||||
|
||||
namespace Exiv2 {
|
||||
static inline bool inRange(int lo, int value, int hi) {
|
||||
|
||||
namespace {
|
||||
// JPEG Segment markers (The first byte is always 0xFF, the value of these constants correspond to the 2nd byte)
|
||||
constexpr byte sos_ = 0xda; //!< JPEG SOS marker
|
||||
constexpr byte app0_ = 0xe0; //!< JPEG APP0 marker
|
||||
constexpr byte app1_ = 0xe1; //!< JPEG APP1 marker
|
||||
constexpr byte app2_ = 0xe2; //!< JPEG APP2 marker
|
||||
constexpr byte app13_ = 0xed; //!< JPEG APP13 marker
|
||||
constexpr byte com_ = 0xfe; //!< JPEG Comment marker
|
||||
|
||||
// Markers without payload
|
||||
constexpr byte soi_ = 0xd8; ///!< SOI marker
|
||||
constexpr byte eoi_ = 0xd9; //!< JPEG EOI marker
|
||||
constexpr byte rst1_ = 0xd0; //!< JPEG Restart 0 Marker (from 0xD0 to 0xD7 there might be 8 of these markers)
|
||||
|
||||
// Start of Frame markers, nondifferential Huffman-coding frames
|
||||
constexpr byte sof0_ = 0xc0; //!< JPEG Start-Of-Frame marker
|
||||
constexpr byte sof3_ = 0xc3; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
// Start of Frame markers, differential Huffman-coding frames
|
||||
constexpr byte sof5_ = 0xc5; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
// Start of Frame markers, differential arithmetic-coding frames
|
||||
constexpr byte sof15_ = 0xcf; //!< JPEG Start-Of-Frame marker
|
||||
|
||||
constexpr auto exifId_ = "Exif\0\0"; //!< Exif identifier
|
||||
// constexpr auto jfifId_ = "JFIF\0"; //!< JFIF identifier
|
||||
constexpr auto xmpId_ = "http://ns.adobe.com/xap/1.0/\0"; //!< XMP packet identifier
|
||||
constexpr auto iccId_ = "ICC_PROFILE\0"; //!< ICC profile identifier
|
||||
|
||||
inline bool inRange(int lo, int value, int hi) {
|
||||
return lo <= value && value <= hi;
|
||||
}
|
||||
|
||||
static inline bool inRange2(int value, int lo1, int hi1, int lo2, int hi2) {
|
||||
inline bool inRange2(int value, int lo1, int hi1, int lo2, int hi2) {
|
||||
return inRange(lo1, value, hi1) || inRange(lo2, value, hi2);
|
||||
}
|
||||
|
||||
bool Photoshop::isIrb(const byte* pPsData) {
|
||||
if (pPsData == nullptr) {
|
||||
return false;
|
||||
}
|
||||
/// \todo check if direct array comparison is faster than a call to memcmp
|
||||
return std::any_of(irbId_.begin(), irbId_.end(), [pPsData](auto id) { return memcmp(pPsData, id, 4) == 0; });
|
||||
/// @brief has the segment a non-zero payload?
|
||||
/// @param m The marker at the start of a segment
|
||||
/// @return true if the segment has a length field/payload
|
||||
bool markerHasLength(byte m) {
|
||||
bool markerWithoutLength = m >= rst1_ && m <= eoi_;
|
||||
return !markerWithoutLength;
|
||||
}
|
||||
|
||||
bool Photoshop::valid(const byte* pPsData, size_t sizePsData) {
|
||||
const byte* record = nullptr;
|
||||
uint32_t sizeIptc = 0;
|
||||
uint32_t sizeHdr = 0;
|
||||
const byte* pCur = pPsData;
|
||||
const byte* pEnd = pPsData + sizePsData;
|
||||
int ret = 0;
|
||||
while (pCur < pEnd && 0 == (ret = Photoshop::locateIptcIrb(pCur, (pEnd - pCur), &record, &sizeHdr, &sizeIptc))) {
|
||||
pCur = record + sizeHdr + sizeIptc + (sizeIptc & 1);
|
||||
std::pair<std::array<byte, 2>, uint16_t> readSegmentSize(const byte marker, BasicIo& io) {
|
||||
std::array<byte, 2> buf{0, 0}; // 2-byte buffer for reading the size.
|
||||
uint16_t size{0}; // Size of the segment, including the 2-byte size field
|
||||
if (markerHasLength(marker)) {
|
||||
io.readOrThrow(buf.data(), buf.size(), ErrorCode::kerFailedToReadImageData);
|
||||
size = getUShort(buf.data(), bigEndian);
|
||||
enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
|
||||
}
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
// Todo: Generalised from JpegBase::locateIptcData without really understanding
|
||||
// the format (in particular the header). So it remains to be confirmed
|
||||
// if this also makes sense for psTag != Photoshop::iptc
|
||||
int Photoshop::locateIrb(const byte* pPsData, size_t sizePsData, uint16_t psTag, const byte** record,
|
||||
uint32_t* const sizeHdr, uint32_t* const sizeData) {
|
||||
if (sizePsData < 12) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Used for error checking
|
||||
size_t position = 0;
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Photoshop::locateIrb: ";
|
||||
#endif
|
||||
// Data should follow Photoshop format, if not exit
|
||||
while (position <= (sizePsData - 12) && isIrb(pPsData + position)) {
|
||||
const byte* hrd = pPsData + position;
|
||||
position += 4;
|
||||
uint16_t type = getUShort(pPsData + position, bigEndian);
|
||||
position += 2;
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "0x" << std::hex << type << std::dec << " ";
|
||||
#endif
|
||||
// Pascal string is padded to have an even size (including size byte)
|
||||
byte psSize = pPsData[position] + 1;
|
||||
psSize += (psSize & 1);
|
||||
position += psSize;
|
||||
if (position + 4 > sizePsData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
uint32_t dataSize = getULong(pPsData + position, bigEndian);
|
||||
position += 4;
|
||||
if (dataSize > (sizePsData - position)) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid Photoshop IRB data size " << dataSize << " or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
if ((dataSize & 1) && position + dataSize == sizePsData) {
|
||||
std::cerr << "Warning: "
|
||||
<< "Photoshop IRB data is not padded to even size\n";
|
||||
}
|
||||
#endif
|
||||
if (type == psTag) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "ok\n";
|
||||
#endif
|
||||
*sizeData = dataSize;
|
||||
*sizeHdr = psSize + 10;
|
||||
*record = hrd;
|
||||
return 0;
|
||||
}
|
||||
// Data size is also padded to be even
|
||||
position += dataSize + (dataSize & 1);
|
||||
}
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "pPsData doesn't start with '8BIM'\n";
|
||||
#endif
|
||||
if (position < sizePsData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
int Photoshop::locateIptcIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData) {
|
||||
return locateIrb(pPsData, sizePsData, iptc_, record, sizeHdr, sizeData);
|
||||
}
|
||||
|
||||
int Photoshop::locatePreviewIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData) {
|
||||
return locateIrb(pPsData, sizePsData, preview_, record, sizeHdr, sizeData);
|
||||
}
|
||||
|
||||
DataBuf Photoshop::setIptcIrb(const byte* pPsData, size_t sizePsData, const IptcData& iptcData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "IRB block at the beginning of Photoshop::setIptcIrb\n";
|
||||
if (sizePsData == 0)
|
||||
std::cerr << " None.\n";
|
||||
else
|
||||
hexdump(std::cerr, pPsData, sizePsData);
|
||||
#endif
|
||||
const byte* record = pPsData;
|
||||
uint32_t sizeIptc = 0;
|
||||
uint32_t sizeHdr = 0;
|
||||
DataBuf rc;
|
||||
if (0 > Photoshop::locateIptcIrb(pPsData, sizePsData, &record, &sizeHdr, &sizeIptc)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
Blob psBlob;
|
||||
const auto sizeFront = static_cast<size_t>(record - pPsData);
|
||||
// Write data before old record.
|
||||
if (sizePsData > 0 && sizeFront > 0) {
|
||||
append(psBlob, pPsData, sizeFront);
|
||||
}
|
||||
|
||||
// Write new iptc record if we have it
|
||||
DataBuf rawIptc = IptcParser::encode(iptcData);
|
||||
if (!rawIptc.empty()) {
|
||||
std::array<byte, 12> tmpBuf;
|
||||
std::copy_n(Photoshop::irbId_[0], 4, tmpBuf.data());
|
||||
us2Data(tmpBuf.data() + 4, iptc_, bigEndian);
|
||||
tmpBuf[6] = 0;
|
||||
tmpBuf[7] = 0;
|
||||
ul2Data(tmpBuf.data() + 8, static_cast<uint32_t>(rawIptc.size()), bigEndian);
|
||||
append(psBlob, tmpBuf.data(), 12);
|
||||
append(psBlob, rawIptc.c_data(), rawIptc.size());
|
||||
// Data is padded to be even (but not included in size)
|
||||
if (rawIptc.size() & 1)
|
||||
psBlob.push_back(0x00);
|
||||
}
|
||||
|
||||
// Write existing stuff after record, skip the current and all remaining IPTC blocks
|
||||
size_t pos = sizeFront;
|
||||
auto nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
|
||||
enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
|
||||
while (0 == Photoshop::locateIptcIrb(pPsData + pos, nextSizeData, &record, &sizeHdr, &sizeIptc)) {
|
||||
const auto newPos = static_cast<size_t>(record - pPsData);
|
||||
if (newPos > pos) { // Copy data up to the IPTC IRB
|
||||
append(psBlob, pPsData + pos, newPos - pos);
|
||||
}
|
||||
pos = newPos + sizeHdr + sizeIptc + (sizeIptc & 1); // Skip the IPTC IRB
|
||||
nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
|
||||
enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
|
||||
}
|
||||
if (pos < sizePsData) {
|
||||
append(psBlob, pPsData + pos, sizePsData - pos);
|
||||
}
|
||||
|
||||
// Data is rounded to be even
|
||||
if (!psBlob.empty())
|
||||
rc = DataBuf(&psBlob[0], psBlob.size());
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "IRB block at the end of Photoshop::setIptcIrb\n";
|
||||
if (rc.empty())
|
||||
std::cerr << " None.\n";
|
||||
else
|
||||
hexdump(std::cerr, rc.c_data(), rc.size());
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool JpegBase::markerHasLength(byte marker) {
|
||||
return (marker >= sof0_ && marker <= sof15_) || (marker >= app0_ && marker <= (app0_ | 0x0F)) || marker == dht_ ||
|
||||
marker == dqt_ || marker == dri_ || marker == com_ || marker == sos_;
|
||||
return {buf, size};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
JpegBase::JpegBase(ImageType type, BasicIo::UniquePtr io, bool create, const byte initData[], size_t dataSize) :
|
||||
Image(type, mdExif | mdIptc | mdXmp | mdComment, std::move(io)) {
|
||||
@ -273,14 +146,7 @@ void JpegBase::readMetadata() {
|
||||
byte marker = advanceToMarker(ErrorCode::kerNotAJpeg);
|
||||
|
||||
while (marker != sos_ && marker != eoi_ && search > 0) {
|
||||
// 2-byte buffer for reading the size.
|
||||
std::array<byte, 2> sizebuf;
|
||||
uint16_t size = 0; // Size of the segment, including the 2-byte size field
|
||||
if (markerHasLength(marker)) {
|
||||
io_->readOrThrow(sizebuf.data(), sizebuf.size(), ErrorCode::kerFailedToReadImageData);
|
||||
size = getUShort(sizebuf.data(), bigEndian);
|
||||
enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
|
||||
}
|
||||
auto [sizebuf, size] = readSegmentSize(marker, *io_);
|
||||
|
||||
// Read the rest of the segment.
|
||||
DataBuf buf(size);
|
||||
@ -480,16 +346,7 @@ void JpegBase::printStructure(std::ostream& out, PrintStructureOption option, in
|
||||
first = false;
|
||||
bool bLF = bPrint;
|
||||
|
||||
// 2-byte buffer for reading the size.
|
||||
std::array<byte, 2> sizebuf;
|
||||
uint16_t size = 0;
|
||||
if (markerHasLength(marker)) {
|
||||
io_->readOrThrow(sizebuf.data(), sizebuf.size(), ErrorCode::kerFailedToReadImageData);
|
||||
size = getUShort(sizebuf.data(), bigEndian);
|
||||
// `size` is the size of the segment, including the 2-byte size field
|
||||
// that we just read.
|
||||
enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
|
||||
}
|
||||
auto [sizebuf, size] = readSegmentSize(marker, *io_);
|
||||
|
||||
// Read the rest of the segment.
|
||||
DataBuf buf(size);
|
||||
@ -733,16 +590,7 @@ void JpegBase::writeMetadata() {
|
||||
}
|
||||
|
||||
DataBuf JpegBase::readNextSegment(byte marker) {
|
||||
// 2-byte buffer for reading the size.
|
||||
std::array<byte, 2> sizebuf;
|
||||
uint16_t size = 0;
|
||||
if (markerHasLength(marker)) {
|
||||
io_->readOrThrow(sizebuf.data(), sizebuf.size(), ErrorCode::kerFailedToReadImageData);
|
||||
size = getUShort(sizebuf.data(), bigEndian);
|
||||
// `size` is the size of the segment, including the 2-byte size field
|
||||
// that we just read.
|
||||
enforce(size >= 2, ErrorCode::kerFailedToReadImageData);
|
||||
}
|
||||
auto [sizebuf, size] = readSegmentSize(marker, *io_);
|
||||
|
||||
// Read the rest of the segment.
|
||||
DataBuf buf(size);
|
||||
@ -1160,7 +1008,7 @@ bool isJpegType(BasicIo& iIo, bool advance) {
|
||||
if (iIo.error() || iIo.eof())
|
||||
return false;
|
||||
|
||||
if (0xff != tmpBuf[0] || JpegImage::soi_ != tmpBuf[1]) {
|
||||
if (0xff != tmpBuf[0] || soi_ != tmpBuf[1]) {
|
||||
result = false;
|
||||
}
|
||||
if (!advance || !result)
|
||||
|
||||
184
src/photoshop.cpp
Normal file
184
src/photoshop.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include "photoshop.hpp"
|
||||
|
||||
#include "enforce.hpp"
|
||||
#include "image.hpp"
|
||||
#include "safe_op.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Exiv2 {
|
||||
|
||||
bool Photoshop::isIrb(const byte* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return std::any_of(irbId_.begin(), irbId_.end(), [data](auto id) { return memcmp(data, id, 4) == 0; });
|
||||
}
|
||||
|
||||
bool Photoshop::valid(const byte* pPsData, size_t sizePsData) {
|
||||
const byte* record = nullptr;
|
||||
uint32_t sizeIptc = 0;
|
||||
uint32_t sizeHdr = 0;
|
||||
const byte* pCur = pPsData;
|
||||
const byte* pEnd = pPsData + sizePsData;
|
||||
int ret = 0;
|
||||
while (pCur < pEnd && 0 == (ret = Photoshop::locateIptcIrb(pCur, (pEnd - pCur), &record, &sizeHdr, &sizeIptc))) {
|
||||
pCur = record + sizeHdr + sizeIptc + (sizeIptc & 1);
|
||||
}
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
// Todo: Generalised from JpegBase::locateIptcData without really understanding
|
||||
// the format (in particular the header). So it remains to be confirmed
|
||||
// if this also makes sense for psTag != Photoshop::iptc
|
||||
int Photoshop::locateIrb(const byte* pPsData, size_t sizePsData, uint16_t psTag, const byte** record,
|
||||
uint32_t* const sizeHdr, uint32_t* const sizeData) {
|
||||
if (sizePsData < 12) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Used for error checking
|
||||
size_t position = 0;
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Photoshop::locateIrb: ";
|
||||
#endif
|
||||
// Data should follow Photoshop format, if not exit
|
||||
while (position <= (sizePsData - 12) && isIrb(pPsData + position)) {
|
||||
const byte* hrd = pPsData + position;
|
||||
position += 4;
|
||||
uint16_t type = getUShort(pPsData + position, bigEndian);
|
||||
position += 2;
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "0x" << std::hex << type << std::dec << " ";
|
||||
#endif
|
||||
// Pascal string is padded to have an even size (including size byte)
|
||||
byte psSize = pPsData[position] + 1;
|
||||
psSize += (psSize & 1);
|
||||
position += psSize;
|
||||
if (position + 4 > sizePsData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
uint32_t dataSize = getULong(pPsData + position, bigEndian);
|
||||
position += 4;
|
||||
if (dataSize > (sizePsData - position)) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid Photoshop IRB data size " << dataSize << " or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
if ((dataSize & 1) && position + dataSize == sizePsData) {
|
||||
std::cerr << "Warning: "
|
||||
<< "Photoshop IRB data is not padded to even size\n";
|
||||
}
|
||||
#endif
|
||||
if (type == psTag) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "ok\n";
|
||||
#endif
|
||||
*sizeData = dataSize;
|
||||
*sizeHdr = psSize + 10;
|
||||
*record = hrd;
|
||||
return 0;
|
||||
}
|
||||
// Data size is also padded to be even
|
||||
position += dataSize + (dataSize & 1);
|
||||
}
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "pPsData doesn't start with '8BIM'\n";
|
||||
#endif
|
||||
if (position < sizePsData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "Warning: "
|
||||
<< "Invalid or extended Photoshop IRB\n";
|
||||
#endif
|
||||
return -2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
int Photoshop::locateIptcIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData) {
|
||||
return locateIrb(pPsData, sizePsData, iptc_, record, sizeHdr, sizeData);
|
||||
}
|
||||
|
||||
int Photoshop::locatePreviewIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t* const sizeHdr,
|
||||
uint32_t* const sizeData) {
|
||||
return locateIrb(pPsData, sizePsData, preview_, record, sizeHdr, sizeData);
|
||||
}
|
||||
|
||||
DataBuf Photoshop::setIptcIrb(const byte* pPsData, size_t sizePsData, const IptcData& iptcData) {
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "IRB block at the beginning of Photoshop::setIptcIrb\n";
|
||||
if (sizePsData == 0)
|
||||
std::cerr << " None.\n";
|
||||
else
|
||||
hexdump(std::cerr, pPsData, sizePsData);
|
||||
#endif
|
||||
const byte* record = pPsData;
|
||||
uint32_t sizeIptc = 0;
|
||||
uint32_t sizeHdr = 0;
|
||||
DataBuf rc;
|
||||
if (0 > Photoshop::locateIptcIrb(pPsData, sizePsData, &record, &sizeHdr, &sizeIptc)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
Blob psBlob;
|
||||
const auto sizeFront = static_cast<size_t>(record - pPsData);
|
||||
// Write data before old record.
|
||||
if (sizePsData > 0 && sizeFront > 0) {
|
||||
append(psBlob, pPsData, sizeFront);
|
||||
}
|
||||
|
||||
// Write new iptc record if we have it
|
||||
DataBuf rawIptc = IptcParser::encode(iptcData);
|
||||
if (!rawIptc.empty()) {
|
||||
std::array<byte, 12> tmpBuf;
|
||||
std::copy_n(Photoshop::irbId_.front(), 4, tmpBuf.data());
|
||||
us2Data(tmpBuf.data() + 4, iptc_, bigEndian);
|
||||
tmpBuf[6] = 0;
|
||||
tmpBuf[7] = 0;
|
||||
ul2Data(tmpBuf.data() + 8, static_cast<uint32_t>(rawIptc.size()), bigEndian);
|
||||
append(psBlob, tmpBuf.data(), 12);
|
||||
append(psBlob, rawIptc.c_data(), rawIptc.size());
|
||||
// Data is padded to be even (but not included in size)
|
||||
if (rawIptc.size() & 1)
|
||||
psBlob.push_back(0x00);
|
||||
}
|
||||
|
||||
// Write existing stuff after record, skip the current and all remaining IPTC blocks
|
||||
size_t pos = sizeFront;
|
||||
long nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
|
||||
enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
|
||||
while (0 == Photoshop::locateIptcIrb(pPsData + pos, nextSizeData, &record, &sizeHdr, &sizeIptc)) {
|
||||
const auto newPos = static_cast<size_t>(record - pPsData);
|
||||
if (newPos > pos) { // Copy data up to the IPTC IRB
|
||||
append(psBlob, pPsData + pos, newPos - pos);
|
||||
}
|
||||
pos = newPos + sizeHdr + sizeIptc + (sizeIptc & 1); // Skip the IPTC IRB
|
||||
nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
|
||||
enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
|
||||
}
|
||||
if (pos < sizePsData) {
|
||||
append(psBlob, pPsData + pos, sizePsData - pos);
|
||||
}
|
||||
|
||||
// Data is rounded to be even
|
||||
if (!psBlob.empty())
|
||||
rc = DataBuf(&psBlob[0], psBlob.size());
|
||||
#ifdef EXIV2_DEBUG_MESSAGES
|
||||
std::cerr << "IRB block at the end of Photoshop::setIptcIrb\n";
|
||||
if (rc.empty())
|
||||
std::cerr << " None.\n";
|
||||
else
|
||||
hexdump(std::cerr, rc.c_data(), rc.size());
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace Exiv2
|
||||
@ -13,12 +13,14 @@
|
||||
#include "image.hpp"
|
||||
#include "iptc.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "photoshop.hpp"
|
||||
#include "pngchunk_int.hpp"
|
||||
#include "safe_op.hpp"
|
||||
#include "tiffimage.hpp"
|
||||
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
|
||||
@ -13,11 +13,13 @@
|
||||
#include "image.hpp"
|
||||
#include "image_int.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "photoshop.hpp"
|
||||
#include "pngchunk_int.hpp"
|
||||
#include "pngimage.hpp"
|
||||
#include "tiffimage.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "futils.hpp"
|
||||
#include "image.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "photoshop.hpp"
|
||||
#include "safe_op.hpp"
|
||||
#include "tiffimage.hpp"
|
||||
#include "tiffimage_int.hpp"
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "futils.hpp"
|
||||
#include "image.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "photoshop.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "iptc.hpp"
|
||||
#include "jpgimage.hpp"
|
||||
#include "makernote_int.hpp"
|
||||
#include "photoshop.hpp"
|
||||
#include "sonymn_int.hpp"
|
||||
#include "tiffcomposite_int.hpp" // Do not change the order of these 2 includes,
|
||||
#include "tiffimage_int.hpp"
|
||||
|
||||
@ -11,7 +11,9 @@ class TiffMnEntryDoCountInvalidTiffType(metaclass=CaseMeta):
|
||||
|
||||
filename = path("$data_path/issue_1833_poc.jpg")
|
||||
commands = ["$exiv2 -pS $filename"]
|
||||
stderr = [""]
|
||||
retval = [0]
|
||||
stderr = ["""$exiv2_exception_message """ + filename + """:
|
||||
$kerFailedToReadImageData
|
||||
"""]
|
||||
retval = [1]
|
||||
|
||||
compare_stdout = check_no_ASAN_UBSAN_errors
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from system_tests import CaseMeta, CopyTmpFiles, path
|
||||
from system_tests import CaseMeta, CopyTmpFiles, path, check_no_ASAN_UBSAN_errors
|
||||
@CopyTmpFiles("$data_path/issue_1881_poc.jpg", "$data_path/issue_1881_coverage.jpg")
|
||||
|
||||
class SonyPreviewImageLargeAllocation(metaclass=CaseMeta):
|
||||
@ -13,6 +13,9 @@ class SonyPreviewImageLargeAllocation(metaclass=CaseMeta):
|
||||
filename1 = path("$tmp_path/issue_1881_poc.jpg")
|
||||
filename2 = path("$tmp_path/issue_1881_coverage.jpg")
|
||||
commands = ["$exiv2 -q -d I rm $filename1", "$exiv2 -q -d I rm $filename2"]
|
||||
stdout = ["",""]
|
||||
stderr = ["",""]
|
||||
retval = [0,0]
|
||||
stderr = ["""$exception_in_erase """ + filename1 + """:
|
||||
$kerFailedToReadImageData
|
||||
""", ""]
|
||||
retval = [1,0]
|
||||
|
||||
compare_stdout = check_no_ASAN_UBSAN_errors
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import system_tests
|
||||
|
||||
|
||||
class BufferOverReadInNikon1MakerNotePrint0x0088(
|
||||
metaclass=system_tests.CaseMeta):
|
||||
|
||||
@ -9,12 +8,10 @@ class BufferOverReadInNikon1MakerNotePrint0x0088(
|
||||
filename = system_tests.path(
|
||||
"$data_path/NikonMakerNotePrint0x088_overread"
|
||||
)
|
||||
commands = ["$exiv2 -pt --grep AFFocusPos $filename"]
|
||||
stdout = [
|
||||
"""Exif.Nikon1.AFFocusPos Undefined 4 Invalid value; Center
|
||||
"""
|
||||
]
|
||||
stderr = [""]
|
||||
retval = [0]
|
||||
commands = ["$exiv2 -q -pt --grep AFFocusPos $filename"]
|
||||
stderr = ["""$exiv2_exception_message """ + filename + """:
|
||||
$kerFailedToReadImageData
|
||||
"""]
|
||||
retval = [1]
|
||||
|
||||
compare_stderr = system_tests.check_no_ASAN_UBSAN_errors
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <exiv2/jpgimage.hpp>
|
||||
#include "photoshop.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "iptc.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user