1 /* 2 * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 6541476 6782079 27 * @summary Write and read a PNG file including an non-latin1 iTXt chunk 28 * Test also verifies that trunkated png images does not cause 29 * an OoutOfMemory error. 30 * 31 * @run main ItxtUtf8Test 32 * 33 * @run main/othervm/timeout=10 -Xmx2m ItxtUtf8Test truncate 34 */ 35 36 import java.awt.image.BufferedImage; 37 import java.io.ByteArrayInputStream; 38 import java.io.ByteArrayOutputStream; 39 import java.io.OutputStream; 40 import java.util.Arrays; 41 import java.util.List; 42 import javax.imageio.IIOException; 43 import javax.imageio.IIOImage; 44 import javax.imageio.ImageIO; 45 import javax.imageio.ImageReader; 46 import javax.imageio.ImageTypeSpecifier; 47 import javax.imageio.ImageWriter; 48 import javax.imageio.metadata.IIOMetadata; 49 import javax.imageio.stream.ImageInputStream; 50 import javax.imageio.stream.ImageOutputStream; 51 import javax.imageio.stream.MemoryCacheImageInputStream; 52 import javax.imageio.stream.MemoryCacheImageOutputStream; 53 import org.w3c.dom.DOMImplementation; 54 import org.w3c.dom.Document; 55 import org.w3c.dom.Element; 56 import org.w3c.dom.Node; 57 import org.w3c.dom.bootstrap.DOMImplementationRegistry; 58 59 public class ItxtUtf8Test { 60 61 public static final String 62 TEXT = "\u24c9\u24d4\u24e7\u24e3" + 63 "\ud835\udc13\ud835\udc1e\ud835\udc31\ud835\udc2d" + 64 "\u24c9\u24d4\u24e7\u24e3", // a repetition for compression 65 VERBATIM = "\u24e5\u24d4\u24e1\u24d1\u24d0\u24e3\u24d8\u24dc", 66 COMPRESSED = "\u24d2\u24de\u24dc\u24df\u24e1\u24d4\u24e2\u24e2\u24d4\u24d3"; 67 68 public static final byte[] 69 VBYTES = { 70 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x56, // chunk length 71 (byte)0x69, (byte)0x54, (byte)0x58, (byte)0x74, // chunk type "iTXt" 72 (byte)0x76, (byte)0x65, (byte)0x72, (byte)0x62, 73 (byte)0x61, (byte)0x74, (byte)0x69, (byte)0x6d, // keyword "verbatim" 74 (byte)0x00, // separator terminating keyword 75 (byte)0x00, // compression flag 76 (byte)0x00, // compression method, must be zero 77 (byte)0x78, (byte)0x2d, (byte)0x63, (byte)0x69, 78 (byte)0x72, (byte)0x63, (byte)0x6c, (byte)0x65, 79 (byte)0x64, // language tag "x-circled" 80 (byte)0x00, // separator terminating language tag 81 (byte)0xe2, (byte)0x93, (byte)0xa5, // '\u24e5' 82 (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' 83 (byte)0xe2, (byte)0x93, (byte)0xa1, // '\u24e1' 84 (byte)0xe2, (byte)0x93, (byte)0x91, // '\u24d1' 85 (byte)0xe2, (byte)0x93, (byte)0x90, // '\u24d0' 86 (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' 87 (byte)0xe2, (byte)0x93, (byte)0x98, // '\u24d8' 88 (byte)0xe2, (byte)0x93, (byte)0x9c, // '\u24dc' 89 (byte)0x00, // separator terminating the translated keyword 90 (byte)0xe2, (byte)0x93, (byte)0x89, // '\u24c9' 91 (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' 92 (byte)0xe2, (byte)0x93, (byte)0xa7, // '\u24e7' 93 (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' 94 (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0x93, // '\ud835\udc13' 95 (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0x9e, // '\ud835\udc1e' 96 (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0xb1, // '\ud835\udc31' 97 (byte)0xf0, (byte)0x9d, (byte)0x90, (byte)0xad, // '\ud835\udc2d' 98 (byte)0xe2, (byte)0x93, (byte)0x89, // '\u24c9' 99 (byte)0xe2, (byte)0x93, (byte)0x94, // '\u24d4' 100 (byte)0xe2, (byte)0x93, (byte)0xa7, // '\u24e7' 101 (byte)0xe2, (byte)0x93, (byte)0xa3, // '\u24e3' 102 (byte)0xb5, (byte)0xcc, (byte)0x97, (byte)0x56 // CRC 103 }, 104 CBYTES = { 105 // we don't want to check the chunk length, 106 // as this might depend on implementation. 107 (byte)0x69, (byte)0x54, (byte)0x58, (byte)0x74, // chunk type "iTXt" 108 (byte)0x63, (byte)0x6f, (byte)0x6d, (byte)0x70, 109 (byte)0x72, (byte)0x65, (byte)0x73, (byte)0x73, 110 (byte)0x65, (byte)0x64, // keyword "compressed" 111 (byte)0x00, // separator terminating keyword 112 (byte)0x01, // compression flag 113 (byte)0x00, // compression method, 0=deflate 114 (byte)0x78, (byte)0x2d, (byte)0x63, (byte)0x69, 115 (byte)0x72, (byte)0x63, (byte)0x6c, (byte)0x65, 116 (byte)0x64, // language tag "x-circled" 117 (byte)0x00, // separator terminating language tag 118 // we don't want to check the actual compressed data, 119 // as this might depend on implementation. 120 }; 121 /* 122 */ 123 124 public static void main(String[] args) throws Exception { 125 List argList = Arrays.asList(args); 126 if (argList.contains("truncate")) { 127 try { 128 runTest(false, true); 129 throw new AssertionError("Expect an error for truncated file"); 130 } 131 catch (IIOException e) { 132 // expected an error for a truncated image file. 133 } 134 } 135 else { 136 runTest(argList.contains("dump"), false); 137 } 138 } 139 140 public static void runTest(boolean dump, boolean truncate) 141 throws Exception 142 { 143 String format = "javax_imageio_png_1.0"; 144 BufferedImage img = 145 new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB); 146 ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/png").next(); 147 ByteArrayOutputStream os = new ByteArrayOutputStream(); 148 ImageOutputStream ios = new MemoryCacheImageOutputStream(os); 149 iw.setOutput(ios); 150 IIOMetadata meta = 151 iw.getDefaultImageMetadata(new ImageTypeSpecifier(img), null); 152 DOMImplementationRegistry registry; 153 registry = DOMImplementationRegistry.newInstance(); 154 DOMImplementation impl = registry.getDOMImplementation("XML 3.0"); 155 Document doc = impl.createDocument(null, format, null); 156 Element root, itxt, entry; 157 root = doc.getDocumentElement(); 158 root.appendChild(itxt = doc.createElement("iTXt")); 159 itxt.appendChild(entry = doc.createElement("iTXtEntry")); 160 entry.setAttribute("keyword", "verbatim"); 161 entry.setAttribute("compressionFlag", "false"); 162 entry.setAttribute("compressionMethod", "0"); 163 entry.setAttribute("languageTag", "x-circled"); 164 entry.setAttribute("translatedKeyword", VERBATIM); 165 entry.setAttribute("text", TEXT); 166 itxt.appendChild(entry = doc.createElement("iTXtEntry")); 167 entry.setAttribute("keyword", "compressed"); 168 entry.setAttribute("compressionFlag", "true"); 169 entry.setAttribute("compressionMethod", "0"); 170 entry.setAttribute("languageTag", "x-circled"); 171 entry.setAttribute("translatedKeyword", COMPRESSED); 172 entry.setAttribute("text", TEXT); 173 meta.mergeTree(format, root); 174 iw.write(new IIOImage(img, null, meta)); 175 iw.dispose(); 176 177 byte[] bytes = os.toByteArray(); 178 if (dump) 179 System.out.write(bytes); 180 if (findBytes(VBYTES, bytes) < 0) 181 throw new AssertionError("verbatim block not found"); 182 if (findBytes(CBYTES, bytes) < 0) 183 throw new AssertionError("compressed block not found"); 184 int length = bytes.length; 185 if (truncate) 186 length = findBytes(VBYTES, bytes) + 32; 187 188 ImageReader ir = ImageIO.getImageReader(iw); 189 ByteArrayInputStream is = new ByteArrayInputStream(bytes, 0, length); 190 ImageInputStream iis = new MemoryCacheImageInputStream(is); 191 ir.setInput(iis); 192 meta = ir.getImageMetadata(0); 193 Node node = meta.getAsTree(format); 194 for (node = node.getFirstChild(); 195 !"iTXt".equals(node.getNodeName()); 196 node = node.getNextSibling()); 197 boolean verbatimSeen = false, compressedSeen = false; 198 for (node = node.getFirstChild(); 199 node != null; 200 node = node.getNextSibling()) { 201 entry = (Element)node; 202 String keyword = entry.getAttribute("keyword"); 203 String translatedKeyword = entry.getAttribute("translatedKeyword"); 204 String text = entry.getAttribute("text"); 205 if ("verbatim".equals(keyword)) { 206 if (verbatimSeen) throw new AssertionError("Duplicate"); 207 verbatimSeen = true; 208 if (!VERBATIM.equals(translatedKeyword)) 209 throw new AssertionError("Wrong translated keyword"); 210 if (!TEXT.equals(text)) 211 throw new AssertionError("Wrong text"); 212 } 213 else if ("compressed".equals(keyword)) { 214 if (compressedSeen) throw new AssertionError("Duplicate"); 215 compressedSeen = true; 216 if (!COMPRESSED.equals(translatedKeyword)) 217 throw new AssertionError("Wrong translated keyword"); 218 if (!TEXT.equals(text)) 219 throw new AssertionError("Wrong text"); 220 } 221 else { 222 throw new AssertionError("Unexpected keyword"); 223 } 224 } 225 if (!(verbatimSeen && compressedSeen)) 226 throw new AssertionError("Missing chunk"); 227 } 228 229 private static final int findBytes(byte[] needle, byte[] haystack) { 230 HAYSTACK: for (int h = 0; h <= haystack.length - needle.length; ++h) { 231 for (int n = 0; n < needle.length; ++n) { 232 if (needle[n] != haystack[h + n]) { 233 continue HAYSTACK; 234 } 235 } 236 return h; 237 } 238 return -1; 239 } 240 241 }