| // 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 |