| // 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Boolean>(Object::Boolean{.value = true})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Integer>(Object::Integer{.value = 123})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Integer>(Object::Integer{.value = 43445})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Integer>(Object::Integer{.value = 17})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Integer>(Object::Integer{.value = -98})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>( | |
| Object::RealNumber{.raw_value = "34.5"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>( | |
| Object::RealNumber{.raw_value = "-3.62"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>( | |
| Object::RealNumber{.raw_value = "+123.6"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>( | |
| Object::RealNumber{.raw_value = "4."})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::RealNumber>( | |
| Object::RealNumber{.raw_value = "-.002"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::LiteralString>( | |
| Object::LiteralString{.raw_value = R"(This is a string)"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::LiteralString>(Object::LiteralString{ | |
| .raw_value = R"(Strings can contain newlines | |
| and such.)"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::LiteralString>(Object::LiteralString{ | |
| .raw_value = R"(Strings can contain balanced parentheses () | |
| and special characters ( * ! & } ^ %and so on) .)"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::LiteralString>(Object::LiteralString{ | |
| .raw_value = R"(The following is an empty string .)"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::LiteralString>( | |
| Object::LiteralString{.raw_value = R"()"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::HexadecimalString>(Object::HexadecimalString{ | |
| .raw_value = "4E6F762073686D6F7A206B6120706F702E"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::HexadecimalString>( | |
| Object::HexadecimalString{.raw_value = "901FA3"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "Name1"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::Name>( | |
| Object::Name{.value = "ASomewhatLongerName"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::Name>(Object::Name{ | |
| .value = "A;Name_With-Various***Characters?"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "1.2"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "$$"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "@pattern"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = ".notdef"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "Lime Green"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::Name>( | |
| Object::Name{.value = "paired()parentheses"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, VariantWith<Object::Name>( | |
| Object::Name{.value = "The_Key_of_F#_Minor"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::Name>(Object::Name{.value = "abc"})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadObject(input, /*obj_offset=*/0, obj)); | |
| // "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})); | |
| ABSL_ASSERT_OK(ReadObject(input, /*obj_offset=*/10, obj)); | |
| // "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; | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| EXPECT_THAT(obj.value, | |
| VariantWith<Object::IndirectReference>(Object::IndirectReference{ | |
| .object_number = 123, .generation_number = 321})); | |
| ABSL_ASSERT_OK(ReadObject(input, input.pos(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadIndirectObject(input, input.pos(), MockResolver(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadIndirectObject(input, input.pos(), MockResolver(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadIndirectObject(input, input.pos(), MockResolver(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK(ReadIndirectObject(input, input.pos(), MockResolver(), obj)); | |
| 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; | |
| ABSL_ASSERT_OK( | |
| ReadIndirectObject(input, input.pos(), std::move(mock_resolver), obj)); | |
| 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 |