blob: 89409f6723c3fcdbc7cf9f00cda0797ed0d66314 [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.
//
// TIFF Format Information:
// https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf
// C2PA embedding:
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_embedding_manifests_into_tiff_based_assets
#include "formats/tiff/extractor.h"
#include <sys/types.h>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include "absl/container/flat_hash_set.h"
#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/string_view.h"
#include "constants/labels.h"
#include "formats/byte_range.h"
#include "formats/tiff/constants.h"
#include "formats/tiff/reader.h"
#include "jumbf/utils.h"
#include "riegeli/bytes/reader.h"
namespace credentio {
namespace {
constexpr uint64_t kMaxPayloadSize = 1024 * 1024 * 10; // 10 MiB
absl::StatusOr<std::optional<ByteRange>> FindManifestStore(
riegeli::Reader& source) {
absl::flat_hash_set<uint32_t> visited_offsets;
std::optional<ByteRange> manifest_store_location = std::nullopt;
bool reading_first_ifd = true;
absl::Status iteration_status = IterateOverImageFileDirectories(
source, std::nullopt, visited_offsets,
[&reading_first_ifd, &manifest_store_location](
const TiffImageFileDirectory& ifd,
TiffEndianness endianness) -> absl::StatusOr<bool> {
// A previous loop found a manifest store.
if (manifest_store_location.has_value()) {
return absl::NotFoundError("Manifest Store must be in the last IFD");
}
for (const TiffImageFileDirectoryEntry& entry : ifd.entries) {
if (entry.tag != kTiffTagC2pa) {
continue;
}
if (!reading_first_ifd && ifd.entries.size() != 1) {
return absl::NotFoundError(
"Manifest Store must be the only entry in the IFD");
}
if (manifest_store_location.has_value()) {
return absl::NotFoundError("Multiple manifest stores found");
}
manifest_store_location = entry.RangeOfValue();
}
reading_first_ifd = false;
return true;
});
ABSL_RETURN_IF_ERROR(iteration_status);
return manifest_store_location;
}
bool IsPartialAsset(int64_t current_offset, int64_t end_offset,
int64_t asset_size) {
if (end_offset == -1) {
// Not a windowed read, so not partial.
return false;
}
if (current_offset == 0 && end_offset == asset_size) {
// The window covers the entire asset, so it is not partial.
return false;
}
return true;
}
} // namespace
absl::StatusOr<std::string> TiffExtractor::ExtractManifestStore(
riegeli::Reader& input) const {
ABSL_ASSIGN_OR_RETURN(std::optional<ByteRange> location,
ExtractManifestStoreLocation(input, {}));
if (!location.has_value()) {
return absl::NotFoundError("No manifest store found");
}
if (location->length > kMaxPayloadSize) {
return absl::InvalidArgumentError(
absl::StrCat("Manifest store is too large: ", location->length, " > ",
kMaxPayloadSize));
}
if (!input.Seek(location->offset)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to seek to offset"));
}
std::string manifest_store;
if (!input.Read(location->length, manifest_store)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read manifest store"));
}
return std::move(manifest_store);
}
absl::StatusOr<std::optional<ByteRange>>
TiffExtractor::ExtractManifestStoreLocation(riegeli::Reader& input,
ExtractOptions options) const {
if (IsPartialAsset(input.pos(), options.end_offset,
input.Size().value_or(0))) {
return absl::InvalidArgumentError(
"TiffExtractor::ExtractManifestStoreLocation only supports operations "
"over the entire file.");
}
if (!input.Seek(0)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to seek to offset"));
}
ABSL_ASSIGN_OR_RETURN(auto result, FindManifestStore(input));
if (!options.requires_c2pa) {
return result;
}
if (!result.has_value()) {
return absl::NotFoundError("No manifest store found");
}
return result;
}
bool TiffExtractor::MightBeC2paManifestStore(absl::string_view payload) const {
return jumbf::HasDescriptionBoxMatching(payload, kManifestStoreUuid,
kMinimumJumbfDescriptionToggles,
kManifestStoreLabel)
.value_or(false);
}
} // namespace credentio