| // 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 "assertion/parse_assertion.h" | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/strings/string_view.h" | |
| #include "cbor/parse.h" | |
| #include "google/protobuf/struct.pb.h" | |
| #include "nlohmann/json.hpp" | |
| #include "proto/actions_assertion.cbor.h" | |
| #include "proto/actions_assertion.pb.h" | |
| #include "proto/ai_disclosure_assertion.cbor.h" | |
| #include "proto/ai_disclosure_assertion.pb.h" | |
| #include "proto/assertion.pb.h" | |
| #include "proto/asset_reference_assertion.cbor.h" | |
| #include "proto/asset_reference_assertion.pb.h" | |
| #include "proto/asset_types_assertion.cbor.h" | |
| #include "proto/asset_types_assertion.pb.h" | |
| #include "proto/bmff_based_hash_assertion.cbor.h" | |
| #include "proto/bmff_based_hash_assertion.pb.h" | |
| #include "proto/boxes_hash_assertion.cbor.h" | |
| #include "proto/boxes_hash_assertion.pb.h" | |
| #include "proto/collection_data_hash_assertion.cbor.h" | |
| #include "proto/data_hash_assertion.cbor.h" | |
| #include "proto/data_hash_assertion.pb.h" | |
| #include "proto/environmental_sustainability_assertion.cbor.h" | |
| #include "proto/hashed_uri.pb.h" | |
| #include "proto/ingredient_assertion.cbor.h" | |
| #include "proto/ingredient_assertion.pb.h" | |
| #include "proto/metadata_assertion.pb.h" | |
| #include "proto/multi_asset_hash_assertion.cbor.h" | |
| #include "proto/repository_receipt_assertion.pb.h" | |
| #include "proto/soft_binding_assertion.cbor.h" | |
| #include "proto/soft_binding_assertion.pb.h" | |
| #include "proto/thumbnail_assertion.pb.h" | |
| namespace credentio { | |
| namespace { | |
| using Json = ::nlohmann::json; | |
| absl::Status ParseListValueHelper(const Json& json, | |
| google::protobuf::ListValue* proto); | |
| absl::Status ParseValueHelper(const Json& json, google::protobuf::Value* proto); | |
| absl::Status ParseStructHelper(const Json& json, | |
| google::protobuf::Struct* proto); | |
| absl::Status ParseListValueHelper(const Json& json, | |
| google::protobuf::ListValue* proto) { | |
| for (const auto& wrapped_value : json.items()) { | |
| // The parsed JSON has a redundant object wrapper for each list item. | |
| // We only need the beginning item as it contains the index and the | |
| // actual value. | |
| const auto& [_, value] = wrapped_value.value().items().begin(); | |
| google::protobuf::Value* proto_value = proto->add_values(); | |
| ABSL_RETURN_IF_ERROR(ParseValueHelper(value, proto_value)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ParseValueHelper(const Json& json, | |
| google::protobuf::Value* proto) { | |
| if (json.is_null()) { | |
| proto->set_null_value(google::protobuf::NULL_VALUE); | |
| return absl::OkStatus(); | |
| } | |
| if (json.is_number()) { | |
| proto->set_number_value(json.get<double>()); | |
| return absl::OkStatus(); | |
| } | |
| if (json.is_string()) { | |
| proto->set_string_value(json.get<std::string>()); | |
| return absl::OkStatus(); | |
| } | |
| if (json.is_boolean()) { | |
| proto->set_bool_value(json.get<bool>()); | |
| return absl::OkStatus(); | |
| } | |
| if (json.is_object()) { | |
| return ParseStructHelper(json, proto->mutable_struct_value()); | |
| } | |
| if (json.is_array()) { | |
| return ParseListValueHelper(json, proto->mutable_list_value()); | |
| } | |
| return absl::InvalidArgumentError("Should not happen"); | |
| } | |
| absl::Status ParseStructHelper(const Json& json, | |
| google::protobuf::Struct* proto) { | |
| for (const auto& [key, value] : json.items()) { | |
| google::protobuf::Value proto_value; | |
| ABSL_RETURN_IF_ERROR(ParseValueHelper(value, &proto_value)); | |
| proto->mutable_fields()->insert({key, proto_value}); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| template <typename T> | |
| absl::Status ParseAssertionHelper(absl::string_view assertion, T* proto) { | |
| ABSL_ASSIGN_OR_RETURN(auto item, cbor::Parse(assertion)); | |
| ABSL_ASSIGN_OR_RETURN(auto map, item->AsMap()); | |
| return cbor::ToProto(map, proto); | |
| } | |
| template <typename T> | |
| void ClearPaddingFields(T* proto) { | |
| proto->clear_pad(); | |
| proto->clear_pad2(); | |
| } | |
| std::optional<std::string> GetJsonString(const Json& obj, | |
| absl::string_view key) { | |
| if (auto it = obj.find(key); it != obj.end() && it->is_string()) { | |
| return it->get<std::string>(); | |
| } | |
| return std::nullopt; | |
| } | |
| const Json* GetJsonObject(const Json& obj, absl::string_view key) { | |
| if (auto it = obj.find(key); it != obj.end() && it->is_object()) { | |
| return &(*it); | |
| } | |
| return nullptr; | |
| } | |
| } // namespace | |
| #define C2PA_DEFINE_CBOR_ASSERTION_PARSER(ProtoType) \ | |
| absl::Status ParseAssertion(absl::string_view assertion, ProtoType* proto) { \ | |
| return ParseAssertionHelper(assertion, proto); \ | |
| } | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(ActionsAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(BmffBasedHashAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(IngredientAssertionV3) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(AssetReferenceAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(AssetTypesAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(BoxesHashAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(MultiAssetHashAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(CollectionDataHashAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(AiDisclosureAssertion) | |
| C2PA_DEFINE_CBOR_ASSERTION_PARSER(EnvironmentalSustainabilityAssertion) | |
| #undef C2PA_DEFINE_CBOR_ASSERTION_PARSER | |
| absl::Status ParseAssertion(absl::string_view assertion, | |
| ActionsAssertionV1* proto) { | |
| ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto)); | |
| // Move v1 parameters field `ingredient` to v2 field `ingredients`. | |
| for (auto& action : *proto->mutable_actions()) { | |
| auto* params = action.mutable_parameters(); | |
| if (params->has_ingredient()) { | |
| params->add_ingredients()->Swap(params->mutable_ingredient()); | |
| params->clear_ingredient(); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ParseAssertion(absl::string_view assertion, | |
| DataHashAssertion* proto) { | |
| ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto)); | |
| // Clear irrelevant padding fields (for internal generator use only). | |
| ClearPaddingFields(proto); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ParseAssertion(absl::string_view assertion, | |
| SoftBindingAssertion* proto) { | |
| ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto)); | |
| // Clear irrelevant padding fields (for internal generator use only). | |
| ClearPaddingFields(proto); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ParseAssertion(absl::string_view assertion, | |
| RepositoryReceiptAssertion* proto) { | |
| auto json = Json::parse(assertion, nullptr, false); | |
| if (json.is_discarded()) { | |
| return absl::InvalidArgumentError("JSON parsing failed"); | |
| } | |
| if (!json.is_object()) { | |
| return absl::InvalidArgumentError("Assertion is not a JSON object"); | |
| } | |
| if (const Json* repo_json = GetJsonObject(json, "repository")) { | |
| auto* repository = proto->mutable_repository(); | |
| if (auto uri = GetJsonString(*repo_json, "uri")) { | |
| repository->set_uri(*std::move(uri)); | |
| } | |
| if (auto manifest_id = GetJsonString(*repo_json, "manifestId")) { | |
| repository->set_manifest_id(*std::move(manifest_id)); | |
| } | |
| } | |
| if (const Json* anchor_json = GetJsonObject(json, "anchor")) { | |
| auto* anchor = proto->mutable_anchor(); | |
| if (auto uri = GetJsonString(*anchor_json, "uri")) { | |
| anchor->set_uri(*std::move(uri)); | |
| } | |
| if (const Json* params = GetJsonObject(*anchor_json, "parameters")) { | |
| ABSL_RETURN_IF_ERROR( | |
| ParseStructHelper(*params, anchor->mutable_parameters())); | |
| } | |
| if (const Json* proof = GetJsonObject(*anchor_json, "proof")) { | |
| ABSL_RETURN_IF_ERROR(ParseStructHelper(*proof, anchor->mutable_proof())); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| ThumbnailAssertion ParseThumbnailAssertion( | |
| absl::string_view media_type, absl::string_view data, | |
| std::optional<absl::string_view> file_name) { | |
| ThumbnailAssertion proto; | |
| proto.set_media_type(media_type); | |
| proto.set_data(data); | |
| if (file_name.has_value()) { | |
| proto.set_file_name(*file_name); | |
| } | |
| return proto; | |
| } | |
| absl::Status ParseStruct(absl::string_view content, | |
| google::protobuf::Struct* proto) { | |
| Json result = Json::parse(content.begin(), content.end(), /*cb=*/nullptr, | |
| /*allow_exceptions=*/false); | |
| if (result.is_discarded()) { | |
| return absl::InvalidArgumentError("Invalid JSON: could not parse"); | |
| } | |
| return ParseStructHelper(result, proto); | |
| } | |
| } // namespace credentio |