| // 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 "formats/jpeg/extractor.h" |
| |
| #include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "constants/labels.h" |
| #include "formats/asset_box.h" |
| #include "formats/byte_range.h" |
| #include "formats/jpeg/c2pa_segment_handler.h" |
| #include "formats/jpeg/reader.h" |
| #include "jumbf/utils.h" |
| #include "riegeli/bytes/reader.h" |
| #include "utils/byte_readers.h" |
| |
| namespace credentio { |
| namespace { |
| |
| // APP11 Marker and APP11 JPEG Extension Type values are sourced from |
| // Section A.4 JPEG XT boxes of the ISO/IEC 18477-3:2023 |
| const uint16_t kApp11ExtensionType = 0x4a50; |
| |
| } // namespace |
| |
| absl::StatusOr<std::string> JpegExtractor::ExtractManifestStore( |
| riegeli::Reader& input) const { |
| C2paSegmentHandler segment_handler; |
| |
| ABSL_RETURN_IF_ERROR( |
| IterateOverJpegSegments(input, [&segment_handler](JpegSegment segment) { |
| segment_handler.RecordSegment(std::move(segment)); |
| return true; |
| })); |
| |
| ABSL_ASSIGN_OR_RETURN(auto c2pa_segment, |
| segment_handler.GetC2paSegment(input)); |
| |
| return std::move(c2pa_segment.manifest_store); |
| } |
| |
| absl::StatusOr<std::optional<ByteRange>> |
| JpegExtractor::ExtractManifestStoreLocation(riegeli::Reader& input, |
| ExtractOptions options) const { |
| C2paSegmentHandler c2pa_handler; |
| ABSL_RETURN_IF_ERROR(IterateOverJpegSegments( |
| input, |
| [&c2pa_handler, &options, &input](JpegSegment segment) { |
| if (options.end_offset >= 0 && |
| (segment.offset + segment.length) > options.end_offset) { |
| // This segment starts or ends beyond the window, ignore, stop reading |
| // and move the input's position to the declared end. |
| if (!input.Seek(options.end_offset)) { |
| return absl::StatusOr<bool>(input.StatusOrAnnotate( |
| absl::DataLossError("Failed to seek to end offset"))); |
| } |
| return absl::StatusOr<bool>(false); // stop iteration |
| } |
| |
| c2pa_handler.RecordSegment(std::move(segment)); |
| return absl::StatusOr<bool>(true); |
| }, |
| options.end_offset)); |
| |
| absl::StatusOr<C2paSegmentHandler::Segment> c2pa_segment = |
| c2pa_handler.GetC2paSegment(input); |
| if (!c2pa_segment.ok()) { |
| if (options.requires_c2pa) { |
| return c2pa_segment.status(); |
| } |
| return std::nullopt; |
| } |
| |
| return ByteRange{ |
| .offset = c2pa_segment->segment.offset, |
| .length = c2pa_segment->segment.length, |
| }; |
| } |
| |
| absl::StatusOr<std::vector<AssetBox>> JpegExtractor::ExtractBoxes( |
| riegeli::Reader& input, ExtractOptions options) const { |
| C2paSegmentHandler c2pa_handler; |
| std::vector<AssetBox> boxes; |
| ABSL_RETURN_IF_ERROR(IterateOverJpegSegments( |
| input, |
| [&boxes, &c2pa_handler](JpegSegment segment) { |
| boxes.push_back(AssetBox{ |
| .identifier = segment.marker_label, |
| .byte_range = {.offset = segment.offset, .length = segment.length}, |
| }); |
| |
| c2pa_handler.RecordSegment(std::move(segment)); |
| return true; |
| }, |
| options.end_offset)); |
| |
| absl::StatusOr<C2paSegmentHandler::Segment> c2pa_segment = |
| c2pa_handler.GetC2paSegment(input); |
| if (!c2pa_segment.ok()) { |
| if (options.requires_c2pa) { |
| return c2pa_segment.status(); |
| } |
| return boxes; |
| } |
| |
| uint64_t c2pa_start = c2pa_segment->segment.offset; |
| uint64_t c2pa_end = c2pa_start + c2pa_segment->segment.length; |
| |
| std::vector<AssetBox> merged_boxes; |
| merged_boxes.reserve(boxes.size()); |
| for (const auto& box : boxes) { |
| uint64_t box_start = box.byte_range.offset; |
| uint64_t box_end = box_start + box.byte_range.length; |
| |
| if (box_end <= c2pa_start || box_start >= c2pa_end) { |
| // Box is before or after the C2PA segment, add it. |
| merged_boxes.push_back(std::move(box)); |
| continue; |
| } |
| |
| if (box_end == c2pa_end) { |
| // Box is the last C2PA Segment, add the box |
| merged_boxes.push_back(AssetBox{ |
| .identifier = "C2PA", |
| .byte_range = {.offset = c2pa_start, |
| .length = c2pa_segment->segment.length}, |
| }); |
| continue; |
| } |
| |
| // Box is inside the C2PA segment, skip it. |
| } |
| return merged_boxes; |
| } |
| |
| bool JpegExtractor::MightBeC2paManifestStore(absl::string_view payload) const { |
| absl::StatusOr<uint16_t> extension_type = ConsumeUint<uint16_t>(&payload); |
| if (!extension_type.ok() || *extension_type != kApp11ExtensionType) { |
| return false; |
| } |
| |
| // Segment ID |
| auto iter_status = SkipBytes<uint16_t>(&payload); |
| if (!iter_status.ok()) { |
| return false; |
| } |
| |
| // Sequence Number |
| iter_status = SkipBytes<uint32_t>(&payload); |
| if (!iter_status.ok()) { |
| return false; |
| } |
| |
| return jumbf::HasDescriptionBoxMatching(payload, kManifestStoreUuid, |
| kMinimumJumbfDescriptionToggles, |
| kManifestStoreLabel) |
| .value_or(false); |
| } |
| |
| } // namespace credentio |