6541476: PNG imageio plugin incorrectly handles iTXt chunk
authorbae
Sat, 01 Nov 2008 20:42:18 +0300
changeset 1734 861400729115
parent 1733 95c41a86eac9
child 1735 181e75cca3b2
6541476: PNG imageio plugin incorrectly handles iTXt chunk Reviewed-by: igor, prr
jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java
jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java
jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java
jdk/test/javax/imageio/plugins/png/ITXtTest.java
--- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java	Wed Oct 29 01:52:22 2008 +0300
+++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java	Sat Nov 01 20:42:18 2008 +0300
@@ -44,7 +44,6 @@
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.Iterator;
-import java.util.List;
 import java.util.zip.Inflater;
 import java.util.zip.InflaterInputStream;
 import javax.imageio.IIOException;
@@ -57,6 +56,7 @@
 import com.sun.imageio.plugins.common.InputStreamAdapter;
 import com.sun.imageio.plugins.common.ReaderUtil;
 import com.sun.imageio.plugins.common.SubImageInputStream;
+import java.io.ByteArrayOutputStream;
 import sun.awt.image.ByteInterleavedRaster;
 
 class PNGImageDataEnumeration implements Enumeration {
@@ -207,6 +207,15 @@
         resetStreamSettings();
     }
 
+    private String readNullTerminatedString(String charset) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int b;
+        while ((b = stream.read()) != 0) {
+            baos.write(b);
+        }
+        return new String(baos.toByteArray(), charset);
+    }
+
     private String readNullTerminatedString() throws IOException {
         StringBuilder b = new StringBuilder();
         int c;
@@ -445,26 +454,27 @@
         metadata.iTXt_keyword.add(keyword);
 
         int compressionFlag = stream.readUnsignedByte();
-        metadata.iTXt_compressionFlag.add(new Integer(compressionFlag));
+        metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1));
 
         int compressionMethod = stream.readUnsignedByte();
-        metadata.iTXt_compressionMethod.add(new Integer(compressionMethod));
+        metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
 
-        String languageTag = readNullTerminatedString();
+        String languageTag = readNullTerminatedString("UTF8");
         metadata.iTXt_languageTag.add(languageTag);
 
-        String translatedKeyword = stream.readUTF();
+        String translatedKeyword =
+            readNullTerminatedString("UTF8");
         metadata.iTXt_translatedKeyword.add(translatedKeyword);
-        stream.skipBytes(1); // Null separator
 
         String text;
+        long pos = stream.getStreamPosition();
+        byte[] b = new byte[(int)(chunkStart + chunkLength - pos)];
+        stream.readFully(b);
+
         if (compressionFlag == 1) { // Decompress the text
-            long pos = stream.getStreamPosition();
-            byte[] b = new byte[(int)(chunkStart + chunkLength - pos)];
-            stream.readFully(b);
-            text = inflate(b);
+            text = new String(inflate(b), "UTF8");
         } else {
-            text = stream.readUTF();
+            text = new String(b, "UTF8");
         }
         metadata.iTXt_text.add(text);
     }
@@ -613,20 +623,20 @@
         metadata.tRNS_present = true;
     }
 
-    private static String inflate(byte[] b) throws IOException {
+    private static byte[] inflate(byte[] b) throws IOException {
         InputStream bais = new ByteArrayInputStream(b);
         InputStream iis = new InflaterInputStream(bais);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-        StringBuilder sb = new StringBuilder(80);
         int c;
         try {
             while ((c = iis.read()) != -1) {
-                sb.append((char)c);
+                baos.write(c);
             }
         } finally {
             iis.close();
         }
-        return sb.toString();
+        return baos.toByteArray();
     }
 
     private void parse_zTXt_chunk(int chunkLength) throws IOException {
@@ -638,7 +648,7 @@
 
         byte[] b = new byte[chunkLength - keyword.length() - 2];
         stream.readFully(b);
-        metadata.zTXt_text.add(inflate(b));
+        metadata.zTXt_text.add(new String(inflate(b)));
     }
 
     private void readMetadata() throws IIOException {
--- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java	Wed Oct 29 01:52:22 2008 +0300
+++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java	Sat Nov 01 20:42:18 2008 +0300
@@ -671,13 +671,13 @@
         }
     }
 
-    private byte[] deflate(String s) throws IOException {
+    private byte[] deflate(byte[] b) throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         DeflaterOutputStream dos = new DeflaterOutputStream(baos);
 
-        int len = s.length();
+        int len = b.length;
         for (int i = 0; i < len; i++) {
-            dos.write((int)s.charAt(i));
+            dos.write((int)(0xff & b[i]));
         }
         dos.close();
 
@@ -685,38 +685,37 @@
     }
 
     private void write_iTXt() throws IOException {
-        Iterator keywordIter = metadata.iTXt_keyword.iterator();
-        Iterator flagIter = metadata.iTXt_compressionFlag.iterator();
-        Iterator methodIter = metadata.iTXt_compressionMethod.iterator();
-        Iterator languageIter = metadata.iTXt_languageTag.iterator();
-        Iterator translatedKeywordIter =
+        Iterator<String> keywordIter = metadata.iTXt_keyword.iterator();
+        Iterator<Boolean> flagIter = metadata.iTXt_compressionFlag.iterator();
+        Iterator<Integer> methodIter = metadata.iTXt_compressionMethod.iterator();
+        Iterator<String> languageIter = metadata.iTXt_languageTag.iterator();
+        Iterator<String> translatedKeywordIter =
             metadata.iTXt_translatedKeyword.iterator();
-        Iterator textIter = metadata.iTXt_text.iterator();
+        Iterator<String> textIter = metadata.iTXt_text.iterator();
 
         while (keywordIter.hasNext()) {
             ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream);
-            String keyword = (String)keywordIter.next();
-            cs.writeBytes(keyword);
+
+            cs.writeBytes(keywordIter.next());
             cs.writeByte(0);
 
-            int flag = ((Integer)flagIter.next()).intValue();
-            cs.writeByte(flag);
-            int method = ((Integer)methodIter.next()).intValue();
-            cs.writeByte(method);
+            Boolean compressed = flagIter.next();
+            cs.writeByte(compressed ? 1 : 0);
 
-            String languageTag = (String)languageIter.next();
-            cs.writeBytes(languageTag);
+            cs.writeByte(methodIter.next().intValue());
+
+            cs.writeBytes(languageIter.next());
             cs.writeByte(0);
 
-            String translatedKeyword = (String)translatedKeywordIter.next();
-            cs.writeBytes(translatedKeyword);
+
+            cs.write(translatedKeywordIter.next().getBytes("UTF8"));
             cs.writeByte(0);
 
-            String text = (String)textIter.next();
-            if (flag == 1) {
-                cs.write(deflate(text));
+            String text = textIter.next();
+            if (compressed) {
+                cs.write(deflate(text.getBytes("UTF8")));
             } else {
-                cs.writeUTF(text);
+                cs.write(text.getBytes("UTF8"));
             }
             cs.finish();
         }
@@ -737,7 +736,7 @@
             cs.writeByte(compressionMethod);
 
             String text = (String)textIter.next();
-            cs.write(deflate(text));
+            cs.write(deflate(text.getBytes()));
             cs.finish();
         }
     }
--- a/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java	Wed Oct 29 01:52:22 2008 +0300
+++ b/jdk/src/share/classes/com/sun/imageio/plugins/png/PNGMetadata.java	Sat Nov 01 20:42:18 2008 +0300
@@ -174,12 +174,12 @@
     public byte[] iCCP_compressedProfile;
 
     // iTXt chunk
-    public ArrayList iTXt_keyword = new ArrayList(); // Strings
-    public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers
-    public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers
-    public ArrayList iTXt_languageTag = new ArrayList(); // Strings
-    public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings
-    public ArrayList iTXt_text = new ArrayList(); // Strings
+    public ArrayList<String> iTXt_keyword = new ArrayList<String>();
+    public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>();
+    public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>();
+    public ArrayList<String> iTXt_languageTag = new ArrayList<String>();
+    public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>();
+    public ArrayList<String> iTXt_text = new ArrayList<String>();
 
     // pHYs chunk
     public boolean pHYs_present;
@@ -597,19 +597,17 @@
         if (iTXt_keyword.size() > 0) {
             IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
             for (int i = 0; i < iTXt_keyword.size(); i++) {
-                Integer val;
-
                 IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
-                iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i));
-                val = (Integer)iTXt_compressionFlag.get(i);
-                iTXt_node.setAttribute("compressionFlag", val.toString());
-                val = (Integer)iTXt_compressionMethod.get(i);
-                iTXt_node.setAttribute("compressionMethod", val.toString());
+                iTXt_node.setAttribute("keyword", iTXt_keyword.get(i));
+                iTXt_node.setAttribute("compressionFlag",
+                        iTXt_compressionFlag.get(i) ? "1" : "0");
+                iTXt_node.setAttribute("compressionMethod",
+                        iTXt_compressionMethod.get(i).toString());
                 iTXt_node.setAttribute("languageTag",
-                                       (String)iTXt_languageTag.get(i));
+                                       iTXt_languageTag.get(i));
                 iTXt_node.setAttribute("translatedKeyword",
-                                       (String)iTXt_translatedKeyword.get(i));
-                iTXt_node.setAttribute("text", (String)iTXt_text.get(i));
+                                       iTXt_translatedKeyword.get(i));
+                iTXt_node.setAttribute("text", iTXt_text.get(i));
 
                 iTXt_parent.appendChild(iTXt_node);
             }
@@ -1037,11 +1035,11 @@
 
         for (int i = 0; i < iTXt_keyword.size(); i++) {
             node = new IIOMetadataNode("TextEntry");
-            node.setAttribute("keyword", (String)iTXt_keyword.get(i));
-            node.setAttribute("value", (String)iTXt_text.get(i));
+            node.setAttribute("keyword", iTXt_keyword.get(i));
+            node.setAttribute("value", iTXt_text.get(i));
             node.setAttribute("language",
-                              (String)iTXt_languageTag.get(i));
-            if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) {
+                              iTXt_languageTag.get(i));
+            if (iTXt_compressionFlag.get(i)) {
                 node.setAttribute("compression", "deflate");
             } else {
                 node.setAttribute("compression", "none");
@@ -1427,11 +1425,11 @@
 
                     boolean compressionFlag =
                         getBooleanAttribute(iTXt_node, "compressionFlag");
-                    iTXt_compressionFlag.add(new Boolean(compressionFlag));
+                    iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag));
 
                     String compressionMethod =
                         getAttribute(iTXt_node, "compressionMethod");
-                    iTXt_compressionMethod.add(compressionMethod);
+                    iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
 
                     String languageTag =
                         getAttribute(iTXt_node, "languageTag");
@@ -1950,13 +1948,10 @@
                                 tEXt_text.add(value);
                             }
                         } else {
-                            int flag = compression.equals("zip") ?
-                                1 : 0;
-
                             // Use an iTXt node
                             iTXt_keyword.add(keyword);
-                            iTXt_compressionFlag.add(new Integer(flag));
-                            iTXt_compressionMethod.add(new Integer(0));
+                            iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip")));
+                            iTXt_compressionMethod.add(Integer.valueOf(0));
                             iTXt_languageTag.add(language);
                             iTXt_translatedKeyword.add(keyword); // fake it
                             iTXt_text.add(value);
@@ -1993,12 +1988,12 @@
         gAMA_present = false;
         hIST_present = false;
         iCCP_present = false;
-        iTXt_keyword = new ArrayList();
-        iTXt_compressionFlag = new ArrayList();
-        iTXt_compressionMethod = new ArrayList();
-        iTXt_languageTag = new ArrayList();
-        iTXt_translatedKeyword = new ArrayList();
-        iTXt_text = new ArrayList();
+        iTXt_keyword = new ArrayList<String>();
+        iTXt_compressionFlag = new ArrayList<Boolean>();
+        iTXt_compressionMethod = new ArrayList<Integer>();
+        iTXt_languageTag = new ArrayList<String>();
+        iTXt_translatedKeyword = new ArrayList<String>();
+        iTXt_text = new ArrayList<String>();
         pHYs_present = false;
         sBIT_present = false;
         sPLT_present = false;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/imageio/plugins/png/ITXtTest.java	Sat Nov 01 20:42:18 2008 +0300
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/**
+ * @test
+ * @bug     6541476
+ * @summary Test verifies that ImageIO PNG plugin correcly handles the
+ *          iTxt chunk (International textual data).
+ *
+ * @run     main ITXtTest
+ */
+
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.ImageInputStream;
+
+import org.w3c.dom.Node;
+
+public class ITXtTest {
+    static public void main(String args[]) {
+        ITXtTest t_en = new ITXtTest();
+        t_en.description = "xml - en";
+        t_en.keyword = "XML:com.adobe.xmp";
+        t_en.isCompressed = false;
+        t_en.compression = 0;
+        t_en.language = "en";
+        t_en.trasKeyword = "XML:com.adobe.xmp";
+        t_en.text = "<xml>Something</xml>";
+
+        doTest(t_en);
+
+        // check compression case
+        t_en.isCompressed = true;
+        t_en.description = "xml - en - compressed";
+
+        doTest(t_en);
+
+        ITXtTest t_ru = new ITXtTest();
+        t_ru.description = "xml - ru";
+        t_ru.keyword = "XML:com.adobe.xmp";
+        t_ru.isCompressed = false;
+        t_ru.compression = 0;
+        t_ru.language = "ru";
+        t_ru.trasKeyword = "\u0410\u0410\u0410\u0410\u0410 XML";
+        t_ru.text = "<xml>\u042A\u042F\u042F\u042F\u042F\u042F\u042F</xml>";
+
+        doTest(t_ru);
+
+        t_ru.isCompressed = true;
+        t_ru.description = "xml - ru - compressed";
+
+        doTest(t_ru);
+    }
+
+
+    String description;
+
+    String keyword;
+    boolean isCompressed;
+    int compression;
+    String language;
+    String trasKeyword;
+    String text;
+
+
+    public IIOMetadataNode getNode() {
+        IIOMetadataNode iTXt = new IIOMetadataNode("iTXt");
+        IIOMetadataNode iTXtEntry = new IIOMetadataNode("iTXtEntry");
+        iTXtEntry.setAttribute("keyword", keyword);
+        iTXtEntry.setAttribute("compressionFlag",
+                               isCompressed ? "true" : "false");
+        iTXtEntry.setAttribute("compressionMethod",
+                               Integer.toString(compression));
+        iTXtEntry.setAttribute("languageTag", language);
+        iTXtEntry.setAttribute("translatedKeyword",
+                               trasKeyword);
+        iTXtEntry.setAttribute("text", text);
+        iTXt.appendChild(iTXtEntry);
+        return iTXt;
+    }
+
+    public static ITXtTest getFromNode(IIOMetadataNode n) {
+        ITXtTest t = new ITXtTest();
+
+        if (!"iTXt".equals(n.getNodeName())) {
+            throw new RuntimeException("Invalid node");
+        }
+        IIOMetadataNode e = (IIOMetadataNode)n.getFirstChild();
+        if (!"iTXtEntry".equals(e.getNodeName())) {
+            throw new RuntimeException("Invalid entry node");
+        }
+        t.keyword = e.getAttribute("keyword");
+        t.isCompressed =
+            (Integer.valueOf(e.getAttribute("compressionFlag")).intValue() == 1);
+        t.compression =
+            Integer.valueOf(e.getAttribute("compressionMethod")).intValue();
+        t.language = e.getAttribute("languageTag");
+        t.trasKeyword = e.getAttribute("translatedKeyword");
+        t.text = e.getAttribute("text");
+
+        return t;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (! (o instanceof ITXtTest)) {
+            return false;
+        }
+        ITXtTest t = (ITXtTest)o;
+        if (!keyword.equals(t.keyword)) { return false; }
+        if (isCompressed != t.isCompressed) { return false; }
+        if (compression != t.compression) { return false; }
+        if (!language.equals(t.language)) { return false; }
+        if (!trasKeyword.equals(t.trasKeyword)) { return false; }
+        if (!text.equals(t.text)) { return false; }
+
+        return true;
+    }
+
+
+
+    private static void doTest(ITXtTest src) {
+
+        System.out.println("Test: " + src.description);
+
+        File file = new File("test.png");
+
+        writeTo(file, src);
+        ITXtTest dst = readFrom(file);
+
+        if (dst == null || !dst.equals(src)) {
+            throw new RuntimeException("Test failed.");
+        }
+
+        System.out.println("Test passed.");
+    }
+
+    private static void writeTo(File f, ITXtTest t) {
+        BufferedImage src = createBufferedImage();
+        try {
+            ImageOutputStream imageOutputStream =
+                ImageIO.createImageOutputStream(f);
+
+            ImageTypeSpecifier imageTypeSpecifier =
+                new ImageTypeSpecifier(src);
+            ImageWriter imageWriter =
+                ImageIO.getImageWritersByFormatName("PNG").next();
+
+            imageWriter.setOutput(imageOutputStream);
+
+            IIOMetadata m =
+                imageWriter.getDefaultImageMetadata(imageTypeSpecifier, null);
+
+            String format = m.getNativeMetadataFormatName();
+            Node root = m.getAsTree(format);
+
+            IIOMetadataNode iTXt = t.getNode();
+            root.appendChild(iTXt);
+            m.setFromTree(format, root);
+
+            imageWriter.write(new IIOImage(src, null, m));
+            imageOutputStream.close();
+            System.out.println("Writing done.");
+        } catch (Throwable e) {
+            throw new RuntimeException("Writing test failed.", e);
+        }
+    }
+
+    private static ITXtTest readFrom(File f) {
+        try {
+            ImageInputStream iis = ImageIO.createImageInputStream(f);
+            ImageReader r = ImageIO.getImageReaders(iis).next();
+            r.setInput(iis);
+
+            IIOImage dst = r.readAll(0, null);
+
+            // look for iTXt node
+            IIOMetadata m = dst.getMetadata();
+            Node root = m.getAsTree(m.getNativeMetadataFormatName());
+            Node n = root.getFirstChild();
+            while (n != null && !"iTXt".equals(n.getNodeName())) {
+                n = n.getNextSibling();
+            }
+            if (n == null) {
+                throw new RuntimeException("No iTXt node!");
+            }
+            ITXtTest t = ITXtTest.getFromNode((IIOMetadataNode)n);
+            return t;
+        } catch (Throwable e) {
+            throw new RuntimeException("Reading test failed.", e);
+        }
+    }
+
+    private static BufferedImage createBufferedImage() {
+        BufferedImage image = new BufferedImage(128, 128,
+                      BufferedImage.TYPE_4BYTE_ABGR_PRE);
+        Graphics2D graph = image.createGraphics();
+        graph.setPaintMode();
+        graph.setColor(Color.orange);
+        graph.fillRect(32, 32, 64, 64);
+        graph.dispose();
+        return image;
+    }
+}