blob: 89afe5e8b8ae7ded00b4e4dbb924fb4b1d9544ca [file]
// 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