| // 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/gif/reader.h" |
| |
| #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/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "formats/gif/constants.h" |
| #include "riegeli/bytes/reader.h" |
| |
| namespace credentio { |
| |
| namespace { |
| |
| struct ColorTable { |
| bool has_table; |
| uint8_t size; |
| |
| static ColorTable FromPacked(uint8_t packed) { |
| return { |
| .has_table = (packed & 0x80) != 0, |
| .size = static_cast<uint8_t>(packed & 0x07), |
| }; |
| } |
| }; |
| |
| absl::Status SeekToEndOfData(riegeli::Reader& input) { |
| uint8_t size = 0x00; |
| do { |
| if (!input.ReadByte(size)) { |
| return input.StatusOrAnnotate(absl::DataLossError("Failed to read byte")); |
| } |
| if (!input.ok()) { |
| return input.status(); |
| } |
| |
| if (size > 0) { |
| if (!input.Skip(size)) { |
| return absl::InternalError("Failed to seek to offset"); |
| } |
| if (!input.ok()) { |
| return input.status(); |
| } |
| } |
| } while (size > 0); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<GifBlock> ReadHeader(riegeli::Reader& input) { |
| GifBlock block = {.offset = input.pos()}; |
| |
| std::string data; |
| if (!input.Read(kGifHeaderBlock.size(), data)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read GIF header block")); |
| } |
| if (data != kGifHeaderBlock) { |
| return absl::InvalidArgumentError("Invalid GIF header block"); |
| } |
| |
| block.length = data.size(); |
| block.type = data; |
| return std::move(block); |
| } |
| |
| absl::Status PopulateDescriptor(riegeli::Reader& input, |
| int64_t packed_byte_offset, GifBlock& block) { |
| std::string data; |
| if (!input.Read(block.length, data)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read descriptor")); |
| } |
| |
| ColorTable packed = ColorTable::FromPacked(data[packed_byte_offset]); |
| |
| if (packed.has_table) { |
| int64_t ct_length = 3 * (1 << (packed.size + 1)); |
| block.length += ct_length; |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status PopulateLogicalDescriptor(riegeli::Reader& input, |
| GifBlock& block) { |
| block.length = 7; |
| block.type = std::string(kGifLogicalScreenDescriptorLabel); |
| return PopulateDescriptor(input, 4, block); |
| } |
| |
| absl::Status PopulateImageDescriptor(riegeli::Reader& input, GifBlock& block) { |
| // Backup one byte so we can read the introducer byte. |
| if (!input.Seek(input.pos() - 1)) { |
| return absl::InternalError("Failed to seek to offset"); |
| } |
| block.length = 10; |
| block.type = std::string(kGifImageDescriptorLabel); |
| return PopulateDescriptor(input, 9, block); |
| } |
| |
| absl::Status PopulateBlock(riegeli::Reader& input, GifBlock& block) { |
| ABSL_RETURN_IF_ERROR(SeekToEndOfData(input)); |
| block.length = input.pos() - block.offset; |
| return absl::OkStatus(); |
| } |
| |
| absl::Status PopulateApplicationExtension(riegeli::Reader& input, |
| GifBlock& block) { |
| int64_t next_size_offset = input.pos(); |
| |
| uint8_t extension_info_size = 0x00; |
| if (!input.ReadByte(extension_info_size)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read extension info size")); |
| } |
| |
| next_size_offset += 1 + extension_info_size; |
| |
| if (extension_info_size == kGifC2paExtensionInfoSize) { |
| std::string data; |
| if (!input.Read(kGifC2paIdentifier.size(), data)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read c2pa identifier")); |
| } |
| if (data == kGifC2paIdentifier) { |
| block.type = kGifC2paLabel; |
| } |
| } |
| |
| // Seek past the remaining Application Extension Information |
| if (!input.Seek(next_size_offset)) { |
| return absl::InternalError("Failed to seek to offset"); |
| } |
| |
| return PopulateBlock(input, block); |
| } |
| |
| } // namespace |
| |
| absl::Status IterateOverGifBlocks(riegeli::Reader& input, |
| GifBlockProcessor processor, |
| int64_t end_offset) { |
| if (!input.SupportsSize() || !input.Size().has_value()) { |
| return absl::InvalidArgumentError( |
| "Input does not support size or size is unknown"); |
| } |
| |
| uint64_t local_end_offset = |
| end_offset < 0 ? input.Size().value() : end_offset; |
| |
| ABSL_ASSIGN_OR_RETURN(auto header, ReadHeader(input)); |
| ABSL_ASSIGN_OR_RETURN(bool should_continue, processor(header)); |
| ABSL_RETURN_IF_ERROR(input.status()); |
| if (!should_continue) { |
| return absl::OkStatus(); |
| } |
| |
| if (input.pos() >= local_end_offset) { |
| return absl::OkStatus(); |
| } |
| |
| GifBlock logical_descriptor = {.offset = input.pos()}; |
| ABSL_RETURN_IF_ERROR(PopulateLogicalDescriptor(input, logical_descriptor)); |
| ABSL_ASSIGN_OR_RETURN(should_continue, processor(logical_descriptor)); |
| ABSL_RETURN_IF_ERROR(input.status()); |
| if (!should_continue) { |
| return absl::OkStatus(); |
| } |
| |
| if (!input.Seek(logical_descriptor.offset + logical_descriptor.length)) { |
| return input.StatusOrAnnotate(absl::InternalError( |
| "Failed to seek to offset after logical descriptor")); |
| } |
| |
| while (input.pos() < local_end_offset) { |
| GifBlock block = {.offset = input.pos()}; |
| |
| uint8_t introducer = 0x00; |
| if (!input.ReadByte(introducer)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read introducer")); |
| } |
| |
| switch (introducer) { |
| case kGifExtensionIntroducer: { |
| // is extension |
| uint8_t extension_type = 0x00; |
| if (!input.ReadByte(extension_type)) { |
| return input.StatusOrAnnotate( |
| absl::DataLossError("Failed to read extension type")); |
| } |
| |
| switch (extension_type) { |
| case kGifExtensionPlainText: |
| block.type = std::string(kGifExtensionPlainTextLabel); |
| ABSL_RETURN_IF_ERROR(PopulateBlock(input, block)); |
| break; |
| case kGifExtensionGraphicsControl: |
| block.type = std::string(kGifExtensionGraphicsControlLabel); |
| ABSL_RETURN_IF_ERROR(PopulateBlock(input, block)); |
| break; |
| case kGifExtensionComment: |
| block.type = std::string(kGifExtensionCommentLabel); |
| ABSL_RETURN_IF_ERROR(PopulateBlock(input, block)); |
| break; |
| case kGifExtensionApplication: |
| block.type = std::string(kGifExtensionApplicationLabel); |
| ABSL_RETURN_IF_ERROR(PopulateApplicationExtension(input, block)); |
| break; |
| default: |
| return absl::UnimplementedError(absl::StrFormat( |
| "Extension Type not supported: %#04x", extension_type)); |
| } |
| break; |
| } |
| case kGifImageDescriptorIntroducer: { |
| ABSL_RETURN_IF_ERROR(PopulateImageDescriptor(input, block)); |
| break; |
| } |
| case kGifTrailerIntroducer: { |
| block.length = 1; |
| block.type = std::string(kGifTrailerLabel); |
| break; |
| } |
| default: |
| block.type = std::string(kGifImageDataLabel); |
| ABSL_RETURN_IF_ERROR(PopulateBlock(input, block)); |
| } |
| |
| ABSL_ASSIGN_OR_RETURN(should_continue, processor(block)); |
| ABSL_RETURN_IF_ERROR(input.status()); |
| if (!should_continue) { |
| return absl::OkStatus(); |
| } |
| |
| if (!input.Seek(block.offset + block.length)) { |
| return input.StatusOrAnnotate( |
| absl::InternalError("Failed to seek to offset")); |
| } |
| |
| if (block.type == kGifTrailerLabel) { |
| break; |
| } |
| } |
| |
| if (input.pos() < local_end_offset) { |
| GifBlock after_label = {.offset = input.pos(), |
| .length = local_end_offset - input.pos(), |
| .type = "c2pa.after"}; |
| return processor(after_label).status(); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| } // namespace credentio |