blob: e676c3d9ccdcc4a12bd112e2a3798a6699bf5706 [file] [log] [blame]
/*
* Copyright 2023 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/codec/SkTiffUtility.h"
#include "include/core/SkData.h"
#include "src/codec/SkCodecPriv.h"
#include <cstddef>
#include <utility>
namespace SkTiff {
bool ImageFileDirectory::IsValidType(uint16_t type) { return type >= 1 && type <= 12; }
size_t ImageFileDirectory::BytesForType(uint16_t type) {
switch (type) {
case kTypeUnsignedByte:
return 1;
case kTypeAsciiString:
return 1;
case kTypeUnsignedShort:
return kSizeShort;
case kTypeUnsignedLong:
return kSizeLong;
case kTypeUnsignedRational:
return 8;
case kTypeSignedByte:
return 1;
case kTypeUndefined:
return 1;
case kTypeSignedShort:
return kSizeShort;
case kTypeSignedLong:
return kSizeLong;
case kTypeSignedRational:
return 8;
case kTypeSingleFloat:
return 4;
case kTypeDoubleFloat:
return 8;
}
return 0;
}
// Helper function for computing the address of an entry.
static const uint8_t* get_entry_address(const SkData* data,
uint32_t ifdOffset,
uint16_t entryIndex) {
return data->bytes() + // Base address
ifdOffset + // IFD offset
kSizeShort + // IFD number of entries
kSizeEntry * entryIndex; // Entries
}
// Return true if the IFD starting at |ifdOffset| contains valid number of entries (that doesn't
// overrun |data|).
static bool validate_ifd(const SkData* data,
bool littleEndian,
uint32_t ifdOffset,
bool allowTruncated,
uint16_t* outNumEntries,
uint32_t* outNextIfdOffset) {
const uint8_t* dataCurrent = data->bytes();
size_t dataSize = data->size();
// Seek to the IFD offset.
if (dataSize < ifdOffset) {
SkCodecPrintf("IFD offset is too large.\n");
return false;
}
dataCurrent += ifdOffset;
dataSize -= ifdOffset;
// Read the number of entries.
if (dataSize < kSizeShort) {
SkCodecPrintf("Insufficient space to store number of entries.\n");
return false;
}
uint16_t numEntries = get_endian_short(dataCurrent, littleEndian);
dataCurrent += kSizeShort;
dataSize -= kSizeShort;
// Check that there is enough space for all entries.
if (dataSize < kSizeEntry * numEntries) {
SkCodecPrintf("Insufficient space (%u) to store all %u entries.\n",
static_cast<uint32_t>(data->size()),
numEntries);
if (allowTruncated) {
// Set the number of entries to the number of entries that can be fully read, and set
// the next IFD offset to 0 (indicating that there is no next IFD).
*outNumEntries = dataSize / kSizeEntry;
*outNextIfdOffset = 0;
return true;
}
return false;
}
// Save the number of entries.
*outNumEntries = numEntries;
// Seek past the entries.
dataCurrent += kSizeEntry * numEntries;
dataSize -= kSizeEntry * numEntries;
// Read the next IFD offset.
if (dataSize < kSizeLong) {
SkCodecPrintf("Insufficient space to store next IFD offset.\n");
if (allowTruncated) {
// Set the next IFD offset to 0 (indicating that there is no next IFD).
*outNextIfdOffset = 0;
return true;
}
return false;
}
// Save the next IFD offset.
*outNextIfdOffset = get_endian_int(dataCurrent, littleEndian);
return true;
}
bool ImageFileDirectory::ParseHeader(const SkData* data,
bool* outLittleEndian,
uint32_t* outIfdOffset) {
// Read the endianness (4 bytes) and IFD offset (4 bytes).
if (data->size() < 8) {
SkCodecPrintf("Tiff header must be at least 8 bytes.\n");
return false;
}
if (!is_valid_endian_marker(data->bytes(), outLittleEndian)) {
SkCodecPrintf("Tiff header had invalid endian marker 0x%x,0x%x,0x%x,0x%x.\n",
data->bytes()[0],
data->bytes()[1],
data->bytes()[2],
data->bytes()[3]);
return false;
}
*outIfdOffset = get_endian_int(data->bytes() + 4, *outLittleEndian);
return true;
}
std::unique_ptr<ImageFileDirectory> ImageFileDirectory::MakeFromOffset(sk_sp<SkData> data,
bool littleEndian,
uint32_t ifdOffset,
bool allowTruncated) {
uint16_t numEntries = 0;
uint32_t nextOffset = 0;
if (!validate_ifd(
data.get(), littleEndian, ifdOffset, allowTruncated, &numEntries, &nextOffset)) {
SkCodecPrintf("Failed to validate IFD.\n");
return nullptr;
}
return std::unique_ptr<ImageFileDirectory>(new ImageFileDirectory(
std::move(data), littleEndian, ifdOffset, numEntries, nextOffset));
}
ImageFileDirectory::ImageFileDirectory(sk_sp<SkData> data,
bool littleEndian,
uint32_t offset,
uint16_t numEntries,
uint32_t nextIfdOffset)
: fData(std::move(data))
, fLittleEndian(littleEndian)
, fOffset(offset)
, fNumEntries(numEntries)
, fNextIfdOffset(nextIfdOffset) {}
uint16_t ImageFileDirectory::getEntryTag(uint16_t entryIndex) const {
const uint8_t* entry = get_entry_address(fData.get(), fOffset, entryIndex);
return get_endian_short(entry, fLittleEndian);
}
bool ImageFileDirectory::getEntryRawData(uint16_t entryIndex,
uint16_t* outTag,
uint16_t* outType,
uint32_t* outCount,
const uint8_t** outData,
size_t* outDataSize) const {
const uint8_t* entry = get_entry_address(fData.get(), fOffset, entryIndex);
// Read the tag
const uint16_t tag = get_endian_short(entry, fLittleEndian);
entry += kSizeShort;
// Read the type.
const uint16_t type = get_endian_short(entry, fLittleEndian);
entry += kSizeShort;
if (!IsValidType(type)) {
return false;
}
// Read the count.
const uint32_t count = get_endian_int(entry, fLittleEndian);
entry += kSizeLong;
// If the entry fits in the remaining 4 bytes, use that.
const size_t entryDataBytes = BytesForType(type) * count;
const uint8_t* entryData = nullptr;
if (entryDataBytes <= kSizeLong) {
entryData = entry;
} else {
// Otherwise, the next 4 bytes specify an offset where the data can be found.
const uint32_t entryDataOffset = get_endian_int(entry, fLittleEndian);
if (fData->size() < entryDataOffset || fData->size() - entryDataOffset < entryDataBytes) {
return false;
}
entryData = fData->bytes() + entryDataOffset;
}
if (outTag) *outTag = tag;
if (outType) *outType = type;
if (outCount) *outCount = count;
if (outData) *outData = entryData;
if (outDataSize) *outDataSize = entryDataBytes;
return true;
}
sk_sp<SkData> ImageFileDirectory::getEntryUndefinedData(uint16_t entryIndex) const {
uint16_t type = 0;
uint32_t count = 0;
const uint8_t* data = nullptr;
size_t size = 0;
if (!getEntryRawData(entryIndex, nullptr, &type, &count, &data, &size)) {
return nullptr;
}
if (type != kTypeUndefined) {
return nullptr;
}
return SkData::MakeSubset(fData.get(), data - fData->bytes(), size);
}
bool ImageFileDirectory::getEntryValuesGeneric(uint16_t entryIndex,
uint16_t type,
uint32_t count,
void* values) const {
uint16_t entryType = 0;
uint32_t entryCount = 0;
const uint8_t* entryData = nullptr;
if (!getEntryRawData(entryIndex, nullptr, &entryType, &entryCount, &entryData, nullptr)) {
return false;
}
if (type != entryType) {
return false;
}
if (count != entryCount) {
return false;
}
for (uint32_t i = 0; i < count; ++i) {
const uint8_t* data = entryData + i * BytesForType(kTypeUnsignedLong);
switch (type) {
case kTypeUnsignedShort:
reinterpret_cast<uint16_t*>(values)[i] = get_endian_short(data, fLittleEndian);
break;
case kTypeUnsignedLong:
reinterpret_cast<uint32_t*>(values)[i] = get_endian_int(data, fLittleEndian);
break;
case kTypeSignedRational: {
uint32_t numerator = get_endian_int(data, fLittleEndian);
uint32_t denominator = get_endian_int(data + kSizeLong, fLittleEndian);
if (denominator == 0) {
// The TIFF specification does not indicate a behavior when the denominator is
// zero. The behavior of returning zero for a denominator of zero is a
// preservation of the behavior introduced in https://crrev.com/767874.
reinterpret_cast<float*>(values)[i] = 0;
} else {
reinterpret_cast<float*>(values)[i] =
numerator / static_cast<float>(denominator);
}
break;
}
case kTypeUnsignedRational: {
uint32_t numerator = get_endian_int(data, fLittleEndian);
uint32_t denominator = get_endian_int(data + kSizeLong, fLittleEndian);
if (denominator == 0) {
// See comments in kTypeSignedRational.
reinterpret_cast<float*>(values)[i] = 0.f;
} else {
reinterpret_cast<float*>(values)[i] =
numerator / static_cast<float>(denominator);
}
break;
}
default:
SkCodecPrintf("Unsupported type %u\n", type);
return false;
break;
}
}
return true;
}
} // namespace SkTiff