| // 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 |