[Git][java-team/libthumbnailator-java][master] 4 commits: New upstream version 0.4.16

Markus Koschany (@apo) gitlab at salsa.debian.org
Thu Jan 6 22:50:11 GMT 2022



Markus Koschany pushed to branch master at Debian Java Maintainers / libthumbnailator-java


Commits:
6696d19d by Markus Koschany at 2022-01-06T23:46:29+01:00
New upstream version 0.4.16
- - - - -
d91ed1a5 by Markus Koschany at 2022-01-06T23:46:30+01:00
Update upstream source from tag 'upstream/0.4.16'

Update to upstream version '0.4.16'
with Debian dir a3a80efb1ffb315de789bb3e481ecb17faa7e5d2
- - - - -
f68d3148 by Markus Koschany at 2022-01-06T23:47:53+01:00
Ignore hamcrest artifact

- - - - -
1b494590 by Markus Koschany at 2022-01-06T23:48:20+01:00
Update changelog

- - - - -


15 changed files:

- README.md
- debian/changelog
- debian/maven.ignoreRules
- pom.xml
- src/main/java/net/coobird/thumbnailator/tasks/io/InputStreamImageSource.java
- src/main/java/net/coobird/thumbnailator/util/exif/ExifUtils.java
- src/test/java/net/coobird/thumbnailator/tasks/io/FileImageSourceTest.java
- + src/test/java/net/coobird/thumbnailator/tasks/io/InputStreamImageSourceMalformedTest.java
- + src/test/java/net/coobird/thumbnailator/util/exif/ExifWorkaroundTest.java
- + src/test/resources/Exif/fragments/README
- + src/test/resources/Exif/fragments/app0.segment
- + src/test/resources/Exif/fragments/exif.segment
- + src/test/resources/Exif/fragments/rest
- + src/test/resources/Exif/fragments/soi.segment
- + src/test/resources/Exif/fragments/xmp.segment


Changes:

=====================================
README.md
=====================================
@@ -1,4 +1,4 @@
-_*December 5, 2021: Thumbnailator 0.4.15 has been released!
+_*January 2, 2022: Thumbnailator 0.4.16 has been released!
 See [Changes](https://github.com/coobird/thumbnailator/wiki/Changes) for details.*_
 
 _*Thumbnailator is now available through
@@ -49,7 +49,7 @@ The following pages have more information on what _Thumbnailator_ can do:
 
 * [Features](https://github.com/coobird/thumbnailator/wiki/Features)
 * [Examples](https://github.com/coobird/thumbnailator/wiki/Examples)
-* [Thumbnailator API Documentation](https://coobird.github.io/thumbnailator/javadoc/0.4.14/)
+* [Thumbnailator API Documentation](https://coobird.github.io/thumbnailator/javadoc/0.4.16/)
 * [Frequently Asked Questions](https://github.com/coobird/thumbnailator/wiki/FAQ)
 
 # Disclaimer


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+libthumbnailator-java (0.4.16-1) unstable; urgency=medium
+
+  * New upstream version 0.4.16.
+
+ -- Markus Koschany <apo at debian.org>  Thu, 06 Jan 2022 23:48:06 +0100
+
 libthumbnailator-java (0.4.15-1) unstable; urgency=medium
 
   * New upstream version 0.4.15.


=====================================
debian/maven.ignoreRules
=====================================
@@ -6,3 +6,4 @@ org.apache.maven.plugins maven-javadoc-plugin * * * *
 org.apache.maven.plugins maven-source-plugin * * * *
 org.apache.maven.plugins maven-surefire-plugin * * * *
 org.mockito mockito-core * * * *
+org.hamcrest hamcrest-core * * * *


=====================================
pom.xml
=====================================
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>net.coobird</groupId>
   <artifactId>thumbnailator</artifactId>
-  <version>0.4.15</version>
+  <version>0.4.16</version>
   <packaging>jar</packaging>
   <name>thumbnailator</name>
   <description>Thumbnailator - a thumbnail generation library for Java</description>
@@ -186,7 +186,13 @@
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.10</version>
+      <version>4.13.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-core</artifactId>
+      <version>1.3</version>
       <scope>test</scope>
     </dependency>
   </dependencies>


=====================================
src/main/java/net/coobird/thumbnailator/tasks/io/InputStreamImageSource.java
=====================================
@@ -1,7 +1,7 @@
 /*
  * Thumbnailator - a thumbnail generation library
  *
- * Copyright (c) 2008-2020 Chris Kroells
+ * Copyright (c) 2008-2021 Chris Kroells
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -29,6 +29,8 @@ import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -37,6 +39,7 @@ import javax.imageio.ImageReadParam;
 import javax.imageio.ImageReader;
 import javax.imageio.stream.ImageInputStream;
 
+import net.coobird.thumbnailator.ThumbnailParameter;
 import net.coobird.thumbnailator.filters.ImageFilter;
 import net.coobird.thumbnailator.geometry.Region;
 import net.coobird.thumbnailator.tasks.UnsupportedFormatException;
@@ -58,9 +61,9 @@ public class InputStreamImageSource extends AbstractImageSource<InputStream> {
 	private static final int FIRST_IMAGE_INDEX = 0;
 	
 	/**
-	 * The {@link InputStream} from which the source image is to be read.
+	 * A {@link InputStream} from which the source image is to be read.
 	 */
-	private final InputStream is;
+	private InputStream is;
 	
 	/**
 	 * Instantiates an {@link InputStreamImageSource} with the
@@ -77,8 +80,335 @@ public class InputStreamImageSource extends AbstractImageSource<InputStream> {
 		if (is == null) {
 			throw new NullPointerException("InputStream cannot be null.");
 		}
-		
-		this.is = is;
+
+		if (!Boolean.getBoolean("thumbnailator.disableExifWorkaround")) {
+			this.is = new ExifCaptureInputStream(is);
+		} else {
+			this.is = is;
+		}
+	}
+
+	@Override
+	public void setThumbnailParameter(ThumbnailParameter param) {
+		super.setThumbnailParameter(param);
+
+		if (param == null || !param.useExifOrientation()) {
+			if (is instanceof ExifCaptureInputStream) {
+				// Revert to original `InputStream` and use that directly.
+				is = ((ExifCaptureInputStream)is).is;
+			}
+		}
+	}
+
+	/**
+	 * An {@link InputStream} which intercepts the data stream to find Exif
+	 * data and captures it if present.
+	 */
+	private static final class ExifCaptureInputStream extends InputStream {
+		/**
+		 * Original {@link InputStream} which reads from the image source.
+		 */
+		private final InputStream is;
+
+		// Following are states for this input stream.
+
+		/**
+		 * Flag to indicate data stream should be intercepted and collected.
+		 */
+		private boolean doIntercept = true;
+
+		/**
+		 * A threshold on how much data to be intercepted.
+		 * This is a safety mechanism to prevent buffering too much information.
+		 */
+		private static final int INTERCEPT_THRESHOLD = 1024 * 1024;
+
+		/**
+		 * Buffer to collect the input data to read JPEG images for JFIF marker segments.
+		 * This will also be used to store the Exif data, if found.
+ 		 */
+		private byte[] buffer = new byte[0];
+
+		/**
+		 * Current position for reading the buffer.
+		 */
+		int position = 0;
+
+		/**
+		 * Total bytes intercepted from the data stream.
+		 */
+		int totalRead = 0;
+
+		/**
+		 * Number of remaining bytes to skip ahead in the buffer.
+		 * This value is positive when next location to skip to is outside the
+		 * buffer's current contents.
+		 */
+		int remainingSkip = 0;
+
+		/**
+		 * Marker for the beginning of the APP1 marker segment.
+		 * Its position is where the APP1 marker starts, not the payload.
+		 */
+		private int startApp1 = Integer.MIN_VALUE;
+
+		/**
+		 * Marker for the end of the APP1 marker segment.
+		 */
+		private int endApp1 = Integer.MAX_VALUE;
+
+		/**
+		 * A flag to indicate that we expect APP1 payload (which contains Exif
+		 * contents) is being streamed, so they should be captured into the
+		 * {@code buffer}.
+		 */
+		private boolean doCaptureApp1 = false;
+
+		/**
+		 * A flag to indicate that the {@code buffer} contains the complete
+		 * Exif information.
+		 */
+		private boolean hasCapturedExif = false;
+
+		/**
+		 * A flag to indicate whether to output debug logs.
+		 */
+		private final boolean isDebug = Boolean.getBoolean("thumbnailator.debugLog.exifWorkaround")
+				|| Boolean.getBoolean("thumbnailator.debugLog");
+
+		/**
+		 * Returns Exif data captured from the JPEG image.
+		 * @return	Returns captured Exif data, or {@code null} if unavailable.
+		 */
+		private byte[] getExifData() {
+			return hasCapturedExif ? buffer : null;
+		}
+
+		// TODO Any performance penalties?
+		private ExifCaptureInputStream(InputStream is) {
+			this.is = is;
+		}
+
+		/**
+		 * Terminate intercept.
+		 * Drops the collected buffer to relieve pressure on memory.
+		 *
+		 * Do not call this when Exif was found, as buffer (containing Exif)
+		 * will be lost.
+		 */
+		private void terminateIntercept() {
+			doIntercept = false;
+			buffer = null;
+		}
+
+		/**
+		 * Debug message.
+		 */
+		private void debugln(String format, Object... args) {
+			if (isDebug) {
+				System.err.printf("[thumbnailator.exifWorkaround] " + format + "%n", args);
+			}
+		}
+
+		@Override
+		public int read(byte[] b, int off, int len) throws IOException {
+			int bytesRead = is.read(b, off, len);
+			if (bytesRead == -1) {
+				return bytesRead;
+			}
+
+			if (!doIntercept) {
+				debugln("Skip intercept.");
+				return bytesRead;
+			}
+
+			if (off != 0) {
+				debugln("Offset: %s != 0; terminating intercept.", off);
+				terminateIntercept();
+				return bytesRead;
+			}
+
+			totalRead += bytesRead;
+			if (totalRead > INTERCEPT_THRESHOLD) {
+				debugln("Exceeded intercept threshold, terminating intercept. %s > %s", totalRead, INTERCEPT_THRESHOLD);
+				terminateIntercept();
+				return bytesRead;
+			}
+
+			debugln("Total read: %s", totalRead);
+			debugln("Bytes read: %s", bytesRead);
+
+			byte[] tmpBuffer = new byte[totalRead];
+			System.arraycopy(buffer, 0, tmpBuffer, 0, Math.min(tmpBuffer.length, buffer.length));
+			System.arraycopy(b, off, tmpBuffer, totalRead - bytesRead, bytesRead);
+			buffer = tmpBuffer;
+
+			debugln("Source: %s", Arrays.toString(b));
+			debugln("Buffer: %s", Arrays.toString(buffer));
+
+			while (position < totalRead && (totalRead - position) >= 2) {
+				debugln("Start loop, position: %s", position);
+
+				if (remainingSkip > 0) {
+					position += remainingSkip;
+					remainingSkip = 0;
+					debugln("Skip requested, new position: %s", position);
+					continue;
+				}
+
+				if (doCaptureApp1) {
+					// Check we can buffer up to "Exif" identifier.
+					if (startApp1 + 8 > position) {
+						debugln("APP1 shorter than expected, terminating intercept.");
+						terminateIntercept();
+						break;
+					}
+					byte[] header = new byte[4];
+					System.arraycopy(buffer, startApp1 + 4, header, 0, header.length);
+
+					if (new String(header).equals("Exif")) {
+						debugln("Found Exif!");
+						hasCapturedExif = true;
+						doIntercept = false;
+						byte[] exifData = new byte[endApp1 - (startApp1 + 4)];
+						System.arraycopy(buffer, startApp1 + 4, exifData, 0, exifData.length);
+						buffer = exifData;
+						break;
+					} else {
+						debugln("APP1 was not Exif.");
+						hasCapturedExif = false;
+						doIntercept = true;
+						doCaptureApp1 = false;
+					}
+				}
+
+				if (position == 0 && totalRead >= 2) {
+					// Check the first two bytes of stream to see if SOI exists.
+					// If SOI is not found, this is not a JPEG.
+					debugln("Check if JPEG. buffer: %s", Arrays.toString(buffer));
+					if (!(buffer[position] == (byte) 0xFF && buffer[position + 1] == (byte) 0xD8)) {
+						// Not SOI, so it's not a JPEG.
+						// We no longer need to keep intercepting.
+						debugln("JFIF SOI not found. Not JPEG.");
+						terminateIntercept();
+						break;
+					}
+
+					position += 2;
+					continue;
+				}
+
+				debugln("Prior to 2-byte section. position: %s, total read: %s", position, totalRead);
+				if (position + 2 <= totalRead) {
+					if (buffer[position] == (byte) 0xFF) {
+						if (buffer[position + 1] >= (byte) 0xD0 && buffer[position + 1] <= (byte) 0xD7) {
+							// RSTn - a 2-byte marker.
+							debugln("Found RSTn marker.");
+							position += 2;
+							continue;
+						} else if (buffer[position + 1] == (byte) 0xDA || buffer[position + 1] == (byte) 0xD9) {
+							// 0xDA -> SOS - Start of Scan
+							// 0xD9 -> EOI - End of Image
+							// In both cases, terminate the scan for Exif data.
+							debugln("Stop scan for Exif. Found: %s, %s", buffer[position], buffer[position + 1]);
+							terminateIntercept();
+							break;
+						}
+					}
+				}
+
+				debugln("Prior to 4-byte section. position: %s, total read: %s", position, totalRead);
+				if (position + 4 <= totalRead) {
+					try {
+						if (buffer[position] == (byte) 0xFF) {
+							if (buffer[position + 1] == (byte) 0xE1) {
+								// APP1
+								doCaptureApp1 = true;
+								startApp1 = position;
+
+								// payload + marker
+								int incrementBy = getPayloadLength(buffer[position + 2], buffer[position + 3]) + 4;
+								debugln("Prior to 2-byte section. position: %s, total read: %s", position, totalRead);
+
+								int newPosition = incrementBy + position;
+								endApp1 = newPosition;
+								debugln("Found APP1. position: %s, total read: %s, increment by: %s", position, totalRead, incrementBy);
+								debugln("Found APP1. start: %s, end: %s", startApp1, endApp1);
+								if (newPosition > totalRead) {
+									remainingSkip = newPosition - totalRead;
+									position = totalRead;
+									debugln("Skip request; remaining skip: %s", remainingSkip);
+								} else {
+									position = newPosition;
+									debugln("No skip needed; new position: %s", newPosition);
+								}
+								continue;
+
+							} else if (buffer[1] == (byte) 0xDD) {
+								// DRI (this is a 4-byte marker w/o payload.)
+								debugln("Found DRI.");
+								position += 4;
+								continue;
+							}
+
+							// Other markers like APP0, DQT don't need any special processing.
+
+							int incrementBy = getPayloadLength(buffer[position + 2], buffer[position + 3]) + 4;
+							int newPosition = incrementBy + position;
+							debugln("Other 4-byte. position: %s, total read: %s, increment by: %s", position, totalRead, incrementBy);
+							debugln("Other 4-byte. start: %s, end: %s", startApp1, endApp1);
+							if (newPosition > totalRead) {
+								remainingSkip = newPosition - totalRead;
+								position = totalRead;
+								debugln("Skip request; remaining skip: %s", remainingSkip);
+							} else {
+								position = newPosition;
+								debugln("No skip needed; new position: %s", newPosition);
+							}
+							continue;
+						}
+					} catch (Exception e) {
+						// Immediately drop everything, as we can't recover.
+						// TODO Record what went wrong.
+						debugln("[Exception] Exception thrown. Terminating intercept.");
+						debugln("[Exception] %s", e.toString());
+						for (StackTraceElement el : e.getStackTrace()) {
+							debugln("[Exception] %s", el.toString());
+						}
+						terminateIntercept();
+						break;
+					}
+				}
+
+				terminateIntercept();
+				debugln("Shouldn't be here. Terminating intercept.");
+				break;
+			}
+
+			return bytesRead;
+		}
+
+		@Override
+		public int read() throws IOException {
+			return is.read();
+		}
+
+		/**
+		 * Returns the payload length from the marker header.
+		 * @param a			First byte of payload length.
+		 * @param b			Second byte of payload length.
+		 * @return			Length as an integer.
+		 */
+		private static int getPayloadLength(byte a, byte b) {
+			int length = ByteBuffer.wrap(new byte[] {a, b}).getShort() - 2;
+			if (length <= 0) {
+				throw new IllegalStateException(
+						"Expected a positive payload length, but was " + length
+				);
+			}
+
+			return length;
+		}
 	}
 
 	public BufferedImage read() throws IOException {
@@ -136,12 +466,28 @@ public class InputStreamImageSource extends AbstractImageSource<InputStream> {
 	}
 
 	private BufferedImage readImage(ImageReader reader) throws IOException {
-		inputFormatName = reader.getFormatName();
 
 		try {
 			if (param.useExifOrientation()) {
-				Orientation orientation;
-				orientation = ExifUtils.getExifOrientation(reader, FIRST_IMAGE_INDEX);
+				Orientation orientation = null;
+
+				// Attempt to use Exif reader of the ImageReader.
+				// If the ImageReader fails like seen in Issue #108, use the
+				// backup method of using the captured Exif data.
+				boolean useExifFromRawData = false;
+				try {
+					orientation = ExifUtils.getExifOrientation(reader, FIRST_IMAGE_INDEX);
+				} catch (Exception e) {
+					// TODO Would be useful to capture why it didn't work.
+					useExifFromRawData = true;
+				}
+
+				if (useExifFromRawData && is instanceof ExifCaptureInputStream) {
+					byte[] exifData = ((ExifCaptureInputStream)is).getExifData();
+					if (exifData != null) {
+						orientation = ExifUtils.getOrientationFromExif(exifData);
+					}
+				}
 
 				// Skip this code block if there's no rotation needed.
 				if (orientation != null && orientation != Orientation.TOP_LEFT) {
@@ -159,6 +505,8 @@ public class InputStreamImageSource extends AbstractImageSource<InputStream> {
 			// TODO Ought to have some way to track errors.
 		}
 
+		inputFormatName = reader.getFormatName();
+
 		ImageReadParam irParam = reader.getDefaultReadParam();
 		int width = reader.getWidth(FIRST_IMAGE_INDEX);
 		int height = reader.getHeight(FIRST_IMAGE_INDEX);
@@ -177,9 +525,9 @@ public class InputStreamImageSource extends AbstractImageSource<InputStream> {
 		 * https://github.com/coobird/thumbnailator/issues/69
 		 */
 		if (param != null &&
-				"true".equals(System.getProperty("thumbnailator.conserveMemoryWorkaround")) &&
+				Boolean.getBoolean("thumbnailator.conserveMemoryWorkaround") &&
 				width > 1800 && height > 1800 &&
-				(width * height * 4 > Runtime.getRuntime().freeMemory() / 4)
+				(width * height * 4L > Runtime.getRuntime().freeMemory() / 4)
 		) {
 			int subsampling = 1;
 


=====================================
src/main/java/net/coobird/thumbnailator/util/exif/ExifUtils.java
=====================================
@@ -1,7 +1,7 @@
 /*
  * Thumbnailator - a thumbnail generation library
  *
- * Copyright (c) 2008-2020 Chris Kroells
+ * Copyright (c) 2008-2021 Chris Kroells
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -60,6 +60,7 @@ public final class ExifUtils {
 	 * 						metadata should be read from.
 	 * @return				The orientation information obtained from the
 	 * 						Exif metadata, as a {@link Orientation} enum.
+	 * 						Returns {@code null} if no orientation is found.
 	 * @throws IOException				When an error occurs during reading.
 	 * @throws IllegalArgumentException	If the {@link ImageReader} does not
 	 * 									have the target image set, or if the
@@ -97,7 +98,15 @@ public final class ExifUtils {
 		return null;
 	}
 
-	private static Orientation getOrientationFromExif(byte[] exifData) {
+	/**
+	 * Returns the orientation obtained from the Exif metadata.
+	 *
+	 * @param exifData		A byte array containing Exif data.
+	 * @return				The orientation information obtained from the
+	 * 						Exif metadata, as a {@link Orientation} enum.
+	 * 						Returns {@code null} if no orientation is found.
+	 */
+	public static Orientation getOrientationFromExif(byte[] exifData) {
 		// Needed to make byte-wise reading easier.
 		ByteBuffer buffer = ByteBuffer.wrap(exifData);
 


=====================================
src/test/java/net/coobird/thumbnailator/tasks/io/FileImageSourceTest.java
=====================================
@@ -1,7 +1,7 @@
 /*
  * Thumbnailator - a thumbnail generation library
  *
- * Copyright (c) 2008-2020 Chris Kroells
+ * Copyright (c) 2008-2021 Chris Kroells
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -24,9 +24,6 @@
 
 package net.coobird.thumbnailator.tasks.io;
 
-import static org.junit.Assert.*;
-import static org.junit.matchers.JUnitMatchers.*;
-
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -48,6 +45,12 @@ import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
 
 public class FileImageSourceTest {
 	/**


=====================================
src/test/java/net/coobird/thumbnailator/tasks/io/InputStreamImageSourceMalformedTest.java
=====================================
@@ -0,0 +1,96 @@
+/*
+ * Thumbnailator - a thumbnail generation library
+ *
+ * Copyright (c) 2008-2021 Chris Kroells
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package net.coobird.thumbnailator.tasks.io;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+ at RunWith(Parameterized.class)
+public class InputStreamImageSourceMalformedTest {
+
+	@Parameterized.Parameters(name = "type={0}, length={1}")
+	public static Collection<Object> testCases() {
+		List<Object[]> cases = new ArrayList<Object[]>();
+		for (String type : Arrays.asList("jpg", "png", "bmp")) {
+			for (int i = 1; i <= 40; i++) {
+				cases.add(new Object[] { type, i });
+			}
+		}
+		return Arrays.asList(cases.toArray());
+	}
+
+	@Parameterized.Parameter
+	public String type;
+
+	@Parameterized.Parameter(value = 1)
+	public Integer length;
+
+	@Before	@After
+	public void cleanup() {
+		System.clearProperty("thumbnailator.disableExifWorkaround");
+	}
+
+	@Test
+	public void terminatesProperlyWithWorkaround() {
+		runTest();
+	}
+
+	@Test
+	public void terminatesProperlyWithoutWorkaround() {
+		System.setProperty("thumbnailator.disableExifWorkaround", "true");
+		runTest();
+	}
+
+	/**
+	 * Test to check that reading an abnormal file won't cause image reading
+	 * to end up in a bad state like in an infinite loop.
+	 */
+	private void runTest() {
+		try {
+			byte[] bytes = new byte[length];
+			InputStream sourceIs = ClassLoader.getSystemResourceAsStream(String.format("Thumbnailator/grid.%s", type));
+			sourceIs.read(bytes);
+			sourceIs.close();
+
+			ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+			InputStreamImageSource source = new InputStreamImageSource(is);
+
+			source.read();
+
+		} catch (Exception e) {
+			// terminates properly, even if an exception is thrown.
+		}
+	}
+}


=====================================
src/test/java/net/coobird/thumbnailator/util/exif/ExifWorkaroundTest.java
=====================================
@@ -0,0 +1,137 @@
+/*
+ * Thumbnailator - a thumbnail generation library
+ *
+ * Copyright (c) 2008-2021 Chris Kroells
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package net.coobird.thumbnailator.util.exif;
+
+import net.coobird.thumbnailator.Thumbnails;
+import net.coobird.thumbnailator.test.BufferedImageAssert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+ at RunWith(Parameterized.class)
+public class ExifWorkaroundTest {
+
+    @Parameterized.Parameters(name = "tags={0}")
+    public static Collection<List<String>> tagOrder() {
+        return Arrays.asList(
+                Arrays.asList("app0.segment", "exif.segment"),
+                Arrays.asList("exif.segment", "app0.segment"),
+                Arrays.asList("app0.segment", "exif.segment", "xmp.segment"),
+                Arrays.asList("app0.segment", "xmp.segment", "exif.segment"),
+                Arrays.asList("exif.segment", "app0.segment", "xmp.segment"),
+                Arrays.asList("xmp.segment", "app0.segment", "exif.segment"),
+                Arrays.asList("exif.segment", "xmp.segment", "app0.segment"),
+                Arrays.asList("xmp.segment", "exif.segment", "app0.segment")
+        );
+    }
+
+    @Parameterized.Parameter
+    public List<String> tags;
+
+    private InputStream getFromResource(String name) {
+        return this.getClass().getClassLoader().getResourceAsStream("Exif/fragments/" + name);
+    }
+
+    private InputStream buildJpeg() throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        List<String> resources = new ArrayList<String>();
+        resources.add("soi.segment");
+        resources.addAll(tags);
+        resources.add("rest");
+
+        for (String resource : resources) {
+            InputStream is = getFromResource(resource);
+            while (is.available() > 0) {
+                baos.write(is.read());
+            }
+        }
+
+        return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    @Test
+    public void withWorkaround() throws IOException {
+        BufferedImage result = Thumbnails.of(buildJpeg())
+                .scale(1.0f)
+                .asBufferedImage();
+
+        assertPasses(result);
+    }
+
+    @Test
+    public void withoutWorkaround() throws IOException {
+        System.setProperty("thumbnailator.disableExifWorkaround", "true");
+
+        BufferedImage result = Thumbnails.of(buildJpeg())
+                .scale(1.0f)
+                .asBufferedImage();
+
+        if (tags.get(0).equals("app0.segment")) {
+            assertPasses(result);
+        } else {
+            assertFails(result);
+        }
+    }
+
+    @Before @After
+    public void cleanup() {
+        System.clearProperty("thumbnailator.disableExifWorkaround");
+    }
+
+    private void assertPasses(BufferedImage result) {
+        BufferedImageAssert.assertMatches(
+                result,
+                new float[] {
+                        1, 1, 1,
+                        1, 1, 1,
+                        1, 0, 0,
+                }
+        );
+    }
+
+    private void assertFails(BufferedImage result) {
+        BufferedImageAssert.assertMatches(
+                result,
+                new float[] {
+                        1, 1, 1,
+                        1, 1, 1,
+                        0, 0, 1,
+                }
+        );
+    }
+}


=====================================
src/test/resources/Exif/fragments/README
=====================================
@@ -0,0 +1,3 @@
+Files here are fragments from adding JFIF segments to "source_2.jpg".
+XMP tag is included as it is also a APP1 like Exif.
+The Exif reader should cope with XMP properly.
\ No newline at end of file


=====================================
src/test/resources/Exif/fragments/app0.segment
=====================================
Binary files /dev/null and b/src/test/resources/Exif/fragments/app0.segment differ


=====================================
src/test/resources/Exif/fragments/exif.segment
=====================================
Binary files /dev/null and b/src/test/resources/Exif/fragments/exif.segment differ


=====================================
src/test/resources/Exif/fragments/rest
=====================================
Binary files /dev/null and b/src/test/resources/Exif/fragments/rest differ


=====================================
src/test/resources/Exif/fragments/soi.segment
=====================================
@@ -0,0 +1 @@
+
\ No newline at end of file


=====================================
src/test/resources/Exif/fragments/xmp.segment
=====================================
Binary files /dev/null and b/src/test/resources/Exif/fragments/xmp.segment differ



View it on GitLab: https://salsa.debian.org/java-team/libthumbnailator-java/-/compare/8759988464589a00213a85dad8ba482088a7914f...1b4945906527de17457dca0d299eddda247c013b

-- 
View it on GitLab: https://salsa.debian.org/java-team/libthumbnailator-java/-/compare/8759988464589a00213a85dad8ba482088a7914f...1b4945906527de17457dca0d299eddda247c013b
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20220106/65fbd2ad/attachment.htm>


More information about the pkg-java-commits mailing list