blob: 97b21b813aed8aa15b57c83e8f8bb630d8c23db6 [file] [edit]
// 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/object_reader.h"
#include <optional>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "formats/pdf/objects.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "riegeli/bytes/string_reader.h"
namespace credentio {
namespace {
using ::absl_testing::IsOk;
using ::absl_testing::StatusIs;
using ::testing::HasSubstr;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using ::testing::VariantWith;
// This is used to pad the end of the test input because an object should never
// be terminated by EOF per PDF spec.
constexpr absl::string_view kContentPadding = " %%EOF";
absl::AnyInvocable<
absl::StatusOr<IndirectObject>(const Object::IndirectReference&)>
MockResolver(const IndirectObject& obj = IndirectObject()) {
return [obj](const Object::IndirectReference& ref) { return obj; };
}
TEST(ReadObjectTest, ReadValidBoolean) {
std::string content = absl::StrCat("true false", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Boolean>(Object::Boolean{.value = true}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Boolean>(Object::Boolean{.value = false}));
}
TEST(ReadObjectTest, ReadInvalidBooleanFailure) {
std::string content = absl::StrCat("fake", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(
ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Failed to parse boolean. Invalid value: fake")));
}
TEST(ReadObjectTest, ReadValidInteger) {
std::string content =
absl::StrCat("123 43445 +17 -98 0", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 123}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 43445}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 17}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = -98}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 0}));
}
TEST(ReadObjectTest, ReadInvalidIntegerFailure) {
std::string content = absl::StrCat("-123+123", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(
ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Failed to parse integer. Invalid value: -123+123")));
}
TEST(ReadObjectTest, ReadDelimiterTerminatedInteger) {
std::string content = absl::StrCat("123(", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 123}));
}
TEST(ReadObjectTest, ReadWhiteSpaceTerminatedInteger) {
std::string content = absl::StrCat("123\t", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 123}));
}
TEST(ReadObjectTest, ReadRealNumberRawValue) {
std::string content =
absl::StrCat("34.5 -3.62 +123.6 4. -.002 0.0", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "34.5"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "-3.62"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "+123.6"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "4."}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "-.002"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>(
Object::RealNumber{.raw_value = "0.0"}));
}
TEST(ReadObjectTest, ReadLiteralStringRawValue) {
std::string content = R"((This is a string)
(Strings can contain newlines
and such.)
(Strings can contain balanced parentheses ()
and special characters ( * ! & } ^ %and so on) .)
(The following is an empty string .)
()
(It has zero (0) length.))";
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::LiteralString>(
Object::LiteralString{.raw_value = R"(This is a string)"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::LiteralString>(Object::LiteralString{
.raw_value = R"(Strings can contain newlines
and such.)"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::LiteralString>(Object::LiteralString{
.raw_value = R"(Strings can contain balanced parentheses ()
and special characters ( * ! & } ^ %and so on) .)"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::LiteralString>(Object::LiteralString{
.raw_value = R"(The following is an empty string .)"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::LiteralString>(
Object::LiteralString{.raw_value = R"()"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::LiteralString>(Object::LiteralString{
.raw_value = R"(It has zero (0) length.)"}));
}
TEST(ReadObjectTest, ReadHexadecimalStringRawValue) {
std::string content = "<4E6F762073686D6F7A206B6120706F702E> <901FA3> <901FA>";
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::HexadecimalString>(Object::HexadecimalString{
.raw_value = "4E6F762073686D6F7A206B6120706F702E"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::HexadecimalString>(
Object::HexadecimalString{.raw_value = "901FA3"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::HexadecimalString>(
Object::HexadecimalString{.raw_value = "901FA"}));
}
TEST(ReadObjectTest, ReadName) {
std::string content = absl::StrCat(
"/Name1 /ASomewhatLongerName /A;Name_With-Various***Characters? /1.2 "
"/$$ "
"/@pattern /.notdef /Lime#20Green /paired#28#29parentheses "
"/The_Key_of_F#23_Minor /A#42",
kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "Name1"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::Name>(
Object::Name{.value = "ASomewhatLongerName"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::Name>(Object::Name{
.value = "A;Name_With-Various***Characters?"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "1.2"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "$$"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "@pattern"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = ".notdef"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "Lime Green"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::Name>(
Object::Name{.value = "paired()parentheses"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::Name>(
Object::Name{.value = "The_Key_of_F#_Minor"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "AB"}));
}
TEST(ReadObjectTest, ReadDelimiterTerminatedName) {
std::string content = absl::StrCat("/Name]", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "Name"}));
}
TEST(ReadObjectTest, ReadWhiteSpaceTerminatedName) {
std::string content = absl::StrCat("/Name\n", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "Name"}));
}
TEST(ReadObjectTest, ReadArray) {
std::string content =
absl::StrCat("[549 3.14 false (Ralph) /SomeName]", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(
obj.value,
VariantWith<Object::Array>(Object::Array{
.objects = {
Object{.value = Object::Integer{.value = 549}},
Object{.value = Object::RealNumber{.raw_value = "3.14"}},
Object{.value = Object::Boolean{.value = false}},
Object{.value = Object::LiteralString{.raw_value = "Ralph"}},
Object{.value = Object::Name{.value = "SomeName"}}}}));
}
TEST(ReadObjectTest, ReadArrayNested) {
std::string content = absl::StrCat("[549 [3.14]]", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(
obj.value,
VariantWith<Object::Array>(Object::Array{
.objects = {
Object{.value = Object::Integer{.value = 549}},
Object{
.value = Object::Array{.objects = {Object{
.value =
Object::RealNumber{
.raw_value = "3.14"}}}},
}}}));
}
TEST(ReadObjectTest, ReadArrayWithComments) {
std::string content =
absl::StrCat("[549 %comment \n 3.14 % comment \n]", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(
obj.value,
VariantWith<Object::Array>(Object::Array{
.objects = {
Object{.value = Object::Integer{.value = 549}},
Object{.value = Object::RealNumber{.raw_value = "3.14"}}}}));
}
TEST(ReadObjectTest, ReadDictionary) {
std::string content =
absl::StrCat("<</Type /Example /Subtype /Table>>", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
Object expected_obj;
Object::Dictionary& dict = expected_obj.value.emplace<Object::Dictionary>();
dict.entries = {{"Type", Object{.value = Object::Name{.value = "Example"}}},
{"Subtype", Object{.value = Object::Name{.value = "Table"}}}};
EXPECT_EQ(obj, expected_obj);
}
TEST(ReadObjectTest, ReadDictionaryWithComments) {
std::string content = absl::StrCat(
"<</Type /Example %comment \n /Subtype /Table>>", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
Object expected_obj;
Object::Dictionary& dict = expected_obj.value.emplace<Object::Dictionary>();
dict.entries = {{"Type", Object{.value = Object::Name{.value = "Example"}}},
{"Subtype", Object{.value = Object::Name{.value = "Table"}}}};
EXPECT_EQ(obj, expected_obj);
}
TEST(ReadObjectTest, CommentsTreatedAsDelimiters) {
std::string content = absl::StrCat(R"(/abc%comment (/%) blah blah blah
123)",
kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "abc"}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 123}));
}
TEST(ReadObjectTest, ReadDictionaryEmpty) {
std::string content = absl::StrCat("<<>>", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
Object expected_obj = Object{.value = Object::Dictionary{}};
EXPECT_EQ(obj, expected_obj);
}
TEST(ReadObjectTest, ReadDictionaryNested) {
std::string content =
absl::StrCat("<</Subdictionary << /Item1 0.4 >> >>", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
Object expected_obj;
Object::Dictionary& dict = expected_obj.value.emplace<Object::Dictionary>();
dict.entries = {
{"Subdictionary",
Object{.value = Object::Dictionary{
.entries = {{"Item1", Object{.value = Object::RealNumber{
.raw_value = "0.4"}}}}}}}};
EXPECT_EQ(obj, expected_obj);
}
TEST(ReadObjectTest, ReadValidNull) {
std::string content = "null";
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value, VariantWith<Object::Null>(Object::Null{}));
}
TEST(ReadObjectTest, ReadInvalidNullFailure) {
std::string content = "nULL";
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Invalid value: nULL")));
}
TEST(ReadObjectTest, ReadIndirectReference) {
std::string content = absl::StrCat("123 321 R", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::IndirectReference>(Object::IndirectReference{
.object_number = 123, .generation_number = 321}));
}
TEST(ReadObjectTest, IndirectReferencePatternMatchesBeginningOfString) {
std::string content = absl::StrCat("123 /Root 1 2 R", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, /*obj_offset=*/0, obj), IsOk());
// "123 /Root 1 2 R ...." should not be matched as an indirect reference, the
// preceding integer should be read instead.
EXPECT_THAT(obj.value,
VariantWith<Object::Integer>(Object::Integer{.value = 123}));
ASSERT_THAT(ReadObject(input, /*obj_offset=*/10, obj), IsOk());
// "1 2 R ..." should be matched as an indirect reference.
EXPECT_THAT(obj.value,
VariantWith<Object::IndirectReference>(Object::IndirectReference{
.object_number = 1, .generation_number = 2}));
}
TEST(ReadObjectTest, ReadIndirectReferenceAdvanceInputStreamPositionCorrectly) {
std::string content = absl::StrCat("123 321 R /Name", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::IndirectReference>(Object::IndirectReference{
.object_number = 123, .generation_number = 321}));
ASSERT_THAT(ReadObject(input, input.pos(), obj), IsOk());
EXPECT_THAT(obj.value,
VariantWith<Object::Name>(Object::Name{.value = "Name"}));
}
TEST(ReadObjectTest, ReadStreamNotAllowed) {
std::string content = absl::StrCat("stream endstream", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(
ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Stream must reside in an indirect object. Call "
"ReadIndirectObject() instead.")));
}
TEST(ReadObjectTest, ReadMalformedStreamKeywordFailure) {
std::string content = absl::StrCat("steam endstream", kContentPadding);
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(
ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed keyword. Expected 'stream' but got")));
}
TEST(ReadIndirectObjectTest, ReadIndirectObject) {
std::string content = R"(12 0 obj
(Brillig)
endobj)";
riegeli::StringReader<> input(content);
IndirectObject obj;
ASSERT_THAT(ReadIndirectObject(input, input.pos(), MockResolver(), obj),
IsOk());
EXPECT_EQ(obj.object_number, 12);
EXPECT_EQ(obj.generation_number, 0);
EXPECT_THAT(obj.object.value,
VariantWith<Object::LiteralString>(
Object::LiteralString{.raw_value = "Brillig"}));
}
TEST(ReadIndirectObjectTest, ReadInvalidObjectNumberFailure) {
std::string content = "1+2 0 obj <A0E2FA> endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Failed to parse object number. Invalid value: 1+2")));
}
TEST(ReadIndirectObjectTest, ReadInvalidGenerationNumberFailure) {
std::string content = "12 0.0 obj <A0E2FA> endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Failed to parse generation number. Invalid value: 0.0")));
}
TEST(ReadIndirectObjectTest, ReadMalformedKeywordFailure) {
std::string content = "12 0 R <A0E2FA> endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed keyword. Expected 'obj' but got")));
}
TEST(ReadIndirectObjectTest, ReadMalformedKeywordFailure2) {
std::string content = "12 0 obj <A0E2FA> objend";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed keyword. Expected 'endobj' but got")));
}
TEST(ReadIndirectObjectTest, ReadStreamWithCrlfMarker) {
std::string content =
"1 2 obj <</Length 10>> stream\r\n1234567890\r\nendstream endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
ASSERT_THAT(ReadIndirectObject(input, input.pos(), MockResolver(), obj),
IsOk());
EXPECT_EQ(obj.object_number, 1);
EXPECT_EQ(obj.generation_number, 2);
EXPECT_THAT(obj.stream_dictionary.value().entries,
UnorderedElementsAre(Pair(
"Length", Object{.value = Object::Integer{.value = 10}})));
EXPECT_THAT(obj.object.value, VariantWith<Object::Stream>(
Object::Stream{.raw_value = "1234567890"}));
}
TEST(ReadIndirectObjectTest, ReadStreamWithLfMarker) {
std::string content =
"1 2 obj <</Length 10>> stream\n1234567890\nendstream endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
ASSERT_THAT(ReadIndirectObject(input, input.pos(), MockResolver(), obj),
IsOk());
EXPECT_EQ(obj.object_number, 1);
EXPECT_EQ(obj.generation_number, 2);
EXPECT_THAT(obj.stream_dictionary.value().entries,
UnorderedElementsAre(Pair(
"Length", Object{.value = Object::Integer{.value = 10}})));
EXPECT_THAT(obj.object.value, VariantWith<Object::Stream>(
Object::Stream{.raw_value = "1234567890"}));
}
TEST(ReadIndirectObjectTest, ReadStreamWithCrMarkerFailure) {
std::string content =
"1 2 obj <</Length 10>> stream\r1234567890\rendstream endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed stream. Expected '\r\n' or '\n' but got")));
}
TEST(ReadIndirectObjectTest, ReadStreamUnspecifiedLengthFailure) {
std::string content = "1 2 obj <<>> stream\r\n1234567890\r\nendstream endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(
absl::StatusCode::kDataLoss,
HasSubstr("Stream dictionary does not contain a 'Length' entry.")));
}
TEST(ReadIndirectObjectTest, ReadStreamByChunks) {
std::string content =
"1 2 obj <</Length 50>> "
"stream\r\n12345678901234567890123456789012345678901234567890\r\nendstrea"
"m endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
ASSERT_THAT(ReadIndirectObject(input, input.pos(), MockResolver(), obj),
IsOk());
EXPECT_EQ(obj.object_number, 1);
EXPECT_EQ(obj.generation_number, 2);
EXPECT_THAT(obj.stream_dictionary.value().entries,
UnorderedElementsAre(Pair(
"Length", Object{.value = Object::Integer{.value = 50}})));
EXPECT_THAT(
obj.object.value,
VariantWith<Object::Stream>(Object::Stream{
.raw_value = "12345678901234567890123456789012345678901234567890"}));
}
TEST(ReadIndirectObjectTest, ReadStreamOversizedLengthFails) {
std::string content =
"1 2 obj <</Length 11000000>> "
"stream\r\n123\r\nendstream endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Stream length exceeds maximum allowed size")));
}
TEST(ReadIndirectObjectTest, IndirectReferenceResolved) {
std::string content =
"1 2 obj <</Length 3 4 R>> stream\r\n1234567890\r\nendstream endobj";
riegeli::StringReader<> input(content);
auto mock_resolver =
MockResolver({.object_number = 3,
.generation_number = 4,
.object = Object{.value = Object::Integer{.value = 10}}});
IndirectObject obj;
ASSERT_THAT(
ReadIndirectObject(input, input.pos(), std::move(mock_resolver), obj),
IsOk());
EXPECT_EQ(obj.object_number, 1);
EXPECT_EQ(obj.generation_number, 2);
EXPECT_THAT(
obj.stream_dictionary.value().entries,
UnorderedElementsAre(Pair(
"Length", Object{.value = Object::IndirectReference{
.object_number = 3, .generation_number = 4}})));
EXPECT_THAT(obj.object.value, VariantWith<Object::Stream>(
Object::Stream{.raw_value = "1234567890"}));
}
TEST(ReadIndirectObjectTest, ReadStreamMalformedEndstreamKeywordFailure) {
std::string content =
"1 2 obj <</Length 10>> stream\r\n1234567890\r\nendstram endobj";
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(
ReadIndirectObject(input, input.pos(), MockResolver(), obj),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Malformed keyword. Expected 'endstream' but got 'endstram '")));
}
TEST(SkipOverWhiteSpacesTest, SkipsOverLeadingWhiteSpaces) {
std::string content =
absl::StrCat("\x09\x0a\x0c\x0d\x20", "abc", kContentPadding);
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input), IsOk());
EXPECT_EQ(input.pos(), 5);
}
TEST(SkipOverWhiteSpacesTest, NoLeadingWhiteSpaces) {
std::string content = absl::StrCat("abc", kContentPadding);
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input), IsOk());
EXPECT_EQ(input.pos(), 0);
}
TEST(SkipOverWhiteSpacesTest, OnlyWhiteSpacesFailure) {
std::string content = "\t\n\r ";
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input),
StatusIs(absl::StatusCode::kDataLoss));
}
TEST(SkipOverWhiteSpacesTest, EmptyInputFailure) {
std::string content = "";
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input),
StatusIs(absl::StatusCode::kDataLoss));
}
TEST(SkipOverWhiteSpacesTest, SkipsOverComments) {
std::string content = "% comment\nabc";
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input), IsOk());
EXPECT_EQ(input.pos(), 10);
}
TEST(SkipOverWhiteSpacesTest, SkipsOverMultipleComments) {
std::string content = "% comment 1\n% comment 2\nabc";
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input), IsOk());
EXPECT_EQ(input.pos(), 24);
}
TEST(SkipOverWhiteSpacesTest, SkipsOverCommentsMixedWithWhitespaces) {
std::string content = " % comment \n abc";
riegeli::StringReader<> input(content);
EXPECT_THAT(SkipOverWhiteSpaces(input), IsOk());
EXPECT_EQ(input.pos(), 15);
}
TEST(ReadObjectTest, DeeplyNestedArrayFails) {
std::string content(102, '[');
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Max nesting depth exceeded")));
}
TEST(ReadObjectTest, DeeplyNestedDictionaryFails) {
std::string content;
for (int i = 0; i < 102; ++i) {
content += "<</K ";
}
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Max nesting depth exceeded")));
}
TEST(ReadObjectTest, UnboundedNumberTokenExceedsLimitFails) {
std::string content = std::string(130, '1');
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
TEST(ReadObjectTest, UnboundedLiteralStringExceedsLimitFails) {
std::string content = "(" + std::string(1024 * 1024 * 10 + 5, 'a');
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
TEST(ReadObjectTest, UnboundedHexadecimalStringExceedsLimitFails) {
std::string content = "<" + std::string(1024 * 1024 * 10 + 5, 'a');
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
TEST(ReadObjectTest, UnboundedNameTokenExceedsLimitFails) {
std::string content = "/" + std::string(4100, 'a');
riegeli::StringReader<> input(content);
Object obj;
EXPECT_THAT(ReadObject(input, input.pos(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
TEST(ReadIndirectObjectTest, UnboundedObjectNumberExceedsLimitFails) {
std::string content = std::string(130, '1');
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(ReadIndirectObject(input, 0, MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
TEST(ReadIndirectObjectTest, UnboundedGenerationNumberExceedsLimitFails) {
std::string content = "1 " + std::string(130, '1');
riegeli::StringReader<> input(content);
IndirectObject obj;
EXPECT_THAT(ReadIndirectObject(input, 0, MockResolver(), obj),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("exceeds maximum allowed length")));
}
} // namespace
} // namespace credentio