| // 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_writer.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <string> | |
| #include <variant> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/string_view.h" | |
| #include "formats/pdf/number_validator.h" | |
| #include "formats/pdf/objects.h" | |
| #include "riegeli/bytes/string_writer.h" | |
| #include "riegeli/bytes/writer.h" | |
| namespace credentio { | |
| namespace { | |
| constexpr absl::string_view kTrue = "true"; | |
| constexpr absl::string_view kFalse = "false"; | |
| constexpr absl::string_view kNull = "null"; | |
| absl::Status WriteObject(const Object::Boolean& obj, riegeli::Writer& output) { | |
| std::string buffer = obj.value ? std::string(kTrue) : std::string(kFalse); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Integer& obj, riegeli::Writer& output) { | |
| std::string buffer = absl::StrCat(obj.value); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::RealNumber& obj, | |
| riegeli::Writer& output) { | |
| if (!IsValidRealNumber(obj.raw_value)) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid real number: ", obj.raw_value)); | |
| } | |
| std::string buffer = obj.raw_value; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::LiteralString& obj, | |
| riegeli::Writer& output) { | |
| std::string buffer = "("; | |
| for (const char c : obj.raw_value) { | |
| if (c == '(' || c == ')' || c == '\\') { | |
| buffer += '\\'; | |
| buffer += c; | |
| } else { | |
| buffer += c; | |
| } | |
| } | |
| buffer += ')'; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::HexadecimalString& obj, | |
| riegeli::Writer& output) { | |
| if (!IsValidHexadecimalString(obj.raw_value)) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid hexadecimal string: ", obj.raw_value)); | |
| } | |
| std::string buffer = absl::StrCat("<", obj.raw_value, ">"); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Name& obj, riegeli::Writer& output) { | |
| std::string buffer = "/"; | |
| for (const char c : obj.value) { | |
| // Escape control characters, non-ASCII, slash, and any PDF delimiter | |
| // character. Delimiters include: (, ), <, >, [, ], {, }, %, #, / | |
| if (c < 0x21 || c > 0x7e || c == 0x2f || c == '(' || c == ')' || c == '<' || | |
| c == '>' || c == '[' || c == ']' || c == '{' || c == '}' || c == '%' || | |
| c == '#') { | |
| absl::StrAppendFormat(&buffer, "#%02x", static_cast<unsigned char>(c)); | |
| } else { | |
| buffer += c; | |
| } | |
| } | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Array& obj, riegeli::Writer& output) { | |
| // Write the open bracket. | |
| std::string delimiter = "["; | |
| if (!output.Write(delimiter)) { | |
| return output.status(); | |
| } | |
| // Write the elements separated by spaces. | |
| std::string space = " "; | |
| for (auto i = 0; i < obj.objects.size(); ++i) { | |
| ABSL_RETURN_IF_ERROR(WriteObject(obj.objects[i], output)); | |
| // Write the space as a delimiter when necessary. | |
| if (i < obj.objects.size() - 1) { | |
| if (!output.Write(space)) { | |
| return output.status(); | |
| } | |
| } | |
| } | |
| // Write the close bracket. | |
| delimiter = "]"; | |
| if (!output.Write(delimiter)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Dictionary& obj, | |
| riegeli::Writer& output) { | |
| // Write the open brackets. | |
| std::string delimiter = "<<"; | |
| if (!output.Write(delimiter)) { | |
| return output.status(); | |
| } | |
| // Write the entries separated by spaces. | |
| std::string space = " "; | |
| int entries_written = 0; | |
| for (const auto& [key, value] : obj.entries) { | |
| // Write the key as a Name object. | |
| auto key_obj = Object{.value = Object::Name{.value = key}}; | |
| ABSL_RETURN_IF_ERROR(WriteObject(key_obj, output)); | |
| // Write the space as a delimiter. | |
| if (!output.Write(space)) { | |
| return output.status(); | |
| } | |
| // Write the value object. | |
| ABSL_RETURN_IF_ERROR(WriteObject(value, output)); | |
| // Write the space as a delimiter when necessary. | |
| if (entries_written < obj.entries.size() - 1) { | |
| if (!output.Write(space)) { | |
| return output.status(); | |
| } | |
| } | |
| ++entries_written; | |
| } | |
| delimiter = ">>"; | |
| if (!output.Write(delimiter)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Stream& obj, riegeli::Writer& output) { | |
| std::string buffer = "stream\n"; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| buffer = obj.raw_value; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| buffer = "\nendstream"; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::Null& obj, riegeli::Writer& output) { | |
| std::string buffer = std::string(kNull); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status WriteObject(const Object::IndirectReference& obj, | |
| riegeli::Writer& output) { | |
| std::string buffer = | |
| absl::StrCat(obj.object_number, " ", obj.generation_number, " R"); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| } // namespace | |
| absl::Status WriteObject(const Object& obj, riegeli::Writer& output) { | |
| if (std::holds_alternative<Object::Stream>(obj.value)) { | |
| return absl::InvalidArgumentError( | |
| "Stream must be written as an indirect object. Call " | |
| "WriteIndirectObject() instead."); | |
| } | |
| return std::visit( | |
| [&output](const auto& obj) { return WriteObject(obj, output); }, | |
| obj.value); | |
| } | |
| absl::Status WriteIndirectObject(const IndirectObject& obj, | |
| riegeli::Writer& output, bool is_measurement) { | |
| // Write the object header. | |
| std::string buffer = | |
| absl::StrCat(obj.object_number, " ", obj.generation_number, " obj\n"); | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| if (obj.stream_dictionary.has_value()) { | |
| ABSL_ASSIGN_OR_RETURN(auto obj_stream, | |
| Object::As<Object::Stream>(obj.object)); | |
| Object::Dictionary serialized_dict = *obj.stream_dictionary; | |
| if (!obj_stream.raw_value.empty()) { | |
| serialized_dict.entries["Length"] = | |
| Object{.value = Object::Integer{.value = static_cast<int64_t>( | |
| obj_stream.raw_value.length())}}; | |
| } else if (!is_measurement) { | |
| // Safely lock genuinely empty streams to /Length 0 to prevent smuggling. | |
| serialized_dict.entries["Length"] = | |
| Object{.value = Object::Integer{.value = 0}}; | |
| } | |
| ABSL_RETURN_IF_ERROR(WriteObject(Object{.value = serialized_dict}, output)); | |
| // Write a newline to separate the stream dictionary from the stream. | |
| buffer = "\n"; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| ABSL_RETURN_IF_ERROR(WriteObject(obj_stream, output)); | |
| } else { | |
| ABSL_RETURN_IF_ERROR(WriteObject(obj.object, output)); | |
| } | |
| // Write the end-of-object marker. | |
| buffer = "\nendobj\n"; | |
| if (!output.Write(buffer)) { | |
| return output.status(); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::StatusOr<int64_t> ComputeIndirectStreamHeaderByteSize( | |
| const IndirectObject& indirect_stream_obj) { | |
| if (!std::holds_alternative<Object::Stream>( | |
| indirect_stream_obj.object.value)) { | |
| return absl::InvalidArgumentError( | |
| "The object is not an indirect stream object."); | |
| } | |
| // Create a copy of the indirect stream object with only the header info to | |
| // avoid serializing the entire stream. | |
| IndirectObject indirect_stream_obj_copy; | |
| indirect_stream_obj_copy.object_number = indirect_stream_obj.object_number; | |
| indirect_stream_obj_copy.generation_number = | |
| indirect_stream_obj.generation_number; | |
| indirect_stream_obj_copy.stream_dictionary = | |
| indirect_stream_obj.stream_dictionary; | |
| indirect_stream_obj_copy.object = Object{.value = Object::Stream{}}; | |
| std::string serialized_obj; | |
| riegeli::StringWriter<> output(&serialized_obj); | |
| ABSL_RETURN_IF_ERROR(WriteIndirectObject(indirect_stream_obj_copy, output, | |
| /*is_measurement=*/true)); | |
| if (!output.Close()) { | |
| return output.status(); | |
| } | |
| size_t pos = serialized_obj.length(); | |
| while ((pos = serialized_obj.rfind("stream\n", pos)) != std::string::npos) { | |
| if (pos >= 3 && serialized_obj.substr(pos - 3, 3) == "end") { | |
| if (pos == 0) break; | |
| pos--; | |
| continue; | |
| } | |
| return pos + 7; | |
| } | |
| return absl::InternalError( | |
| "Failed to find the stream marker in the serialized object."); | |
| } | |
| } // namespace credentio |