diff options
Diffstat (limited to 'gallerycommon/src/com/android/gallery3d/exif/ExifParser.java')
-rw-r--r-- | gallerycommon/src/com/android/gallery3d/exif/ExifParser.java | 183 |
1 files changed, 128 insertions, 55 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java index f1e52c5b3..2cff12a3d 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java @@ -16,8 +16,9 @@ package com.android.gallery3d.exif; +import android.util.Log; + import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; @@ -64,6 +65,7 @@ import java.util.TreeMap; * </pre> */ public class ExifParser { + private static final String TAG = "ExifParser"; /** * When the parser reaches a new IFD area. Call * {@link #getCurrentIfd()} to know which IFD we are in. @@ -133,6 +135,10 @@ public class ExifParser { private static final int TAG_SIZE = 12; private static final int OFFSET_SIZE = 2; + private static final Charset US_ASCII = Charset.forName("US-ASCII"); + + private static final int DEFAULT_IFD0_OFFSET = 8; + private final CountedDataInputStream mTiffStream; private final int mOptions; private int mIfdStartOffset = 0; @@ -145,6 +151,9 @@ public class ExifParser { private ExifTag mJpegSizeTag; private boolean mNeedToParseOffsetsInCurrentIfd; private boolean mContainExifData = false; + private int mApp1End; + private byte[] mDataAboveIfd0; + private int mIfd0Position; private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>(); @@ -174,10 +183,17 @@ public class ExifParser { mTiffStream = new CountedDataInputStream(inputStream); mOptions = options; if (!mContainExifData) return; - if (mTiffStream.getReadByteCount() == 0) { - parseTiffHeader(); - long offset = mTiffStream.readUnsignedInt(); - registerIfd(IfdId.TYPE_IFD_0, offset); + + parseTiffHeader(); + long offset = mTiffStream.readUnsignedInt(); + if (offset > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException("Invalid offset " + offset); + } + mIfd0Position = (int) offset; + registerIfd(IfdId.TYPE_IFD_0, offset); + if (offset != DEFAULT_IFD0_OFFSET) { + mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; + read(mDataAboveIfd0); } } @@ -225,33 +241,59 @@ public class ExifParser { int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; if (offset < endOfTags) { mTag = readTag(); + if (mTag == null) { + return next(); + } if (mNeedToParseOffsetsInCurrentIfd) { checkOffsetOrImageTag(mTag); } return EVENT_NEW_TAG; } else if (offset == endOfTags) { - long ifdOffset = readUnsignedLong(); // There is a link to ifd1 at the end of ifd0 if (mIfdType == IfdId.TYPE_IFD_0) { + long ifdOffset = readUnsignedLong(); if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { if (ifdOffset != 0) { registerIfd(IfdId.TYPE_IFD_1, ifdOffset); } } } else { - if (ifdOffset != 0) { - throw new ExifInvalidFormatException("Invalid link to next IFD"); + int offsetSize = 4; + // Some camera models use invalid length of the offset + if (mCorrespondingEvent.size() > 0) { + offsetSize = mCorrespondingEvent.firstEntry().getKey() - + mTiffStream.getReadByteCount(); + } + if (offsetSize < 4) { + Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize); + } else { + long ifdOffset = readUnsignedLong(); + if (ifdOffset != 0) { + Log.w(TAG, "Invalid link to next IFD: " + ifdOffset); + } } } } while(mCorrespondingEvent.size() != 0) { Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry(); Object event = entry.getValue(); - skipTo(entry.getKey()); + try { + skipTo(entry.getKey()); + } catch (IOException e) { + Log.w(TAG, "Failed to skip to data at: " + entry.getKey() + + " for " + event.getClass().getName() + ", the file may be broken."); + continue; + } if (event instanceof IfdEvent) { mIfdType = ((IfdEvent) event).ifd; mNumOfTagInIfd = mTiffStream.readUnsignedShort(); mIfdStartOffset = entry.getKey(); + + if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) { + Log.w(TAG, "Invalid size of IFD " + mIfdType); + return EVENT_END; + } + mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd(); if (((IfdEvent) event).isRequested) { return EVENT_START_OF_IFD; @@ -290,8 +332,9 @@ public class ExifParser { if (mNeedToParseOffsetsInCurrentIfd) { while (offset < endOfTags) { mTag = readTag(); - checkOffsetOrImageTag(mTag); offset += TAG_SIZE; + if (mTag == null) continue; + checkOffsetOrImageTag(mTag); } } else { skipTo(endOfTags); @@ -342,7 +385,6 @@ public class ExifParser { * @see #read(byte[], int, int) * @see #readLong() * @see #readRational() - * @see #readShort() * @see #readString(int) * @see #readString(int, Charset) */ @@ -393,13 +435,7 @@ public class ExifParser { */ public int getStripSize() { if (mStripSizeTag == null) return 0; - if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex); - } else { - // Cast unsigned int to int since the strip size is always smaller - // than the size of APP1 (65536) - return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex); - } + return (int) mStripSizeTag.getValueAt(0); } /** @@ -408,9 +444,7 @@ public class ExifParser { */ public int getCompressedImageSize() { if (mJpegSizeTag == null) return 0; - // Cast unsigned int to int since the thumbnail is always smaller - // than the size of APP1 (65536) - return (int) mJpegSizeTag.getUnsignedLong(0); + return (int) mJpegSizeTag.getValueAt(0); } private void skipTo(int offset) throws IOException { @@ -425,7 +459,6 @@ public class ExifParser { * the tag may not contain the value if the size of the value is greater than 4 bytes. * When the value is not available here, call this method so that the parser will emit * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located. - * @see #EVENT_VALUE_OF_REGISTERED_TAG */ public void registerForTagValue(ExifTag tag) { @@ -455,6 +488,12 @@ public class ExifParser { throw new ExifInvalidFormatException( "Number of component is larger then Integer.MAX_VALUE"); } + // Some invalid image file contains invalid data type. Ignore those tags + if (!ExifTag.isValidType(dataFormat)) { + Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat)); + mTiffStream.skip(4); + return null; + } ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType); int dataSize = tag.getDataSize(); if (dataSize > 4) { @@ -463,7 +502,16 @@ public class ExifParser { throw new ExifInvalidFormatException( "offset is larger then Integer.MAX_VALUE"); } - tag.setOffset((int) offset); + // Some invalid images put some undefined data before IFD0. + // Read the data here. + if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) { + byte[] buf = new byte[(int) numOfComp]; + System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, + buf, 0, (int) numOfComp); + tag.setValue(buf); + } else { + tag.setOffset((int) offset); + } } else { readFullTagValue(tag); mTiffStream.skip(4 - dataSize); @@ -476,26 +524,30 @@ public class ExifParser { * caller is interested in, register the IFD or image. */ private void checkOffsetOrImageTag(ExifTag tag) { + // Some invalid formattd image contains tag with 0 size. + if (tag.getComponentCount() == 0) { + return; + } switch (tag.getTagId()) { case ExifTag.TAG_EXIF_IFD: if (isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); } break; case ExifTag.TAG_GPS_IFD: if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); } break; case ExifTag.TAG_INTEROPERABILITY_IFD: if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT: if (isThumbnailRequested()) { - registerCompressedImage(tag.getUnsignedLong(0)); + registerCompressedImage(tag.getValueAt(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: @@ -508,9 +560,9 @@ public class ExifParser { if (tag.hasValue()) { for (int i = 0; i < tag.getComponentCount(); i++) { if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - registerUncompressedStrip(i, tag.getUnsignedShort(i)); + registerUncompressedStrip(i, tag.getValueAt(i)); } else { - registerUncompressedStrip(i, tag.getUnsignedLong(i)); + registerUncompressedStrip(i, tag.getValueAt(i)); } } } else { @@ -528,7 +580,22 @@ public class ExifParser { } } - private void readFullTagValue(ExifTag tag) throws IOException { + void readFullTagValue(ExifTag tag) throws IOException { + // Some invalid images contains tags with wrong size, check it here + short type = tag.getDataType(); + if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || + type == ExifTag.TYPE_UNSIGNED_BYTE) { + int size = tag.getComponentCount(); + if (mCorrespondingEvent.size() > 0) { + if (mCorrespondingEvent.firstEntry().getKey() < + mTiffStream.getReadByteCount() + size) { + Log.w(TAG, "Invalid size of tag."); + size = mCorrespondingEvent.firstEntry().getKey() + - mTiffStream.getReadByteCount(); + tag.setComponentCount(size); + } + } + } switch(tag.getDataType()) { case ExifTag.TYPE_UNSIGNED_BYTE: case ExifTag.TYPE_UNDEFINED: @@ -610,29 +677,36 @@ public class ExifParser { ExifInvalidFormatException { DataInputStream dataStream = new DataInputStream(inputStream); - // SOI and APP1 if (dataStream.readShort() != JpegHeader.SOI) { throw new ExifInvalidFormatException("Invalid JPEG format"); } short marker = dataStream.readShort(); - while(marker != JpegHeader.APP1 && marker != JpegHeader.EOI + while(marker != JpegHeader.EOI && !JpegHeader.isSofMarker(marker)) { int length = dataStream.readUnsignedShort(); - if ((length - 2) != dataStream.skip(length - 2)) { - throw new EOFException(); + // Some invalid formatted image contains multiple APP1, + // try to find the one with Exif data. + if (marker == JpegHeader.APP1) { + int header = 0; + short headerTail = 0; + if (length >= 8) { + header = dataStream.readInt(); + headerTail = dataStream.readShort(); + length -= 6; + if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { + mApp1End = length; + return true; + } + } + } + if (length < 2 || (length - 2) != dataStream.skip(length - 2)) { + Log.w(TAG, "Invalid JPEG format."); + return false; } marker = dataStream.readShort(); } - - if (marker != JpegHeader.APP1) return false; // No APP1 segment - - // APP1 length, it's not used for us - dataStream.readShort(); - - // Exif header - return (dataStream.readInt() == EXIF_HEADER - && dataStream.readShort() == EXIF_HEADER_TAIL); + return false; } /** @@ -650,27 +724,26 @@ public class ExifParser { } /** - * Reads a String from the InputStream with UTF8 charset. + * Reads a String from the InputStream with US-ASCII charset. + * The parser will read n bytes and convert it to ascii string. * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. */ public String readString(int n) throws IOException { - if (n > 0) { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, "UTF8"); - } else { - return ""; - } + return readString(n, US_ASCII); } /** * Reads a String from the InputStream with the given charset. + * The parser will read n bytes and convert it to string. * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. */ public String readString(int n, Charset charset) throws IOException { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, charset); + if (n > 0) { + byte[] buf = new byte[n]; + return mTiffStream.readString(n, charset); + } else { + return ""; + } } /** @@ -749,4 +822,4 @@ public class ExifParser { public ByteOrder getByteOrder() { return mTiffStream.getByteOrder(); } -}
\ No newline at end of file +} |