Merge pull request #2155 from Exiv2/mainFixJp2_2

Fix in Jp2 metadata writing & improvements in reading (2)
main
Luis Díaz Más 3 years ago committed by GitHub
commit 5ed9fb4120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,6 +17,7 @@ add_library( exiv2lib_int OBJECT
fujimn_int.cpp fujimn_int.hpp
helper_functions.cpp helper_functions.hpp
image_int.cpp image_int.hpp
jp2image_int.cpp jp2image_int.hpp
makernote_int.cpp makernote_int.hpp
minoltamn_int.cpp minoltamn_int.hpp
nikonmn_int.cpp nikonmn_int.hpp

@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// included header files
#include "jp2image.hpp"
#include "config.h"
#include "basicio.hpp"
@ -9,7 +11,7 @@
#include "futils.hpp"
#include "image.hpp"
#include "image_int.hpp"
#include "jp2image.hpp"
#include "jp2image_int.hpp"
#include "safe_op.hpp"
#include "tiffimage.hpp"
#include "types.hpp"
@ -17,53 +19,32 @@
#include <array>
#include <iostream>
namespace Exiv2 {
namespace {
// JPEG-2000 box types
static constexpr uint32_t kJp2BoxTypeJp2Header = 0x6a703268; // 'jp2h'
static constexpr uint32_t kJp2BoxTypeImageHeader = 0x69686472; // 'ihdr'
static constexpr uint32_t kJp2BoxTypeColorHeader = 0x636f6c72; // 'colr'
static constexpr uint32_t kJp2BoxTypeUuid = 0x75756964; // 'uuid'
static constexpr uint32_t kJp2BoxTypeClose = 0x6a703263; // 'jp2c'
// from openjpeg-2.1.2/src/lib/openjp2/jp2.h
/*#define JPIP_JPIP 0x6a706970*/
#define JP2_JP 0x6a502020 /**< JPEG 2000 signature box */
#define JP2_FTYP 0x66747970 /**< File type box */
#define JP2_JP2H 0x6a703268 /**< JP2 header box (super-box) */
#define JP2_IHDR 0x69686472 /**< Image header box */
#define JP2_COLR 0x636f6c72 /**< Colour specification box */
#define JP2_JP2C 0x6a703263 /**< Contiguous codestream box */
#define JP2_URL 0x75726c20 /**< Data entry URL box */
#define JP2_PCLR 0x70636c72 /**< Palette box */
#define JP2_CMAP 0x636d6170 /**< Component Mapping box */
#define JP2_CDEF 0x63646566 /**< Channel Definition box */
#define JP2_DTBL 0x6474626c /**< Data Reference box */
#define JP2_BPCC 0x62706363 /**< Bits per component box */
#define JP2_JP2 0x6a703220 /**< File type fields */
/* For the future */
/* #define JP2_RES 0x72657320 */ /**< Resolution box (super-box) */
/* #define JP2_JP2I 0x6a703269 */ /**< Intellectual property box */
/* #define JP2_XML 0x786d6c20 */ /**< XML box */
/* #define JP2_UUID 0x75756994 */ /**< UUID box */
/* #define JP2_UINF 0x75696e66 */ /**< UUID info box (super-box) */
/* #define JP2_ULST 0x756c7374 */ /**< UUID list box */
constexpr uint32_t kJp2BoxTypeSignature = 0x6a502020; // signature box, required,
constexpr uint32_t kJp2BoxTypeFileTypeBox = 0x66747970; // File type box, required
constexpr uint32_t kJp2BoxTypeHeader = 0x6a703268; // Jp2 Header Box, required, Superbox
constexpr uint32_t kJp2BoxTypeImageHeader = 0x69686472; // Image Header Box ('ihdr'), required,
constexpr uint32_t kJp2BoxTypeColorSpec = 0x636f6c72; // Color Specification box ('colr'), required
constexpr uint32_t kJp2BoxTypeUuid = 0x75756964; // 'uuid'
constexpr uint32_t kJp2BoxTypeClose = 0x6a703263; // 'jp2c'
// JPEG-2000 UUIDs for embedded metadata
//
// See http://www.jpeg.org/public/wg1n2600.doc for information about embedding IPTC-NAA data in JPEG-2000 files
// See http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf for information about embedding XMP data in JPEG-2000
// files
static constexpr unsigned char kJp2UuidExif[] = "JpgTiffExif->JP2";
static constexpr unsigned char kJp2UuidIptc[] = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38";
static constexpr unsigned char kJp2UuidXmp[] = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac";
constexpr unsigned char kJp2UuidExif[] = "JpgTiffExif->JP2";
constexpr unsigned char kJp2UuidIptc[] = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38";
constexpr unsigned char kJp2UuidXmp[] = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac";
// See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification
static constexpr unsigned char Jp2Signature[] = {
constexpr std::array<byte, 12> Jp2Signature{
0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a,
};
static constexpr unsigned char Jp2Blank[] = {
constexpr std::array<byte, 249> Jp2Blank{
0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x66, 0x74,
0x79, 0x70, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x2d,
0x6a, 0x70, 0x32, 0x68, 0x00, 0x00, 0x00, 0x16, 0x69, 0x68, 0x64, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
@ -80,57 +61,9 @@ static constexpr unsigned char Jp2Blank[] = {
0x00, 0x00, 0xff, 0x93, 0xcf, 0xb4, 0x04, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, 0xd9,
};
//! @cond IGNORE
struct Jp2BoxHeader {
uint32_t length;
uint32_t type;
};
struct Jp2ImageHeaderBox {
uint32_t imageHeight;
uint32_t imageWidth;
uint16_t componentCount;
uint8_t bitsPerComponent;
uint8_t compressionType;
uint8_t colorspaceIsUnknown;
uint8_t intellectualPropertyFlag;
uint16_t compressionTypeProfile;
};
struct Jp2UuidBox {
uint8_t uuid[16];
};
//! @endcond
// *****************************************************************************
// class member definitions
namespace Exiv2 {
Jp2Image::Jp2Image(BasicIo::UniquePtr io, bool create) : Image(ImageType::jp2, mdExif | mdIptc | mdXmp, std::move(io)) {
if (create) {
if (io_->open() == 0) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << std::endl;
#endif
IoCloser closer(*io_);
if (io_->write(Jp2Blank, sizeof(Jp2Blank)) != sizeof(Jp2Blank)) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << std::endl;
#endif
}
}
}
} // Jp2Image::Jp2Image
std::string Jp2Image::mimeType() const {
return "image/jp2";
}
void Jp2Image::setComment(std::string_view /*comment*/) {
// Todo: implement me!
throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2"));
}
const size_t boxHSize = sizeof(Internal::Jp2BoxHeader);
static void lf(std::ostream& out, bool& bLF) {
void lf(std::ostream& out, bool& bLF) {
if (bLF) {
out << std::endl;
out.flush();
@ -138,7 +71,7 @@ static void lf(std::ostream& out, bool& bLF) {
}
}
static bool isBigEndian() {
bool isBigEndian() {
union {
uint32_t i;
char c[4];
@ -147,7 +80,8 @@ static bool isBigEndian() {
return e.c[0] != 0;
}
static std::string toAscii(long n) {
// Obtains the ascii version from the box.type
std::string toAscii(long n) {
const auto p = reinterpret_cast<const char*>(&n);
std::string result;
bool bBigEndian = isBigEndian();
@ -157,7 +91,7 @@ static std::string toAscii(long n) {
return result;
}
static void boxes_check(size_t b, size_t m) {
void boxes_check(size_t b, size_t m) {
if (b > m) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata box maximum exceeded" << std::endl;
@ -166,6 +100,32 @@ static void boxes_check(size_t b, size_t m) {
}
}
} // namespace
Jp2Image::Jp2Image(BasicIo::UniquePtr io, bool create) : Image(ImageType::jp2, mdExif | mdIptc | mdXmp, std::move(io)) {
if (create) {
if (io_->open() == 0) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << std::endl;
#endif
IoCloser closer(*io_);
if (io_->write(Jp2Blank.data(), Jp2Blank.size()) != Jp2Blank.size()) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << std::endl;
#endif
}
}
}
}
std::string Jp2Image::mimeType() const {
return "image/jp2";
}
void Jp2Image::setComment(std::string_view /*comment*/) {
throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2"));
}
void Jp2Image::readMetadata() {
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Exiv2::Jp2Image::readMetadata: Reading JPEG-2000 file " << io_->path() << std::endl;
@ -174,22 +134,22 @@ void Jp2Image::readMetadata() {
throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
}
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (!isJp2Type(*io_, true)) {
if (io_->error() || io_->eof())
throw Error(ErrorCode::kerFailedToReadImageData);
if (!isJp2Type(*io_, false)) {
throw Error(ErrorCode::kerNotAnImage, "JPEG-2000");
}
Jp2BoxHeader box = {0, 0};
Jp2BoxHeader subBox = {0, 0};
Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0, 0};
Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
size_t boxes = 0;
size_t boxem = 1000; // boxes max
Internal::Jp2BoxHeader box = {0, 0};
Internal::Jp2BoxHeader subBox = {0, 0};
Internal::Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0};
Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
size_t boxesCount = 0;
const size_t boxem = 1000; // boxes max
uint32_t lastBoxTypeRead = 0;
bool boxSignatureFound = false;
bool boxFileTypeFound = false;
while (io_->read(reinterpret_cast<byte*>(&box), sizeof(box)) == sizeof(box)) {
boxes_check(boxes++, boxem);
while (io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) {
boxes_check(boxesCount++, boxem);
long position = io_->tell();
box.length = getLong(reinterpret_cast<byte*>(&box.length), bigEndian);
box.type = getLong(reinterpret_cast<byte*>(&box.type), bigEndian);
@ -198,24 +158,42 @@ void Jp2Image::readMetadata() {
<< "Position: " << position << " box type: " << toAscii(box.type) << " length: " << box.length
<< std::endl;
#endif
enforce(box.length <= sizeof(box) + io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
if (box.length == 0)
return;
if (box.length == 1) {
// FIXME. Special case. the real box size is given in another place.
/// \todo In this case, the real box size is given in XLBox (bytes 8-15)
}
switch (box.type) {
case kJp2BoxTypeJp2Header: {
case kJp2BoxTypeSignature: {
if (boxSignatureFound) // Only one is allowed
throw Error(ErrorCode::kerCorruptedMetadata);
boxSignatureFound = true;
break;
}
case kJp2BoxTypeFileTypeBox: {
// This box shall immediately follow the JPEG 2000 Signature box
if (boxFileTypeFound || lastBoxTypeRead != kJp2BoxTypeSignature) { // Only one is allowed
throw Error(ErrorCode::kerCorruptedMetadata);
}
boxFileTypeFound = true;
std::vector<byte> boxData(box.length - boxHSize);
io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata);
if (!Internal::isValidBoxFileType(boxData))
throw Error(ErrorCode::kerCorruptedMetadata);
break;
}
case kJp2BoxTypeHeader: {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata: JP2Header box found" << std::endl;
#endif
long restore = io_->tell();
while (io_->read(reinterpret_cast<byte*>(&subBox), sizeof(subBox)) == sizeof(subBox) && subBox.length) {
boxes_check(boxes++, boxem);
while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize && subBox.length) {
boxes_check(boxesCount++, boxem);
subBox.length = getLong(reinterpret_cast<byte*>(&subBox.length), bigEndian);
subBox.type = getLong(reinterpret_cast<byte*>(&subBox.type), bigEndian);
if (subBox.length > io_->size()) {
@ -225,7 +203,7 @@ void Jp2Image::readMetadata() {
std::cout << "Exiv2::Jp2Image::readMetadata: "
<< "subBox = " << toAscii(subBox.type) << " length = " << subBox.length << std::endl;
#endif
if (subBox.type == kJp2BoxTypeColorHeader && subBox.length != 15) {
if (subBox.type == kJp2BoxTypeColorSpec && subBox.length != 15) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata: "
<< "Color data found" << std::endl;
@ -268,14 +246,14 @@ void Jp2Image::readMetadata() {
ihdr.imageHeight = getLong(reinterpret_cast<byte*>(&ihdr.imageHeight), bigEndian);
ihdr.imageWidth = getLong(reinterpret_cast<byte*>(&ihdr.imageWidth), bigEndian);
ihdr.componentCount = getShort(reinterpret_cast<byte*>(&ihdr.componentCount), bigEndian);
ihdr.compressionTypeProfile = getShort(reinterpret_cast<byte*>(&ihdr.compressionTypeProfile), bigEndian);
enforce(ihdr.c == 7, ErrorCode::kerCorruptedMetadata);
pixelWidth_ = ihdr.imageWidth;
pixelHeight_ = ihdr.imageHeight;
}
io_->seek(restore, BasicIo::beg);
if (io_->seek(subBox.length, Exiv2::BasicIo::cur) != 0) {
if (io_->seek(subBox.length, BasicIo::cur) != 0) {
throw Error(ErrorCode::kerCorruptedMetadata);
}
restore = io_->tell();
@ -299,8 +277,8 @@ void Jp2Image::readMetadata() {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata: Exif data found" << std::endl;
#endif
enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (sizeof(box) + sizeof(uuid)));
enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
bufRead = io_->read(rawData.data(), rawData.size());
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
@ -346,8 +324,8 @@ void Jp2Image::readMetadata() {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata: Iptc data found" << std::endl;
#endif
enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (sizeof(box) + sizeof(uuid)));
enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
bufRead = io_->read(rawData.data(), rawData.size());
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
@ -366,8 +344,8 @@ void Jp2Image::readMetadata() {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::readMetadata: Xmp data found" << std::endl;
#endif
enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (sizeof(box) + sizeof(uuid)));
enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - (boxHSize + sizeof(uuid)));
bufRead = io_->read(rawData.data(), rawData.size());
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
@ -398,9 +376,10 @@ void Jp2Image::readMetadata() {
break;
}
}
lastBoxTypeRead = box.type;
// Move to the next box.
io_->seek(static_cast<long>(position - sizeof(box) + box.length), BasicIo::beg);
io_->seek(static_cast<long>(position - boxHSize + box.length), BasicIo::beg);
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
}
@ -411,18 +390,19 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
if (io_->open() != 0)
throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
// Ensure that this is the correct image type
if (!isJp2Type(*io_, false)) {
if (io_->error() || io_->eof())
throw Error(ErrorCode::kerFailedToReadImageData);
throw Error(ErrorCode::kerNotAJpeg);
}
// According to the JP2 standard: The start of the first box shall be the first byte of the file, and the
// last byte of the last box shall be the last byte of the file.
bool bPrint = option == kpsBasic || option == kpsRecursive;
bool bRecursive = option == kpsRecursive;
bool bICC = option == kpsIccProfile;
bool bXMP = option == kpsXMP;
bool bIPTCErase = option == kpsIptcErase;
bool boxSignatureFound = false;
if (bPrint) {
out << "STRUCTURE OF JPEG2000 FILE: " << io_->path() << std::endl;
@ -430,20 +410,20 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
}
if (bPrint || bXMP || bICC || bIPTCErase) {
Jp2BoxHeader box = {1, 1};
Jp2BoxHeader subBox = {1, 1};
Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
Internal::Jp2BoxHeader box = {1, 1};
Internal::Jp2BoxHeader subBox = {1, 1};
Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
bool bLF = false;
while (box.length && box.type != kJp2BoxTypeClose &&
io_->read(reinterpret_cast<byte*>(&box), sizeof(box)) == sizeof(box)) {
io_->read(reinterpret_cast<byte*>(&box), boxHSize) == boxHSize) {
long position = io_->tell();
box.length = getLong(reinterpret_cast<byte*>(&box.length), bigEndian);
box.type = getLong(reinterpret_cast<byte*>(&box.type), bigEndian);
enforce(box.length <= sizeof(box) + io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata);
if (bPrint) {
out << Internal::stringFormat("%8ld | %8ld | ", position - sizeof(box), static_cast<size_t>(box.length))
out << Internal::stringFormat("%8ld | %8ld | ", position - boxHSize, static_cast<size_t>(box.length))
<< toAscii(box.type) << " | ";
bLF = true;
if (box.type == kJp2BoxTypeClose)
@ -453,21 +433,37 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
break;
switch (box.type) {
case kJp2BoxTypeJp2Header: {
case kJp2BoxTypeSignature: {
if (boxSignatureFound) // Only one is allowed
throw Error(ErrorCode::kerCorruptedMetadata);
boxSignatureFound = true;
break;
}
case kJp2BoxTypeFileTypeBox: {
// This box shall immediately follow the JPEG 2000 Signature box
/// \todo All files shall contain one and only one File Type box.
std::vector<byte> boxData(box.length - boxHSize);
io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata);
if (!Internal::isValidBoxFileType(boxData))
throw Error(ErrorCode::kerCorruptedMetadata);
break;
}
case kJp2BoxTypeHeader: {
lf(out, bLF);
/// \todo All files shall contain one and only one Header box.
while (io_->read(reinterpret_cast<byte*>(&subBox), sizeof(subBox)) == sizeof(subBox) &&
while (io_->read(reinterpret_cast<byte*>(&subBox), boxHSize) == boxHSize &&
io_->tell() < position + static_cast<long>(box.length)) // don't read beyond the box!
{
const size_t address = io_->tell() - sizeof(subBox);
const size_t address = io_->tell() - boxHSize;
subBox.length = getLong(reinterpret_cast<byte*>(&subBox.length), bigEndian);
subBox.type = getLong(reinterpret_cast<byte*>(&subBox.type), bigEndian);
if (subBox.length < sizeof(box) || subBox.length > io_->size() - io_->tell()) {
if (subBox.length < boxHSize || subBox.length > io_->size() - io_->tell()) {
throw Error(ErrorCode::kerCorruptedMetadata);
}
DataBuf data(subBox.length - sizeof(box));
DataBuf data(subBox.length - boxHSize);
io_->read(data.data(), data.size());
if (bPrint) {
out << Internal::stringFormat("%8ld | %8ld | sub:", address, subBox.length) << toAscii(subBox.type)
@ -476,18 +472,32 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
bLF = true;
}
if (subBox.type == kJp2BoxTypeColorHeader) {
if (subBox.type == kJp2BoxTypeImageHeader) {
assert(subBox.length == 22);
// height (4), width (4), componentsCount (2), bpc (1)
auto compressionType = data.read_uint8(11);
auto unkC = data.read_uint8(12);
auto ipr = data.read_uint8(13);
if (compressionType != 7 || unkC > 1 || ipr > 1) {
throw Error(ErrorCode::kerCorruptedMetadata);
}
} else if (subBox.type == kJp2BoxTypeColorSpec) {
const size_t pad = 3; // don't know why there are 3 padding bytes
// Bounds-check for the `getULong()` below, which reads 4 bytes, starting at `pad`.
enforce(data.size() >= pad + 4, ErrorCode::kerCorruptedMetadata);
if (bPrint) {
out << " | pad:";
for (int i = 0; i < 3; i++)
out << " " << static_cast<int>(data.read_uint8(i));
/// \todo A conforming JP2 reader shall ignore all Colour Specification boxes after the first.
auto METH = data.read_uint8(0);
// auto PREC = data.read_uint8(1);
// auto APPROX = data.read_uint8(2);
if (METH == 1) { // Enumerated Colourspace
auto enumCS = data.read_uint32(3, bigEndian);
if (enumCS != 16 && enumCS != 17) {
throw Error(ErrorCode::kerCorruptedMetadata);
}
} else { // Restricted ICC Profile
// see the ICC Profile Format Specification, version ICC.1:1998-09
const size_t iccLength = data.read_uint32(pad, bigEndian);
if (bPrint) {
out << " | iccLength:" << iccLength;
@ -497,6 +507,7 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
out.write(data.c_str(pad), iccLength);
}
}
}
lf(out, bLF);
}
} break;
@ -521,8 +532,8 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
}
DataBuf rawData;
enforce(box.length >= sizeof(uuid) + sizeof(box), ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - sizeof(uuid) - sizeof(box));
enforce(box.length >= sizeof(uuid) + boxHSize, ErrorCode::kerCorruptedMetadata);
rawData.alloc(box.length - sizeof(uuid) - boxHSize);
const size_t bufRead = io_->read(rawData.data(), rawData.size());
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
@ -559,14 +570,14 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in
}
// Move to the next box.
io_->seek(static_cast<long>(position - sizeof(box) + box.length), BasicIo::beg);
io_->seek(static_cast<long>(position - boxHSize + box.length), BasicIo::beg);
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
if (bPrint)
lf(out, bLF);
}
}
} // JpegBase::printStructure
}
void Jp2Image::writeMetadata() {
if (io_->open() != 0) {
@ -589,24 +600,24 @@ void Jp2Image::writeMetadata() {
void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
DataBuf output(boxBuf.size() + iccProfile_.size() + 100); // allocate sufficient space
size_t outlen = sizeof(Jp2BoxHeader); // now many bytes have we written to output?
long inlen = sizeof(Jp2BoxHeader); // how many bytes have we read from boxBuf?
enforce(sizeof(Jp2BoxHeader) <= output.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
auto pBox = reinterpret_cast<const Jp2BoxHeader*>(boxBuf.c_data());
size_t outlen = boxHSize; // now many bytes have we written to output?
size_t inlen = boxHSize; // how many bytes have we read from boxBuf?
enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata);
auto pBox = reinterpret_cast<const Internal::Jp2BoxHeader*>(boxBuf.c_data());
uint32_t length = getLong(reinterpret_cast<const byte*>(&pBox->length), bigEndian);
enforce(length <= output.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
uint32_t count = sizeof(Jp2BoxHeader);
enforce(length <= output.size(), ErrorCode::kerCorruptedMetadata);
uint32_t count = boxHSize;
auto p = boxBuf.c_str();
bool bWroteColor = false;
while (count < length && !bWroteColor) {
enforce(sizeof(Jp2BoxHeader) <= length - count, Exiv2::ErrorCode::kerCorruptedMetadata);
auto pSubBox = reinterpret_cast<const Jp2BoxHeader*>(p + count);
enforce(boxHSize <= length - count, ErrorCode::kerCorruptedMetadata);
auto pSubBox = reinterpret_cast<const Internal::Jp2BoxHeader*>(p + count);
// copy data. pointer could be into a memory mapped file which we will decode!
Jp2BoxHeader subBox;
memcpy(&subBox, pSubBox, sizeof(subBox));
Jp2BoxHeader newBox = subBox;
Internal::Jp2BoxHeader subBox;
memcpy(&subBox, pSubBox, boxHSize);
Internal::Jp2BoxHeader newBox = subBox;
if (count < length) {
subBox.length = getLong(reinterpret_cast<byte*>(&subBox.length), bigEndian);
@ -615,24 +626,23 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
std::cout << "Jp2Image::encodeJp2Header subbox: " << toAscii(subBox.type) << " length = " << subBox.length
<< std::endl;
#endif
enforce(subBox.length > 0, Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(subBox.length <= length - count, Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(subBox.length > 0, ErrorCode::kerCorruptedMetadata);
enforce(subBox.length <= length - count, ErrorCode::kerCorruptedMetadata);
count += subBox.length;
newBox.type = subBox.type;
} else {
subBox.length = 0;
newBox.type = kJp2BoxTypeColorHeader;
newBox.type = kJp2BoxTypeColorSpec;
count = length;
}
size_t newlen = subBox.length;
if (newBox.type == kJp2BoxTypeColorHeader) {
if (newBox.type == kJp2BoxTypeColorSpec) {
bWroteColor = true;
if (!iccProfileDefined()) {
const char* pad = "\x01\x00\x00\x00\x00\x00\x10\x00\x00\x05\x1cuuid";
uint32_t psize = 15;
newlen = sizeof(newBox) + psize;
enforce(newlen <= output.size() - outlen, Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
ul2Data(reinterpret_cast<byte*>(&newBox.length), psize, bigEndian);
ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian);
output.copyBytes(outlen, &newBox, sizeof(newBox));
@ -641,7 +651,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
const char* pad = "\x02\x00\x00";
uint32_t psize = 3;
newlen = sizeof(newBox) + psize + iccProfile_.size();
enforce(newlen <= static_cast<size_t>(output.size() - outlen), Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
ul2Data(reinterpret_cast<byte*>(&newBox.length), static_cast<uint32_t>(newlen), bigEndian);
ul2Data(reinterpret_cast<byte*>(&newBox.type), newBox.type, bigEndian);
output.copyBytes(outlen, &newBox, sizeof(newBox));
@ -649,7 +659,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
output.copyBytes(outlen + sizeof(newBox) + psize, iccProfile_.c_data(), iccProfile_.size());
}
} else {
enforce(newlen <= static_cast<size_t>(output.size() - outlen), Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata);
output.copyBytes(outlen, boxBuf.c_data(inlen), subBox.length);
}
@ -660,8 +670,8 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) {
// allocate the correct number of bytes, copy the data and update the box header
outBuf.alloc(outlen);
outBuf.copyBytes(0, output.c_data(), outlen);
auto oBox = reinterpret_cast<Jp2BoxHeader*>(outBuf.data());
ul2Data(reinterpret_cast<byte*>(&oBox->type), kJp2BoxTypeJp2Header, bigEndian);
auto oBox = reinterpret_cast<Internal::Jp2BoxHeader*>(outBuf.data());
ul2Data(reinterpret_cast<byte*>(&oBox->type), kJp2BoxTypeHeader, bigEndian);
ul2Data(reinterpret_cast<byte*>(&oBox->length), static_cast<uint32_t>(outlen), bigEndian);
}
@ -682,23 +692,22 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
// Ensure that this is the correct image type
if (!isJp2Type(*io_, true)) {
if (io_->error() || io_->eof())
throw Error(ErrorCode::kerInputDataReadFailed);
throw Error(ErrorCode::kerNoImageInInputData);
}
// Write JPEG2000 Signature.
if (outIo.write(Jp2Signature, 12) != 12)
// Write JPEG2000 Signature (This is the 1st box)
if (outIo.write(Jp2Signature.data(), Jp2Signature.size()) != 12)
throw Error(ErrorCode::kerImageWriteFailed);
Jp2BoxHeader box = {0, 0};
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Jp2Image::doWriteMetadata: JPEG 2000 Signature box written" << std::endl;
#endif
Internal::Jp2BoxHeader box = {0, 0};
byte boxDataSize[4];
byte boxUUIDtype[4];
DataBuf bheaderBuf(8); // Box header : 4 bytes (data size) + 4 bytes (box type).
// FIXME: Andreas, why the loop do not stop when EOF is taken from _io. The loop go out by an exception
// generated by a zero size data read.
DataBuf bheaderBuf(8);
while (io_->tell() < static_cast<long>(io_->size())) {
#ifdef EXIV2_DEBUG_MESSAGES
@ -706,14 +715,9 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
#endif
// Read chunk header.
size_t bufRead = io_->read(bheaderBuf.data(), bheaderBuf.size());
if (io_->error())
throw Error(ErrorCode::kerFailedToReadImageData);
if (bufRead != bheaderBuf.size())
throw Error(ErrorCode::kerInputDataReadFailed);
io_->readOrThrow(bheaderBuf.data(), bheaderBuf.size(), ErrorCode::kerInputDataReadFailed);
// Decode box header.
box.length = bheaderBuf.read_uint32(0, bigEndian);
box.type = bheaderBuf.read_uint32(4, bigEndian);
@ -741,24 +745,10 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
// Read whole box : Box header + Box data (not fixed size - can be null).
DataBuf boxBuf(box.length); // Box header (8 bytes) + box data.
boxBuf.copyBytes(0, bheaderBuf.c_data(), 8); // Copy header.
bufRead = io_->read(boxBuf.data(8), box.length - 8); // Extract box data.
if (io_->error()) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Error reading source file" << std::endl;
#endif
throw Error(ErrorCode::kerFailedToReadImageData);
}
if (bufRead != (box.length - 8)) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::doWriteMetadata: Cannot read source file data" << std::endl;
#endif
throw Error(ErrorCode::kerInputDataReadFailed);
}
io_->readOrThrow(boxBuf.data(8), box.length - 8, ErrorCode::kerInputDataReadFailed); // Extract box data.
switch (box.type) {
case kJp2BoxTypeJp2Header: {
case kJp2BoxTypeHeader: {
DataBuf newBuf;
encodeJp2Header(boxBuf, newBuf);
#ifdef EXIV2_DEBUG_MESSAGES
@ -780,8 +770,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
rawExif.copyBytes(0, &blob[0], blob.size());
DataBuf boxData(8 + 16 + rawExif.size());
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), Exiv2::bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian);
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian);
boxData.copyBytes(0, boxDataSize, 4);
boxData.copyBytes(4, boxUUIDtype, 4);
boxData.copyBytes(8, kJp2UuidExif, 16);
@ -802,8 +792,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
DataBuf rawIptc = IptcParser::encode(iptcData_);
if (!rawIptc.empty()) {
DataBuf boxData(8 + 16 + rawIptc.size());
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), Exiv2::bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian);
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian);
boxData.copyBytes(0, boxDataSize, 4);
boxData.copyBytes(4, boxUUIDtype, 4);
boxData.copyBytes(8, kJp2UuidIptc, 16);
@ -830,8 +820,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
DataBuf xmp(reinterpret_cast<const byte*>(xmpPacket_.data()), xmpPacket_.size());
DataBuf boxData(8 + 16 + xmp.size());
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), Exiv2::bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian);
ul2Data(boxDataSize, static_cast<uint32_t>(boxData.size()), bigEndian);
ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian);
boxData.copyBytes(0, boxDataSize, 4);
boxData.copyBytes(4, boxUUIDtype, 4);
boxData.copyBytes(8, kJp2UuidXmp, 16);
@ -849,7 +839,7 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) {
}
case kJp2BoxTypeUuid: {
enforce(boxBuf.size() >= 24, Exiv2::ErrorCode::kerCorruptedMetadata);
enforce(boxBuf.size() >= 24, ErrorCode::kerCorruptedMetadata);
if (boxBuf.cmpBytes(8, kJp2UuidExif, 16) == 0) {
#ifdef EXIV2_DEBUG_MESSAGES
std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Exif Uuid box" << std::endl;
@ -901,15 +891,14 @@ Image::UniquePtr newJp2Instance(BasicIo::UniquePtr io, bool create) {
}
bool isJp2Type(BasicIo& iIo, bool advance) {
const int32_t len = 12;
byte buf[len];
iIo.read(buf, len);
if (iIo.error() || iIo.eof()) {
byte buf[Jp2Signature.size()];
const size_t bytesRead = iIo.read(buf, Jp2Signature.size());
if (iIo.error() || iIo.eof() || bytesRead != Jp2Signature.size()) {
return false;
}
bool matched = (memcmp(buf, Jp2Signature, len) == 0);
bool matched = (memcmp(buf, Jp2Signature.data(), Jp2Signature.size()) == 0);
if (!advance || !matched) {
iIo.seek(-len, BasicIo::cur);
iIo.seek(-static_cast<long>(Jp2Signature.size()), BasicIo::cur); // Return to original position
}
return matched;
}

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "jp2image_int.hpp"
#include "error.hpp"
#include "types.hpp"
#include <cassert>
namespace Exiv2::Internal {
bool isValidBoxFileType(const std::vector<uint8_t> &boxData) {
// BR & MinV are obligatory (4 + 4 bytes). Afterwards we have N compatibility lists (of size 4)
if ((boxData.size() - 8u) % 4u != 0) {
return false;
}
const size_t N = (boxData.size() - 8u) / 4u;
const uint32_t brand = getULong(boxData.data(), bigEndian);
const uint32_t minorVersion = getULong(boxData.data() + 4, bigEndian);
bool clWithRightBrand = false;
for (size_t i = 0; i < N; i++) {
uint32_t compatibilityList = getULong(boxData.data() + 8 + i * 4, bigEndian);
if (compatibilityList == brandJp2) {
clWithRightBrand = true;
break;
}
}
return (brand == brandJp2 && minorVersion == 0 && clWithRightBrand);
}
} // namespace Exiv2::Internal

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef JP2IMAGE_INT_HPP
#define JP2IMAGE_INT_HPP
#include <cstdint>
#include <vector>
namespace Exiv2::Internal {
struct Jp2BoxHeader {
uint32_t length;
uint32_t type;
};
struct Jp2ImageHeaderBox {
uint32_t imageHeight;
uint32_t imageWidth;
uint16_t componentCount;
uint8_t bpc; //<! Bits per component
uint8_t c; //<! Compression type
uint8_t unkC; //<! Colourspace unknown
uint8_t ipr; //<! Intellectual property
};
struct Jp2UuidBox {
uint8_t uuid[16];
};
constexpr uint32_t brandJp2{0x6a703220};
/// @brief Determines if the File Type box is valid
bool isValidBoxFileType(const std::vector<std::uint8_t>& boxData);
} // namespace Exiv2::Internal
#endif // JP2IMAGE_INT_HPP

@ -735,7 +735,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2
12 | 20 | ftyp |
32 | 3185 | jp2h |
40 | 22 | sub:ihdr | .............
62 | 3155 | sub:colr | ......HLino....mntrRGB XYZ .. | pad: 2 0 0 | iccLength:3144
62 | 3155 | sub:colr | ......HLino....mntrRGB XYZ .. | iccLength:3144
3217 | 0 | jp2c |
STRUCTURE OF JPEG2000 FILE: Reagan2.jp2
address | length | box | data
@ -743,7 +743,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2
12 | 20 | ftyp |
32 | 1613641 | jp2h |
40 | 22 | sub:ihdr | .............
62 | 1613611 | sub:colr | ...... APPL....prtrRGB Lab .. | pad: 2 0 0 | iccLength:1613600
62 | 1613611 | sub:colr | ...... APPL....prtrRGB Lab .. | iccLength:1613600
1613673 | 0 | jp2c |
STRUCTURE OF JPEG2000 FILE: Reagan2.jp2
address | length | box | data
@ -751,7 +751,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2
12 | 20 | ftyp |
32 | 601 | jp2h |
40 | 22 | sub:ihdr | .............
62 | 571 | sub:colr | ......0ADBE....mntrRGB XYZ .. | pad: 2 0 0 | iccLength:560
62 | 571 | sub:colr | ......0ADBE....mntrRGB XYZ .. | iccLength:560
633 | 0 | jp2c |
1d3fda2edb4a89ab60a23c5f7c7d81dd
1d3fda2edb4a89ab60a23c5f7c7d81dd

@ -12,6 +12,9 @@ class TiffDirectoryWriteDirEntryAssert(metaclass=CaseMeta):
filename = path("$tmp_path/issue_1845_poc.jp2")
commands = ["$exiv2 -q -D +1 ad $filename"]
stderr = [""]
stderr = [
"""$exception_in_adjust """ + filename + """:
$kerCorruptedMetadata
"""]
stdout = [""]
retval = [0]
retval = [1]

@ -12,7 +12,7 @@ class Jp2ImagePrintStructureICC(metaclass=CaseMeta):
filename = path("$data_path/issue_ghsa_583f_w9pm_99r2_poc.jp2")
commands = ["$exiv2 -p C $filename"]
stdout = [""]
stderr = ["""Exiv2 exception in print action for file $filename:
stderr = ["""$exiv2_exception_message """ + filename + """:
$kerCorruptedMetadata
"""]
retval = [1]

@ -13,6 +13,8 @@ class Jp2ImageEncodeJp2HeaderOutOfBoundsRead2(metaclass=CaseMeta):
filename = path("$tmp_path/issue_ghsa_mxw9_qx4c_6m8v_poc.jp2")
commands = ["$exiv2 rm $filename"]
stdout = [""]
retval = [0]
compare_stderr = check_no_ASAN_UBSAN_errors
stderr = [
"""$exception_in_erase """ + filename + """:
$kerCorruptedMetadata
"""]
retval = [1]

@ -91,6 +91,7 @@ def get_valid_files(data_dir):
"issue_2160_poc.jpg",
"issue_ghsa_583f_w9pm_99r2_poc.jp2",
"issue_ghsa_7569_phvm_vwc2_poc.jp2",
"issue_ghsa_mxw9_qx4c_6m8v_poc.jp2",
"pocIssue283.jpg",
"poc_1522.jp2",
"xmpsdk.xmp",

@ -50,5 +50,7 @@ addition_overflow_message: Overflow in addition
exiv2_exception_message: Exiv2 exception in print action for file
exiv2_overflow_exception_message: std::overflow_error exception in print action for file
exception_in_extract: Exiv2 exception in extract action for file
exception_in_adjust: Exiv2 exception in adjust action for file
exception_in_erase: Exiv2 exception in erase action for file
uncaught_exception: Uncaught exception:
no_exif_data_found_retval: 253

@ -14,6 +14,8 @@ add_executable(unit_tests
test_helper_functions.cpp
test_image_int.cpp
test_ImageFactory.cpp
test_jp2image.cpp
test_jp2image_int.cpp
test_IptcKey.cpp
test_LangAltValueRead.cpp
test_pngimage.cpp

@ -7,6 +7,50 @@
using namespace Exiv2;
TEST(MemIo_Default, readEReturns0) {
std::array<byte, 10> buf;
MemIo io;
ASSERT_EQ(0, io.read(buf.data(), buf.size()));
}
TEST(MemIo_Default, isNotAtEof) {
MemIo io;
ASSERT_FALSE(io.eof());
}
TEST(MemIo_Default, seekBeyondBufferSizeReturns1AndSetsEofToTrue) {
MemIo io;
ASSERT_EQ(1, io.seek(1, BasicIo::beg));
ASSERT_TRUE(io.eof());
}
TEST(MemIo_Default, seekBefore0Returns1ButItDoesNotSetEofToTrue) {
MemIo io;
ASSERT_EQ(1, io.seek(-1, BasicIo::beg));
ASSERT_FALSE(io.eof());
}
TEST(MemIo_Default, seekToEndPosition_doesNotTriggerEof) {
MemIo io;
ASSERT_EQ(0, io.tell());
ASSERT_EQ(0, io.seek(0, BasicIo::end));
ASSERT_EQ(0, io.tell());
ASSERT_FALSE(io.eof());
}
TEST(MemIo_Default, seekToEndPositionAndReadTriggersEof) {
MemIo io;
ASSERT_EQ(0, io.seek(0, BasicIo::end));
ASSERT_EQ(0, io.tell());
std::array<byte, 64> buf2;
buf2.fill(0);
ASSERT_EQ(0, io.read(buf2.data(), 1)); // Note that we cannot even read 1 byte being at the end
ASSERT_TRUE(io.eof());
}
// -------------------------
TEST(MemIo, isNotAtEofInitially) {
std::array<byte, 64> buf;
buf.fill(0);

@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <exiv2/jp2image.hpp>
#include <gtest/gtest.h>
using namespace Exiv2;
TEST(Jp2Image, canBeCreatedFromScratch) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
ASSERT_NO_THROW(Jp2Image image(std::move(memIo), create));
}
TEST(Jp2Image, canBeOpenedEvenWithAnEmptyMemIo) {
auto memIo = std::make_unique<MemIo>();
const bool create{false};
ASSERT_NO_THROW(Jp2Image image(std::move(memIo), create));
}
TEST(Jp2Image, mimeTypeIsPng) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
Jp2Image image(std::move(memIo), create);
ASSERT_EQ("image/jp2", image.mimeType());
}
TEST(Jp2Image, printStructurePrintsNothingWithKpsNone) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
Jp2Image image(std::move(memIo), create);
std::ostringstream stream;
image.printStructure(stream, Exiv2::kpsNone, 1);
ASSERT_TRUE(stream.str().empty());
}
TEST(Jp2Image, printStructurePrintsDataWithKpsBasic) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
Jp2Image image(std::move(memIo), create);
std::ostringstream stream;
image.printStructure(stream, Exiv2::kpsBasic, 1);
ASSERT_FALSE(stream.str().empty());
}
TEST(Jp2Image, cannotReadMetadataFromEmptyIo) {
auto memIo = std::make_unique<MemIo>();
const bool create{false};
Jp2Image image(std::move(memIo), create);
try {
image.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(ErrorCode::kerNotAnImage, e.code());
ASSERT_STREQ("This does not look like a JPEG-2000 image", e.what());
}
}
TEST(Jp2Image, cannotReadMetadataFromIoWhichCannotBeOpened) {
auto memIo = std::make_unique<FileIo>("NonExistingPath.jp2");
const bool create{false};
Jp2Image image(std::move(memIo), create);
try {
image.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(ErrorCode::kerDataSourceOpenFailed, e.code());
}
}
TEST(Jp2Image, cannotWriteMetadataToEmptyIo) {
auto memIo = std::make_unique<MemIo>();
const bool create{false};
Jp2Image image(std::move(memIo), create);
try {
image.writeMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(ErrorCode::kerNoImageInInputData, e.code());
}
}
TEST(Jp2Image, canWriteMetadataFromCreatedJp2Image) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
Jp2Image image(std::move(memIo), create);
ASSERT_NO_THROW(image.writeMetadata());
}
TEST(Jp2Image, cannotWriteMetadataToIoWhichCannotBeOpened) {
auto memIo = std::make_unique<FileIo>("NonExistingPath.jp2");
const bool create{false};
Jp2Image image(std::move(memIo), create);
try {
image.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(ErrorCode::kerDataSourceOpenFailed, e.code());
}
}
TEST(Jp2Image, canWriteMetadataAndReadAfterwards) {
auto memIo = std::make_unique<MemIo>();
const bool create{true};
Jp2Image image(std::move(memIo), create);
ASSERT_NO_THROW(image.writeMetadata());
ASSERT_NO_THROW(image.readMetadata());
}

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "jp2image_int.hpp" // Internals of JPEG-2000 standard
#include <gtest/gtest.h>
using namespace Exiv2::Internal;
namespace {
void setValidValues(std::vector<std::uint8_t>& boxData) {
// The first 4 bytes correspond to the BR (Brand). It must have the value 'jp2\040'
boxData[0] = 'j';
boxData[1] = 'p';
boxData[2] = '2';
boxData[3] = '\040';
// The next 4 bytes correspond to the MinV (Minor version). It is a 4-byte unsigned int with value 0
// The only available Compatibility list also has the value 'jp2\040'
boxData[8] = 'j';
boxData[9] = 'p';
boxData[10] = '2';
boxData[11] = '\040';
}
} // namespace
TEST(Jp2_FileTypeBox, isNotValidWithoutProperValuesSet) {
const std::vector<std::uint8_t> boxData(12);
ASSERT_FALSE(isValidBoxFileType(boxData));
}
TEST(Jp2_FileTypeBox, isValidWithMinimumPossibleSizeAndValidValues) {
std::vector<std::uint8_t> boxData(12);
setValidValues(boxData);
ASSERT_TRUE(isValidBoxFileType(boxData));
}
TEST(Jp2_FileTypeBox, isNotValidWithMinimumPossibleSizeButInvalidBrand) {
std::vector<std::uint8_t> boxData(12);
setValidValues(boxData);
boxData[2] = '3'; // Change byte in the brand field
ASSERT_FALSE(isValidBoxFileType(boxData));
}
TEST(Jp2_FileTypeBox, isNotValidWithMinimumPossibleSizeButInvalidCL1) {
std::vector<std::uint8_t> boxData(12);
setValidValues(boxData);
boxData[10] = '3'; // Change byte in the CL1
ASSERT_FALSE(isValidBoxFileType(boxData));
}
// ----------------------------------------------------------
TEST(Jp2_FileTypeBox, withInvalidBoxDataSizeIsInvalid) {
std::vector<std::uint8_t> boxData(13); // 12 + 1 (the extra byte causes problems)
ASSERT_FALSE(isValidBoxFileType(boxData));
}
TEST(Jp2_FileTypeBox, with2CLs_lastOneWithBrandValue_isValid) {
std::vector<std::uint8_t> boxData(16);
// The first 4 bytes correspond to the BR (Brand). It must have the value 'jp2\040'
boxData[0] = 'j';
boxData[1] = 'p';
boxData[2] = '2';
boxData[3] = '\040';
// The next 4 bytes correspond to the MinV (Minor version). It is a 4-byte unsigned int with value 0
// The 2nd Compatibility list has the value 'jp2\040'
boxData[12] = 'j';
boxData[13] = 'p';
boxData[14] = '2';
boxData[15] = '\040';
ASSERT_TRUE(isValidBoxFileType(boxData));
}
Loading…
Cancel
Save