From 3fc85eb71451abb34c165ee5583f006fe3967c4c Mon Sep 17 00:00:00 2001 From: Luis Diaz Date: Thu, 6 Jan 2022 16:29:23 +0100 Subject: [PATCH 01/10] cmake: Add missing source file --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea5ea353f6..567b2f9a38 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,7 +137,7 @@ generate_export_header(exiv2lib if( EXIV2_ENABLE_PNG ) set(PUBLIC_HEADERS ${PUBLIC_HEADERS} ../include/exiv2/pngimage.hpp) - target_sources(exiv2lib_int PRIVATE pngchunk_int.cpp) + target_sources(exiv2lib_int PRIVATE pngchunk_int.cpp pngchunk_int.hpp) target_sources(exiv2lib PRIVATE pngimage.cpp ../include/exiv2/pngimage.hpp) endif() From 2ce41a6913cce5d3983bdfb094d97140f6e5198a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 18:22:08 +0100 Subject: [PATCH 02/10] cmake: default c++ standard => 14 --- cmake/compilerFlags.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/compilerFlags.cmake b/cmake/compilerFlags.cmake index 15e936264a..64717d110f 100644 --- a/cmake/compilerFlags.cmake +++ b/cmake/compilerFlags.cmake @@ -1,7 +1,7 @@ # These flags applies to exiv2lib, the applications, and to the xmp code include(CheckCXXCompilerFlag) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) if (CYGWIN) set(CMAKE_CXX_EXTENSIONS ON) From 558cdc6c13dc0641d0209fbc774241bcbee51bc4 Mon Sep 17 00:00:00 2001 From: Luis Diaz Date: Thu, 6 Jan 2022 16:30:07 +0100 Subject: [PATCH 03/10] Add unit tests for PngChunk::keyTXTChunk --- src/pngchunk_int.cpp | 12 ++++---- src/pngimage.cpp | 6 ++-- unitTests/CMakeLists.txt | 1 + unitTests/test_pngimage.cpp | 56 +++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 unitTests/test_pngimage.cpp diff --git a/src/pngchunk_int.cpp b/src/pngchunk_int.cpp index 0a7926c67b..56dbd42f5a 100644 --- a/src/pngchunk_int.cpp +++ b/src/pngchunk_int.cpp @@ -103,13 +103,13 @@ namespace Exiv2 { DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) { - // From a tEXt, zTXt, or iTXt chunk, - // we get the key, it's a null terminated string at the chunk start + // From a tEXt, zTXt, or iTXt chunk, we get the keyword which is null terminated. const int offset = stripHeader ? 8 : 0; - if (data.size() <= offset) throw Error(kerFailedToReadImageData); + if (data.size() <= offset) + throw Error(kerFailedToReadImageData); const byte *key = data.c_data(offset); - // Find null string at end of key. + // Find null chatecter at end of keyword. int keysize=0; while (key[keysize] != 0) { @@ -117,11 +117,11 @@ namespace Exiv2 { // look if keysize is valid. if (keysize+offset >= data.size()) throw Error(kerFailedToReadImageData); + /// \todo move conditional out of the loop } return DataBuf(key, keysize); - - } // PngChunk::keyTXTChunk + } DataBuf PngChunk::parseTXTChunk(const DataBuf& data, int keysize, diff --git a/src/pngimage.cpp b/src/pngimage.cpp index ccdc624ef0..45f21ee01d 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -712,14 +712,14 @@ namespace Exiv2 { std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << szChunk << " chunk (length: " << dataOffset << ")" << std::endl; #endif - } - else + } else { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PngImage::doWriteMetadata: write " << szChunk << " chunk (length: " << dataOffset << ")" << std::endl; #endif - if (outIo.write(chunkBuf.c_data(), chunkBuf.size()) != chunkBuf.size()) throw Error(kerImageWriteFailed); + if (outIo.write(chunkBuf.c_data(), chunkBuf.size()) != chunkBuf.size()) + throw Error(kerImageWriteFailed); } } else { // Write all others chunk as well. diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index f67f61227e..1933c9223c 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(unit_tests test_futils.cpp test_helper_functions.cpp test_image_int.cpp + test_pngimage.cpp test_safe_op.cpp test_slice.cpp test_tiffheader.cpp diff --git a/unitTests/test_pngimage.cpp b/unitTests/test_pngimage.cpp new file mode 100644 index 0000000000..0474bc3154 --- /dev/null +++ b/unitTests/test_pngimage.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2004-2022 Exiv2 authors + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include "pngchunk_int.hpp" // This is not part of the public API + +#include + +#include +#include + +using namespace Exiv2; + +TEST(PngChunk, keyTxtChunkExtractsKeywordCorrectlyInPresenceOfNullChar) +{ + // The following data is: '\0\0"AzTXtRaw profile type exif\0\0x' + std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, + 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x78, 0x69, 0x66, 0x00, 0x00, 0x78}; + + DataBuf chunkBuf(data.data(), static_cast(data.size())); + DataBuf key = Internal::PngChunk::keyTXTChunk(chunkBuf, true); + ASSERT_EQ(21, key.size()); + + ASSERT_TRUE(std::equal(key.data(), key.data()+key.size(), data.data()+8, data.data()+8+key.size())); +} + + +TEST(PngChunk, keyTxtChunkThrowsExceptionWhenThereIsNoNullChar) +{ + // The following data is: '\0\0"AzTXtRaw profile type exifx' + std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, + 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x78, 0x69, 0x66, 0x78}; + + DataBuf chunkBuf(data.data(), static_cast(data.size())); + ASSERT_THROW(Internal::PngChunk::keyTXTChunk(chunkBuf, true), Exiv2::Error); +} From d4b0ee0e26af4194fa167e6d6ffac7cdf591d567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 16:55:14 +0100 Subject: [PATCH 04/10] keyTXTChunk improvement --- src/pngchunk_int.cpp | 20 +++++++++----------- unitTests/test_pngimage.cpp | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/pngchunk_int.cpp b/src/pngchunk_int.cpp index 56dbd42f5a..c3c2a1b4d8 100644 --- a/src/pngchunk_int.cpp +++ b/src/pngchunk_int.cpp @@ -107,20 +107,18 @@ namespace Exiv2 { const int offset = stripHeader ? 8 : 0; if (data.size() <= offset) throw Error(kerFailedToReadImageData); - const byte *key = data.c_data(offset); - // Find null chatecter at end of keyword. - int keysize=0; - while (key[keysize] != 0) - { - keysize++; - // look if keysize is valid. - if (keysize+offset >= data.size()) - throw Error(kerFailedToReadImageData); - /// \todo move conditional out of the loop + // Search for null char until the end of the DataBuf + const byte* dataPtr = data.c_data(); + int keysize=offset; + while (dataPtr[keysize] != 0 && keysize < data.size()) { + keysize++; } - return DataBuf(key, keysize); + if (keysize == data.size()) + throw Error(kerFailedToReadImageData); + + return DataBuf(dataPtr+offset, keysize-offset); } DataBuf PngChunk::parseTXTChunk(const DataBuf& data, diff --git a/unitTests/test_pngimage.cpp b/unitTests/test_pngimage.cpp index 0474bc3154..82fec5749b 100644 --- a/unitTests/test_pngimage.cpp +++ b/unitTests/test_pngimage.cpp @@ -39,7 +39,7 @@ TEST(PngChunk, keyTxtChunkExtractsKeywordCorrectlyInPresenceOfNullChar) DataBuf key = Internal::PngChunk::keyTXTChunk(chunkBuf, true); ASSERT_EQ(21, key.size()); - ASSERT_TRUE(std::equal(key.data(), key.data()+key.size(), data.data()+8, data.data()+8+key.size())); + ASSERT_TRUE(std::equal(key.data(), key.data()+key.size(), data.data()+8)); } From 0756534713a2e0cf581d00ba1e1da31cbb8b2cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 17:01:39 +0100 Subject: [PATCH 05/10] Remove dead code --- src/pngimage.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pngimage.cpp b/src/pngimage.cpp index 45f21ee01d..1e2cf6dce2 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -63,9 +63,6 @@ namespace { assert(strlen(str) <= length); const long minlen = std::min(static_cast(length), buf.size()); - if (minlen == 0) { - return true; - } return buf.cmpBytes(0, str, minlen) == 0; } } // namespace From b63db7e96198cd55c556217e7dab27bfbe064d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 18:21:26 +0100 Subject: [PATCH 06/10] Initialize uninitialized variables --- src/pngimage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pngimage.cpp b/src/pngimage.cpp index 1e2cf6dce2..befaa3b6af 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -92,7 +92,7 @@ namespace Exiv2 { } } } - } // PngImage::PngImage + } std::string PngImage::mimeType() const { @@ -102,7 +102,7 @@ namespace Exiv2 { static bool zlibToDataBuf(const byte* bytes,long length, DataBuf& result) { uLongf uncompressedLen = length * 2; // just a starting point - int zlibResult; + int zlibResult = Z_BUF_ERROR; do { result.alloc(uncompressedLen); @@ -130,7 +130,7 @@ namespace Exiv2 { static bool zlibToCompressed(const byte* bytes,long length, DataBuf& result) { uLongf compressedLen = length; // just a starting point - int zlibResult; + int zlibResult = Z_BUF_ERROR; do { result.alloc(compressedLen); From 9ab4dda430e848072ffc81556f4727ffb8ab9cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 17:28:32 +0100 Subject: [PATCH 07/10] Replace magicValue with constant variable --- src/pngchunk_int.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/pngchunk_int.cpp b/src/pngchunk_int.cpp index c3c2a1b4d8..621cdd3278 100644 --- a/src/pngchunk_int.cpp +++ b/src/pngchunk_int.cpp @@ -54,6 +54,9 @@ iTXt chunk : http://www.vias.org/pngguide/chapter11_05.html PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData */ +namespace { +constexpr int nullSeparators = 2; +} // ***************************************************************************** // class member definitions @@ -71,7 +74,7 @@ namespace Exiv2 { *outWidth = data.read_uint32(0, bigEndian); *outHeight = data.read_uint32(4, bigEndian); - } // PngChunk::decodeIHDRChunk + } void PngChunk::decodeTXTChunk(Image* pImage, const DataBuf& data, @@ -86,7 +89,7 @@ namespace Exiv2 { #endif parseChunkContent(pImage, key.c_data(), key.size(), arr); - } // PngChunk::decodeTXTChunk + } DataBuf PngChunk::decodeTXTChunk(const DataBuf& data, TxtChunkType type) @@ -99,7 +102,7 @@ namespace Exiv2 { #endif return parseTXTChunk(data, key.size(), type); - } // PngChunk::decodeTXTChunk + } DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) { @@ -129,7 +132,7 @@ namespace Exiv2 { if(type == zTXt_Chunk) { - enforce(data.size() >= Safe::add(keysize, 2), Exiv2::kerCorruptedMetadata); + enforce(data.size() >= Safe::add(keysize, nullSeparators), Exiv2::kerCorruptedMetadata); // Extract a deflate compressed Latin-1 text chunk @@ -145,8 +148,8 @@ namespace Exiv2 { } // compressed string after the compression technique spec - const byte* compressedText = data.c_data(keysize + 2); - long compressedTextSize = data.size() - keysize - 2; + const byte* compressedText = data.c_data(keysize + nullSeparators); + long compressedTextSize = data.size() - keysize - nullSeparators; enforce(compressedTextSize < data.size(), kerCorruptedMetadata); zlibUncompress(compressedText, compressedTextSize, arr); @@ -165,8 +168,8 @@ namespace Exiv2 { else if(type == iTXt_Chunk) { enforce(data.size() >= Safe::add(keysize, 3), Exiv2::kerCorruptedMetadata); - const size_t nullSeparators = std::count(data.c_data(keysize+3), data.c_data(data.size()), '\0'); - enforce(nullSeparators >= 2, Exiv2::kerCorruptedMetadata); + const size_t nullCount = std::count(data.c_data(keysize+3), data.c_data(data.size()), '\0'); + enforce(nullCount >= nullSeparators, Exiv2::kerCorruptedMetadata); // Extract a deflate compressed or uncompressed UTF-8 text chunk @@ -235,8 +238,7 @@ namespace Exiv2 { } return arr; - - } // PngChunk::parsePngChunk + } void PngChunk::parseChunkContent(Image* pImage, const byte* key, long keySize, const DataBuf& arr) { From 186c7e33d22f013863be4f8c285294aff560dd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Thu, 6 Jan 2022 17:28:57 +0100 Subject: [PATCH 08/10] Add new tests in PngChunk & PngImage for increasing coverage --- unitTests/test_pngimage.cpp | 174 ++++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 8 deletions(-) diff --git a/unitTests/test_pngimage.cpp b/unitTests/test_pngimage.cpp index 82fec5749b..ccdbb893cd 100644 --- a/unitTests/test_pngimage.cpp +++ b/unitTests/test_pngimage.cpp @@ -24,16 +24,18 @@ #include #include +#include +#include using namespace Exiv2; TEST(PngChunk, keyTxtChunkExtractsKeywordCorrectlyInPresenceOfNullChar) { // The following data is: '\0\0"AzTXtRaw profile type exif\0\0x' - std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, - 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, - 0x20, 0x65, 0x78, 0x69, 0x66, 0x00, 0x00, 0x78}; + const std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, + 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x78, 0x69, 0x66, 0x00, 0x00, 0x78}; DataBuf chunkBuf(data.data(), static_cast(data.size())); DataBuf key = Internal::PngChunk::keyTXTChunk(chunkBuf, true); @@ -46,11 +48,167 @@ TEST(PngChunk, keyTxtChunkExtractsKeywordCorrectlyInPresenceOfNullChar) TEST(PngChunk, keyTxtChunkThrowsExceptionWhenThereIsNoNullChar) { // The following data is: '\0\0"AzTXtRaw profile type exifx' - std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, - 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, - 0x20, 0x65, 0x78, 0x69, 0x66, 0x78}; + const std::array data{0x00, 0x00, 0x22, 0x41, 0x7a, 0x54, 0x58, 0x74, + 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x78, 0x69, 0x66, 0x78}; DataBuf chunkBuf(data.data(), static_cast(data.size())); ASSERT_THROW(Internal::PngChunk::keyTXTChunk(chunkBuf, true), Exiv2::Error); } + +TEST(PngChunk, keyTxtChunkThrowsIfSizeIsNotEnough) +{ + const std::array data{0x00, 0x00, 0x22, 0x41}; + DataBuf chunkBuf(data.data(), static_cast(data.size())); + ASSERT_THROW(Internal::PngChunk::keyTXTChunk(chunkBuf, true), Exiv2::Error); + + DataBuf emptyChunk(data.data(), 0); + ASSERT_THROW(Internal::PngChunk::keyTXTChunk(emptyChunk, false), Exiv2::Error); +} + + +TEST(PngImage, canBeCreatedFromScratch) +{ + auto memIo = std::make_unique(); + const bool create {true}; + ASSERT_NO_THROW(PngImage png(std::move(memIo), create)); +} + +TEST(PngImage, canBeOpenedEvenWithAnEmptyMemIo) +{ + auto memIo = std::make_unique(); + const bool create {false}; + ASSERT_NO_THROW(PngImage png(std::move(memIo), create)); +} + +TEST(PngImage, mimeTypeIsPng) +{ + auto memIo = std::make_unique(); + const bool create {true}; + PngImage png(std::move(memIo), create); + + ASSERT_EQ("image/png", png.mimeType()); +} + +TEST(PngImage, printStructurePrintsNothingWithKpsNone) +{ + auto memIo = std::make_unique(); + const bool create {true}; + PngImage png(std::move(memIo), create); + + std::ostringstream stream; + png.printStructure(stream, Exiv2::kpsNone, 1); + + ASSERT_TRUE(stream.str().empty()); +} + +TEST(PngImage, printStructurePrintsDataWithKpsBasic) +{ + auto memIo = std::make_unique(); + const bool create {true}; + PngImage png(std::move(memIo), create); + + std::ostringstream stream; + png.printStructure(stream, Exiv2::kpsBasic, 1); + + ASSERT_FALSE(stream.str().empty()); +} + +TEST(PngImage, cannotReadMetadataFromEmptyIo) +{ + auto memIo = std::make_unique(); + const bool create {false}; + PngImage png(std::move(memIo), create); + + try { + png.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(kerNotAnImage, e.code()); + ASSERT_STREQ("This does not look like a PNG image", e.what()); + } +} + +TEST(PngImage, cannotReadMetadataFromIoWhichCannotBeOpened) +{ + auto memIo = std::make_unique("NonExistingPath.png"); + const bool create {false}; + PngImage png(std::move(memIo), create); + + try { + png.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(kerDataSourceOpenFailed, e.code()); + } +} + +TEST(PngImage, cannotWriteMetadataToEmptyIo) +{ + auto memIo = std::make_unique(); + const bool create {false}; + PngImage png(std::move(memIo), create); + + try { + png.writeMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(kerNoImageInInputData, e.code()); + } +} + +TEST(PngImage, cannotWriteMetadataToIoWhichCannotBeOpened) +{ + auto memIo = std::make_unique("NonExistingPath.png"); + const bool create {false}; + PngImage png(std::move(memIo), create); + + try { + png.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(kerDataSourceOpenFailed, e.code()); + } +} + + +TEST(isPngType, withValidSignatureReturnsTrue) +{ + const unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + MemIo memIo(pngSignature, 8); + ASSERT_TRUE(isPngType(memIo, false)); +} + +TEST(isPngType, withInvalidSignatureReturnsFalse) +{ + const unsigned char pngSignature[8] = { 0x69, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + MemIo memIo(pngSignature, 8); + ASSERT_FALSE(isPngType(memIo, false)); +} + +TEST(isPngType, withShorterDataReturnsFalse) +{ + const unsigned char pngSignature[6] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A}; + MemIo memIo(pngSignature, 6); + ASSERT_FALSE(isPngType(memIo, false)); +} + +TEST(isPngType, withEmptyDataReturnsFalse) +{ + MemIo memIo; + ASSERT_FALSE(isPngType(memIo, false)); +} + +TEST(isPngType, withMemIoInErroneousStatusThrows) +{ + MemIo memIo; + memIo.getb(); + + try { + isPngType(memIo, false); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(kerInputDataReadFailed, e.code()); + } +} From e26733308656d862a80f35f6f662803cc3351a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 7 Jan 2022 08:18:59 +0100 Subject: [PATCH 09/10] Clang-format in pngchunk_int.cpp --- src/pngchunk_int.cpp | 1065 ++++++++++++++++++++---------------------- 1 file changed, 495 insertions(+), 570 deletions(-) diff --git a/src/pngchunk_int.cpp b/src/pngchunk_int.cpp index 621cdd3278..4068da86f9 100644 --- a/src/pngchunk_int.cpp +++ b/src/pngchunk_int.cpp @@ -22,28 +22,28 @@ #include "config.h" #ifdef EXV_HAVE_LIBZ -#include "pngchunk_int.hpp" -#include "tiffimage.hpp" -#include "jpgimage.hpp" -#include "exif.hpp" -#include "iptc.hpp" -#include "image.hpp" -#include "error.hpp" #include "enforce.hpp" +#include "error.hpp" +#include "exif.hpp" #include "helper_functions.hpp" +#include "image.hpp" +#include "iptc.hpp" +#include "jpgimage.hpp" +#include "pngchunk_int.hpp" #include "safe_op.hpp" +#include "tiffimage.hpp" -// + standard includes -#include -#include -#include -#include -#include +#include // To uncompress or compress text chunk + +// standard includes +#include #include #include -#include - -#include // To uncompress or compress text chunk +#include +#include +#include +#include +#include /* @@ -54,687 +54,612 @@ iTXt chunk : http://www.vias.org/pngguide/chapter11_05.html PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData */ -namespace { -constexpr int nullSeparators = 2; +namespace +{ + constexpr int nullSeparators = 2; } // ***************************************************************************** // class member definitions -namespace Exiv2 { - namespace Internal { - - void PngChunk::decodeIHDRChunk(const DataBuf& data, - int* outWidth, - int* outHeight) +namespace Exiv2 +{ + namespace Internal { - assert(data.size() >= 8); - - // Extract image width and height from IHDR chunk. + void PngChunk::decodeIHDRChunk(const DataBuf& data, int* outWidth, int* outHeight) + { + assert(data.size() >= 8); - *outWidth = data.read_uint32(0, bigEndian); - *outHeight = data.read_uint32(4, bigEndian); + // Extract image width and height from IHDR chunk. - } + *outWidth = data.read_uint32(0, bigEndian); + *outHeight = data.read_uint32(4, bigEndian); + } - void PngChunk::decodeTXTChunk(Image* pImage, - const DataBuf& data, - TxtChunkType type) - { - DataBuf key = keyTXTChunk(data); - DataBuf arr = parseTXTChunk(data, key.size(), type); + void PngChunk::decodeTXTChunk(Image* pImage, const DataBuf& data, TxtChunkType type) + { + DataBuf key = keyTXTChunk(data); + DataBuf arr = parseTXTChunk(data, key.size(), type); #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk data: " - << std::string(arr.c_str(), arr.size()) << std::endl; + std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk data: " << std::string(arr.c_str(), arr.size()) + << std::endl; #endif - parseChunkContent(pImage, key.c_data(), key.size(), arr); - - } + parseChunkContent(pImage, key.c_data(), key.size(), arr); + } - DataBuf PngChunk::decodeTXTChunk(const DataBuf& data, - TxtChunkType type) - { - DataBuf key = keyTXTChunk(data); + DataBuf PngChunk::decodeTXTChunk(const DataBuf& data, TxtChunkType type) + { + DataBuf key = keyTXTChunk(data); #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: " - << std::string(key.c_str(), key.size()) << std::endl; + std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: " << std::string(key.c_str(), key.size()) + << std::endl; #endif - return parseTXTChunk(data, key.size(), type); - - } - - DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) - { - // From a tEXt, zTXt, or iTXt chunk, we get the keyword which is null terminated. - const int offset = stripHeader ? 8 : 0; - if (data.size() <= offset) - throw Error(kerFailedToReadImageData); - - // Search for null char until the end of the DataBuf - const byte* dataPtr = data.c_data(); - int keysize=offset; - while (dataPtr[keysize] != 0 && keysize < data.size()) { - keysize++; + return parseTXTChunk(data, key.size(), type); } - if (keysize == data.size()) - throw Error(kerFailedToReadImageData); + DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) + { + // From a tEXt, zTXt, or iTXt chunk, we get the keyword which is null terminated. + const int offset = stripHeader ? 8 : 0; + if (data.size() <= offset) + throw Error(kerFailedToReadImageData); - return DataBuf(dataPtr+offset, keysize-offset); - } + // Search for null char until the end of the DataBuf + const byte* dataPtr = data.c_data(); + int keysize = offset; + while (dataPtr[keysize] != 0 && keysize < data.size()) { + keysize++; + } - DataBuf PngChunk::parseTXTChunk(const DataBuf& data, - int keysize, - TxtChunkType type) - { - DataBuf arr; + if (keysize == data.size()) + throw Error(kerFailedToReadImageData); - if(type == zTXt_Chunk) + return DataBuf(dataPtr + offset, keysize - offset); + } + + DataBuf PngChunk::parseTXTChunk(const DataBuf& data, int keysize, TxtChunkType type) { - enforce(data.size() >= Safe::add(keysize, nullSeparators), Exiv2::kerCorruptedMetadata); + DataBuf arr; + + if (type == zTXt_Chunk) { + enforce(data.size() >= Safe::add(keysize, nullSeparators), Exiv2::kerCorruptedMetadata); - // Extract a deflate compressed Latin-1 text chunk + // Extract a deflate compressed Latin-1 text chunk - // we get the compression method after the key - const byte* compressionMethod = data.c_data(keysize + 1); - if ( *compressionMethod != 0x00 ) - { - // then it isn't zlib compressed and we are sunk + // we get the compression method after the key + const byte* compressionMethod = data.c_data(keysize + 1); + if (*compressionMethod != 0x00) { + // then it isn't zlib compressed and we are sunk #ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard zTXt compression method.\n"; + std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard zTXt compression method.\n"; #endif - throw Error(kerFailedToReadImageData); - } + throw Error(kerFailedToReadImageData); + } - // compressed string after the compression technique spec - const byte* compressedText = data.c_data(keysize + nullSeparators); - long compressedTextSize = data.size() - keysize - nullSeparators; - enforce(compressedTextSize < data.size(), kerCorruptedMetadata); + // compressed string after the compression technique spec + const byte* compressedText = data.c_data(keysize + nullSeparators); + long compressedTextSize = data.size() - keysize - nullSeparators; + enforce(compressedTextSize < data.size(), kerCorruptedMetadata); - zlibUncompress(compressedText, compressedTextSize, arr); - } - else if(type == tEXt_Chunk) - { - enforce(data.size() >= Safe::add(keysize, 1), Exiv2::kerCorruptedMetadata); - // Extract a non-compressed Latin-1 text chunk + zlibUncompress(compressedText, compressedTextSize, arr); + } else if (type == tEXt_Chunk) { + enforce(data.size() >= Safe::add(keysize, 1), Exiv2::kerCorruptedMetadata); + // Extract a non-compressed Latin-1 text chunk - // the text comes after the key, but isn't null terminated - const byte* text = data.c_data(keysize + 1); - long textsize = data.size() - keysize - 1; + // the text comes after the key, but isn't null terminated + const byte* text = data.c_data(keysize + 1); + long textsize = data.size() - keysize - 1; - arr = DataBuf(text, textsize); - } - else if(type == iTXt_Chunk) - { - enforce(data.size() >= Safe::add(keysize, 3), Exiv2::kerCorruptedMetadata); - const size_t nullCount = std::count(data.c_data(keysize+3), data.c_data(data.size()), '\0'); - enforce(nullCount >= nullSeparators, Exiv2::kerCorruptedMetadata); - - // Extract a deflate compressed or uncompressed UTF-8 text chunk - - // we get the compression flag after the key - const byte compressionFlag = data.read_uint8(keysize + 1); - // we get the compression method after the compression flag - const byte compressionMethod = data.read_uint8(keysize + 2); - - enforce(compressionFlag == 0x00 || compressionFlag == 0x01, Exiv2::kerCorruptedMetadata); - enforce(compressionMethod == 0x00, Exiv2::kerCorruptedMetadata); - - // language description string after the compression technique spec - const size_t languageTextMaxSize = data.size() - keysize - 3; - std::string languageText = string_from_unterminated( - data.c_str(Safe::add(keysize, 3)), languageTextMaxSize); - const size_t languageTextSize = languageText.size(); - - enforce(static_cast(data.size()) >= - Safe::add(static_cast(Safe::add(keysize, 4)), languageTextSize), - Exiv2::kerCorruptedMetadata); - // translated keyword string after the language description - std::string translatedKeyText = string_from_unterminated( - data.c_str(keysize + 3 + languageTextSize + 1), - data.size() - (keysize + 3 + languageTextSize + 1)); - const auto translatedKeyTextSize = static_cast(translatedKeyText.size()); - - if ((compressionFlag == 0x00) || (compressionFlag == 0x01 && compressionMethod == 0x00)) { - enforce(Safe::add(static_cast(keysize + 3 + languageTextSize + 1), - Safe::add(translatedKeyTextSize, 1U)) <= static_cast(data.size()), - Exiv2::kerCorruptedMetadata); + arr = DataBuf(text, textsize); + } else if (type == iTXt_Chunk) { + enforce(data.size() >= Safe::add(keysize, 3), Exiv2::kerCorruptedMetadata); + const size_t nullCount = std::count(data.c_data(keysize + 3), data.c_data(data.size()), '\0'); + enforce(nullCount >= nullSeparators, Exiv2::kerCorruptedMetadata); + + // Extract a deflate compressed or uncompressed UTF-8 text chunk + + // we get the compression flag after the key + const byte compressionFlag = data.read_uint8(keysize + 1); + // we get the compression method after the compression flag + const byte compressionMethod = data.read_uint8(keysize + 2); - const byte* text = data.c_data(keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1); - const long textsize = static_cast(data.size() - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1)); + enforce(compressionFlag == 0x00 || compressionFlag == 0x01, Exiv2::kerCorruptedMetadata); + enforce(compressionMethod == 0x00, Exiv2::kerCorruptedMetadata); - if (compressionFlag == 0x00) { - // then it's an uncompressed iTXt chunk + // language description string after the compression technique spec + const size_t languageTextMaxSize = data.size() - keysize - 3; + std::string languageText = + string_from_unterminated(data.c_str(Safe::add(keysize, 3)), languageTextMaxSize); + const size_t languageTextSize = languageText.size(); + + enforce(static_cast(data.size()) >= + Safe::add(static_cast(Safe::add(keysize, 4)), languageTextSize), + Exiv2::kerCorruptedMetadata); + // translated keyword string after the language description + std::string translatedKeyText = string_from_unterminated( + data.c_str(keysize + 3 + languageTextSize + 1), data.size() - (keysize + 3 + languageTextSize + 1)); + const auto translatedKeyTextSize = static_cast(translatedKeyText.size()); + + if ((compressionFlag == 0x00) || (compressionFlag == 0x01 && compressionMethod == 0x00)) { + enforce(Safe::add(static_cast(keysize + 3 + languageTextSize + 1), + Safe::add(translatedKeyTextSize, 1U)) <= static_cast(data.size()), + Exiv2::kerCorruptedMetadata); + + const byte* text = data.c_data(keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1); + const long textsize = static_cast( + data.size() - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1)); + + if (compressionFlag == 0x00) { + // then it's an uncompressed iTXt chunk #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::parseTXTChunk: We found an uncompressed iTXt field\n"; + std::cout << "Exiv2::PngChunk::parseTXTChunk: We found an uncompressed iTXt field\n"; #endif - arr.alloc(textsize); - arr = DataBuf(text, textsize); - } else if (compressionFlag == 0x01 && compressionMethod == 0x00) { - // then it's a zlib compressed iTXt chunk + arr.alloc(textsize); + arr = DataBuf(text, textsize); + } else if (compressionFlag == 0x01 && compressionMethod == 0x00) { + // then it's a zlib compressed iTXt chunk #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::parseTXTChunk: We found a zlib compressed iTXt field\n"; + std::cout << "Exiv2::PngChunk::parseTXTChunk: We found a zlib compressed iTXt field\n"; #endif - // the compressed text comes after the translated keyword, but isn't null terminated - zlibUncompress(text, textsize, arr); + // the compressed text comes after the translated keyword, but isn't null terminated + zlibUncompress(text, textsize, arr); + } + } else { + // then it isn't zlib compressed and we are sunk +#ifdef EXIV2_DEBUG_MESSAGES + std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard iTXt compression method.\n"; +#endif + throw Error(kerFailedToReadImageData); } } else { - // then it isn't zlib compressed and we are sunk -#ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard iTXt compression method.\n"; +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parseTXTChunk: We found a field, not expected though\n"; #endif throw Error(kerFailedToReadImageData); } - } - else - { -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parseTXTChunk: We found a field, not expected though\n"; -#endif - throw Error(kerFailedToReadImageData); - } - - return arr; - } - void PngChunk::parseChunkContent(Image* pImage, const byte* key, long keySize, const DataBuf& arr) - { - // We look if an ImageMagick EXIF raw profile exist. + return arr; + } - if ( keySize >= 21 - && ( memcmp("Raw profile type exif", key, 21) == 0 - || memcmp("Raw profile type APP1", key, 21) == 0) - && pImage->exifData().empty()) + void PngChunk::parseChunkContent(Image* pImage, const byte* key, long keySize, const DataBuf& arr) { - DataBuf exifData = readRawProfile(arr,false); - long length = exifData.size(); + // We look if an ImageMagick EXIF raw profile exist. - if (length > 0) - { - // Find the position of Exif header in bytes array. + if (keySize >= 21 && + (memcmp("Raw profile type exif", key, 21) == 0 || memcmp("Raw profile type APP1", key, 21) == 0) && + pImage->exifData().empty()) { + DataBuf exifData = readRawProfile(arr, false); + long length = exifData.size(); - const byte exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; - long pos = -1; + if (length > 0) { + // Find the position of Exif header in bytes array. - for (long i = 0; i < length - static_cast(sizeof(exifHeader)); i++) { - if (exifData.cmpBytes(i, exifHeader, sizeof(exifHeader)) == 0) - { - pos = i; - break; + const byte exifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + long pos = -1; + + for (long i = 0; i < length - static_cast(sizeof(exifHeader)); i++) { + if (exifData.cmpBytes(i, exifHeader, sizeof(exifHeader)) == 0) { + pos = i; + break; + } } - } - // If found it, store only these data at from this place. + // If found it, store only these data at from this place. - if (pos !=-1) - { + if (pos != -1) { #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::parseChunkContent: Exif header found at position " << pos << "\n"; + std::cout << "Exiv2::PngChunk::parseChunkContent: Exif header found at position " << pos + << "\n"; #endif - pos = pos + sizeof(exifHeader); - ByteOrder bo = TiffParser::decode(pImage->exifData(), - pImage->iptcData(), - pImage->xmpData(), - exifData.c_data(pos), - length - pos); - pImage->setByteOrder(bo); - } - else - { + pos = pos + sizeof(exifHeader); + ByteOrder bo = TiffParser::decode(pImage->exifData(), pImage->iptcData(), pImage->xmpData(), + exifData.c_data(pos), length - pos); + pImage->setByteOrder(bo); + } else { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to decode Exif metadata.\n"; + EXV_WARNING << "Failed to decode Exif metadata.\n"; #endif - pImage->exifData().clear(); + pImage->exifData().clear(); + } } } - } - // We look if an ImageMagick IPTC raw profile exist. - - if ( keySize >= 21 - && memcmp("Raw profile type iptc", key, 21) == 0 - && pImage->iptcData().empty()) { - DataBuf psData = readRawProfile(arr,false); - if (psData.size() > 0) { - Blob iptcBlob; - const byte* record = nullptr; - uint32_t sizeIptc = 0; - uint32_t sizeHdr = 0; - - const byte* pEnd = psData.c_data(psData.size()); - const byte* pCur = psData.c_data(); - while ( pCur < pEnd - && 0 == Photoshop::locateIptcIrb(pCur, - static_cast(pEnd - pCur), - &record, - &sizeHdr, - &sizeIptc)) { - if (sizeIptc) { + // We look if an ImageMagick IPTC raw profile exist. + + if (keySize >= 21 && memcmp("Raw profile type iptc", key, 21) == 0 && pImage->iptcData().empty()) { + DataBuf psData = readRawProfile(arr, false); + if (psData.size() > 0) { + Blob iptcBlob; + const byte* record = nullptr; + uint32_t sizeIptc = 0; + uint32_t sizeHdr = 0; + + const byte* pEnd = psData.c_data(psData.size()); + const byte* pCur = psData.c_data(); + while (pCur < pEnd && 0 == Photoshop::locateIptcIrb(pCur, static_cast(pEnd - pCur), &record, + &sizeHdr, &sizeIptc)) { + if (sizeIptc) { #ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Found IPTC IRB, size = " << sizeIptc << "\n"; + std::cerr << "Found IPTC IRB, size = " << sizeIptc << "\n"; #endif - append(iptcBlob, record + sizeHdr, sizeIptc); + append(iptcBlob, record + sizeHdr, sizeIptc); + } + pCur = record + sizeHdr + sizeIptc; + pCur += (sizeIptc & 1); } - pCur = record + sizeHdr + sizeIptc; - pCur += (sizeIptc & 1); - } - if (!iptcBlob.empty() && - IptcParser::decode(pImage->iptcData(), &iptcBlob[0], static_cast(iptcBlob.size()))) { + if (!iptcBlob.empty() && + IptcParser::decode(pImage->iptcData(), &iptcBlob[0], static_cast(iptcBlob.size()))) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to decode IPTC metadata.\n"; + EXV_WARNING << "Failed to decode IPTC metadata.\n"; #endif - pImage->clearIptcData(); - } - // If there is no IRB, try to decode the complete chunk data - if ( iptcBlob.empty() - && IptcParser::decode(pImage->iptcData(), - psData.c_data(), - psData.size())) { + pImage->clearIptcData(); + } + // If there is no IRB, try to decode the complete chunk data + if (iptcBlob.empty() && IptcParser::decode(pImage->iptcData(), psData.c_data(), psData.size())) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to decode IPTC metadata.\n"; + EXV_WARNING << "Failed to decode IPTC metadata.\n"; #endif - pImage->clearIptcData(); - } - } // if (psData.size() > 0) - } + pImage->clearIptcData(); + } + } // if (psData.size() > 0) + } - // We look if an ImageMagick XMP raw profile exist. + // We look if an ImageMagick XMP raw profile exist. - if ( keySize >= 20 - && memcmp("Raw profile type xmp", key, 20) == 0 - && pImage->xmpData().empty()) - { - DataBuf xmpBuf = readRawProfile(arr,false); - long length = xmpBuf.size(); - - if (length > 0) - { - std::string& xmpPacket = pImage->xmpPacket(); - xmpPacket.assign(xmpBuf.c_str(), length); - std::string::size_type idx = xmpPacket.find_first_of('<'); - if (idx != std::string::npos && idx > 0) - { + if (keySize >= 20 && memcmp("Raw profile type xmp", key, 20) == 0 && pImage->xmpData().empty()) { + DataBuf xmpBuf = readRawProfile(arr, false); + long length = xmpBuf.size(); + + if (length > 0) { + std::string& xmpPacket = pImage->xmpPacket(); + xmpPacket.assign(xmpBuf.c_str(), length); + std::string::size_type idx = xmpPacket.find_first_of('<'); + if (idx != std::string::npos && idx > 0) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Removing " << idx - << " characters from the beginning of the XMP packet\n"; + EXV_WARNING << "Removing " << idx << " characters from the beginning of the XMP packet\n"; #endif - xmpPacket = xmpPacket.substr(idx); - } - if (XmpParser::decode(pImage->xmpData(), xmpPacket)) - { + xmpPacket = xmpPacket.substr(idx); + } + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to decode XMP metadata.\n"; + EXV_WARNING << "Failed to decode XMP metadata.\n"; #endif + } } } - } - // We look if an Adobe XMP string exist. + // We look if an Adobe XMP string exist. - if ( keySize >= 17 - && memcmp("XML:com.adobe.xmp", key, 17) == 0 - && pImage->xmpData().empty()) - { - if (arr.size() > 0) - { - std::string& xmpPacket = pImage->xmpPacket(); - xmpPacket.assign(arr.c_str(), arr.size()); - std::string::size_type idx = xmpPacket.find_first_of('<'); - if (idx != std::string::npos && idx > 0) - { + if (keySize >= 17 && memcmp("XML:com.adobe.xmp", key, 17) == 0 && pImage->xmpData().empty()) { + if (arr.size() > 0) { + std::string& xmpPacket = pImage->xmpPacket(); + xmpPacket.assign(arr.c_str(), arr.size()); + std::string::size_type idx = xmpPacket.find_first_of('<'); + if (idx != std::string::npos && idx > 0) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Removing " << idx << " characters " - << "from the beginning of the XMP packet\n"; + EXV_WARNING << "Removing " << idx << " characters " + << "from the beginning of the XMP packet\n"; #endif - xmpPacket = xmpPacket.substr(idx); - } - if (XmpParser::decode(pImage->xmpData(), xmpPacket)) - { + xmpPacket = xmpPacket.substr(idx); + } + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to decode XMP metadata.\n"; + EXV_WARNING << "Failed to decode XMP metadata.\n"; #endif + } } } - } - // We look if a comments string exist. Note than we use only 'Description' keyword which - // is dedicaced to store long comments. 'Comment' keyword is ignored. + // We look if a comments string exist. Note than we use only 'Description' keyword which + // is dedicaced to store long comments. 'Comment' keyword is ignored. - if ( keySize >= 11 - && memcmp("Description", key, 11) == 0 - && pImage->comment().empty()) - { - pImage->setComment(std::string(arr.c_str(), arr.size())); - } + if (keySize >= 11 && memcmp("Description", key, 11) == 0 && pImage->comment().empty()) { + pImage->setComment(std::string(arr.c_str(), arr.size())); + } - } // PngChunk::parseChunkContent + } // PngChunk::parseChunkContent - std::string PngChunk::makeMetadataChunk(const std::string& metadata, - MetadataId type) - { - std::string chunk; - std::string rawProfile; - - switch (type) { - case mdComment: - chunk = makeUtf8TxtChunk("Description", metadata, true); - break; - case mdExif: - rawProfile = writeRawProfile(metadata, "exif"); - chunk = makeAsciiTxtChunk("Raw profile type exif", rawProfile, true); - break; - case mdIptc: - rawProfile = writeRawProfile(metadata, "iptc"); - chunk = makeAsciiTxtChunk("Raw profile type iptc", rawProfile, true); - break; - case mdXmp: - chunk = makeUtf8TxtChunk("XML:com.adobe.xmp", metadata, false); - break; - case mdIccProfile: - break; - case mdNone: - assert(false); - } - - return chunk; - - } // PngChunk::makeMetadataChunk - - void PngChunk::zlibUncompress(const byte* compressedText, - unsigned int compressedTextSize, - DataBuf& arr) - { - uLongf uncompressedLen = compressedTextSize * 2; // just a starting point - int zlibResult; - int dos = 0; - - do { - arr.alloc(uncompressedLen); - zlibResult = uncompress(arr.data(), - &uncompressedLen, - compressedText, - compressedTextSize); - if (zlibResult == Z_OK) { - assert((uLongf)arr.size() >= uncompressedLen); - arr.resize(uncompressedLen); + std::string PngChunk::makeMetadataChunk(const std::string& metadata, MetadataId type) + { + std::string chunk; + std::string rawProfile; + + switch (type) { + case mdComment: + chunk = makeUtf8TxtChunk("Description", metadata, true); + break; + case mdExif: + rawProfile = writeRawProfile(metadata, "exif"); + chunk = makeAsciiTxtChunk("Raw profile type exif", rawProfile, true); + break; + case mdIptc: + rawProfile = writeRawProfile(metadata, "iptc"); + chunk = makeAsciiTxtChunk("Raw profile type iptc", rawProfile, true); + break; + case mdXmp: + chunk = makeUtf8TxtChunk("XML:com.adobe.xmp", metadata, false); + break; + case mdIccProfile: + break; + case mdNone: + assert(false); } - else if (zlibResult == Z_BUF_ERROR) { - // the uncompressedArray needs to be larger - uncompressedLen *= 2; - // DoS protection. can't be bigger than 64k - if (uncompressedLen > 131072) { - if (++dos > 1) break; - uncompressedLen = 131072; + + return chunk; + + } // PngChunk::makeMetadataChunk + + void PngChunk::zlibUncompress(const byte* compressedText, unsigned int compressedTextSize, DataBuf& arr) + { + uLongf uncompressedLen = compressedTextSize * 2; // just a starting point + int zlibResult; + int dos = 0; + + do { + arr.alloc(uncompressedLen); + zlibResult = uncompress(arr.data(), &uncompressedLen, compressedText, compressedTextSize); + if (zlibResult == Z_OK) { + assert((uLongf)arr.size() >= uncompressedLen); + arr.resize(uncompressedLen); + } else if (zlibResult == Z_BUF_ERROR) { + // the uncompressedArray needs to be larger + uncompressedLen *= 2; + // DoS protection. can't be bigger than 64k + if (uncompressedLen > 131072) { + if (++dos > 1) + break; + uncompressedLen = 131072; + } + } else { + // something bad happened + throw Error(kerFailedToReadImageData); } - } - else { - // something bad happened + } while (zlibResult == Z_BUF_ERROR); + + if (zlibResult != Z_OK) { throw Error(kerFailedToReadImageData); } - } - while (zlibResult == Z_BUF_ERROR); + } // PngChunk::zlibUncompress - if (zlibResult != Z_OK) { - throw Error(kerFailedToReadImageData); - } - } // PngChunk::zlibUncompress + std::string PngChunk::zlibCompress(const std::string& text) + { + auto compressedLen = static_cast(text.size() * 2); // just a starting point + int zlibResult; - std::string PngChunk::zlibCompress(const std::string& text) - { - auto compressedLen = static_cast(text.size() * 2); // just a starting point - int zlibResult; - - DataBuf arr; - do { - arr.resize(compressedLen); - zlibResult = compress2(arr.data(), &compressedLen, reinterpret_cast(text.data()), - static_cast(text.size()), Z_BEST_COMPRESSION); - - switch (zlibResult) { - case Z_OK: - assert((uLongf)arr.size() >= compressedLen); + DataBuf arr; + do { arr.resize(compressedLen); - break; - case Z_BUF_ERROR: - // The compressed array needs to be larger + zlibResult = compress2(arr.data(), &compressedLen, reinterpret_cast(text.data()), + static_cast(text.size()), Z_BEST_COMPRESSION); + + switch (zlibResult) { + case Z_OK: + assert((uLongf)arr.size() >= compressedLen); + arr.resize(compressedLen); + break; + case Z_BUF_ERROR: + // The compressed array needs to be larger #ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for compression.\n"; + std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for compression.\n"; #endif - compressedLen *= 2; - // DoS protection. Cap max compressed size - if ( compressedLen > 131072 ) throw Error(kerFailedToReadImageData); - break; - default: - // Something bad happened - throw Error(kerFailedToReadImageData); - } - } while (zlibResult == Z_BUF_ERROR); + compressedLen *= 2; + // DoS protection. Cap max compressed size + if (compressedLen > 131072) + throw Error(kerFailedToReadImageData); + break; + default: + // Something bad happened + throw Error(kerFailedToReadImageData); + } + } while (zlibResult == Z_BUF_ERROR); - return std::string(arr.c_str(), arr.size()); + return std::string(arr.c_str(), arr.size()); - } // PngChunk::zlibCompress + } // PngChunk::zlibCompress - std::string PngChunk::makeAsciiTxtChunk(const std::string& keyword, - const std::string& text, - bool compress) - { - // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes) - // Length is the size of the chunk data - // CRC is calculated on chunk type + chunk data - - // Compressed text chunk using zlib. - // Chunk data format : keyword + 0x00 + compression method (0x00) + compressed text - - // Not Compressed text chunk. - // Chunk data format : keyword + 0x00 + text - - // Build chunk data, determine chunk type - std::string chunkData = keyword + '\0'; - std::string chunkType; - if (compress) { - chunkData += '\0' + zlibCompress(text); - chunkType = "zTXt"; - } - else { - chunkData += text; - chunkType = "tEXt"; - } - // Determine length of the chunk data - byte length[4]; - ul2Data(length, static_cast(chunkData.size()), bigEndian); - // Calculate CRC on chunk type and chunk data - std::string crcData = chunkType + chunkData; - uLong tmp = crc32(0L, Z_NULL, 0); - tmp = crc32(tmp, reinterpret_cast(crcData.data()), static_cast(crcData.size())); - byte crc[4]; - ul2Data(crc, tmp, bigEndian); - // Assemble the chunk - return std::string(reinterpret_cast(length), 4) + chunkType + chunkData + - std::string(reinterpret_cast(crc), 4); - - } // PngChunk::makeAsciiTxtChunk - - std::string PngChunk::makeUtf8TxtChunk(const std::string& keyword, - const std::string& text, - bool compress) - { - // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes) - // Length is the size of the chunk data - // CRC is calculated on chunk type + chunk data - - // Chunk data format : keyword + 0x00 + compression flag (0x00: uncompressed - 0x01: compressed) - // + compression method (0x00: zlib format) + language tag (null) + 0x00 - // + translated keyword (null) + 0x00 + text (compressed or not) - - // Build chunk data, determine chunk type - std::string chunkData = keyword; - if (compress) { - static const char flags[] = { 0x00, 0x01, 0x00, 0x00, 0x00 }; - chunkData += std::string(flags, 5) + zlibCompress(text); - } - else { - static const char flags[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; - chunkData += std::string(flags, 5) + text; - } - // Determine length of the chunk data - byte length[4]; - ul2Data(length, static_cast(chunkData.size()), bigEndian); - // Calculate CRC on chunk type and chunk data - std::string chunkType = "iTXt"; - std::string crcData = chunkType + chunkData; - uLong tmp = crc32(0L, Z_NULL, 0); - tmp = crc32(tmp, reinterpret_cast(crcData.data()), static_cast(crcData.size())); - byte crc[4]; - ul2Data(crc, tmp, bigEndian); - // Assemble the chunk - return std::string(reinterpret_cast(length), 4) + chunkType + chunkData + - std::string(reinterpret_cast(crc), 4); - - } // PngChunk::makeUtf8TxtChunk - - DataBuf PngChunk::readRawProfile(const DataBuf& text,bool iTXt) - { - DataBuf info; - unsigned char unhex[103]={0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,1, 2,3,4,5,6,7,8,9,0,0, - 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,10,11,12, - 13,14,15}; - if (text.size() == 0) { - return DataBuf(); - } - - if ( iTXt ) { - info.alloc(text.size()); - info.copyBytes(0, text.c_data(), text.size()); - return info; - } + std::string PngChunk::makeAsciiTxtChunk(const std::string& keyword, const std::string& text, bool compress) + { + // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes) + // Length is the size of the chunk data + // CRC is calculated on chunk type + chunk data + + // Compressed text chunk using zlib. + // Chunk data format : keyword + 0x00 + compression method (0x00) + compressed text + + // Not Compressed text chunk. + // Chunk data format : keyword + 0x00 + text + + // Build chunk data, determine chunk type + std::string chunkData = keyword + '\0'; + std::string chunkType; + if (compress) { + chunkData += '\0' + zlibCompress(text); + chunkType = "zTXt"; + } else { + chunkData += text; + chunkType = "tEXt"; + } + // Determine length of the chunk data + byte length[4]; + ul2Data(length, static_cast(chunkData.size()), bigEndian); + // Calculate CRC on chunk type and chunk data + std::string crcData = chunkType + chunkData; + uLong tmp = crc32(0L, Z_NULL, 0); + tmp = crc32(tmp, reinterpret_cast(crcData.data()), static_cast(crcData.size())); + byte crc[4]; + ul2Data(crc, tmp, bigEndian); + // Assemble the chunk + return std::string(reinterpret_cast(length), 4) + chunkType + chunkData + + std::string(reinterpret_cast(crc), 4); + + } // PngChunk::makeAsciiTxtChunk + + std::string PngChunk::makeUtf8TxtChunk(const std::string& keyword, const std::string& text, bool compress) + { + // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes) + // Length is the size of the chunk data + // CRC is calculated on chunk type + chunk data + + // Chunk data format : keyword + 0x00 + compression flag (0x00: uncompressed - 0x01: compressed) + // + compression method (0x00: zlib format) + language tag (null) + 0x00 + // + translated keyword (null) + 0x00 + text (compressed or not) + + // Build chunk data, determine chunk type + std::string chunkData = keyword; + if (compress) { + static const char flags[] = {0x00, 0x01, 0x00, 0x00, 0x00}; + chunkData += std::string(flags, 5) + zlibCompress(text); + } else { + static const char flags[] = {0x00, 0x00, 0x00, 0x00, 0x00}; + chunkData += std::string(flags, 5) + text; + } + // Determine length of the chunk data + byte length[4]; + ul2Data(length, static_cast(chunkData.size()), bigEndian); + // Calculate CRC on chunk type and chunk data + std::string chunkType = "iTXt"; + std::string crcData = chunkType + chunkData; + uLong tmp = crc32(0L, Z_NULL, 0); + tmp = crc32(tmp, reinterpret_cast(crcData.data()), static_cast(crcData.size())); + byte crc[4]; + ul2Data(crc, tmp, bigEndian); + // Assemble the chunk + return std::string(reinterpret_cast(length), 4) + chunkType + chunkData + + std::string(reinterpret_cast(crc), 4); + + } // PngChunk::makeUtf8TxtChunk + + DataBuf PngChunk::readRawProfile(const DataBuf& text, bool iTXt) + { + DataBuf info; + unsigned char unhex[103] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15}; + if (text.size() == 0) { + return DataBuf(); + } - const char* sp = text.c_str(1); // current byte (space pointer) - const char* eot = text.c_str(text.size()); // end of text + if (iTXt) { + info.alloc(text.size()); + info.copyBytes(0, text.c_data(), text.size()); + return info; + } - if (sp >= eot) { - return DataBuf(); - } + const char* sp = text.c_str(1); // current byte (space pointer) + const char* eot = text.c_str(text.size()); // end of text - // Look for newline - while (*sp != '\n') - { - sp++; - if ( sp == eot ) - { + if (sp >= eot) { return DataBuf(); } - } - sp++ ; // step over '\n' - if (sp == eot) { - return DataBuf(); - } - // Look for length - while (*sp == '\0' || *sp == ' ' || *sp == '\n') - { - sp++; - if (sp == eot ) - { + // Look for newline + while (*sp != '\n') { + sp++; + if (sp == eot) { + return DataBuf(); + } + } + sp++; // step over '\n' + if (sp == eot) { return DataBuf(); } - } - // Parse the length. - long length = 0; - while ('0' <= *sp && *sp <= '9') - { - // Compute the new length using unsigned long, so that we can - // check for overflow. - const unsigned long newlength = (10 * static_cast(length)) + (*sp - '0'); - if (newlength > static_cast(std::numeric_limits::max())) { - return DataBuf(); // Integer overflow. + // Look for length + while (*sp == '\0' || *sp == ' ' || *sp == '\n') { + sp++; + if (sp == eot) { + return DataBuf(); + } + } + + // Parse the length. + long length = 0; + while ('0' <= *sp && *sp <= '9') { + // Compute the new length using unsigned long, so that we can + // check for overflow. + const unsigned long newlength = (10 * static_cast(length)) + (*sp - '0'); + if (newlength > static_cast(std::numeric_limits::max())) { + return DataBuf(); // Integer overflow. + } + length = static_cast(newlength); + sp++; + if (sp == eot) { + return DataBuf(); + } } - length = static_cast(newlength); - sp++; - if (sp == eot ) - { + sp++; // step over '\n' + if (sp == eot) { return DataBuf(); } - } - sp++ ; // step over '\n' - if (sp == eot) { - return DataBuf(); - } - enforce(length <= (eot - sp)/2, Exiv2::kerCorruptedMetadata); + enforce(length <= (eot - sp) / 2, Exiv2::kerCorruptedMetadata); - // Allocate space - if (length == 0) - { + // Allocate space + if (length == 0) { #ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: invalid profile length\n"; + std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: invalid profile length\n"; #endif - } - info.alloc(length); - if (info.size() != length) - { + } + info.alloc(length); + if (info.size() != length) { #ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: cannot allocate memory\n"; + std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: cannot allocate memory\n"; #endif - return DataBuf(); - } + return DataBuf(); + } - // Copy profile, skipping white space and column 1 "=" signs + // Copy profile, skipping white space and column 1 "=" signs - unsigned char *dp = info.data(); // decode pointer - unsigned int nibbles = length * 2; + unsigned char* dp = info.data(); // decode pointer + unsigned int nibbles = length * 2; - for (long i = 0; i < static_cast(nibbles); i++) { - enforce(sp < eot, Exiv2::kerCorruptedMetadata); - while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') - { - if (*sp == '\0') - { + for (long i = 0; i < static_cast(nibbles); i++) { + enforce(sp < eot, Exiv2::kerCorruptedMetadata); + while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { + if (*sp == '\0') { #ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: ran out of data\n"; + std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: ran out of data\n"; #endif - return DataBuf(); + return DataBuf(); + } + + sp++; + enforce(sp < eot, Exiv2::kerCorruptedMetadata); } - sp++; - enforce(sp < eot, Exiv2::kerCorruptedMetadata); + if (i % 2 == 0) + *dp = static_cast(16 * unhex[static_cast(*sp++)]); + else + (*dp++) += unhex[static_cast(*sp++)]; } - if (i%2 == 0) - *dp = static_cast(16 * unhex[static_cast(*sp++)]); - else - (*dp++) += unhex[static_cast(*sp++)]; - } - - return info; + return info; - } // PngChunk::readRawProfile + } // PngChunk::readRawProfile - std::string PngChunk::writeRawProfile(const std::string& profileData, - const char* profileType) - { - static byte hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - - std::ostringstream oss; - oss << '\n' << profileType << '\n' << std::setw(8) << profileData.size(); - const char* sp = profileData.data(); - for (std::string::size_type i = 0; i < profileData.size(); ++i) { - if (i % 36 == 0) oss << '\n'; - oss << hex[((*sp >> 4) & 0x0f)]; - oss << hex[((*sp++) & 0x0f)]; - } - oss << '\n'; - return oss.str(); + std::string PngChunk::writeRawProfile(const std::string& profileData, const char* profileType) + { + static byte hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + std::ostringstream oss; + oss << '\n' << profileType << '\n' << std::setw(8) << profileData.size(); + const char* sp = profileData.data(); + for (std::string::size_type i = 0; i < profileData.size(); ++i) { + if (i % 36 == 0) + oss << '\n'; + oss << hex[((*sp >> 4) & 0x0f)]; + oss << hex[((*sp++) & 0x0f)]; + } + oss << '\n'; + return oss.str(); - } // PngChunk::writeRawProfile + } // PngChunk::writeRawProfile } // namespace Internal } // namespace Exiv2 -#endif // ifdef EXV_HAVE_LIBZ - +#endif // ifdef EXV_HAVE_LIBZ From 846b7f0b85aac7ca92012bf656227319bbad951e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 7 Jan 2022 08:21:05 +0100 Subject: [PATCH 10/10] Check array index before inspecting it --- src/pngchunk_int.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pngchunk_int.cpp b/src/pngchunk_int.cpp index 4068da86f9..b1db499903 100644 --- a/src/pngchunk_int.cpp +++ b/src/pngchunk_int.cpp @@ -22,6 +22,8 @@ #include "config.h" #ifdef EXV_HAVE_LIBZ +#include // To uncompress or compress text chunk + #include "enforce.hpp" #include "error.hpp" #include "exif.hpp" @@ -33,8 +35,6 @@ #include "safe_op.hpp" #include "tiffimage.hpp" -#include // To uncompress or compress text chunk - // standard includes #include #include @@ -108,7 +108,7 @@ namespace Exiv2 // Search for null char until the end of the DataBuf const byte* dataPtr = data.c_data(); int keysize = offset; - while (dataPtr[keysize] != 0 && keysize < data.size()) { + while (keysize < data.size() && dataPtr[keysize] != 0) { keysize++; }