6541476: PNG imageio plugin incorrectly handles iTXt chunk
Reviewed-by: igor, prr
--- 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;
+ }
+}