6541476: PNG imageio plugin incorrectly handles iTXt chunk

Reviewed-by: igor, prr
This commit is contained in:
Andrew Brygin 2008-11-01 20:42:18 +03:00
parent df32a451da
commit f0bc3cdc91
4 changed files with 313 additions and 73 deletions

View File

@ -44,7 +44,6 @@ import java.util.ArrayList;
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 javax.imageio.stream.ImageInputStream;
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 @@ public class PNGImageReader extends ImageReader {
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 @@ public class PNGImageReader extends ImageReader {
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 @@ public class PNGImageReader extends ImageReader {
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 @@ public class PNGImageReader extends ImageReader {
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 {

View File

@ -671,13 +671,13 @@ public class PNGImageWriter extends ImageWriter {
}
}
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 @@ public class PNGImageWriter extends ImageWriter {
}
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 @@ public class PNGImageWriter extends ImageWriter {
cs.writeByte(compressionMethod);
String text = (String)textIter.next();
cs.write(deflate(text));
cs.write(deflate(text.getBytes()));
cs.finish();
}
}

View File

@ -174,12 +174,12 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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 @@ public class PNGMetadata extends IIOMetadata implements Cloneable {
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;

View File

@ -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;
}
}