8164750: TIFF reading fails when ignoring metadata with BaselineTIFFTagSet removed
authorbpb
Wed, 02 Nov 2016 11:07:16 -0700
changeset 42188 471f232f8ea8
parent 42187 b2f0bdbfd4f2
child 42189 d3cb1015f025
8164750: TIFF reading fails when ignoring metadata with BaselineTIFFTagSet removed Summary: Disallow not adding to metadata fields which are critical to reading the image data even when the BaselineTIFFTagSet has been removed from the TIFFImageReadParam and the ignoreMetadata flag is set. Reviewed-by: prr
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java
jdk/test/javax/imageio/plugins/tiff/ReadWithoutBaselineTagSet.java
--- a/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java	Wed Nov 02 09:44:01 2016 -0700
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java	Wed Nov 02 11:07:16 2016 -0700
@@ -49,6 +49,52 @@
     private long stripOrTileOffsetsPosition = -1;
     private long lastPosition = -1;
 
+    //
+    // A set of tag numbers corresponding to tags essential to decoding
+    // the image and metadata required to interpret its samples.
+    //
+    private static volatile Set<Integer> essentialTags = null;
+
+    private static void initializeEssentialTags() {
+        Set<Integer> tags = essentialTags;
+        if (tags == null) {
+            essentialTags = tags = Set.of(
+                BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE,
+                BaselineTIFFTagSet.TAG_COLOR_MAP,
+                BaselineTIFFTagSet.TAG_COMPRESSION,
+                BaselineTIFFTagSet.TAG_EXTRA_SAMPLES,
+                BaselineTIFFTagSet.TAG_FILL_ORDER,
+                BaselineTIFFTagSet.TAG_ICC_PROFILE,
+                BaselineTIFFTagSet.TAG_IMAGE_LENGTH,
+                BaselineTIFFTagSet.TAG_IMAGE_WIDTH,
+                BaselineTIFFTagSet.TAG_JPEG_AC_TABLES,
+                BaselineTIFFTagSet.TAG_JPEG_DC_TABLES,
+                BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT,
+                BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                BaselineTIFFTagSet.TAG_JPEG_PROC,
+                BaselineTIFFTagSet.TAG_JPEG_Q_TABLES,
+                BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL,
+                BaselineTIFFTagSet.TAG_JPEG_TABLES,
+                BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION,
+                BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION,
+                BaselineTIFFTagSet.TAG_PREDICTOR,
+                BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE,
+                BaselineTIFFTagSet.TAG_ROWS_PER_STRIP,
+                BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL,
+                BaselineTIFFTagSet.TAG_SAMPLE_FORMAT,
+                BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS,
+                BaselineTIFFTagSet.TAG_STRIP_OFFSETS,
+                BaselineTIFFTagSet.TAG_T4_OPTIONS,
+                BaselineTIFFTagSet.TAG_T6_OPTIONS,
+                BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS,
+                BaselineTIFFTagSet.TAG_TILE_LENGTH,
+                BaselineTIFFTagSet.TAG_TILE_OFFSETS,
+                BaselineTIFFTagSet.TAG_TILE_WIDTH,
+                BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS,
+                BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING
+            );
+        }
+    }
 
     /**
      * Converts a {@code TIFFDirectory} to a {@code TIFFIFD}.
@@ -507,6 +553,15 @@
 
         List<TIFFTagSet> tagSetList = getTagSetList();
 
+        boolean ensureEssentialTags = false;
+        TIFFTagSet baselineTagSet = null;
+        if (isPrimaryIFD && ignoreUnknownFields
+            && !tagSetList.contains(BaselineTIFFTagSet.getInstance())) {
+            ensureEssentialTags = true;
+            initializeEssentialTags();
+            baselineTagSet = BaselineTIFFTagSet.getInstance();
+        }
+
         List<Object> entries = new ArrayList<>();
         Object[] entryData = new Object[1]; // allocate once for later reuse.
 
@@ -530,6 +585,11 @@
             // Get the associated TIFFTag.
             TIFFTag tag = getTag(tagNumber, tagSetList);
 
+            if (tag == null && ensureEssentialTags
+                && essentialTags.contains(tagNumber)) {
+                tag = baselineTagSet.getTag(tagNumber);
+            }
+
             // Ignore unknown fields, fields with unknown type, and fields
             // with count out of int range.
             if((tag == null && ignoreUnknownFields)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/imageio/plugins/tiff/ReadWithoutBaselineTagSet.java	Wed Nov 02 11:07:16 2016 -0700
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8164750
+ * @summary Verify reader does not fail when the BaselineTIFFTagSet is
+ *          removed via the TIFFImageReadParam both when ignoring and
+ *          not ignoring metadata.
+ */
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFImageReadParam;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+
+public class ReadWithoutBaselineTagSet {
+    private static final int WIDTH = 47;
+    private static final int HEIGHT = 53;
+
+    private static final Map<Integer,String[]> typeToCompression =
+        Map.of(BufferedImage.TYPE_3BYTE_BGR,
+            new String[] {null, "LZW", "JPEG", "ZLib", "PackBits"},
+            BufferedImage.TYPE_BYTE_BINARY,
+            new String[] {null, "CCITT RLE", "CCITT T.4", "CCITT T.6",
+                "LZW", "PackBits"},
+            BufferedImage.TYPE_BYTE_GRAY,
+            new String[] {null, "LZW", "JPEG", "ZLib", "PackBits"},
+            BufferedImage.TYPE_USHORT_GRAY,
+            new String[] {null, "LZW", "ZLib", "PackBits"});
+
+    public static void main(String[] args) throws IOException {
+        test();
+    }
+
+    private static void test() throws IOException {
+        int failures = 0;
+
+        for (int imageType : typeToCompression.keySet()) {
+            BufferedImage image = new BufferedImage(WIDTH, HEIGHT, imageType);
+            System.out.println("Image: " + image.toString());
+
+            for (String compression : typeToCompression.get(imageType)) {
+                System.out.println("Compression: "
+                        + (compression == null ? "None" : compression));
+                ByteArrayOutputStream output = new ByteArrayOutputStream();
+                ImageOutputStream ios = new MemoryCacheImageOutputStream(output);
+                ImageWriter writer =
+                        ImageIO.getImageWritersByFormatName("TIFF").next();
+                ImageWriteParam wparam = writer.getDefaultWriteParam();
+                if (compression == null) {
+                    wparam.setCompressionMode(ImageWriteParam.MODE_DEFAULT);
+                } else {
+                    wparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+                    wparam.setCompressionType(compression);
+                }
+                writer.setOutput(ios);
+                writer.write(null, new IIOImage(image, null, null), wparam);
+                ios.flush();
+
+                ImageReader reader =
+                        ImageIO.getImageReadersByFormatName("TIFF").next();
+                ByteArrayInputStream input
+                        = new ByteArrayInputStream(output.toByteArray());
+                ImageInputStream iis = new MemoryCacheImageInputStream(input);
+                iis.mark();
+
+                TIFFImageReadParam rparam = new TIFFImageReadParam();
+                rparam.removeAllowedTagSet(BaselineTIFFTagSet.getInstance());
+
+                reader.setInput(iis, false, false);
+                BufferedImage resultFalse = reader.read(0, rparam);
+                if (resultFalse.getWidth() != WIDTH
+                        || resultFalse.getHeight() != HEIGHT) {
+                    System.err.printf("Read image dimensions != %d x %d%n",
+                            WIDTH, HEIGHT);
+                    failures++;
+                } else {
+                    System.out.println("ignoreMetadata == false test passed");
+                }
+
+                iis.reset();
+                reader.setInput(iis, false, true);
+                BufferedImage resultTrue;
+                try {
+                    resultTrue = reader.read(0, rparam);
+                    if (resultTrue.getWidth() != WIDTH
+                            || resultTrue.getHeight() != HEIGHT) {
+                        System.err.printf("Read image dimensions != %d x %d%n",
+                                WIDTH, HEIGHT);
+                        failures++;
+                    } else {
+                        System.out.println("ignoreMetadata == true test passed");
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    failures++;
+                }
+            }
+        }
+
+        if (failures != 0) {
+            throw new RuntimeException("Test failed with "
+                    + failures + " errors!");
+        }
+    }
+}