| // 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 <cstdint> | |
| #include <string> | |
| #include <utility> | |
| #include <variant> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/ascii.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "formats/pdf/objects.h" | |
| #include "re2/re2.h" | |
| #include "riegeli/bytes/reader.h" | |
| namespace credentio { | |
| namespace { | |
| constexpr uint64_t kMaxPayloadSize = 1024 * 1024 * 10; // 10 MiB | |
| constexpr uint64_t kMaxStringTokenLength = 1024 * 1024 * 10; // 10 MiB | |
| constexpr uint64_t kMaxNameTokenLength = 4096; | |
| constexpr uint64_t kMaxNumberTokenLength = 128; | |
| constexpr int kMaxObjectRecursionDepth = 100; | |
| // PDF White-space characters. See ISO 32000-2 Section 7.2.3 Table 1. | |
| constexpr bool IsPdfWhiteSpace(uint8_t c) { | |
| return c == 0x00 || c == 0x09 || c == 0x0a || c == 0x0c || c == 0x0d || | |
| c == 0x20; | |
| } | |
| // PDF Delimiter characters. See ISO 32000-2 Section 7.2.3 Table 2. | |
| constexpr bool IsPdfDelimiter(uint8_t c) { | |
| return c == 0x28 || c == 0x29 || c == 0x3c || c == 0x3e || c == 0x5b || | |
| c == 0x5d || c == 0x7b || c == 0x7d || c == 0x2f || c == 0x25; | |
| } | |
| static constexpr LazyRE2 kIndirectReferencePattern = { | |
| R"(^[+-]?(\d+)\s+(\d+)\s+R[^a-zA-Z])"}; | |
| // Reads either an integer or a real number from the given input stream at the | |
| // current position. | |
| absl::Status ReadNumber(riegeli::Reader& input, Object& obj) { | |
| uint8_t token = 0x00; | |
| std::string buffer; | |
| while (true) { | |
| if (buffer.size() >= kMaxNumberTokenLength) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Number token exceeds maximum allowed length: ", | |
| kMaxNumberTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| // A number can be terminated by any white-space character or delimiter. | |
| if (IsPdfWhiteSpace(token) || IsPdfDelimiter(token)) { | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| break; | |
| } | |
| buffer += token; | |
| } | |
| if (absl::StrContains(buffer, '.')) { | |
| obj.value = Object::RealNumber{.raw_value = std::move(buffer)}; | |
| } else { | |
| int64_t value; | |
| if (!absl::SimpleAtoi(buffer, &value)) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Failed to parse integer. Invalid value: ", buffer)); | |
| } | |
| obj.value = Object::Integer{.value = std::move(value)}; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadBoolean(riegeli::Reader& input, Object& obj) { | |
| std::string buffer; | |
| if (!input.Read(5, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer.substr(0, 4) == "true") { | |
| obj.value = Object::Boolean{.value = true}; | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| } else if (buffer == "false") { | |
| obj.value = Object::Boolean{.value = false}; | |
| } else { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Failed to parse boolean. Invalid value: ", buffer)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadLiteralString(riegeli::Reader& input, Object& obj) { | |
| uint8_t token = 0x00; | |
| std::string buffer; | |
| int32_t parentheses_count = 1; | |
| // Consume the beginning delimiter. | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token != '(') { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid delimiter. Expected '(' but got 0x", | |
| absl::Hex(token), " at offset ", input.pos() - 1)); | |
| } | |
| while (true) { | |
| if (buffer.size() >= kMaxStringTokenLength) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Literal string exceeds maximum allowed length: ", | |
| kMaxStringTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token == '(') { | |
| parentheses_count++; | |
| } | |
| if (token == ')' && --parentheses_count == 0) { | |
| break; | |
| } | |
| buffer += token; | |
| } | |
| obj.value = Object::LiteralString{.raw_value = std::move(buffer)}; | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadHexadecimalString(riegeli::Reader& input, Object& obj) { | |
| uint8_t token = 0x00; | |
| std::string buffer; | |
| // Consume the beginning delimiter. | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token != '<') { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid delimiter. Expected '<' but got 0x", | |
| absl::Hex(token), " at offset ", input.pos() - 1)); | |
| } | |
| while (true) { | |
| if (buffer.size() >= kMaxStringTokenLength) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Hexadecimal string exceeds maximum allowed length: ", | |
| kMaxStringTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token == '>') { | |
| break; | |
| } | |
| buffer += token; | |
| } | |
| obj.value = Object::HexadecimalString{.raw_value = std::move(buffer)}; | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadName(riegeli::Reader& input, Object& obj) { | |
| uint8_t token = 0x00; | |
| std::string buffer; | |
| // Consume the beginning delimiter. | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token != '/') { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid delimiter. Expected '/' but got 0x", | |
| absl::Hex(token), " at offset ", input.pos() - 1)); | |
| } | |
| while (true) { | |
| if (buffer.size() >= kMaxNameTokenLength) { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Name token exceeds maximum allowed length: ", kMaxNameTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| // A name can be terminated by any white-space character or delimiter. | |
| if (IsPdfWhiteSpace(token) || IsPdfDelimiter(token)) { | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| break; | |
| } | |
| if (token == '#') { | |
| std::string hex_str(2, '\0'); | |
| if (!input.Read(2, hex_str)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read hex string")); | |
| } | |
| if (!absl::ascii_isxdigit(hex_str[0]) || | |
| !absl::ascii_isxdigit(hex_str[1])) { | |
| return absl::InvalidArgumentError("Invalid hex string"); | |
| } | |
| int hex_val = 0; | |
| if (!absl::SimpleHexAtoi(hex_str, &hex_val)) { | |
| return absl::InvalidArgumentError("Invalid hex string"); | |
| } | |
| token = hex_val; | |
| } | |
| buffer += token; | |
| } | |
| obj.value = Object::Name{.value = std::move(buffer)}; | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadArray(riegeli::Reader& input, Object& obj, int depth) { | |
| uint8_t token = 0x00; | |
| Object::Array array; | |
| // Consume the beginning delimiter. | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token != '[') { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid delimiter. Expected '[' but got '", | |
| absl::Hex(token), "' at offset ", input.pos() - 1)); | |
| } | |
| while (true) { | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token == ']') { | |
| break; | |
| } | |
| Object sub_obj; | |
| ABSL_RETURN_IF_ERROR(ReadObject(input, input.pos() - 1, sub_obj, depth)); | |
| array.objects.push_back(std::move(sub_obj)); | |
| } | |
| obj.value = std::move(array); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadNull(riegeli::Reader& input, Object& obj) { | |
| std::string buffer; | |
| if (!input.Read(4, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer == "null") { | |
| obj.value = Object::Null{}; | |
| } else { | |
| return absl::InvalidArgumentError(absl::StrCat("Invalid value: ", buffer)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadDictionary(riegeli::Reader& input, Object& obj, int depth) { | |
| std::string buffer; | |
| Object::Dictionary dictionary; | |
| if (!input.Read(2, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer != "<<") { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Invalid delimiter. Expected '<<' but got '", buffer, "'")); | |
| } | |
| while (true) { | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.Read(2, buffer)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer == ">>") { | |
| break; | |
| } | |
| if (!input.Seek(input.pos() - 2)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| Object name_obj; | |
| ABSL_RETURN_IF_ERROR(ReadName(input, name_obj)); | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| Object value_obj; | |
| ABSL_RETURN_IF_ERROR(ReadObject(input, input.pos(), value_obj, depth)); | |
| ABSL_ASSIGN_OR_RETURN(auto name, Object::As<Object::Name>(name_obj)); | |
| dictionary.entries.insert({name.value, std::move(value_obj)}); | |
| } | |
| obj.value = std::move(dictionary); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadStream(riegeli::Reader& input, int64_t length, Object& obj) { | |
| std::string buffer; | |
| Object::Stream stream; | |
| if (!input.Read(6, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer != "stream") { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed keyword. Expected 'stream' but got '", buffer, "'")); | |
| } | |
| if (!input.Read(2, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| // The spec requires "stream" keyword to be followed by a CARRIAGE RETURN and | |
| // a LINE FEED, or just a LINE FEED. | |
| if (buffer == "\r\n") { // CARRIAGE RETURN + LINE FEED | |
| // Do nothing. | |
| } else if (buffer[0] == '\n') { // LINE FEED | |
| // Seek back to the end of the LINE FEED. | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| } else { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed stream. Expected '\r\n' or '\n' but got '", buffer, "'")); | |
| } | |
| if (length > kMaxPayloadSize) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Stream length exceeds maximum allowed size: ", length, | |
| " > ", kMaxPayloadSize)); | |
| } | |
| if (!input.Read(length, stream.raw_value)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| obj.value = std::move(stream); | |
| // Skip over the end-of-line marker. | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.Read(9, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer != "endstream") { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed keyword. Expected 'endstream' but got '", buffer, "'")); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| } // namespace | |
| absl::Status ReadObject(riegeli::Reader& input, int64_t obj_offset, Object& obj, | |
| int depth) { | |
| if (depth > kMaxObjectRecursionDepth) { | |
| return absl::InvalidArgumentError("Max nesting depth exceeded."); | |
| } | |
| if (!input.Seek(obj_offset)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| uint8_t token = 0x00; | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| switch (token) { | |
| case '+': | |
| case '-': | |
| case '.': | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': { // Numbers or indirect reference | |
| std::string buffer(20, '\0'); | |
| // Read more bytes to check if this is an indirect reference. | |
| if (!input.Read(20, buffer)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (!input.Seek(input.pos() - 20)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| if (uint32_t obj_num, gen_num; RE2::PartialMatch( | |
| buffer, *kIndirectReferencePattern, &obj_num, &gen_num)) { | |
| obj.value = Object::IndirectReference{.object_number = obj_num, | |
| .generation_number = gen_num}; | |
| auto pos = buffer.find('R'); | |
| // This should never happen. | |
| if (pos == std::string::npos) { | |
| return absl::InternalError( | |
| "Failed to locate keyword 'R' in indirect reference buffer."); | |
| } | |
| if (!input.Seek(input.pos() + pos + 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| break; | |
| } | |
| ABSL_RETURN_IF_ERROR(ReadNumber(input, obj)); | |
| break; | |
| } | |
| case 't': | |
| case 'f': { // Boolean | |
| ABSL_RETURN_IF_ERROR(ReadBoolean(input, obj)); | |
| break; | |
| } | |
| case '(': { // Literal string | |
| ABSL_RETURN_IF_ERROR(ReadLiteralString(input, obj)); | |
| break; | |
| } | |
| case '<': { // Hexadecimal string or Dictionary | |
| // Read one more byte to determine the object type. | |
| std::string buffer(2, '\0'); | |
| if (!input.Read(2, buffer)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (!input.Seek(input.pos() - 2)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| if (buffer == "<<") { // Dictionary | |
| ABSL_RETURN_IF_ERROR(ReadDictionary(input, obj, depth + 1)); | |
| } else { | |
| // Hexadecimal string | |
| ABSL_RETURN_IF_ERROR(ReadHexadecimalString(input, obj)); | |
| } | |
| break; | |
| } | |
| case '/': { // Name | |
| ABSL_RETURN_IF_ERROR(ReadName(input, obj)); | |
| break; | |
| } | |
| case '[': { // Array | |
| ABSL_RETURN_IF_ERROR(ReadArray(input, obj, depth + 1)); | |
| break; | |
| } | |
| case 'n': { // Null | |
| ABSL_RETURN_IF_ERROR(ReadNull(input, obj)); | |
| break; | |
| } | |
| case 's': { // Stream | |
| std::string buffer(6, '\0'); | |
| if (!input.Read(6, buffer)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (!input.Seek(input.pos() - 6)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| if (buffer == "stream") { | |
| return absl::InvalidArgumentError( | |
| "Stream must reside in an indirect object. Call " | |
| "ReadIndirectObject() instead."); | |
| } else { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed keyword. Expected 'stream' but got '", buffer, "'")); | |
| } | |
| break; | |
| } | |
| default: | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Unsupported delimiter: 0x", absl::Hex(token), | |
| " at offset ", input.pos())); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ReadIndirectObject( | |
| riegeli::Reader& input, int64_t obj_offset, | |
| absl::AnyInvocable< | |
| absl::StatusOr<IndirectObject>(const Object::IndirectReference&)> | |
| indirect_reference_resolver, | |
| IndirectObject& obj) { | |
| if (!input.Seek(obj_offset)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| uint8_t token = 0x00; | |
| std::string buffer; | |
| // Read the object number. | |
| while (true) { | |
| if (buffer.size() >= kMaxNumberTokenLength) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Object number token exceeds maximum allowed length: ", | |
| kMaxNumberTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (IsPdfWhiteSpace(token)) { | |
| break; | |
| } | |
| absl::StrAppend(&buffer, std::string(1, token)); | |
| } | |
| bool success = absl::SimpleAtoi(buffer, &obj.object_number); | |
| if (!success) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Failed to parse object number. Invalid value: ", buffer)); | |
| } | |
| // Read the generation number. | |
| buffer.clear(); | |
| while (true) { | |
| if (buffer.size() >= kMaxNumberTokenLength) { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Generation number token exceeds maximum allowed length: ", | |
| kMaxNumberTokenLength)); | |
| } | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (IsPdfWhiteSpace(token)) { | |
| break; | |
| } | |
| absl::StrAppend(&buffer, std::string(1, token)); | |
| } | |
| success = absl::SimpleAtoi(buffer, &obj.generation_number); | |
| if (!success) { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Failed to parse generation number. Invalid value: ", buffer)); | |
| } | |
| // Read the enclosed object. | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.Read(3, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (buffer != "obj") { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed keyword. Expected 'obj' but got '", buffer, "'")); | |
| } | |
| Object temp_obj; | |
| ABSL_RETURN_IF_ERROR(ReadObject(input, input.pos(), temp_obj)); | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.Read(6, buffer)) { | |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read buffer")); | |
| } | |
| if (!input.Seek(input.pos() - 6)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| if (buffer == "stream") { | |
| // Maker indicates that this is a stream object, hence the object preceding | |
| // it must be the stream dictionary. | |
| ABSL_ASSIGN_OR_RETURN(obj.stream_dictionary, | |
| Object::As<Object::Dictionary>(temp_obj)); | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| // Determine the length of the stream. | |
| int64_t length = -1; | |
| if (const auto it = obj.stream_dictionary->entries.find("Length"); | |
| it != obj.stream_dictionary->entries.end()) { | |
| if (std::holds_alternative<Object::IndirectReference>(it->second.value)) { | |
| ABSL_ASSIGN_OR_RETURN( | |
| auto indirect_ref, | |
| Object::As<Object::IndirectReference>(it->second)); | |
| // Save the current input stream position, as the | |
| // `indirect_reference_resolver` might change it. | |
| auto pos = input.pos(); | |
| ABSL_ASSIGN_OR_RETURN(auto indirect_length_obj, | |
| indirect_reference_resolver(indirect_ref)); | |
| ABSL_ASSIGN_OR_RETURN(auto length_obj, Object::As<Object::Integer>( | |
| indirect_length_obj.object)); | |
| length = length_obj.value; | |
| // Seek back to the saved position to continue reading the stream. | |
| if (!input.Seek(pos)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| } else { | |
| ABSL_ASSIGN_OR_RETURN(auto length_obj, | |
| Object::As<Object::Integer>(it->second)); | |
| length = length_obj.value; | |
| } | |
| } else { | |
| return absl::DataLossError( | |
| "Stream dictionary does not contain a 'Length' entry."); | |
| } | |
| ABSL_RETURN_IF_ERROR(ReadStream(input, length, obj.object)); | |
| // Skip over the end-of-object marker. | |
| ABSL_RETURN_IF_ERROR(SkipOverWhiteSpaces(input)); | |
| if (!input.Read(6, buffer)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read buffer")); | |
| } | |
| } else { | |
| obj.object = std::move(temp_obj); | |
| } | |
| if (buffer != "endobj") { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Malformed keyword. Expected 'endobj' but got '", buffer, "'")); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status SkipOverWhiteSpaces(riegeli::Reader& input) { | |
| uint8_t token = 0x00; | |
| while (true) { | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token == '%') { | |
| // It's a comment, skip until EOL or EOF. | |
| while (true) { | |
| if (!input.ReadByte(token)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to read beginning delimiter")); | |
| } | |
| if (token == 0x0a || token == 0x0d) { // LF or CR | |
| break; | |
| } | |
| } | |
| continue; | |
| } | |
| if (!IsPdfWhiteSpace(token)) { | |
| // Seek back to the last non-whitespace character. | |
| if (!input.Seek(input.pos() - 1)) { | |
| return input.StatusOrAnnotate( | |
| absl::DataLossError("Failed to seek to offset")); | |
| } | |
| break; | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| } // namespace credentio |