| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "formats/tiff/extractor.h" | |
| #include <sys/types.h> | |
| #include <cstdint> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "formats/asset_box.h" | |
| #include "formats/byte_range.h" | |
| #include "formats/extractor_result.h" | |
| #include "formats/tiff/constants.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "riegeli/bytes/string_reader.h" | |
| #include "utils/byte_writers.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::StatusIs; | |
| using ::testing::Eq; | |
| using ::testing::HasSubstr; | |
| struct TestCase { | |
| std::string name; | |
| absl::StatusOr<std::string> contents; | |
| absl::StatusOr<ExtractorResult> result; | |
| }; | |
| void WriteTiffHeader(uint32_t next_ifd_offset, std::vector<uint8_t>& data) { | |
| WriteUint16NetworkOrder(kTiffBigEndian, &data); // Endian marker | |
| WriteUint16NetworkOrder(kTiffMarker, &data); // TIFF marker | |
| WriteUint32NetworkOrder(next_ifd_offset, &data); // Next IFD offset | |
| } | |
| void WriteString(absl::string_view contents, std::vector<uint8_t>& data) { | |
| for (int i = 0; i < contents.size(); ++i) { | |
| data.push_back(contents[i]); | |
| } | |
| } | |
| struct Ifde { | |
| uint16_t tag; | |
| uint16_t type; | |
| uint32_t count; | |
| uint32_t value; | |
| }; | |
| void WriteIfd(std::vector<Ifde> entries, uint32_t next_ifd, | |
| std::vector<uint8_t>& data) { | |
| WriteUint16NetworkOrder(entries.size(), &data); // IFD entry count | |
| for (const Ifde& entry : entries) { | |
| WriteUint16NetworkOrder(entry.tag, &data); // Tag | |
| WriteUint16NetworkOrder(entry.type, &data); // Type | |
| WriteUint32NetworkOrder(entry.count, &data); // Count | |
| WriteUint32NetworkOrder(entry.value, &data); // Value | |
| } | |
| WriteUint32NetworkOrder(next_ifd, &data); // Next IFD offset | |
| } | |
| void WriteUint16(std::vector<uint16_t> entries, std::vector<uint8_t>& data) { | |
| for (const uint16_t entry : entries) { | |
| WriteUint16NetworkOrder(entry, &data); | |
| } | |
| } | |
| void WriteUint32(std::vector<uint32_t> entries, std::vector<uint8_t>& data) { | |
| for (const uint32_t entry : entries) { | |
| WriteUint32NetworkOrder(entry, &data); | |
| } | |
| } | |
| class ExtractorTest : public testing::TestWithParam<TestCase> { | |
| public: | |
| void SetUp() override { ABSL_ASSERT_OK(GetParam().contents.status()); } | |
| }; | |
| INSTANTIATE_TEST_SUITE_P( | |
| ExtractorTests, ExtractorTest, | |
| testing::ValuesIn( | |
| {TestCase{ | |
| .name = "EmptyContent", | |
| .contents = "", | |
| .result = absl::DataLossError("kUnexpectedEof"), | |
| }, | |
| TestCase{ | |
| .name = "InvalidEndianMarker", | |
| .contents = "\x12\x34", | |
| .result = absl::DataLossError("endianness"), | |
| }, | |
| TestCase{ | |
| .name = "InvalidTiffMarker", | |
| .contents = "\x4d\x4d\x12\x34", | |
| .result = absl::DataLossError("invalid endianness"), | |
| }, | |
| TestCase{ | |
| .name = "SingleIfdNoEntries", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| WriteTiffHeader(8, data); | |
| WriteIfd({}, 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = absl::NotFoundError("No manifest store found"), | |
| }, | |
| TestCase{ | |
| .name = "SingleIfdNoC2paEntry", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| WriteTiffHeader(8, data); | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 4, | |
| .count = 1, | |
| .value = 0x12345678}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = absl::NotFoundError("No manifest store found"), | |
| }, | |
| TestCase{ | |
| .name = "ValidThreeValueOffsetTag", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(45, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 33) Offset Data | |
| WriteUint16({0x1234, 0x5678, 0x9abc}, data); | |
| // [33, 39) Offset Val | |
| WriteUint16({27, 29, 31}, data); | |
| // [39, 45) Counts Val | |
| WriteUint16({2, 2, 2}, data); | |
| // [45, 75) IFD | |
| WriteIfd( | |
| {{.tag = 0x0111, .type = 3, .count = 3, .value = 33}, | |
| {.tag = 0x0117, .type = 3, .count = 3, .value = 39}}, | |
| 75, data); | |
| // [75, 93) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "EntryWithMultipleUint8Values", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) First IFD | |
| WriteIfd({Ifde{.tag = 0x1234, | |
| .type = 1, | |
| .count = 3, | |
| .value = 0x01020300}}, | |
| 45, data); | |
| // [45, 61) Second IFD | |
| WriteIfd({Ifde{.tag = kTiffTagC2pa, | |
| .type = 0x0007, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "EntryWithMultipleUint16Values", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) First IFD | |
| WriteIfd({Ifde{.tag = 0x1234, | |
| .type = 3, | |
| .count = 2, | |
| .value = 0x01020304}}, | |
| 45, data); | |
| // [45, 61) Second IFD | |
| WriteIfd({Ifde{.tag = kTiffTagC2pa, | |
| .type = 0x0007, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "ValidSingleValueOffset", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(31, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 31) Offset Data | |
| WriteUint32({0x12345678}, data); | |
| // [31, 73) IFD | |
| WriteIfd( | |
| { | |
| {.tag = 0x0111, .type = 4, .count = 1, .value = 27}, | |
| {.tag = 0x0115, .type = 4, .count = 1, .value = 4}, | |
| {.tag = 0x0117, .type = 4, .count = 1, .value = 4}, | |
| }, | |
| 73, data); | |
| // [73, 91) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "ValidSingleValueOffsetWithExtraData", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(31, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 31) Offset Data | |
| WriteUint32({0x12345678}, data); | |
| // [31, 73) IFD | |
| WriteIfd( | |
| { | |
| {.tag = 0x0111, .type = 4, .count = 1, .value = 27}, | |
| {.tag = 0x0115, .type = 4, .count = 1, .value = 4}, | |
| {.tag = 0x0117, .type = 4, .count = 1, .value = 4}, | |
| }, | |
| 73, data); | |
| // [73, 91) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| // [91, 101) Extra Data (not part of the IFD) | |
| return std::string(data.begin(), data.end()) + "extra_data"; | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "ValidSingleSubIfd", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(45, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) Sub IFD | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 4, | |
| .count = 1, | |
| .value = 0x12345678}}, | |
| 0, data); | |
| // [45, 63) IFD | |
| WriteIfd( | |
| {{.tag = 0x014a, .type = 4, .count = 1, .value = 27}}, | |
| 63, data); | |
| // [63, 81) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "ValidTwoLayerSubIfd", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(63, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) Child Sub IFD | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 4, | |
| .count = 1, | |
| .value = 0x12345678}}, | |
| 0, data); | |
| // [45, 63) Parent Sub IFD | |
| WriteIfd( | |
| {{.tag = 0x014a, .type = 4, .count = 1, .value = 27}}, 0, | |
| data); | |
| // [63, 81) First Base IFD | |
| WriteIfd( | |
| {{.tag = 0x014a, .type = 4, .count = 1, .value = 45}}, | |
| 81, data); | |
| // [81, 99) Second Base IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "ValidSubWithChainedIfd", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(63, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) Second Sub IFD | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 4, | |
| .count = 1, | |
| .value = 0x12345678}}, | |
| 0, data); | |
| // [45, 63) First Sub IFD | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 4, | |
| .count = 1, | |
| .value = 0x12345678}}, | |
| 27, data); | |
| // [63, 81) First Base IFD | |
| WriteIfd( | |
| {{.tag = 0x014a, .type = 4, .count = 1, .value = 45}}, | |
| 81, data); | |
| // [81, 99) Second Base IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = ExtractorResult{.manifest_store = "test_manifest_store", | |
| .asset_byte_info = | |
| {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| // This is technically not a valid TIFF file, as there is no other | |
| // data, but it abides by the C2PA spec. | |
| .name = "SingleIfdOnlyC2paEntry", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| ExtractorResult{ | |
| .manifest_store = "test_manifest_store", | |
| .asset_byte_info = {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "SingleIfdC2paEntryBeforeOtherEntry", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd( | |
| {{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}, | |
| {.tag = 0x1234, .type = 1, .count = 1, .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| ExtractorResult{ | |
| .manifest_store = "test_manifest_store", | |
| .asset_byte_info = {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "SingleIfdC2paEntryAfterOtherEntry", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = 0x1234, .type = 1, .count = 1, .value = 8}, | |
| {.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| ExtractorResult{ | |
| .manifest_store = "test_manifest_store", | |
| .asset_byte_info = {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "MultipleIfdC2paInFirstIfd", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 45, data); | |
| // [45, 81) Second IFD | |
| WriteIfd( | |
| {{.tag = 0x1234, .type = 1, .count = 1, .value = 8}}, 0, | |
| data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| absl::NotFoundError("Manifest Store must be in the last IFD"), | |
| }, | |
| TestCase{ | |
| .name = "MultipleIfdC2paInSecondIfd", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd( | |
| {{.tag = 0x1234, .type = 1, .count = 1, .value = 8}}, 45, | |
| data); | |
| // [45, 81) Second IFD | |
| WriteIfd({{.tag = 0x1234, .type = 1, .count = 1, .value = 8}, | |
| {.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = absl::NotFoundError( | |
| "Manifest Store must be the only entry in the IFD"), | |
| }, | |
| TestCase{ | |
| .name = "UnknownIfdEntryType", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = 0x1234, | |
| .type = 0x1234, | |
| .count = 1, | |
| .value = 8}}, | |
| 45, data); | |
| // [45, 81) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| ExtractorResult{ | |
| .manifest_store = "test_manifest_store", | |
| .asset_byte_info = {.manifest_store_location = | |
| {.offset = 8, .length = 19}}}, | |
| }, | |
| TestCase{ | |
| .name = "MultipleIfdC2paInBoth", | |
| .contents = | |
| []() { | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(49, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 49) Manifest Store | |
| WriteString("another_manifest_store", data); | |
| // [49, 67) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 67, data); | |
| // [67, 85) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(), | |
| .result = | |
| absl::NotFoundError("Manifest Store must be in the last IFD"), | |
| }, | |
| TestCase{ | |
| .name = "FuzzTestRegression_b421332332", | |
| .contents = | |
| std::string("MM\000*" | |
| "\000\000\000\t\000\000\001\001JJJJ\303\303" | |
| "\004\000\000\000\t\004\000\000\000", | |
| 27), | |
| .result = absl::InternalError("Failed to seek to offs"), | |
| }, | |
| { | |
| .name = "FuzzTestRegression_b422060716", | |
| .contents = std::string( | |
| "MM\000*\000\000\000\t\000\000\002\001 " | |
| "\000\003\000\000\000\002\000\000\000\000\001!" | |
| "\000\004\000\000kkkkkkk\000..\000\000\000\303s\000\000", | |
| 46), | |
| .result = absl::InternalError("Failed to seek to offset"), | |
| }, | |
| { | |
| .name = "FuzzTestRegression_SmallC2paSize", | |
| .contents = std::string( | |
| "II*" | |
| "\000e\000\000\000\014\001\232\001\014\000\000\356\000\000\224" | |
| "\000\000\000\001A\002\000l\205\000\0004\000\000\000\002\210" | |
| "\007\000\005\000@" | |
| "\304\214\000\270\000\000\300\n\377\305\324\200\377\377\000b" | |
| "\000\000\000\241\001\003\000pT@" | |
| "\225\257R\000\000J\001\004\000\001\003y\000\326\000\000\000" | |
| "\373\377\373\372\005\022\000\000\254\322\333\000\000\035\001" | |
| "\003\000\001\000A\315\007\000\004\000\000\000\362\001\000\000" | |
| "\000\000\000\000\t\343\337\000\2372\010\000\000\211\000\000" | |
| "\000\000\371\000\000\000r\000\000\200\340\005\000\017\004\000" | |
| "\000\034\010\000\000\000\027\000\230X\353\233\005\270\372=*" | |
| "\032\006\3768\332\034%" | |
| "\000\000\000\000N\310\000\000\332\357\213\213\020@\003 " | |
| "\000\242\027\000\001&" | |
| "\023\316\000E\000\000\000\000\000\002\250\001\001\345\226\000" | |
| "\000\000\000\000\200\000\000\000u\202)" | |
| "\030\327u\000\000\000\000\000\000\000\000\000\000\000\000\000" | |
| "\000\000\000\372\372\373\000\373PPPP\325\000\000\336@" | |
| "\000\000JJ\037\265\345S\000AA\003\332\376\000U\266\004\000" | |
| "\000\000\000\000\000\000\200\0148\007\000\000\000\000\000\000" | |
| "\377\377\177\375\255\351\205\003\000\000\273\001^" | |
| "\004E\201\034\211\371Iw\325\234\377\252\032S.\006n\344$" | |
| "\277\036\000\006;t)\324\307@)\332f\003\001\000[\000\246\307b " | |
| "l\177\010\000\000\000\000\257\354\334\244\243\243\243\243\243" | |
| "\243\243\243\332\332\332\243\247\243\243\243\243\243\243\243" | |
| "\243\243\243\243\243\243\243\243\243\243\315\243\243\243\243" | |
| "\243\243\243\243\243\243\243\247\250\247{" | |
| "\253\236\241\243\000\000\244\302,\230\230\230\230-" | |
| "1\002x\361xx\221\221\000\000\000\346\000m\2544\024\000\030" | |
| "\000\000\000\000\000\000\367\267AAAAAAAA\264\366|\304\264!" | |
| "\000\005\355\000\000\000\367\372\236#" | |
| "\374\000\230\300\300\300\300\300\300\300\300\300\300\300\300" | |
| "\300\300\300\2304\000\000\000", | |
| 487), | |
| .result = | |
| ExtractorResult{ | |
| .manifest_store = std::string("\xF2\x01\x00\x00", 4), | |
| .asset_byte_info = {.manifest_store_location = | |
| {.offset = 111, .length = 4}}}, | |
| }}), | |
| [](const testing::TestParamInfo<ExtractorTest::ParamType>& info) { | |
| return info.param.name; | |
| }); | |
| TEST_P(ExtractorTest, ExtractManifestStore) { | |
| const TestCase& test_case = GetParam(); | |
| const absl::StatusOr<std::string>& contents = test_case.contents; | |
| ABSL_ASSERT_OK(contents); | |
| riegeli::StringReader<> input(*contents); | |
| absl::StatusOr<std::string> result = | |
| TiffExtractor().ExtractManifestStore(input); | |
| const auto& expected_result = test_case.result; | |
| if (expected_result.ok()) { | |
| ABSL_ASSERT_OK(result); | |
| EXPECT_THAT(*result, Eq(expected_result->manifest_store)); | |
| } else { | |
| EXPECT_THAT(result, | |
| StatusIs(expected_result.status().code(), | |
| HasSubstr(expected_result.status().message()))); | |
| } | |
| } | |
| TEST_P(ExtractorTest, ExtractManifestStoreLocation) { | |
| const TestCase& test_case = GetParam(); | |
| const absl::StatusOr<std::string>& contents = test_case.contents; | |
| ABSL_ASSERT_OK(contents); | |
| riegeli::StringReader<> input(*contents); | |
| absl::StatusOr<std::optional<ByteRange>> result = | |
| TiffExtractor().ExtractManifestStoreLocation(input, {}); | |
| const auto& expected_result = test_case.result; | |
| if (expected_result.ok()) { | |
| ABSL_ASSERT_OK(result); | |
| EXPECT_THAT(*result, | |
| Eq(expected_result->asset_byte_info.manifest_store_location)); | |
| } else { | |
| EXPECT_THAT(result, | |
| StatusIs(expected_result.status().code(), | |
| HasSubstr(expected_result.status().message()))); | |
| } | |
| } | |
| TEST_P(ExtractorTest, ExtractBoxes) { | |
| const absl::StatusOr<std::string>& contents = GetParam().contents; | |
| ABSL_ASSERT_OK(contents); | |
| riegeli::StringReader<> input(*contents); | |
| absl::StatusOr<std::vector<AssetBox>> result = | |
| TiffExtractor().ExtractBoxes(input, {}); | |
| EXPECT_THAT( | |
| result, | |
| StatusIs(absl::StatusCode::kUnimplemented, | |
| HasSubstr("Extracting boxes is not supported for TIFF files."))); | |
| } | |
| TEST(OffsetTiffExtractorTest, FailsWhenEndOffsetIsSmallerThanAsset) { | |
| std::string contents = []() { | |
| // Adding a prefix to the contents, need to offset all the offsets by 7. | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(); | |
| riegeli::StringReader<> input(contents); | |
| EXPECT_THAT( | |
| TiffExtractor().ExtractManifestStoreLocation(input, {.end_offset = 20}), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("TiffExtractor::ExtractManifestStoreLocation only " | |
| "supports operations over the entire file."))); | |
| } | |
| TEST(OffsetTiffExtractorTest, | |
| FailsWhenEndOffsetEqualToAssetSizeAndCurrentOffsetIsNonZero) { | |
| std::string contents = []() { | |
| // Adding a prefix to the contents, need to offset all the offsets by 7. | |
| std::vector<uint8_t> data = {}; | |
| // [0, 8) Tiff Header | |
| WriteTiffHeader(27, data); | |
| // [8, 27) Manifest Store | |
| WriteString("test_manifest_store", data); | |
| // [27, 45) IFD | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 19, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(); | |
| riegeli::StringReader<> input(contents); | |
| ASSERT_TRUE(input.Seek(7)); | |
| EXPECT_THAT( | |
| TiffExtractor().ExtractManifestStoreLocation( | |
| input, {.end_offset = static_cast<int64_t>(contents.size())}), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("TiffExtractor::ExtractManifestStoreLocation only " | |
| "supports operations over the entire file."))); | |
| } | |
| TEST(TiffExtractorTest, UnboundedManifestStoreAllocation) { | |
| std::string contents = []() { | |
| std::vector<uint8_t> data = {}; | |
| WriteTiffHeader(13, data); | |
| WriteString("short", data); | |
| WriteIfd({{.tag = kTiffTagC2pa, | |
| .type = kTiffTypeUndefined, | |
| .count = 11 * 1024 * 1024, | |
| .value = 8}}, | |
| 0, data); | |
| return std::string(data.begin(), data.end()); | |
| }(); | |
| riegeli::StringReader<> input(contents); | |
| EXPECT_THAT(TiffExtractor().ExtractManifestStore(input), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("Manifest store is too large"))); | |
| } | |
| } // namespace | |
| } // namespace credentio |