blob: 650f40688e57b39ed3523621bc40bb2e1f16a603 [file]
// 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 "jumbf/convert_json.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "cppbor/cppbor.h"
#include "cppbor/cppbor_parse.h"
#include "jumbf/box.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
namespace jumbf {
namespace {
using Json = ::nlohmann::json;
std::string EncodePrefixedBase64(absl::string_view raw) {
return absl::StrCat("b64'", absl::Base64Escape(raw), "'");
}
std::string EncodeCbor(cppbor::Item* item) {
std::vector<uint8_t> buf;
buf.resize(item->encodedSize());
item->encode(buf.data(), buf.data() + buf.size());
return std::string(reinterpret_cast<const char*>(buf.data()), buf.size());
}
cppbor::Map BstrToB64(cppbor::Map* map);
std::unique_ptr<cppbor::Item> UnwrapTag(std::unique_ptr<cppbor::Item> item) {
while (item->asSemanticTag() != nullptr) {
if (auto t = item->asTstr(); t != nullptr) return t->clone();
if (auto u = item->asUint(); u != nullptr) return u->clone();
if (auto i = item->asInt(); i != nullptr) return i->clone();
if (auto b = item->asBool(); b != nullptr) return b->clone();
if (auto f = item->asFloat(); f != nullptr) return f->clone();
if (auto d = item->asDouble(); d != nullptr) return d->clone();
if (auto s = item->asSimple(); s != nullptr) return s->clone();
break;
}
return item;
}
// Recursively replaces all Bstr with base64-encoded Tstr.
cppbor::Array BstrToB64(cppbor::Array* array) {
cppbor::Array new_array;
for (auto& item_ref : *array) {
auto item = UnwrapTag(std::move(item_ref));
if (auto s = item->asBstr(); s != nullptr) {
absl::string_view sv(reinterpret_cast<const char*>(s->value().data()),
s->value().size());
new_array.add(cppbor::Tstr(EncodePrefixedBase64(sv)));
} else if (auto m = item->asMap(); m != nullptr) {
new_array.add(BstrToB64(m));
} else if (auto a = item->asArray(); a != nullptr) {
new_array.add(BstrToB64(a));
} else {
new_array.add(std::move(item));
}
}
return new_array;
}
// Recursively replaces all Bstr with base64-encoded Tstr.
cppbor::Map BstrToB64(cppbor::Map* map) {
cppbor::Map new_map;
for (auto& [key_ref, value_ref] : *map) {
auto key = UnwrapTag(std::move(key_ref));
auto value = UnwrapTag(std::move(value_ref));
if (auto s = value->asBstr(); s != nullptr) {
absl::string_view sv(reinterpret_cast<const char*>(s->value().data()),
s->value().size());
new_map.add(std::move(key), cppbor::Tstr(EncodePrefixedBase64(sv)));
} else if (auto m = value->asMap(); m != nullptr) {
new_map.add(std::move(key), BstrToB64(m));
} else if (auto v = value->asArray(); v != nullptr) {
cppbor::Array new_array = BstrToB64(v);
new_map.add(std::move(key), std::move(new_array));
} else {
new_map.add(std::move(key), std::move(value));
}
}
return new_map;
}
// Recursively replaces all Bstr with base64-encoded Tstr. Supports only arrays
// and maps.
absl::StatusOr<std::string> BstrToB64(absl::string_view raw) {
auto [item, new_position, error] =
cppbor::parse(reinterpret_cast<const uint8_t*>(raw.data()), raw.length());
if (!error.empty()) {
return absl::InvalidArgumentError(
absl::StrCat("CBOR parsing failed: ", error));
}
if (new_position !=
reinterpret_cast<const uint8_t*>(raw.data() + raw.length())) {
return absl::InvalidArgumentError(
"Trailing bytes after the parsed CBOR item");
}
if (auto map = item->asMap(); map != nullptr) {
auto new_map = BstrToB64(map);
return EncodeCbor(&new_map);
} else if (auto array = item->asArray(); array != nullptr) {
auto new_array = BstrToB64(array);
return EncodeCbor(&new_array);
}
return absl::InvalidArgumentError(
absl::StrCat("Input CBOR item is ", item->type(),
"; expected array (128) or map (160)."));
}
class AllExceptionParser
: public nlohmann::detail::json_sax_dom_parser<
Json, nlohmann::detail::iterator_input_adapter<const char*>> {
public:
explicit AllExceptionParser(Json& j)
: nlohmann::detail::json_sax_dom_parser<
Json, nlohmann::detail::iterator_input_adapter<const char*>>(
j, false) {}
bool parse_error(std::size_t position, absl::string_view last_token,
const Json::exception& ex) {
errors_.push_back(absl::StrFormat("%s", ex.what()));
return false;
}
std::vector<std::string> errors() { return errors_; }
private:
std::vector<std::string> errors_;
};
} // namespace
void to_json(Json& j, const DescriptionBox& box) {
if (box.label.has_value()) {
j["label"] = box.label.value();
}
if (box.id.has_value()) {
j["id"] = box.id.value();
}
if (box.unvalidated_hash.has_value()) {
j["hash"] = box.unvalidated_hash.value();
}
j["uuid"] = box.type_uuid.ToString();
j["requestable"] = box.requestable;
for (const auto& box : box.private_content) {
j["private_content"].push_back(box);
}
j["_meta"]["_type"] = "description";
}
void to_json(Json& j, const SuperBox& box) {
j["description"] = box.description;
for (const auto& box : box.contents) {
j["contents"].push_back(box);
}
j["_meta"]["_type"] = "super";
}
void to_json(Json& j, const UnknownBox& box) {
j["type"] = box.type;
j["_meta"]["_type"] = "unknown";
j["_meta"]["_payload_size"] = box.payload.size();
}
void to_json(Json& j, const CborBox& box) {
j["_meta"]["_type"] = "cbor";
j["_meta"]["_payload_size"] = box.payload.size();
absl::StatusOr<std::string> encoded = BstrToB64(box.payload);
if (!encoded.ok()) {
j["_meta"]["_error"] = encoded.status().ToString();
return;
}
AllExceptionParser parser(j["cbor"]);
auto ia = nlohmann::detail::input_adapter(*encoded);
nlohmann::detail::binary_reader<Json, decltype(ia), AllExceptionParser>
reader(std::move(ia), Json::input_format_t::cbor);
reader.sax_parse(Json::input_format_t::cbor, &parser, /*strict=*/true,
/*tag_handler=*/Json::cbor_tag_handler_t::ignore);
if (!parser.errors().empty()) {
j["_meta"]["_error"] = parser.errors();
}
}
void to_json(Json& j, const SerializedSuperbox& box) {
j["_meta"]["_type"] = "serialized_superbox";
j["_meta"]["_serialized_size"] = box.serialized.size();
}
void to_json(Json& j, const EmbeddedFileDescriptionBox& box) {
j["media_type"] = box.media_type;
j["external"] = box.external;
if (box.file_name.has_value()) {
j["file_name"] = box.file_name.value();
}
j["_meta"]["_type"] = "embedded_file_description";
}
void to_json(Json& j, const BinaryDataBox& box) {
j["_meta"]["_type"] = "binary";
j["_meta"]["_payload_size"] = box.payload.size();
}
void to_json(Json& j, const C2paSaltBox& box) {
j["_meta"]["_type"] = "c2pa_salt";
j["_meta"]["_salt_size"] = box.salt.size();
}
void to_json(Json& j, const JsonBox& box) {
AllExceptionParser parser(j["json"]);
Json::sax_parse(box.payload, &parser, Json::input_format_t::json);
if (!parser.errors().empty()) {
j["_meta"]["_error"] = parser.errors();
}
j["_meta"]["_type"] = "json";
j["_meta"]["_payload_size"] = box.payload.size();
}
void to_json(Json& j, const ContentBox& box) {
if (box.Holds<jumbf::SuperBox>()) {
j = box.Get<jumbf::SuperBox>();
} else if (box.Holds<jumbf::UnknownBox>()) {
j = box.Get<jumbf::UnknownBox>();
} else if (box.Holds<jumbf::CborBox>()) {
j = box.Get<jumbf::CborBox>();
} else if (box.Holds<jumbf::SerializedSuperbox>()) {
j = box.Get<jumbf::SerializedSuperbox>();
} else if (box.Holds<jumbf::EmbeddedFileDescriptionBox>()) {
j = box.Get<jumbf::EmbeddedFileDescriptionBox>();
} else if (box.Holds<jumbf::BinaryDataBox>()) {
j = box.Get<jumbf::BinaryDataBox>();
} else if (box.Holds<jumbf::C2paSaltBox>()) {
j = box.Get<jumbf::C2paSaltBox>();
} else if (box.Holds<jumbf::JsonBox>()) {
j = box.Get<jumbf::JsonBox>();
} else {
j["_meta"]["_error"] = "Contents Not Accounted For";
}
}
} // namespace jumbf