blob: 500f9473b071e05ee537b0b2d588f5356187cfb2 [file]
// 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/pdf/extractor.h"
#include <cstddef>
#include <cstdio>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/absl_check.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/cord.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "constants/labels.h"
#include "formats/byte_range.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "jumbf/box.h"
#include "jumbf/box_builder.h"
#include "jumbf/constants.h"
#include "jumbf/encode_params.h"
#include "jumbf/parse.h"
#include "riegeli/bytes/string_reader.h"
namespace credentio {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::credentio::kManifestStoreLabel;
using ::credentio::kManifestStoreUuid;
using ::credentio::kStandardManifestUuid;
using ::jumbf::DescriptionBoxOptions;
using ::jumbf::kCborBoxType;
using ::jumbf::SuperBoxBuilder;
using ::testing::HasSubstr;
using ::testing::Optional;
constexpr absl::string_view kTestPdfContent =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNoC2paSubtype =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000155 00000 n \n"
"0000000238 00000 n \n"
"0000000298 00000 n \n"
"0000000338 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"397\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNoEmbeddedFilesRef =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(not_a_c2pa_manifest.txt) 1 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNamesNotDict =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names [1 0 R]>>\n"
"endobj\n"
"xref\n"
"0 5\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"trailer\n"
"<</Root 4 0 R>>\n"
"startxref\n"
"328\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentEmbeddedFilesDirect =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names <</EmbeddedFiles <</Names "
"[(content_credential.c2pa) 3 0 R]>> >> >>\n"
"endobj\n"
"xref\n"
"0 5\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"trailer\n"
"<</Root 4 0 R>>\n"
"startxref\n"
"386\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNoAfRelationship =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentDifferentAfRelationship =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /Other /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNonIndirectAf =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [123 ] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentInvalidEmbeddedFilesType =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 12345>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNamesDictNotDict =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"12345 \n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNoEmbeddedFiles =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</OtherFiles___ 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 5 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
constexpr absl::string_view kTestPdfContentNamesObjectNotFound =
"%PDF-1.7\n"
"%%%%%\n"
"1 0 obj\n"
"<</Length 11>>\n"
"stream\n"
"test_stream\n"
"endstream\n"
"endobj\n"
"2 0 obj\n"
"<</Length 33 /Subtype /application#2fc2pa>>\n"
"stream\n"
"test_c2pa_manifest_raw_bytestream\n"
"endstream\n"
"endobj\n"
"3 0 obj\n"
"<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest /Type /FileSpec>>\n"
"endobj\n"
"4 0 obj\n"
"<</Names [(content_credential.c2pa) 3 0 R]>>\n"
"endobj\n"
"5 0 obj\n"
"<</EmbeddedFiles 4 0 R>>\n"
"endobj\n"
"6 0 obj\n"
"<</AF [3 0 R] /Type /Catalog /Names 9 0 R>>\n"
"endobj\n"
"xref\n"
"0 7\n"
"0000000000 65535 f \n"
"0000000015 00000 n \n"
"0000000074 00000 n \n"
"0000000184 00000 n \n"
"0000000267 00000 n \n"
"0000000327 00000 n \n"
"0000000367 00000 n \n"
"trailer\n"
"<</Root 6 0 R>>\n"
"startxref\n"
"426\n"
"%%EOF";
TEST(PdfExtractorTest, ExtractManifestStoreV22EmbeddedFilesDirect) {
riegeli::StringReader<> input(kTestPdfContentEmbeddedFilesDirect);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
IsOkAndHolds("test_c2pa_manifest_raw_bytestream"));
}
TEST(PdfExtractorTest, ExtractManifestStoreV22) {
riegeli::StringReader<> input(kTestPdfContent);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
IsOkAndHolds("test_c2pa_manifest_raw_bytestream"));
}
TEST(PdfExtractorTest, ExtractManifestStoreNoSubtype) {
riegeli::StringReader<> input(kTestPdfContentNoC2paSubtype);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
IsOkAndHolds("test_c2pa_manifest_raw_bytestream"));
}
TEST(PdfExtractorTest,
ExtractManifestStoreThrowNotFoundErrorIfNoEmbeddedFilesRef) {
riegeli::StringReader<> input(kTestPdfContentNoEmbeddedFilesRef);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound));
}
TEST(PdfExtractorTest, ExtractManifestStoreLocation) {
riegeli::StringReader<> input(kTestPdfContent);
PdfExtractor extractor;
EXPECT_THAT(
extractor.ExtractManifestStoreLocation(input, {.requires_c2pa = true}),
IsOkAndHolds(Optional(ByteRange{.offset = 74, .length = 109})));
}
TEST(PdfExtractorTest, ExtractManifestStoreLocationNoSubtype) {
riegeli::StringReader<> input(kTestPdfContentNoC2paSubtype);
PdfExtractor extractor;
EXPECT_THAT(
extractor.ExtractManifestStoreLocation(input, {.requires_c2pa = true}),
IsOkAndHolds(Optional(ByteRange{.offset = 74, .length = 80})));
}
TEST(PdfExtractorTest, ExtractManifestStoreFailsWithInvalidNamesType) {
riegeli::StringReader<> input(kTestPdfContentNamesNotDict);
PdfExtractor extractor;
EXPECT_THAT(
extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kInvalidArgument,
::testing::AllOf(HasSubstr("Unexpected object type found."),
HasSubstr("credentio::Object::"))));
}
std::string MakeValidPdfWithMultipleManifests(absl::string_view manifest1,
absl::string_view manifest2) {
std::vector<std::string> objs;
objs.push_back(
"1 0 obj\n<</Length 11>>\nstream\ntest_stream\nendstream\nendobj\n");
objs.push_back(absl::StrCat("2 0 obj\n<</Length ", manifest1.size(),
" /Subtype /application#2fc2pa>>\nstream\n",
manifest1, "\nendstream\nendobj\n"));
objs.push_back(
"3 0 obj\n<</EF <</F 2 0 R>> /AFRelationship /C2PA_Manifest "
"/Type /FileSpec>>\nendobj\n");
objs.push_back(absl::StrCat("4 0 obj\n<</Length ", manifest2.size(),
" /Subtype /application#2fc2pa>>\nstream\n",
manifest2, "\nendstream\nendobj\n"));
objs.push_back(
"5 0 obj\n<</EF <</F 4 0 R>> /AFRelationship /C2PA_Manifest "
"/Type /FileSpec>>\nendobj\n");
objs.push_back(
"6 0 obj\n<</Names [(manifest1.c2pa) 3 0 R (manifest2.c2pa) 5 0 "
"R]>>\nendobj\n");
objs.push_back("7 0 obj\n<</EmbeddedFiles 6 0 R>>\nendobj\n");
objs.push_back(
"8 0 obj\n<</AF [3 0 R 5 0 R] /Type /Catalog /Names 7 0 R>>\nendobj\n");
std::string pdf = "%PDF-1.7\n";
std::vector<size_t> offsets;
for (const auto& obj : objs) {
offsets.push_back(pdf.size());
pdf += obj;
}
size_t xref_pos = pdf.size();
pdf += "xref\n0 9\n0000000000 65535 f \n";
for (size_t offset : offsets) {
char buf[32];
snprintf(buf, sizeof(buf), "%010zu 00000 n \n", offset);
pdf += buf;
}
absl::StrAppend(&pdf, "trailer\n<</Root 8 0 R>>\nstartxref\n", xref_pos,
"\n%%EOF");
return pdf;
}
TEST(PdfExtractorTest, ExtractManifestStoreSucceedsWithMultipleManifestsInAf) {
SuperBoxBuilder manifest_builder1(
kStandardManifestUuid,
DescriptionBoxOptions{.requestable_iff_label = true,
.label = absl::Cord("manifest_1")});
ABSL_CHECK_OK(
manifest_builder1.AddContent(kCborBoxType, absl::Cord("data_1")));
absl::StatusOr<jumbf::BuiltSuperBox> manifest_box1 =
std::move(manifest_builder1).Finalize();
ABSL_ASSERT_OK(manifest_box1);
SuperBoxBuilder store_builder1(
kManifestStoreUuid,
DescriptionBoxOptions{.requestable_iff_label = true,
.label = absl::Cord(kManifestStoreLabel)});
ABSL_CHECK_OK(store_builder1.AddChild(*std::move(manifest_box1)));
absl::StatusOr<jumbf::BuiltSuperBox> store_box1 =
std::move(store_builder1).Finalize();
ABSL_ASSERT_OK(store_box1);
std::string store1 = std::string(std::move(*store_box1).AsCord());
SuperBoxBuilder manifest_builder2(
kStandardManifestUuid,
DescriptionBoxOptions{.requestable_iff_label = true,
.label = absl::Cord("manifest_2")});
ABSL_CHECK_OK(
manifest_builder2.AddContent(kCborBoxType, absl::Cord("data_2")));
absl::StatusOr<jumbf::BuiltSuperBox> manifest_box2 =
std::move(manifest_builder2).Finalize();
ABSL_ASSERT_OK(manifest_box2);
SuperBoxBuilder store_builder2(
kManifestStoreUuid,
DescriptionBoxOptions{.requestable_iff_label = true,
.label = absl::Cord(kManifestStoreLabel)});
ABSL_CHECK_OK(store_builder2.AddChild(*std::move(manifest_box2)));
absl::StatusOr<jumbf::BuiltSuperBox> store_box2 =
std::move(store_builder2).Finalize();
ABSL_ASSERT_OK(store_box2);
std::string store2 = std::string(std::move(*store_box2).AsCord());
std::string pdf = MakeValidPdfWithMultipleManifests(store1, store2);
riegeli::StringReader<> input(pdf);
PdfExtractor extractor;
absl::StatusOr<std::string> merged = extractor.ExtractManifestStore(input);
ABSL_ASSERT_OK(merged);
absl::string_view view = *merged;
absl::StatusOr<jumbf::SuperBox> superbox =
jumbf::ConsumeSuperBox(&view, /*recursion_limit=*/9);
ABSL_ASSERT_OK(superbox);
ASSERT_EQ(superbox->contents.size(), 2);
EXPECT_EQ(superbox->contents.back().Get<jumbf::SuperBox>().description.label,
"manifest_2");
// Verify location points to the active (second) manifest store stream.
absl::StatusOr<std::optional<ByteRange>> manifest_store_location =
extractor.ExtractManifestStoreLocation(input, {.requires_c2pa = true});
ABSL_ASSERT_OK(manifest_store_location);
ASSERT_TRUE(manifest_store_location->has_value());
absl::string_view active_stream = absl::string_view(pdf).substr(
(*manifest_store_location)->offset, (*manifest_store_location)->length);
EXPECT_THAT(active_stream, HasSubstr("4 0 obj"));
EXPECT_THAT(active_stream, HasSubstr(store2));
}
TEST(PdfExtractorTest, ExtractBoxesUnimplemented) {
riegeli::StringReader<> input("test_content");
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractBoxes(input, {.requires_c2pa = true}),
StatusIs(absl::StatusCode::kUnimplemented));
}
TEST(PdfExtractorTest,
ExtractManifestStoreThrowsNotFoundErrorIfNoAfRelationship) {
riegeli::StringReader<> input(kTestPdfContentNoAfRelationship);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("No manifest store found.")));
}
TEST(PdfExtractorTest,
ExtractManifestStoreThrowsNotFoundErrorIfDifferentAfRelationship) {
riegeli::StringReader<> input(kTestPdfContentDifferentAfRelationship);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("No manifest store found.")));
}
TEST(PdfExtractorTest, ExtractManifestStoreThrowsNotFoundErrorIfNonIndirectAf) {
riegeli::StringReader<> input(kTestPdfContentNonIndirectAf);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("No manifest store found.")));
}
TEST(PdfExtractorTest, ExtractManifestStoreFailsWithInvalidEmbeddedFilesType) {
riegeli::StringReader<> input(kTestPdfContentInvalidEmbeddedFilesType);
PdfExtractor extractor;
EXPECT_THAT(
extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kInvalidArgument,
::testing::AllOf(HasSubstr("Unexpected object type found."),
HasSubstr("credentio::Object::"))));
}
TEST(PdfExtractorTest, ExtractManifestStoreFailsIfNamesObjectNotDict) {
riegeli::StringReader<> input(kTestPdfContentNamesDictNotDict);
PdfExtractor extractor;
EXPECT_THAT(
extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Object is not a credentio::Object::Dictionary")));
}
TEST(PdfExtractorTest,
ExtractManifestStoreThrowsNotFoundErrorIfNoEmbeddedFiles) {
riegeli::StringReader<> input(kTestPdfContentNoEmbeddedFiles);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("No '/EmbeddedFiles' dictionary found.")));
}
TEST(PdfExtractorTest, ExtractManifestStoreFailsIfNamesObjectNotFound) {
riegeli::StringReader<> input(kTestPdfContentNamesObjectNotFound);
PdfExtractor extractor;
EXPECT_THAT(extractor.ExtractManifestStore(input),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("No offset found for object number: 9")));
}
} // namespace
} // namespace credentio