| // 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/riff/reader.h" | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/substitute.h" | |
| #include "formats/riff/chunk_header.h" | |
| #include "riegeli/bytes/reader.h" | |
| namespace credentio { | |
| namespace { | |
| constexpr uint64_t kMaxPayloadSize = 1024 * 1024 * 10; // 10 MiB | |
| absl::StatusOr<bool> IterateOverRiffChunksInternal( | |
| riegeli::Reader& reader, uint64_t end_offset, | |
| RiffChunkProcessor processor) { | |
| while (reader.pos() + ChunkHeader::header_size() <= end_offset) { | |
| uint64_t chunk_offset = reader.pos(); | |
| ChunkHeader chunk_header; | |
| ABSL_RETURN_IF_ERROR(chunk_header.Read(reader)); | |
| RiffChunk chunk = { | |
| .offset = chunk_offset, | |
| .length = chunk_header.chunk_size(), | |
| .id = chunk_header.id, | |
| .type = "", | |
| .data_offset = chunk_offset + chunk_header.header_size(), | |
| .data_length = chunk_header.data_size, | |
| }; | |
| if (chunk.HasSubchunks()) { | |
| if (chunk.data_length < 4) { | |
| return absl::InvalidArgumentError( | |
| "RIFF/LIST chunk too short to contain a form type"); | |
| } | |
| if (!reader.Read(4, chunk.type)) { | |
| return reader.StatusOrAnnotate( | |
| absl::DataLossError("kUnexpectedEof; type")); | |
| } | |
| chunk.data_offset += 4; | |
| chunk.data_length -= 4; | |
| } | |
| if (chunk.length < ChunkHeader::header_size()) { | |
| return absl::InvalidArgumentError("Invalid RIFF chunk size"); | |
| } | |
| if (chunk.length > end_offset - chunk.offset) { | |
| return absl::InvalidArgumentError( | |
| "RIFF chunk extends beyond the end of the file"); | |
| } | |
| uint64_t chunk_end_offset = chunk.offset + chunk.length; | |
| bool continue_processing; | |
| if (auto result = processor(chunk); result.ok()) { | |
| continue_processing = *result; | |
| } else { | |
| return result.status(); | |
| } | |
| if (!continue_processing) { | |
| return false; | |
| } | |
| if (reader.pos() < chunk.data_offset + chunk.data_length && | |
| chunk.HasSubchunks()) { | |
| if (auto sub_continue_or = IterateOverRiffChunksInternal( | |
| reader, chunk_end_offset, processor); | |
| sub_continue_or.ok()) { | |
| continue_processing = *sub_continue_or; | |
| } else { | |
| return sub_continue_or.status(); | |
| } | |
| if (!continue_processing) { | |
| return false; | |
| } | |
| } | |
| reader.Seek(chunk_end_offset); | |
| if (reader.pos() <= chunk_offset) { | |
| return absl::InternalError( | |
| "RIFF parser detected a read stall (infinite loop prevented)"); | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| absl::Status IterateOverRiffChunks(riegeli::Reader& reader, | |
| RiffChunkProcessor processor, | |
| int64_t end_offset) { | |
| if (!reader.SupportsSize() || !reader.Size().has_value()) { | |
| return absl::InvalidArgumentError( | |
| "manifest store not embedded: reader size cannot be determined"); | |
| } | |
| uint64_t actual_end_offset = | |
| end_offset < 0 ? *reader.Size() | |
| : std::min(static_cast<uint64_t>(*reader.Size()), | |
| static_cast<uint64_t>(end_offset)); | |
| if ((actual_end_offset - reader.pos()) == 0) { | |
| return absl::OkStatus(); | |
| } | |
| return IterateOverRiffChunksInternal(reader, actual_end_offset, processor) | |
| .status(); | |
| } | |
| absl::StatusOr<std::string> ReadRiffChunkData(riegeli::Reader& reader, | |
| const RiffChunk& chunk) { | |
| if (IsRiffChunkTooLarge(chunk)) { | |
| return absl::InvalidArgumentError( | |
| absl::Substitute("RIFF C2PA chunk is too large to extract ($0 > $1)", | |
| chunk.data_length, kMaxPayloadSize)); | |
| } | |
| if (reader.pos() != chunk.data_offset) { | |
| if (!reader.Seek(chunk.data_offset) || reader.pos() != chunk.data_offset) { | |
| return absl::InvalidArgumentError("Failed to seek to chunk data offset"); | |
| } | |
| } | |
| std::string data; | |
| if (!reader.Read(chunk.data_length, data)) { | |
| return reader.StatusOrAnnotate(absl::DataLossError("kUnexpectedEof; data")); | |
| } | |
| return std::move(data); | |
| } | |
| bool IsRiffChunkTooLarge(const RiffChunk& chunk) { | |
| return chunk.data_length > kMaxPayloadSize; | |
| } | |
| } // namespace credentio |