Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1/4 TIFF processing simplification by 'shifting' base of random access reader #581

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/jpeg/JpegMetadataReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -126,7 +126,7 @@ public static void process(@NotNull Metadata metadata, @NotNull InputStream inpu
processJpegSegmentData(metadata, readers, segmentData);
}

public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData)
public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData) throws IOException
{
// Pass the appropriate byte arrays to each reader.
for (JpegSegmentMetadataReader reader : readers) {
Expand Down
25 changes: 24 additions & 1 deletion Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
/*
* Copyright 2002-2022 Drew Noakes and contributors
*
* 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
*
* http://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.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.imaging.jpeg;

import java.io.IOException;

import com.drew.lang.annotations.NotNull;
import com.drew.metadata.Metadata;

Expand All @@ -21,6 +43,7 @@ public interface JpegSegmentMetadataReader
* encountered in the original file.
* @param metadata The {@link Metadata} object into which extracted values should be merged.
* @param segmentType The {@link JpegSegmentType} being read.
* @throws IOException an error occurred while accessing the required data
*/
void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType);
void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) throws IOException;
}
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/png/PngMetadataReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -331,7 +331,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c
} else if (chunkType.equals(PngChunkType.eXIf)) {
try {
ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0);
new TiffReader().processTiff(new ByteArrayReader(bytes), handler, 0);
new TiffReader().processTiff(new ByteArrayReader(bytes), handler);
} catch (TiffProcessingException ex) {
PngDirectory directory = new PngDirectory(PngChunkType.eXIf);
directory.addError(ex.getMessage());
Expand Down
3 changes: 1 addition & 2 deletions Source/com/drew/imaging/tiff/TiffHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,7 +57,6 @@ public interface TiffHandler

boolean customProcessTag(int tagOffset,
@NotNull Set<Integer> processedIfdOffsets,
int tiffHeaderOffset,
@NotNull RandomAccessReader reader,
int tagId,
int byteCount) throws IOException;
Expand Down
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/tiff/TiffMetadataReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -68,7 +68,7 @@ public static Metadata readMetadata(@NotNull RandomAccessReader reader) throws I
{
Metadata metadata = new Metadata();
ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0);
new TiffReader().processTiff(reader, handler, 0);
new TiffReader().processTiff(reader, handler);
return metadata;
}
}
50 changes: 23 additions & 27 deletions Source/com/drew/imaging/tiff/TiffReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,17 +40,15 @@ public class TiffReader
*
* @param reader the {@link RandomAccessReader} from which the data should be read
* @param handler the {@link TiffHandler} that will coordinate processing and accept read values
* @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
* @throws TiffProcessingException if an error occurred during the processing of TIFF data that could not be
* ignored or recovered from
* @throws IOException an error occurred while accessing the required data
*/
public void processTiff(@NotNull final RandomAccessReader reader,
@NotNull final TiffHandler handler,
final int tiffHeaderOffset) throws TiffProcessingException, IOException
@NotNull final TiffHandler handler) throws TiffProcessingException, IOException
{
// This must be either "MM" or "II".
short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset);
short byteOrderIdentifier = reader.getInt16(0);

if (byteOrderIdentifier == 0x4d4d) { // "MM"
reader.setMotorolaByteOrder(true);
Expand All @@ -61,21 +59,21 @@ public void processTiff(@NotNull final RandomAccessReader reader,
}

// Check the next two values for correctness.
final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);
final int tiffMarker = reader.getUInt16(2);
handler.setTiffMarker(tiffMarker);

int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
int firstIfdOffset = reader.getInt32(4);

// David Ekholm sent a digital camera image that has this problem
// TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream
if (firstIfdOffset >= reader.getLength() - 1) {
handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset");
// First directory normally starts immediately after the offset bytes, so try that
firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4;
firstIfdOffset = 2 + 2 + 4;
}

Set<Integer> processedIfdOffsets = new HashSet<Integer>();
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset);
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset);
}

/**
Expand All @@ -96,27 +94,28 @@ public void processTiff(@NotNull final RandomAccessReader reader,
*
* @param handler the {@link com.drew.imaging.tiff.TiffHandler} that will coordinate processing and accept read values
* @param reader the {@link com.drew.lang.RandomAccessReader} from which the data should be read
* @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
* @param processedGlobalIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
* @param ifdOffset the offset within <code>reader</code> at which the IFD data starts
* @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
* @throws IOException an error occurred while accessing the required data
*/
public static void processIfd(@NotNull final TiffHandler handler,
@NotNull final RandomAccessReader reader,
@NotNull final Set<Integer> processedIfdOffsets,
final int ifdOffset,
final int tiffHeaderOffset) throws IOException
@NotNull final Set<Integer> processedGlobalIfdOffsets,
final int ifdOffset) throws IOException
{
Boolean resetByteOrder = null;
try {
// check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) {
// Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist.
// Note that we track these offsets in the global frame, not the reader's local frame.
int globalIfdOffset = reader.toUnshiftedOffset(ifdOffset);
if (processedGlobalIfdOffsets.contains(Integer.valueOf(globalIfdOffset))) {
return;
}

// remember that we've visited this directory so that we don't visit it again later
processedIfdOffsets.add(ifdOffset);
processedGlobalIfdOffsets.add(globalIfdOffset);

// Validate IFD offset
if (ifdOffset >= reader.getLength() || ifdOffset < 0) {
handler.error("Ignored IFD marked to start outside data segment");
return;
Expand Down Expand Up @@ -180,13 +179,12 @@ public static void processIfd(@NotNull final TiffHandler handler,
final long tagValueOffset;
if (byteCount > 4) {
// If it's bigger than 4 bytes, the dir entry contains an offset.
final long offsetVal = reader.getUInt32(tagOffset + 8);
if (offsetVal + byteCount > reader.getLength()) {
tagValueOffset = reader.getUInt32(tagOffset + 8);
if (tagValueOffset + byteCount > reader.getLength()) {
// Bogus pointer offset and / or byteCount value
handler.error("Illegal TIFF tag pointer offset");
continue;
}
tagValueOffset = tiffHeaderOffset + offsetVal;
} else {
// 4 bytes or less and value is in the dir entry itself.
tagValueOffset = tagOffset + 8;
Expand All @@ -210,14 +208,14 @@ public static void processIfd(@NotNull final TiffHandler handler,
for (int i = 0; i < componentCount; i++) {
if (handler.tryEnterSubIfd(tagId)) {
isIfdPointer = true;
int subDirOffset = tiffHeaderOffset + reader.getInt32((int) (tagValueOffset + i * 4));
processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset);
long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4));
processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset);
}
}
}

// If it wasn't an IFD pointer, allow custom tag processing to occur
if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, (int) byteCount)) {
if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount)) {
// If no custom processing occurred, process the tag in the standard fashion
processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader);
}
Expand All @@ -227,10 +225,8 @@ public static void processIfd(@NotNull final TiffHandler handler,
final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount);
int nextIfdOffset = reader.getInt32(finalTagOffset);
if (nextIfdOffset != 0) {
nextIfdOffset += tiffHeaderOffset;
if (nextIfdOffset >= reader.getLength()) {
// Last 4 bytes of IFD reference another IFD with an address that is out of bounds
// Note this could have been caused by jhead 1.3 cropping too much
return;
} else if (nextIfdOffset < ifdOffset) {
// TODO is this a valid restriction?
Expand All @@ -239,7 +235,7 @@ public static void processIfd(@NotNull final TiffHandler handler,
}

if (handler.hasFollowerIfd()) {
processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset);
processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset);
}
}
} finally {
Expand Down Expand Up @@ -326,7 +322,7 @@ private static void processTag(@NotNull final TiffHandler handler,
break;
case TiffDataFormat.CODE_INT16_S:
if (componentCount == 1) {
handler.setInt16s(tagId, (int)reader.getInt16(tagValueOffset));
handler.setInt16s(tagId, reader.getInt16(tagValueOffset));
} else {
short[] array = new short[componentCount];
for (int i = 0; i < componentCount; i++)
Expand Down
13 changes: 12 additions & 1 deletion Source/com/drew/lang/ByteArrayReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,6 +60,17 @@ public ByteArrayReader(@NotNull byte[] buffer, int baseOffset)
_baseOffset = baseOffset;
}

@Override
public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException {
if (shift == 0) {
return this;
} else {
RandomAccessReader reader = new ByteArrayReader(_buffer, _baseOffset + shift);
reader.setMotorolaByteOrder(isMotorolaByteOrder());
return reader;
}
}

@Override
public int toUnshiftedOffset(int localOffset)
{
Expand Down
17 changes: 14 additions & 3 deletions Source/com/drew/lang/RandomAccessFileReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,6 +60,17 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) th
_length = _file.length();
}

@Override
public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException {
if (shift == 0) {
return this;
} else {
RandomAccessReader reader = new RandomAccessFileReader(_file, _baseOffset + shift);
reader.setMotorolaByteOrder(isMotorolaByteOrder());
return reader;
}
}

@Override
public int toUnshiftedOffset(int localOffset)
{
Expand All @@ -69,7 +80,7 @@ public int toUnshiftedOffset(int localOffset)
@Override
public long getLength()
{
return _length;
return _length - _baseOffset;
}

@Override
Expand Down Expand Up @@ -108,7 +119,7 @@ private void seek(final int index) throws IOException
if (index == _currentIndex)
return;

_file.seek(index);
_file.seek(index + _baseOffset);
_currentIndex = index;
}

Expand Down
4 changes: 3 additions & 1 deletion Source/com/drew/lang/RandomAccessReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
* Copyright 2002-2022 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -47,6 +47,8 @@ public abstract class RandomAccessReader
{
private boolean _isMotorolaByteOrder = true;

public abstract RandomAccessReader withShiftedBaseOffset(int shift) throws IOException;

public abstract int toUnshiftedOffset(int localOffset);

/**
Expand Down
Loading
Loading