summaryrefslogtreecommitdiffstats
path: root/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'gallerycommon/src/com/android/gallery3d/exif/ExifParser.java')
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifParser.java183
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
+}