| // 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 |