blob: e54b754411baab37097de359ebf23fbe7bba096b [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/pdf/object_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