[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