26de9e247a
8322830: Add test case for ZipFile opening a ZIP with no entries Reviewed-by: lancea
380 lines
14 KiB
Java
380 lines
14 KiB
Java
/*
|
|
* Copyright (c) 1999, 2024, 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 4241361 4842702 4985614 6646605 5032358 6923692 6233323 8144977 8186464 4401122 8322830
|
|
@summary Make sure we can read a zip file.
|
|
@modules jdk.zipfs
|
|
@run junit ReadZip
|
|
*/
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.URI;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.*;
|
|
import java.util.Collections;
|
|
import java.util.HexFormat;
|
|
import java.util.Map;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipException;
|
|
import java.util.zip.ZipFile;
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
public class ReadZip {
|
|
|
|
// ZIP file produced during tests
|
|
private Path zip = Path.of("read-zip.zip");
|
|
|
|
/**
|
|
* Create a sample ZIP file for use by tests
|
|
* @param name name of the ZIP file to create
|
|
* @return a sample ZIP file
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
private Path createZip(String name) throws IOException {
|
|
Path zip = Path.of(name);
|
|
|
|
try (OutputStream out = Files.newOutputStream(zip);
|
|
ZipOutputStream zo = new ZipOutputStream(out)) {
|
|
zo.putNextEntry(new ZipEntry("file.txt"));
|
|
zo.write("hello".getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
return zip;
|
|
}
|
|
|
|
/**
|
|
* Delete the ZIP file produced after each test method
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@AfterEach
|
|
public void cleanup() throws IOException {
|
|
Files.deleteIfExists(zip);
|
|
}
|
|
|
|
/**
|
|
* Make sure we throw NPE when calling getEntry or getInputStream with null params
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void nullPointerExceptionOnNullParams() throws IOException {
|
|
zip = createZip("null-params.zip");
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
|
|
assertThrows(NullPointerException.class, () -> zf.getEntry(null));
|
|
assertThrows(NullPointerException.class, () -> zf.getInputStream(null));
|
|
|
|
// Sanity check that we can still read an entry
|
|
ZipEntry ze = zf.getEntry("file.txt");
|
|
assertNotNull(ze, "cannot read from zip file");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read the zip file that has some garbage bytes padded at the end
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void bytesPaddedAtEnd() throws IOException {
|
|
|
|
zip = createZip("bytes-padded.zip");
|
|
|
|
// pad some bytes
|
|
try (OutputStream os = Files.newOutputStream(zip,
|
|
StandardOpenOption.APPEND)) {
|
|
os.write(1);
|
|
os.write(3);
|
|
os.write(5);
|
|
os.write(7);
|
|
}
|
|
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
ZipEntry ze = zf.getEntry("file.txt");
|
|
assertNotNull(ze, "cannot read from zip file");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that we can read a comment from the ZIP
|
|
* file's 'End of Central Directory' header
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void readZipFileComment() throws IOException {
|
|
|
|
// Create a zip file with a comment in the 'End of Central Directory' header
|
|
try (OutputStream out = Files.newOutputStream(zip);
|
|
ZipOutputStream zos = new ZipOutputStream(out)) {
|
|
ZipEntry ze = new ZipEntry("ZipEntry");
|
|
zos.putNextEntry(ze);
|
|
zos.write(1);
|
|
zos.write(2);
|
|
zos.write(3);
|
|
zos.write(4);
|
|
zos.closeEntry();
|
|
zos.setComment("This is the comment for testing");
|
|
}
|
|
|
|
// Read zip file comment
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
ZipEntry ze = zf.getEntry("ZipEntry");
|
|
assertNotNull(ze, "cannot read entry from zip file");
|
|
assertEquals("This is the comment for testing", zf.getComment());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that a directory entry can be found using the
|
|
* name 'directory/' as well as 'directory/'
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void readDirectoryEntries() throws IOException {
|
|
|
|
// Create a ZIP containing some directory entries
|
|
try (OutputStream fos = Files.newOutputStream(zip);
|
|
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
|
// Add a META-INF directory with STORED compression type
|
|
ZipEntry metaInf = new ZipEntry("META-INF/");
|
|
metaInf.setMethod(ZipEntry.STORED);
|
|
metaInf.setSize(0);
|
|
metaInf.setCrc(0);
|
|
zos.putNextEntry(metaInf);
|
|
|
|
// Add a regular directory
|
|
ZipEntry dir = new ZipEntry("directory/");
|
|
zos.putNextEntry(dir);
|
|
zos.closeEntry();
|
|
}
|
|
|
|
// Verify directory lookups
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
// Look up 'directory/' using the full name
|
|
ZipEntry ze = zf.getEntry("directory/");
|
|
assertNotNull(ze, "read entry \"directory/\" failed");
|
|
assertTrue(ze.isDirectory(), "read entry \"directory/\" failed");
|
|
assertEquals("directory/", ze.getName());
|
|
|
|
try (InputStream is = zf.getInputStream(ze)) {
|
|
is.available();
|
|
} catch (Exception x) {
|
|
x.printStackTrace();
|
|
}
|
|
|
|
// Look up 'directory/' without the trailing slash
|
|
ze = zf.getEntry("directory");
|
|
assertNotNull(ze, "read entry \"directory\" failed");
|
|
assertTrue(ze.isDirectory(), "read entry \"directory\" failed");
|
|
assertEquals("directory/", ze.getName());
|
|
|
|
try (InputStream is = zf.getInputStream(ze)) {
|
|
is.available();
|
|
} catch (Exception x) {
|
|
x.printStackTrace();
|
|
}
|
|
// Sanity check that also META-INF/ can be looked up with or without the trailing slash
|
|
assertNotNull(zf.getEntry("META-INF"));
|
|
assertNotNull(zf.getEntry("META-INF/"));
|
|
assertEquals(zf.getEntry("META-INF").getName(),
|
|
zf.getEntry("META-INF/").getName());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throw a NoSuchFileException exception when reading a non-existing zip file
|
|
*/
|
|
@Test
|
|
public void nonExistingFile() {
|
|
File nonExistingFile = new File("non-existing-file-f6804460f.zip");
|
|
assertThrows(NoSuchFileException.class, () ->
|
|
new ZipFile(nonExistingFile));
|
|
}
|
|
|
|
/**
|
|
* Read a Zip file with a 'Zip64 End of Central Directory header' which was created
|
|
* using ZipFileSystem with the 'forceZIP64End' option.
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void readZip64EndZipFs() throws IOException {
|
|
|
|
// Create zip file with Zip64 end
|
|
Map<String, Object> env = Map.of("create", "true", "forceZIP64End", "true");
|
|
try (FileSystem fs = FileSystems.newFileSystem(zip, env)) {
|
|
Files.write(fs.getPath("hello"), "hello".getBytes());
|
|
}
|
|
// Read using ZipFile
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
try (InputStream in = zf.getInputStream(zf.getEntry("hello"))) {
|
|
assertEquals("hello", new String(in.readAllBytes(), StandardCharsets.US_ASCII));
|
|
}
|
|
}
|
|
// Read using ZipFileSystem
|
|
try (FileSystem fs = FileSystems.newFileSystem(zip, Map.of())) {
|
|
assertEquals("hello", new String(Files.readAllBytes(fs.getPath("hello"))));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read a zip file created via Info-ZIP in streaming mode,
|
|
* which includes a 'Zip64 End of Central Directory header'.
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
* @throws InterruptedException if an unexpected InterruptedException occurs
|
|
*/
|
|
@Test
|
|
public void readZip64EndInfoZIPStreaming() throws IOException, InterruptedException {
|
|
// ZIP created using: "echo -n hello | zip zip64.zip -"
|
|
// Hex encoded using: "cat zip64.zip | xxd -ps"
|
|
byte[] zipBytes = HexFormat.of().parseHex("""
|
|
504b03042d0000000000c441295886a61036ffffffffffffffff01001400
|
|
2d010010000500000000000000050000000000000068656c6c6f504b0102
|
|
1e032d0000000000c441295886a610360500000005000000010000000000
|
|
000001000000b011000000002d504b06062c000000000000001e032d0000
|
|
00000000000000010000000000000001000000000000002f000000000000
|
|
003800000000000000504b06070000000067000000000000000100000050
|
|
4b050600000000010001002f000000380000000000
|
|
""".replaceAll("\n","")
|
|
);
|
|
|
|
Files.write(zip, zipBytes);
|
|
|
|
try (ZipFile zf = new ZipFile(this.zip.toFile())) {
|
|
try (InputStream in = zf.getInputStream(zf.getEntry("-"))) {
|
|
String contents = new String(in.readAllBytes(), StandardCharsets.US_ASCII);
|
|
assertEquals("hello", contents);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that the available() method overriden by the input stream returned by
|
|
* ZipFile.getInputStream correctly returns the number of remaining uncompressed bytes
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void availableShouldReturnRemainingUncompressedBytes() throws IOException {
|
|
// The number of uncompressed bytes to write to the sample ZIP entry
|
|
final int expectedBytes = 512;
|
|
|
|
// Create a sample ZIP with deflated entry of a known uncompressed size
|
|
try (ZipOutputStream zo = new ZipOutputStream(Files.newOutputStream(zip))) {
|
|
zo.putNextEntry(new ZipEntry("file.txt"));
|
|
zo.write(new byte[expectedBytes]);
|
|
}
|
|
|
|
// Verify the behavior of ZipFileInflaterInputStream.available()
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
ZipEntry e = zf.getEntry("file.txt");
|
|
try (InputStream in = zf.getInputStream(e)) {
|
|
// Initially, available() should return the full uncompressed size of the entry
|
|
assertEquals(expectedBytes, in.available(),
|
|
"wrong initial return value of available");
|
|
|
|
// Reading a few bytes should reduce the number of available bytes accordingly
|
|
int bytesToRead = 10;
|
|
in.read(new byte[bytesToRead]);
|
|
assertEquals(expectedBytes - bytesToRead, in.available());
|
|
|
|
// Reading all remaining bytes should reduce the number of available bytes to zero
|
|
in.transferTo(OutputStream.nullOutputStream());
|
|
assertEquals(0, in.available());
|
|
|
|
// available on a closed input stream should return zero
|
|
in.close();
|
|
assertEquals(0, in.available());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that reading an InputStream from a closed ZipFile
|
|
* throws IOException as expected and does not crash the VM.
|
|
* See bugs: 4528128 6846616
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void readAfterClose() throws IOException {
|
|
zip = createZip("read-after-close.zip");
|
|
InputStream in;
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
ZipEntry zent = zf.getEntry("file.txt");
|
|
in = zf.getInputStream(zent);
|
|
}
|
|
|
|
// zf is closed at this point
|
|
assertThrows(IOException.class, () -> {
|
|
in.read();
|
|
});
|
|
assertThrows(IOException.class, () -> {
|
|
in.read(new byte[10]);
|
|
});
|
|
assertThrows(IOException.class, () -> {
|
|
byte[] buf = new byte[10];
|
|
in.read(buf, 0, buf.length);
|
|
});
|
|
assertThrows(IOException.class, () -> {
|
|
in.readAllBytes();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Verify that ZipFile can open a ZIP file with zero entries
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void noEntries() throws IOException {
|
|
// Create a ZIP file with no entries
|
|
try (ZipOutputStream zo = new ZipOutputStream(Files.newOutputStream(zip))) {
|
|
}
|
|
|
|
// Open the "empty" ZIP file
|
|
try (ZipFile zf = new ZipFile(zip.toFile())) {
|
|
// Verify size
|
|
assertEquals(0, zf.size());
|
|
|
|
// Verify entry lookup using ZipFile.getEntry()
|
|
assertNull(zf.getEntry("file.txt"));
|
|
|
|
// Verify iteration using ZipFile.entries()
|
|
assertEquals(Collections.emptyList(), Collections.list(zf.entries()));
|
|
|
|
// Verify iteration using ZipFile.stream()
|
|
assertEquals(Collections.emptyList(), zf.stream().toList());
|
|
}
|
|
}
|
|
} |