blob: a42c4ad24e025c4fb80b41c7d714c1315e750ddf [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 "assertion/assertion_parser.h"
#include <optional>
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "assertion/parse_assertion.h"
#include "constants/labels.h"
#include "constants/status_codes.h"
#include "google/protobuf/struct.pb.h"
#include "proto/assertion.pb.h"
#include "validator/tracker.h"
namespace credentio {
namespace {
void RecordAssertionParsingFailure(absl::string_view label, absl::Status error,
ValidationTracker& validation_tracker) {
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionCborInvalid,
{.url =
absl::StrCat(kAssertionStoreLabel, kManifestLabelDelimiter, label),
.explanation = absl::StrCat("Failed to parse the assertion with label: ",
label, " and error: ", error.message())});
}
bool IsBmffBasedHashLabel(absl::string_view label) {
return label == kBmffBasedHashAssertionV2Label ||
label == kBmffBasedHashAssertionV3Label ||
label == kBmffBasedHashAssertionV2PartLabel ||
label == kBmffBasedHashAssertionV3PartLabel;
}
bool IsDataHashLabel(absl::string_view label) {
return label == kDataHashAssertionLabel ||
label == kDataHashAssertionPartLabel;
}
bool IsUnsupportedIngredientLabel(absl::string_view label) {
return label == kIngredientAssertionV1Label ||
label == kIngredientAssertionV2Label;
}
bool IsBoxesHashLabel(absl::string_view label) {
return label == kBoxesHashAssertionLabel ||
label == kBoxesHashAssertionPartLabel;
}
bool IsCollectionDataHashLabel(absl::string_view label) {
return label == kCollectionDataHashAssertionLabel ||
label == kCollectionDataHashAssertionPartLabel;
}
} // namespace
absl::string_view StripMultipleInstanceSuffix(absl::string_view label) {
return label.substr(0, label.find("__"));
}
bool AssertionTypeMatcher::Matches(absl::string_view label) const {
return StripMultipleInstanceSuffix(label) == label_;
}
std::optional<Assertion> AssertionParser::ParseCbor(
absl::string_view label, absl::string_view content,
ValidationTracker& validation_tracker) const {
absl::string_view stripped_label = StripMultipleInstanceSuffix(label);
if (IsUnsupportedIngredientLabel(stripped_label)) {
// Unlike other assertion types, it's not safe to ignore an unsupported
// ingredient assertion.
validation_tracker.RecordFailure(
FailureStatusCode::kGoogleUnsupportedSpecVersion,
{.url =
absl::StrCat(kAssertionStoreLabel, kManifestLabelDelimiter, label),
.explanation = "c2pa.ingredient (v1) and c2pa.ingredient.v2 "
"assertions are not supported"});
return std::nullopt;
}
Assertion assertion;
assertion.set_label(label);
absl::Status status;
if (stripped_label == kActionsAssertionV2Label) {
status = ParseAssertion(content, assertion.mutable_actions());
} else if (stripped_label == kActionsAssertionV1Label) {
status = ParseAssertion(content, assertion.mutable_actions_v1());
} else if (IsBmffBasedHashLabel(stripped_label)) {
status = ParseAssertion(content, assertion.mutable_bmff_based_hash());
} else if (IsDataHashLabel(stripped_label)) {
status = ParseAssertion(content, assertion.mutable_data_hash());
} else if (stripped_label == kIngredientAssertionV3Label) {
status = ParseAssertion(content, assertion.mutable_ingredient_v3());
} else if (IsBoxesHashLabel(stripped_label)) {
status = ParseAssertion(content, assertion.mutable_boxes_hash());
} else if (stripped_label == kMultiAssetHashAssertionLabel) {
status = ParseAssertion(content, assertion.mutable_multi_asset_hash());
} else if (IsCollectionDataHashLabel(stripped_label)) {
status = ParseAssertion(content, assertion.mutable_collection_data_hash());
} else if (stripped_label == kAiDisclosureAssertionLabel) {
status = ParseAssertion(content, assertion.mutable_ai_disclosure());
} else if (stripped_label == kEnvironmentalSustainabilityAssertionLabel) {
status = ParseAssertion(content,
assertion.mutable_environmental_sustainability());
} else if (stripped_label == kSoftBindingAssertionLabel) {
status = ParseAssertion(content, assertion.mutable_soft_binding());
} else {
return std::nullopt;
}
if (!status.ok()) {
RecordAssertionParsingFailure(label, status, validation_tracker);
return std::nullopt;
}
return assertion;
}
std::optional<Assertion> AssertionParser::ParseThumbnail(
absl::string_view label, absl::string_view media_type,
absl::string_view data, std::optional<absl::string_view> file_name,
ValidationTracker& validation_tracker) const {
Assertion assertion;
assertion.set_label(label);
if (absl::StartsWith(label, kThumbnailClaimAssertionLabelPrefix)) {
*assertion.mutable_claim_thumbnail() =
ParseThumbnailAssertion(media_type, data, file_name);
return assertion;
}
if (absl::StartsWith(label, kThumbnailIngredientAssertionLabelPrefix)) {
*assertion.mutable_ingredient_thumbnail() =
ParseThumbnailAssertion(media_type, data, file_name);
return assertion;
}
return std::nullopt;
}
std::optional<Assertion> AssertionParser::ParseMetadata(
absl::string_view label, absl::string_view content,
ValidationTracker& validation_tracker) const {
Assertion assertion;
assertion.set_label(label);
absl::string_view stripped_label = StripMultipleInstanceSuffix(label);
if (stripped_label == kRepositoryReceiptAssertionLabel) {
if (auto status =
ParseAssertion(content, assertion.mutable_repository_receipt());
!status.ok()) {
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionJsonInvalid,
{.url = absl::StrCat(kAssertionStoreLabel, kManifestLabelDelimiter,
label),
.explanation =
absl::StrCat("Failed to parse the assertion with label: ", label,
" and error: ", status.message())});
return std::nullopt;
}
return assertion;
}
if (!absl::EndsWith(stripped_label, kMetadataAssertionLabelSuffix)) {
return std::nullopt;
}
if (auto status =
ParseStruct(content, assertion.mutable_metadata()->mutable_value());
!status.ok()) {
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionJsonInvalid,
{.url =
absl::StrCat(kAssertionStoreLabel, kManifestLabelDelimiter, label),
.explanation =
absl::StrCat("Failed to parse the assertion with label: ", label,
" and error: ", status.message())});
return std::nullopt;
}
if (!assertion.metadata().value().fields().contains("@context")) {
validation_tracker.RecordFailure(
FailureStatusCode::kGeneralError,
{.url =
absl::StrCat(kAssertionStoreLabel, kManifestLabelDelimiter, label),
.explanation = "Metadata doesn't have @context"});
return std::nullopt;
}
return assertion;
}
} // namespace credentio