diff --git a/jdk/src/share/classes/java/util/zip/Deflater.java b/jdk/src/share/classes/java/util/zip/Deflater.java index 3bb5f9901b..72cca1d894 100644 --- a/jdk/src/share/classes/java/util/zip/Deflater.java +++ b/jdk/src/share/classes/java/util/zip/Deflater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2022, 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 @@ -559,6 +559,16 @@ private void ensureOpen() { throw new NullPointerException("Deflater has been closed"); } + /** + * Returns the value of 'finish' flag. + * 'finish' will be set to true if def.finish() method is called. + */ + boolean shouldFinish() { + synchronized (zsRef) { + return finish; + } + } + private static native void initIDs(); private native static long init(int level, int strategy, boolean nowrap); private native static void setDictionary(long addr, byte[] b, int off, int len); diff --git a/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java b/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java index f4cf79693a..c698a01473 100644 --- a/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java +++ b/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2022, 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 @@ -221,9 +221,15 @@ public void write(byte[] b, int off, int len) throws IOException { */ public void finish() throws IOException { if (!def.finished()) { - def.finish(); - while (!def.finished()) { - deflate(); + try{ + def.finish(); + while (!def.finished()) { + deflate(); + } + } catch(IOException e) { + if (usesDefaultDeflater) + def.end(); + throw e; } } } diff --git a/jdk/src/share/classes/java/util/zip/ZipOutputStream.java b/jdk/src/share/classes/java/util/zip/ZipOutputStream.java index 5c7d119953..b7184cc228 100644 --- a/jdk/src/share/classes/java/util/zip/ZipOutputStream.java +++ b/jdk/src/share/classes/java/util/zip/ZipOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2022, 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 @@ -304,7 +304,7 @@ public void closeEntry() throws IOException { crc.reset(); current = null; } catch (IOException e) { - if (usesDefaultDeflater && !(e instanceof ZipException)) + if (def.shouldFinish() && usesDefaultDeflater && !(e instanceof ZipException)) def.end(); throw e; } diff --git a/jdk/test/java/util/zip/CloseDeflaterTest.java b/jdk/test/java/util/zip/CloseDeflaterTest.java deleted file mode 100644 index 8aa4960f54..0000000000 --- a/jdk/test/java/util/zip/CloseDeflaterTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2021, 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 8193682 - * @summary Test Infinite loop while writing on closed GZipOutputStream , ZipOutputStream and JarOutputStream. - * @run testng CloseDeflaterTest - */ -import java.io.*; -import java.util.Random; -import java.util.jar.JarOutputStream; -import java.util.zip.GZIPOutputStream; -import java.util.zip.ZipOutputStream; -import java.util.zip.ZipEntry; - -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import static org.testng.Assert.fail; - - -public class CloseDeflaterTest { - - //number of bytes to write - private static final int INPUT_LENGTH= 512; - //OutputStream that will throw an exception during a write operation - private static OutputStream outStream = new OutputStream() { - @Override - public void write(byte[] b, int off, int len) throws IOException { - //throw exception during write - throw new IOException(); - } - @Override - public void write(byte b[]) throws IOException {} - @Override - public void write(int b) throws IOException {} - }; - private static byte[] inputBytes = new byte[INPUT_LENGTH]; - private static Random rand = new Random(); - - @DataProvider(name = "testgzipinput") - public Object[][] testGZipInput() { - //testGZip will close the GZipOutputStream using close() method when the boolean - //useCloseMethod is set to true and finish() method if the value is set to false - return new Object[][] { - { GZIPOutputStream.class, true }, - { GZIPOutputStream.class, false }, - }; - } - - @DataProvider(name = "testzipjarinput") - public Object[][] testZipAndJarInput() { - //testZipAndJarInput will perfrom write/closeEntry operations on JarOutputStream when the boolean - //useJar is set to true and on ZipOutputStream if the value is set to false - return new Object[][] { - { JarOutputStream.class, true }, - { ZipOutputStream.class, false }, - }; - } - - @BeforeTest - public void before_test() - { - //add inputBytes array with random bytes to write into Zip - rand.nextBytes(inputBytes); - } - - //Test for infinite loop by writing bytes to closed GZIPOutputStream - @Test(dataProvider = "testgzipinput") - public void testGZip(Class type, boolean useCloseMethod) throws IOException { - GZIPOutputStream zip = new GZIPOutputStream(outStream); - try { - zip.write(inputBytes, 0, INPUT_LENGTH); - //close zip - if(useCloseMethod) { - zip.close(); - } else { - zip.finish(); - } - } catch (IOException e) { - //expected - } - for (int i = 0; i < 3; i++) { - try { - //write on a closed GZIPOutputStream - zip.write(inputBytes, 0, INPUT_LENGTH); - fail("Deflater closed exception not thrown"); - } catch (NullPointerException e) { - //expected , Deflater has been closed exception - } - } - } - - //Test for infinite loop by writing bytes to closed ZipOutputStream/JarOutputStream - @Test(dataProvider = "testzipjarinput") - public void testZipCloseEntry(Class type,boolean useJar) throws IOException { - ZipOutputStream zip = null; - if(useJar) { - zip = new JarOutputStream(outStream); - } else { - zip = new ZipOutputStream(outStream); - } - try { - zip.putNextEntry(new ZipEntry("")); - } catch (IOException e) { - //expected to throw IOException since putNextEntry calls write method - } - try { - zip.write(inputBytes, 0, INPUT_LENGTH); - //close zip entry - zip.closeEntry(); - } catch (IOException e) { - //expected - } - for (int i = 0; i < 3; i++) { - try { - //write on a closed ZipOutputStream - zip.write(inputBytes, 0, INPUT_LENGTH); - fail("Deflater closed exception not thrown"); - } catch (NullPointerException e) { - //expected , Deflater has been closed exception - } - } - } - -} diff --git a/jdk/test/java/util/zip/CloseInflaterDeflaterTest.java b/jdk/test/java/util/zip/CloseInflaterDeflaterTest.java new file mode 100644 index 0000000000..b8f9197106 --- /dev/null +++ b/jdk/test/java/util/zip/CloseInflaterDeflaterTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021, 2022, 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 8193682 8278794 8284771 + * @summary Test Infinite loop while writing on closed Deflater and Inflater. + * @run testng CloseInflaterDeflaterTest + */ +import java.io.*; +import java.util.Random; +import java.util.jar.JarOutputStream; +import java.util.zip.DeflaterInputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.InflaterOutputStream; +import java.util.zip.ZipOutputStream; +import java.util.zip.ZipEntry; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertThrows; + + +public class CloseInflaterDeflaterTest { + + // Number of bytes to write/read from Deflater/Inflater + private static final int INPUT_LENGTH= 512; + // OutputStream that will throw an exception during a write operation + private static OutputStream outStream = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) throws IOException { + throw new IOException(); + } + @Override + public void write(byte[] b) throws IOException {} + @Override + public void write(int b) throws IOException {} + }; + // InputStream that will throw an exception during a read operation + private static InputStream inStream = new InputStream() { + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new IOException(); + } + @Override + public int read(byte[] b) throws IOException { throw new IOException();} + @Override + public int read() throws IOException { throw new IOException();} + }; + // Input bytes for read/write operation + private static byte[] inputBytes = new byte[INPUT_LENGTH]; + // Random function to add bytes to inputBytes + private static Random rand = new Random(); + + /** + * DataProvider to specify whether to use close() or finish() of OutputStream + * + * @return Entry object indicating which method to use for closing OutputStream + */ + @DataProvider + public Object[][] testOutputStreams() { + return new Object[][] { + { true }, + { false }, + }; + } + + /** + * DataProvider to specify on which outputstream closeEntry() has to be called + * + * @return Entry object returning either JarOutputStream or ZipOutputStream + */ + @DataProvider + public Object[][] testZipAndJar() throws IOException{ + return new Object[][] { + { new JarOutputStream(outStream)}, + { new ZipOutputStream(outStream)}, + }; + } + + /** + * Add inputBytes array with random bytes to write into OutputStream + */ + @BeforeTest + public void before_test() + { + rand.nextBytes(inputBytes); + } + + /** + * Test for infinite loop by writing bytes to closed GZIPOutputStream + * + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams") + public void testGZip(boolean useCloseMethod) throws IOException { + GZIPOutputStream gzip = new GZIPOutputStream(outStream); + gzip.write(inputBytes, 0, INPUT_LENGTH); + assertThrows(IOException.class, () -> { + // Close GZIPOutputStream + if (useCloseMethod) { + gzip.close(); + } else { + gzip.finish(); + } + }); + // Write on a closed GZIPOutputStream, closed Deflater IOException expected + assertThrows(NullPointerException.class , () -> gzip.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed DeflaterOutputStream + * + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams") + public void testDeflaterOutputStream(boolean useCloseMethod) throws IOException { + DeflaterOutputStream def = new DeflaterOutputStream(outStream); + assertThrows(IOException.class , () -> def.write(inputBytes, 0, INPUT_LENGTH)); + assertThrows(IOException.class, () -> { + // Close DeflaterOutputStream + if (useCloseMethod) { + def.close(); + } else { + def.finish(); + } + }); + // Write on a closed DeflaterOutputStream, 'Deflater has been closed' NPE is expected + assertThrows(NullPointerException.class , () -> def.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by reading bytes from closed DeflaterInputStream + * + * @throws IOException if an error occurs + */ + @Test + public void testDeflaterInputStream() throws IOException { + DeflaterInputStream def = new DeflaterInputStream(inStream); + assertThrows(IOException.class , () -> def.read(inputBytes, 0, INPUT_LENGTH)); + // Close DeflaterInputStream + def.close(); + // Read from a closed DeflaterInputStream, closed Deflater IOException expected + assertThrows(IOException.class , () -> def.read(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed InflaterOutputStream + * + * Note: Disabling this test as it is failing intermittently. + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams",enabled=false) + public void testInflaterOutputStream(boolean useCloseMethod) throws IOException { + InflaterOutputStream inf = new InflaterOutputStream(outStream); + assertThrows(IOException.class , () -> inf.write(inputBytes, 0, INPUT_LENGTH)); + assertThrows(IOException.class , () -> { + // Close InflaterOutputStream + if (useCloseMethod) { + inf.close(); + } else { + inf.finish(); + } + }); + // Write on a closed InflaterOutputStream , closed Inflater IOException expected + assertThrows(IOException.class , () -> inf.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed ZipOutputStream/JarOutputStream + * + * @param zip will be the instance of either JarOutputStream or ZipOutputStream + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testZipAndJar") + public void testZipCloseEntry(ZipOutputStream zip) throws IOException { + assertThrows(IOException.class , () -> zip.putNextEntry(new ZipEntry(""))); + zip.write(inputBytes, 0, INPUT_LENGTH); + assertThrows(IOException.class , () -> zip.closeEntry()); + // Write on a closed ZipOutputStream , 'Deflater has been closed' NPE is expected + assertThrows(NullPointerException.class , () -> zip.write(inputBytes, 0, INPUT_LENGTH)); + } + +}