| // 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::IsOk; |
| using ::absl_testing::StatusIs; |
| using ::testing::Eq; |
| |
| #ifndef ASSERT_OK_AND_ASSIGN |
| #define ASSERT_OK_AND_ASSIGN_CONCAT2(x, y) x##y |
| #define ASSERT_OK_AND_ASSIGN_CONCAT(x, y) ASSERT_OK_AND_ASSIGN_CONCAT2(x, y) |
| |
| #define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ |
| ASSERT_OK_AND_ASSIGN_IMPL(lhs, rexpr, __COUNTER__) |
| |
| #define ASSERT_OK_AND_ASSIGN_IMPL(lhs, rexpr, id) \ |
| auto ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id) = (rexpr); \ |
| ASSERT_THAT(ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id), \ |
| ::absl_testing::IsOk()); \ |
| lhs = std::move(*ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id)) |
| #endif |
| |
| 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 { ASSERT_THAT(GetParam().contents.status(), IsOk()); } |
| }; |
| |
| 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(); |
| ASSERT_OK_AND_ASSIGN(std::string contents, test_case.contents); |
| riegeli::StringReader<> input(contents); |
| absl::StatusOr<std::string> result = |
| TiffExtractor().ExtractManifestStore(input); |
| |
| const auto& expected_result = test_case.result; |
| if (expected_result.ok()) { |
| ASSERT_THAT(result, IsOk()); |
| 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(); |
| ASSERT_OK_AND_ASSIGN(std::string contents, test_case.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()) { |
| ASSERT_THAT(result, IsOk()); |
| 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) { |
| ASSERT_OK_AND_ASSIGN(std::string contents, GetParam().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 |