| // 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/tiff/reader.h" |
| |
| #include <sys/types.h> |
| |
| #include <cstdint> |
| #include <optional> |
| #include <vector> |
| |
| #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 "formats/byte_range.h" |
| #include "formats/tiff/constants.h" |
| #include "riegeli/bytes/reader.h" |
| #include "riegeli/endian/endian_reading.h" |
| |
| namespace credentio { |
| namespace { |
| template <typename T> |
| absl::Status ReadNumber(riegeli::Reader& source, TiffEndianness endianness, |
| T& value) { |
| bool success = endianness == TiffEndianness::kBigEndian |
| ? riegeli::ReadBigEndian<T>(source, value) |
| : riegeli::ReadLittleEndian<T>(source, value); |
| if (!success) { |
| return source.StatusOrAnnotate(absl::DataLossError("failed to read value")); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<TiffEndianness> GetEndianness(riegeli::Reader& source) { |
| uint32_t header; |
| if (!riegeli::ReadBigEndian<uint32_t>(source, header)) { |
| return source.StatusOrAnnotate( |
| absl::DataLossError("kUnexpectedEof; endianness")); |
| } |
| switch (header) { |
| case kTiffBigEndianHeader: |
| return TiffEndianness::kBigEndian; |
| case kTiffLittleEndianHeader: |
| return TiffEndianness::kLittleEndian; |
| default: |
| return absl::DataLossError("invalid endianness"); |
| } |
| } |
| |
| } // namespace |
| |
| ByteRange TiffImageFileDirectoryEntry::RangeOfCount() const { |
| return ByteRange{.offset = offset + sizeof(uint16_t) + sizeof(uint16_t), |
| .length = sizeof(uint32_t)}; |
| } |
| |
| ByteRange TiffImageFileDirectoryEntry::RangeOfValue() const { |
| uint64_t value_size = size(); |
| |
| return ByteRange{.offset = value_size > sizeof(uint32_t) |
| ? value_or_offset |
| : offset + sizeof(uint16_t) + |
| sizeof(uint16_t) + sizeof(uint32_t), |
| .length = value_size}; |
| } |
| |
| absl::Status IterateOverImageFileDirectories( |
| riegeli::Reader& source, std::optional<TiffEndianness> known_endianness, |
| absl::flat_hash_set<uint32_t>& visited_offsets, |
| ImageFileDirectoryProcessor process_ifd) { |
| TiffEndianness endianness; |
| if (known_endianness.has_value()) { |
| endianness = *known_endianness; |
| } else { |
| ABSL_ASSIGN_OR_RETURN(endianness, GetEndianness(source)); |
| } |
| |
| while (true) { |
| TiffImageFileDirectory ifd{.offset_of_pointer = |
| static_cast<uint32_t>(source.pos())}; |
| |
| uint32_t offset; |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, offset)); |
| ifd.offset = offset; |
| |
| if (!visited_offsets.insert(offset).second) { |
| return absl::InvalidArgumentError("infinite loop detected"); |
| } |
| |
| if (!source.Seek(offset)) { |
| return source.StatusOrAnnotate(absl::InternalError( |
| absl::StrCat("Failed to seek to offset: ", offset))); |
| } |
| |
| uint16_t count; |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, count)); |
| ifd.entries.resize(count); |
| for (int i = 0; i < count; ++i) { |
| TiffImageFileDirectoryEntry& entry = ifd.entries[i]; |
| entry.offset = source.pos(); |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, entry.tag)); |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, entry.type)); |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, entry.count)); |
| ABSL_RETURN_IF_ERROR( |
| ReadNumber(source, endianness, entry.value_or_offset)); |
| } |
| |
| uint64_t next_offset_position = source.pos(); |
| |
| ABSL_ASSIGN_OR_RETURN(bool continue_processing, |
| process_ifd(ifd, endianness)); |
| |
| if (!continue_processing) { |
| break; |
| } |
| |
| if (!source.Seek(next_offset_position)) { |
| return source.StatusOrAnnotate(absl::InternalError( |
| absl::StrCat("Failed to seek to offset: ", next_offset_position))); |
| } |
| |
| uint32_t next_offset; |
| ABSL_RETURN_IF_ERROR(ReadNumber(source, endianness, next_offset)); |
| |
| if (!source.Seek(next_offset_position)) { |
| return source.StatusOrAnnotate(absl::InternalError( |
| absl::StrCat("Failed to seek to offset: ", next_offset_position))); |
| } |
| |
| if (next_offset == 0) { |
| break; |
| } |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| } // namespace credentio |