diff --git a/app/src/main/java/androidxc/exifinterface/media/ExifInterface.java b/app/src/main/java/androidxc/exifinterface/media/ExifInterface.java index 1ea73fe80..ecb8d1788 100644 --- a/app/src/main/java/androidxc/exifinterface/media/ExifInterface.java +++ b/app/src/main/java/androidxc/exifinterface/media/ExifInterface.java @@ -16,7 +16,6 @@ package androidxc.exifinterface.media; -import static androidxc.exifinterface.media.ExifInterfaceUtils.byteArrayToHexString; import static androidxc.exifinterface.media.ExifInterfaceUtils.closeFileDescriptor; import static androidxc.exifinterface.media.ExifInterfaceUtils.closeQuietly; import static androidxc.exifinterface.media.ExifInterfaceUtils.convertToLongArray; @@ -24,6 +23,9 @@ import static androidxc.exifinterface.media.ExifInterfaceUtils.parseSubSeconds; import static androidxc.exifinterface.media.ExifInterfaceUtils.startsWith; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + import android.annotation.SuppressLint; import android.content.res.AssetManager; import android.graphics.Bitmap; @@ -85,7 +87,11 @@ *
* Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF. *
- * Supported for writing: JPEG, PNG, WebP, DNG. + * Supported for writing: JPEG, PNG, WebP. + *
+ * Note: JPEG and HEIF files may contain XMP data either inside the Exif data chunk or outside of
+ * it. This class will search both locations for XMP data, but if XMP data exist both inside and
+ * outside Exif, will favor the XMP data inside Exif over the one outside.
*/
public class ExifInterface {
private static final String TAG = "ExifInterface";
@@ -3722,7 +3728,6 @@ boolean isFormatCompatible(int format) {
new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
- new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE),
new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
@@ -3890,7 +3895,7 @@ boolean isFormatCompatible(int format) {
@SuppressWarnings("unchecked")
private final HashMap
- * This method is supported for JPEG, PNG, WebP, and DNG formats.
+ * This method is supported for JPEG, PNG, and WebP formats.
*
* Note: after calling this method, any attempts to obtain range information
* from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
@@ -4681,7 +4683,7 @@ private void printAttributes() {
public void saveAttributes() throws IOException {
if (!isSupportedFormatForSavingAttributes(mMimeType)) {
throw new IOException("ExifInterface only supports saving attributes for JPEG, PNG, "
- + "WebP, and DNG formats.");
+ + "and WebP formats.");
}
if (mSeekableFileDescriptor == null && mFilename == null) {
throw new IOException(
@@ -4750,10 +4752,6 @@ public void saveAttributes() throws IOException {
savePngAttributes(bufferedIn, bufferedOut);
} else if (mMimeType == IMAGE_TYPE_WEBP) {
saveWebpAttributes(bufferedIn, bufferedOut);
- } else if (mMimeType == IMAGE_TYPE_DNG || mMimeType == IMAGE_TYPE_UNKNOWN) {
- ByteOrderedDataOutputStream dataOutputStream =
- new ByteOrderedDataOutputStream(bufferedOut, ByteOrder.BIG_ENDIAN);
- writeExifSegment(dataOutputStream);
}
} catch (Exception e) {
try {
@@ -4794,7 +4792,7 @@ public void saveAttributes() throws IOException {
public void saveAttributes(InputStream original, OutputStream out) throws IOException {
if (!isSupportedFormatForSavingAttributes(mMimeType)) {
throw new IOException("ExifInterface only supports saving attributes for JPEG, PNG, "
- + "WebP, and DNG formats.");
+ + "and DNG formats.");
}
if (mHasThumbnail && mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
@@ -4815,10 +4813,6 @@ public void saveAttributes(InputStream original, OutputStream out) throws IOExce
savePngAttributes(original, out);
} else if (mMimeType == IMAGE_TYPE_WEBP) {
saveWebpAttributes(original, out);
- } else if (mMimeType == IMAGE_TYPE_DNG || mMimeType == IMAGE_TYPE_UNKNOWN) {
- ByteOrderedDataOutputStream dataOutputStream =
- new ByteOrderedDataOutputStream(out, ByteOrder.BIG_ENDIAN);
- writeExifSegment(dataOutputStream);
}
// Discard the thumbnail in memory
@@ -4896,15 +4890,11 @@ public byte[] getThumbnailBytes() {
throw new FileNotFoundException();
}
- if (in.skip(mThumbnailOffset + mOffsetToExifData)
- != mThumbnailOffset + mOffsetToExifData) {
- throw new IOException("Corrupted image");
- }
+ ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in);
+ inputStream.skipFully(mThumbnailOffset + mOffsetToExifData);
// TODO: Need to handle potential OutOfMemoryError
byte[] buffer = new byte[mThumbnailLength];
- if (in.read(buffer) != mThumbnailLength) {
- throw new IOException("Corrupted image");
- }
+ inputStream.readFully(buffer);
mThumbnailBytes = buffer;
return buffer;
} catch (Exception e) {
@@ -5375,7 +5365,7 @@ private String convertDecimalDegree(double decimalDegree) {
}
// Checks the type of image file
- private int getMimeType(InputStream in) throws IOException {
+ private int getMimeType(BufferedInputStream in) throws IOException {
in.mark(SIGNATURE_CHECK_SIZE);
byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
in.read(signatureCheckBytes);
@@ -5435,7 +5425,7 @@ private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException {
long chunkSize = signatureInputStream.readInt();
byte[] chunkType = new byte[4];
- signatureInputStream.read(chunkType);
+ signatureInputStream.readFully(chunkType);
if (!Arrays.equals(chunkType, HEIF_TYPE_FTYP)) {
return false;
@@ -5470,7 +5460,9 @@ private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException {
boolean isMif1 = false;
boolean isHeic = false;
for (long i = 0; i < chunkDataSize / 4; ++i) {
- if (signatureInputStream.read(brand) != brand.length) {
+ try {
+ signatureInputStream.readFully(brand);
+ } catch (EOFException e) {
return false;
}
if (i == 1) {
@@ -5620,7 +5612,7 @@ private void getJpegAttributes(ByteOrderedDataInputStream in, int offsetToJpeg,
Log.d(TAG, "getJpegAttributes starting with: " + in);
}
// JPEG uses Big Endian by default. See https://people.cs.umass.edu/~verts/cs32/endian.html
- in.setByteOrder(ByteOrder.BIG_ENDIAN);
+ in.setByteOrder(BIG_ENDIAN);
int bytesRead = 0;
@@ -5695,9 +5687,7 @@ private void getJpegAttributes(ByteOrderedDataInputStream in, int offsetToJpeg,
case MARKER_COM: {
byte[] bytes = new byte[length];
- if (in.read(bytes) != length) {
- throw new IOException("Invalid exif");
- }
+ in.readFully(bytes);
length = 0;
if (getAttribute(TAG_USER_COMMENT) == null) {
mAttributes[IFD_TYPE_EXIF].put(TAG_USER_COMMENT, ExifAttribute.createString(
@@ -5804,16 +5794,16 @@ private void getRafAttributes(ByteOrderedDataInputStream in) throws IOException
byte[] offsetToJpegBytes = new byte[4];
byte[] jpegLengthBytes = new byte[4];
byte[] cfaHeaderOffsetBytes = new byte[4];
- in.read(offsetToJpegBytes);
- in.read(jpegLengthBytes);
- in.read(cfaHeaderOffsetBytes);
+ in.readFully(offsetToJpegBytes);
+ in.readFully(jpegLengthBytes);
+ in.readFully(cfaHeaderOffsetBytes);
int offsetToJpeg = ByteBuffer.wrap(offsetToJpegBytes).getInt();
int jpegLength = ByteBuffer.wrap(jpegLengthBytes).getInt();
int cfaHeaderOffset = ByteBuffer.wrap(cfaHeaderOffsetBytes).getInt();
byte[] jpegBytes = new byte[jpegLength];
in.skipFully(offsetToJpeg - in.position());
- in.read(jpegBytes);
+ in.readFully(jpegBytes);
// Retrieve JPEG image metadata
ByteOrderedDataInputStream jpegInputStream = new ByteOrderedDataInputStream(jpegBytes);
@@ -5823,7 +5813,7 @@ private void getRafAttributes(ByteOrderedDataInputStream in) throws IOException
in.skipFully(cfaHeaderOffset - in.position());
// Retrieve primary image length/width values, if TAG_RAF_IMAGE_SIZE exists
- in.setByteOrder(ByteOrder.BIG_ENDIAN);
+ in.setByteOrder(BIG_ENDIAN);
int numberOfDirectoryEntry = in.readInt();
if (DEBUG) {
Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
@@ -5981,9 +5971,7 @@ public long getSize() throws IOException {
}
in.seek(offset);
byte[] identifier = new byte[6];
- if (in.read(identifier) != 6) {
- throw new IOException("Can't read identifier");
- }
+ in.readFully(identifier);
offset += 6;
length -= 6;
if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
@@ -5992,14 +5980,11 @@ public long getSize() throws IOException {
// TODO: Need to handle potential OutOfMemoryError
byte[] bytes = new byte[length];
- if (in.read(bytes) != length) {
- throw new IOException("Can't read exif");
- }
+ in.readFully(bytes);
// Save offset to EXIF data for handling thumbnail and attribute offsets.
mOffsetToExifData = offset;
readExifSegment(bytes, IFD_TYPE_PRIMARY);
}
-
if (DEBUG) {
Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
}
@@ -6154,7 +6139,7 @@ private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException
// PNG uses Big Endian by default.
// See PNG (Portable Network Graphics) Specification, Version 1.2,
// 2.1. Integers and byte order
- in.setByteOrder(ByteOrder.BIG_ENDIAN);
+ in.setByteOrder(BIG_ENDIAN);
int bytesRead = 0;
@@ -6178,10 +6163,7 @@ private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException
bytesRead += 4;
byte[] type = new byte[PNG_CHUNK_TYPE_BYTE_LENGTH];
- if (in.read(type) != type.length) {
- throw new IOException("Encountered invalid length while parsing PNG chunk"
- + "type");
- }
+ in.readFully(type);
bytesRead += PNG_CHUNK_TYPE_BYTE_LENGTH;
// The first chunk must be the IHDR chunk
@@ -6196,11 +6178,7 @@ private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException
} else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
// TODO: Need to handle potential OutOfMemoryError
byte[] data = new byte[length];
- if (in.read(data) != length) {
- throw new IOException("Failed to read given length for given PNG chunk "
- + "type: " + byteArrayToHexString(type));
- }
-
+ in.readFully(data);
// Compare CRC values for potential data corruption.
int dataCrcValue = in.readInt();
// Cyclic Redundancy Code used to check for corruption of the data
@@ -6241,7 +6219,7 @@ private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException
}
// WebP uses little-endian by default.
// See Section "Terminology & Basics"
- in.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ in.setByteOrder(LITTLE_ENDIAN);
in.skipFully(WEBP_SIGNATURE_1.length);
// File size corresponds to the size of the entire file from offset 8.
@@ -6265,11 +6243,8 @@ private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException
// Chunk Size is odd.
// See Section "RIFF File Format"
byte[] code = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
- if (in.read(code) != code.length) {
- throw new IOException("Encountered invalid length while parsing WebP chunk"
- + "type");
- }
- bytesRead += 4;
+ in.readFully(code);
+ bytesRead += WEBP_CHUNK_TYPE_BYTE_LENGTH;
int chunkSize = in.readInt();
bytesRead += 4;
@@ -6277,10 +6252,7 @@ private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException
if (Arrays.equals(WEBP_CHUNK_TYPE_EXIF, code)) {
// TODO: Need to handle potential OutOfMemoryError
byte[] payload = new byte[chunkSize];
- if (in.read(payload) != chunkSize) {
- throw new IOException("Failed to read given length for given PNG chunk "
- + "type: " + byteArrayToHexString(code));
- }
+ in.readFully(payload);
// Save offset to EXIF data for handling thumbnail and attribute offsets.
mOffsetToExifData = bytesRead;
readExifSegment(payload, IFD_TYPE_PRIMARY);
@@ -6321,7 +6293,7 @@ private void saveJpegAttributes(InputStream inputStream, OutputStream outputStre
}
ByteOrderedDataInputStream dataInputStream = new ByteOrderedDataInputStream(inputStream);
ByteOrderedDataOutputStream dataOutputStream =
- new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
+ new ByteOrderedDataOutputStream(outputStream, BIG_ENDIAN);
if (dataInputStream.readByte() != MARKER) {
throw new IOException("Invalid marker");
}
@@ -6365,9 +6337,7 @@ private void saveJpegAttributes(InputStream inputStream, OutputStream outputStre
}
byte[] identifier = new byte[6];
if (length >= 6) {
- if (dataInputStream.read(identifier) != 6) {
- throw new IOException("Invalid exif");
- }
+ dataInputStream.readFully(identifier);
if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
// Skip the original EXIF APP1 segment.
dataInputStream.skipFully(length - 6);
@@ -6428,7 +6398,7 @@ private void savePngAttributes(InputStream inputStream, OutputStream outputStrea
}
ByteOrderedDataInputStream dataInputStream = new ByteOrderedDataInputStream(inputStream);
ByteOrderedDataOutputStream dataOutputStream =
- new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
+ new ByteOrderedDataOutputStream(outputStream, BIG_ENDIAN);
// Copy PNG signature bytes
copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length);
@@ -6465,8 +6435,7 @@ private void savePngAttributes(InputStream inputStream, OutputStream outputStrea
// the chunk type bytes and the chunk data bytes.
exifByteArrayOutputStream = new ByteArrayOutputStream();
ByteOrderedDataOutputStream exifDataOutputStream =
- new ByteOrderedDataOutputStream(exifByteArrayOutputStream,
- ByteOrder.BIG_ENDIAN);
+ new ByteOrderedDataOutputStream(exifByteArrayOutputStream, BIG_ENDIAN);
// Store Exif data in separate byte array
writeExifSegment(exifDataOutputStream);
@@ -6527,9 +6496,9 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
+ ", outputStream: " + outputStream + ")");
}
ByteOrderedDataInputStream totalInputStream =
- new ByteOrderedDataInputStream(inputStream, ByteOrder.LITTLE_ENDIAN);
+ new ByteOrderedDataInputStream(inputStream, LITTLE_ENDIAN);
ByteOrderedDataOutputStream totalOutputStream =
- new ByteOrderedDataOutputStream(outputStream, ByteOrder.LITTLE_ENDIAN);
+ new ByteOrderedDataOutputStream(outputStream, LITTLE_ENDIAN);
// WebP signature
copy(totalInputStream, totalOutputStream, WEBP_SIGNATURE_1.length);
@@ -6541,8 +6510,7 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
try {
nonHeaderByteArrayOutputStream = new ByteArrayOutputStream();
ByteOrderedDataOutputStream nonHeaderOutputStream =
- new ByteOrderedDataOutputStream(nonHeaderByteArrayOutputStream,
- ByteOrder.LITTLE_ENDIAN);
+ new ByteOrderedDataOutputStream(nonHeaderByteArrayOutputStream, LITTLE_ENDIAN);
if (mOffsetToExifData != 0) {
// EXIF chunk exists in the original file
@@ -6556,6 +6524,11 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
// Skip input stream to the end of the EXIF chunk
totalInputStream.skipFully(WEBP_CHUNK_TYPE_BYTE_LENGTH);
int exifChunkLength = totalInputStream.readInt();
+ // RIFF chunks have a single padding byte at the end if the declared chunk size is
+ // odd.
+ if (exifChunkLength % 2 != 0) {
+ exifChunkLength++;
+ }
totalInputStream.skipFully(exifChunkLength);
// Write new EXIF chunk to output stream
@@ -6563,17 +6536,14 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
} else {
// EXIF chunk does not exist in the original file
byte[] firstChunkType = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
- if (totalInputStream.read(firstChunkType) != firstChunkType.length) {
- throw new IOException("Encountered invalid length while parsing WebP chunk "
- + "type");
- }
+ totalInputStream.readFully(firstChunkType);
if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8X)) {
// Original file already includes other extra data
int size = totalInputStream.readInt();
// WebP files have a single padding byte at the end if the chunk size is odd.
byte[] data = new byte[(size % 2) == 1 ? size + 1 : size];
- totalInputStream.read(data);
+ totalInputStream.readFully(data);
// Set the EXIF flag to 1
data[0] = (byte) (data[0] | (1 << 3));
@@ -6599,10 +6569,14 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
while (true) {
byte[] type = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
- @SuppressWarnings("unused")
- int read = inputStream.read(type);
- if (!Arrays.equals(type, WEBP_CHUNK_TYPE_ANMF)) {
- // Either we have reached EOF or the start of a non-ANMF chunk
+ boolean animationFinished = false;
+ try {
+ totalInputStream.readFully(type);
+ animationFinished = !Arrays.equals(type, WEBP_CHUNK_TYPE_ANMF);
+ } catch (EOFException e) {
+ animationFinished = true;
+ }
+ if (animationFinished) {
writeExifSegment(nonHeaderOutputStream);
break;
}
@@ -6627,19 +6601,18 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
int widthAndHeight = 0;
int width = 0;
int height = 0;
- int alpha = 0;
+ boolean alpha = false;
// Save VP8 frame data for later
byte[] vp8Frame = new byte[3];
if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8)) {
- totalInputStream.read(vp8Frame);
+ totalInputStream.readFully(vp8Frame);
// Check signature
byte[] vp8Signature = new byte[3];
- if (totalInputStream.read(vp8Signature) != vp8Signature.length
- || !Arrays.equals(WEBP_VP8_SIGNATURE, vp8Signature)) {
- throw new IOException("Encountered error while checking VP8 "
- + "signature");
+ totalInputStream.readFully(vp8Signature);
+ if (!Arrays.equals(WEBP_VP8_SIGNATURE, vp8Signature)) {
+ throw new IOException("Error checking VP8 signature");
}
// Retrieve image width/height
@@ -6651,18 +6624,17 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
// Check signature
byte vp8lSignature = totalInputStream.readByte();
if (vp8lSignature != WEBP_VP8L_SIGNATURE) {
- throw new IOException("Encountered error while checking VP8L "
- + "signature");
+ throw new IOException("Error checking VP8L signature");
}
// Retrieve image width/height
widthAndHeight = totalInputStream.readInt();
- // VP8L stores width - 1 and height - 1 values. See "2 RIFF Header" of
- // "WebP Lossless Bitstream Specification"
- width = ((widthAndHeight << 18) >> 18) + 1;
- height = ((widthAndHeight << 4) >> 18) + 1;
- // Retrieve alpha bit
- alpha = widthAndHeight & (1 << 3);
+ // VP8L stores 14-bit 'width - 1' and 'height - 1' values. See "RIFF Header"
+ // of "WebP Lossless Bitstream Specification".
+ width = (widthAndHeight & 0x3FFF) + 1; // Read bits 0 - 13
+ height = ((widthAndHeight & 0xFFFC000) >>> 14) + 1; // Read bits 14 - 27
+ // Retrieve alpha bit 28
+ alpha = (widthAndHeight & 1 << 28) != 0;
bytesToRead -= (1 /* VP8L signature */ + 4);
}
@@ -6670,10 +6642,12 @@ private void saveWebpAttributes(InputStream inputStream, OutputStream outputStre
nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X);
nonHeaderOutputStream.writeInt(WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH);
byte[] data = new byte[WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH];
+ // ALPHA flag
+ if (alpha) {
+ data[0] = (byte) (data[0] | (1 << 4));
+ }
// EXIF flag
data[0] = (byte) (data[0] | (1 << 3));
- // ALPHA flag
- data[0] = (byte) (data[0] | (alpha << 4));
// VP8X stores Width - 1 and Height - 1 values
width -= 1;
height -= 1;
@@ -6723,12 +6697,7 @@ private void copyChunksUpToGivenChunkType(ByteOrderedDataInputStream inputStream
byte[] secondGivenType) throws IOException {
while (true) {
byte[] type = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
- if (inputStream.read(type) != type.length) {
- throw new IOException("Encountered invalid length while copying WebP chunks up to"
- + "chunk type " + new String(firstGivenType, ASCII)
- + ((secondGivenType == null) ? "" : " or " + new String(secondGivenType,
- ASCII)));
- }
+ inputStream.readFully(type);
copyWebPChunk(inputStream, outputStream, type);
if (Arrays.equals(type, firstGivenType)
|| (secondGivenType != null && Arrays.equals(type, secondGivenType))) {
@@ -6794,12 +6763,12 @@ private ByteOrder readByteOrder(ByteOrderedDataInputStream dataInputStream)
if (DEBUG) {
Log.d(TAG, "readExifSegment: Byte Align II");
}
- return ByteOrder.LITTLE_ENDIAN;
+ return LITTLE_ENDIAN;
case BYTE_ALIGN_MM:
if (DEBUG) {
Log.d(TAG, "readExifSegment: Byte Align MM");
}
- return ByteOrder.BIG_ENDIAN;
+ return BIG_ENDIAN;
default:
throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
}
@@ -6832,7 +6801,7 @@ private void parseTiffHeaders(ByteOrderedDataInputStream dataInputStream) throws
private void readImageFileDirectory(SeekableByteOrderedDataInputStream dataInputStream,
@IfdType int ifdType) throws IOException {
// Save offset of current IFD to prevent reading an IFD that is already read.
- mAttributesOffsets.add(dataInputStream.mPosition);
+ mAttributesOffsets.add(dataInputStream.position());
// See TIFF 6.0 Section 2: TIFF Structure, Figure 1.
short numberOfDirectoryEntry = dataInputStream.readShort();
@@ -6967,9 +6936,11 @@ private void readImageFileDirectory(SeekableByteOrderedDataInputStream dataInput
}
// Check if the next IFD offset
- // 1. Is a non-negative value, and
+ // 1. Is a non-negative value (within the length of the input, if known), and
// 2. Does not point to a previously read IFD.
- if (offset > 0L) {
+ if (offset > 0L
+ && (dataInputStream.length() == ByteOrderedDataInputStream.LENGTH_UNSET
+ || offset < dataInputStream.length())) {
if (!mAttributesOffsets.contains((int) offset)) {
dataInputStream.seek(offset);
readImageFileDirectory(dataInputStream, nextIfdType);
@@ -6981,7 +6952,12 @@ private void readImageFileDirectory(SeekableByteOrderedDataInputStream dataInput
}
} else {
if (DEBUG) {
- Log.d(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
+ String message =
+ "Skip jump into the IFD since its offset is invalid: " + offset;
+ if (dataInputStream.length() != ByteOrderedDataInputStream.LENGTH_UNSET) {
+ message += " (total length: " + dataInputStream.length() + ")";
+ }
+ Log.d(TAG, message);
}
}
@@ -7079,7 +7055,7 @@ private void retrieveJpegImageSize(SeekableByteOrderedDataInputStream in, int im
// Searches for SOF marker in JPEG data and updates IMAGE_LENGTH & IMAGE_WIDTH tags
in.seek(jpegInterchangeFormat);
byte[] jpegBytes = new byte[jpegInterchangeFormatLength];
- in.read(jpegBytes);
+ in.readFully(jpegBytes);
getJpegAttributes(new ByteOrderedDataInputStream(jpegBytes), jpegInterchangeFormat,
imageType);
}
@@ -7139,8 +7115,8 @@ private void handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thum
// TODO: Need to handle potential OutOfMemoryError
// Save the thumbnail in memory if the input doesn't support reading again.
byte[] thumbnailBytes = new byte[thumbnailLength];
- in.skip(thumbnailOffset);
- in.read(thumbnailBytes);
+ in.skipFully(thumbnailOffset);
+ in.readFully(thumbnailBytes);
mThumbnailBytes = thumbnailBytes;
}
mThumbnailOffset = thumbnailOffset;
@@ -7209,14 +7185,18 @@ private void handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap th
Log.d(TAG, "Invalid strip offset value");
return;
}
- if (in.skip(bytesToSkip) != bytesToSkip) {
+ try {
+ in.skipFully(bytesToSkip);
+ } catch (EOFException e) {
Log.d(TAG, "Failed to skip " + bytesToSkip + " bytes.");
return;
}
bytesRead += bytesToSkip;
// TODO: Need to handle potential OutOfMemoryError
byte[] stripBytes = new byte[stripByteCount];
- if (in.read(stripBytes) != stripByteCount) {
+ try {
+ in.readFully(stripBytes);
+ } catch (EOFException e) {
Log.d(TAG, "Failed to read " + stripByteCount + " bytes.");
return;
}
@@ -7535,6 +7515,11 @@ private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throw
switch (mMimeType) {
case IMAGE_TYPE_JPEG:
+ if (totalSize > 0xFFFF) {
+ throw new IllegalStateException(
+ "Size of exif data (" + totalSize + " bytes) exceeds the max size of a "
+ + "JPEG APP1 segment (65536 bytes)");
+ }
// Write JPEG specific data (APP1 size, APP1 identifier)
dataOutputStream.writeUnsignedShort(totalSize);
dataOutputStream.write(IDENTIFIER_EXIF_APP1);
@@ -7552,8 +7537,7 @@ private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throw
}
// Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
- dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN
- ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
+ dataOutputStream.writeShort(mExifByteOrder == BIG_ENDIAN ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
dataOutputStream.setByteOrder(mExifByteOrder);
dataOutputStream.writeUnsignedShort(START_CODE);
dataOutputStream.writeUnsignedInt(IFD_OFFSET);
@@ -7622,7 +7606,7 @@ private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throw
}
// Reset the byte order to big endian in order to write remaining parts of the JPEG file.
- dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.setByteOrder(BIG_ENDIAN);
return totalSize;
}
@@ -7753,21 +7737,22 @@ public void seek(long position) throws IOException {
// An input stream class that can parse both little and big endian order data.
private static class ByteOrderedDataInputStream extends InputStream implements DataInput {
- private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
- private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
- final DataInputStream mDataInputStream;
- private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- int mPosition;
+ public static final int LENGTH_UNSET = -1;
+ protected final DataInputStream mDataInputStream;
+ protected int mPosition;
+
+ private ByteOrder mByteOrder;
private byte[] mSkipBuffer;
+ private int mLength;
ByteOrderedDataInputStream(byte[] bytes) throws IOException {
- this(new ByteArrayInputStream(bytes), ByteOrder.BIG_ENDIAN);
+ this(new ByteArrayInputStream(bytes), BIG_ENDIAN);
+ this.mLength = bytes.length;
}
ByteOrderedDataInputStream(InputStream in) throws IOException {
- this(in, ByteOrder.BIG_ENDIAN);
+ this(in, BIG_ENDIAN);
}
ByteOrderedDataInputStream(InputStream in, ByteOrder byteOrder) throws IOException {
@@ -7775,6 +7760,9 @@ private static class ByteOrderedDataInputStream extends InputStream implements D
mDataInputStream.mark(0);
mPosition = 0;
mByteOrder = byteOrder;
+ this.mLength = in instanceof ByteOrderedDataInputStream
+ ? ((ByteOrderedDataInputStream) in).length()
+ : LENGTH_UNSET;
}
public void setByteOrder(ByteOrder byteOrder) {
@@ -7982,6 +7970,12 @@ public void mark(int readlimit) {
public void reset() {
throw new UnsupportedOperationException("Reset is currently unsupported");
}
+
+ /** Return the total length (in bytes) of the underlying stream if known, otherwise
+ * {@link #LENGTH_UNSET}. */
+ public int length() {
+ return mLength;
+ }
}
// An output stream to write EXIF data area, which can be written in either little or big endian
@@ -8015,22 +8009,22 @@ public void writeByte(int val) throws IOException {
}
public void writeShort(short val) throws IOException {
- if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
+ if (mByteOrder == LITTLE_ENDIAN) {
mOutputStream.write((val >>> 0) & 0xFF);
mOutputStream.write((val >>> 8) & 0xFF);
- } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
+ } else if (mByteOrder == BIG_ENDIAN) {
mOutputStream.write((val >>> 8) & 0xFF);
mOutputStream.write((val >>> 0) & 0xFF);
}
}
public void writeInt(int val) throws IOException {
- if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
+ if (mByteOrder == LITTLE_ENDIAN) {
mOutputStream.write((val >>> 0) & 0xFF);
mOutputStream.write((val >>> 8) & 0xFF);
mOutputStream.write((val >>> 16) & 0xFF);
mOutputStream.write((val >>> 24) & 0xFF);
- } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
+ } else if (mByteOrder == BIG_ENDIAN) {
mOutputStream.write((val >>> 24) & 0xFF);
mOutputStream.write((val >>> 16) & 0xFF);
mOutputStream.write((val >>> 8) & 0xFF);
@@ -8039,10 +8033,18 @@ public void writeInt(int val) throws IOException {
}
public void writeUnsignedShort(int val) throws IOException {
+ if (val > 0xFFFF) {
+ throw new IllegalArgumentException("val is larger than the maximum value of a "
+ + "16-bit unsigned integer");
+ }
writeShort((short) val);
}
public void writeUnsignedInt(long val) throws IOException {
+ if (val > 0xFFFF_FFFFL) {
+ throw new IllegalArgumentException("val is larger than the maximum value of a "
+ + "32-bit unsigned integer");
+ }
writeInt((int) val);
}
}
@@ -8115,8 +8117,7 @@ private static boolean shouldSupportSeek(int mimeType) {
private static boolean isSupportedFormatForSavingAttributes(int mimeType) {
if (mimeType == IMAGE_TYPE_JPEG || mimeType == IMAGE_TYPE_PNG
- || mimeType == IMAGE_TYPE_WEBP || mimeType == IMAGE_TYPE_DNG
- || mimeType == IMAGE_TYPE_UNKNOWN) {
+ || mimeType == IMAGE_TYPE_WEBP) {
return true;
}
return false;