8255380: (zipfs) ZipFileSystem::readExtra can fail if zipinfo-time is not set to false
Reviewed-by: redestad
This commit is contained in:
parent
88ee973334
commit
6606e0909c
@ -2907,11 +2907,16 @@ class ZipFileSystem extends FileSystem {
|
||||
|
||||
// read NTFS, UNIX and ZIP64 data from cen.extra
|
||||
private void readExtra(ZipFileSystem zipfs) throws IOException {
|
||||
// Note that Section 4.5, Extensible data fields, of the PKWARE ZIP File
|
||||
// Format Specification does not mandate a specific order for the
|
||||
// data in the extra field, therefore Zip FS cannot assume the data
|
||||
// is written in the same order by Zip libraries as Zip FS.
|
||||
if (extra == null)
|
||||
return;
|
||||
int elen = extra.length;
|
||||
int off = 0;
|
||||
int newOff = 0;
|
||||
boolean hasZip64LocOffset = false;
|
||||
while (off + 4 < elen) {
|
||||
// extra spec: HeaderID+DataSize+Data
|
||||
int pos = off;
|
||||
@ -2955,7 +2960,7 @@ class ZipFileSystem extends FileSystem {
|
||||
ctime = winToJavaTime(LL(extra, pos + 20));
|
||||
break;
|
||||
case EXTID_EXTT:
|
||||
// spec says the Extened timestamp in cen only has mtime
|
||||
// spec says the Extended timestamp in cen only has mtime
|
||||
// need to read the loc to get the extra a/ctime, if flag
|
||||
// "zipinfo-time" is not specified to false;
|
||||
// there is performance cost (move up to loc and read) to
|
||||
@ -2965,44 +2970,15 @@ class ZipFileSystem extends FileSystem {
|
||||
mtime = unixToJavaTime(LG(extra, pos + 1));
|
||||
break;
|
||||
}
|
||||
byte[] buf = new byte[LOCHDR];
|
||||
if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
|
||||
!= buf.length)
|
||||
throw new ZipException("loc: reading failed");
|
||||
if (!locSigAt(buf, 0))
|
||||
throw new ZipException("loc: wrong sig ->"
|
||||
+ Long.toString(getSig(buf, 0), 16));
|
||||
int locElen = LOCEXT(buf);
|
||||
if (locElen < 9) // EXTT is at least 9 bytes
|
||||
break;
|
||||
int locNlen = LOCNAM(buf);
|
||||
buf = new byte[locElen];
|
||||
if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
|
||||
!= buf.length)
|
||||
throw new ZipException("loc extra: reading failed");
|
||||
int locPos = 0;
|
||||
while (locPos + 4 < buf.length) {
|
||||
int locTag = SH(buf, locPos);
|
||||
int locSZ = SH(buf, locPos + 2);
|
||||
locPos += 4;
|
||||
if (locTag != EXTID_EXTT) {
|
||||
locPos += locSZ;
|
||||
continue;
|
||||
}
|
||||
int end = locPos + locSZ - 4;
|
||||
int flag = CH(buf, locPos++);
|
||||
if ((flag & 0x1) != 0 && locPos <= end) {
|
||||
mtime = unixToJavaTime(LG(buf, locPos));
|
||||
locPos += 4;
|
||||
}
|
||||
if ((flag & 0x2) != 0 && locPos <= end) {
|
||||
atime = unixToJavaTime(LG(buf, locPos));
|
||||
locPos += 4;
|
||||
}
|
||||
if ((flag & 0x4) != 0 && locPos <= end) {
|
||||
ctime = unixToJavaTime(LG(buf, locPos));
|
||||
}
|
||||
break;
|
||||
// If the LOC offset is 0xFFFFFFFF, then we need to read the
|
||||
// LOC offset from the EXTID_ZIP64 extra data. Therefore
|
||||
// wait until all of the CEN extra data fields have been processed
|
||||
// prior to reading the LOC extra data field in order to obtain
|
||||
// the Info-ZIP Extended Timestamp.
|
||||
if (locoff != ZIP64_MINVAL) {
|
||||
readLocEXTT(zipfs);
|
||||
} else {
|
||||
hasZip64LocOffset = true;
|
||||
}
|
||||
break;
|
||||
default: // unknown tag
|
||||
@ -3011,12 +2987,66 @@ class ZipFileSystem extends FileSystem {
|
||||
}
|
||||
off += (sz + 4);
|
||||
}
|
||||
|
||||
// We need to read the LOC extra data and the LOC offset was obtained
|
||||
// from the EXTID_ZIP64 field.
|
||||
if (hasZip64LocOffset) {
|
||||
readLocEXTT(zipfs);
|
||||
}
|
||||
|
||||
if (newOff != 0 && newOff != extra.length)
|
||||
extra = Arrays.copyOf(extra, newOff);
|
||||
else
|
||||
extra = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the LOC extra field to obtain the Info-ZIP Extended Timestamp fields
|
||||
* @param zipfs The Zip FS to use
|
||||
* @throws IOException If an error occurs
|
||||
*/
|
||||
private void readLocEXTT(ZipFileSystem zipfs) throws IOException {
|
||||
byte[] buf = new byte[LOCHDR];
|
||||
if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
|
||||
!= buf.length)
|
||||
throw new ZipException("loc: reading failed");
|
||||
if (!locSigAt(buf, 0))
|
||||
throw new ZipException("R"
|
||||
+ Long.toString(getSig(buf, 0), 16));
|
||||
int locElen = LOCEXT(buf);
|
||||
if (locElen < 9) // EXTT is at least 9 bytes
|
||||
return;
|
||||
int locNlen = LOCNAM(buf);
|
||||
buf = new byte[locElen];
|
||||
if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
|
||||
!= buf.length)
|
||||
throw new ZipException("loc extra: reading failed");
|
||||
int locPos = 0;
|
||||
while (locPos + 4 < buf.length) {
|
||||
int locTag = SH(buf, locPos);
|
||||
int locSZ = SH(buf, locPos + 2);
|
||||
locPos += 4;
|
||||
if (locTag != EXTID_EXTT) {
|
||||
locPos += locSZ;
|
||||
continue;
|
||||
}
|
||||
int end = locPos + locSZ - 4;
|
||||
int flag = CH(buf, locPos++);
|
||||
if ((flag & 0x1) != 0 && locPos <= end) {
|
||||
mtime = unixToJavaTime(LG(buf, locPos));
|
||||
locPos += 4;
|
||||
}
|
||||
if ((flag & 0x2) != 0 && locPos <= end) {
|
||||
atime = unixToJavaTime(LG(buf, locPos));
|
||||
locPos += 4;
|
||||
}
|
||||
if ((flag & 0x4) != 0 && locPos <= end) {
|
||||
ctime = unixToJavaTime(LG(buf, locPos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(1024);
|
||||
|
213
test/jdk/jdk/nio/zipfs/TestLocOffsetFromZip64EF.java
Normal file
213
test/jdk/jdk/nio/zipfs/TestLocOffsetFromZip64EF.java
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.
|
||||
*/
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8255380
|
||||
* @summary Test that Zip FS can access the LOC offset from the Zip64 extra field
|
||||
* @modules jdk.zipfs
|
||||
* @requires (os.family == "linux") | (os.family == "mac")
|
||||
* @run testng/manual TestLocOffsetFromZip64EF
|
||||
*/
|
||||
public class TestLocOffsetFromZip64EF {
|
||||
|
||||
private static final String ZIP_FILE_NAME = "LargeZipTest.zip";
|
||||
// File that will be created with a size greater than 0xFFFFFFFF
|
||||
private static final String LARGE_FILE_NAME = "LargeZipEntry.txt";
|
||||
// File that will be created with a size less than 0xFFFFFFFF
|
||||
private static final String SMALL_FILE_NAME = "SmallZipEntry.txt";
|
||||
// The size (4GB) of the large file to be created
|
||||
private static final long LARGE_FILE_SIZE = 4L * 1024L * 1024L * 1024L;
|
||||
|
||||
/**
|
||||
* Create the files used by this test
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@BeforeClass
|
||||
public void setUp() throws IOException {
|
||||
System.out.println("In setup");
|
||||
cleanup();
|
||||
createFiles();
|
||||
createZipWithZip64Ext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files used by this test
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@AfterClass
|
||||
public void cleanup() throws IOException {
|
||||
System.out.println("In cleanup");
|
||||
Files.deleteIfExists(Path.of(ZIP_FILE_NAME));
|
||||
Files.deleteIfExists(Path.of(LARGE_FILE_NAME));
|
||||
Files.deleteIfExists(Path.of(SMALL_FILE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Zip file that will result in the an Zip64 Extra (EXT) header
|
||||
* being added to the CEN entry in order to find the LOC offset for
|
||||
* SMALL_FILE_NAME.
|
||||
*/
|
||||
public static void createZipWithZip64Ext() {
|
||||
System.out.println("Executing zip...");
|
||||
List<String> commands = List.of("zip", "-0", ZIP_FILE_NAME,
|
||||
LARGE_FILE_NAME, SMALL_FILE_NAME);
|
||||
Result rc = run(new ProcessBuilder(commands));
|
||||
rc.assertSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate through the Zip file entries using Zip FS
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@Test
|
||||
public void walkZipFSTest() throws IOException {
|
||||
try (FileSystem fs =
|
||||
FileSystems.newFileSystem(Paths.get(ZIP_FILE_NAME), Map.of("zipinfo-time", "False"))) {
|
||||
for (Path root : fs.getRootDirectories()) {
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes
|
||||
attrs) throws IOException {
|
||||
System.out.println(Files.readAttributes(file,
|
||||
BasicFileAttributes.class).toString());
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate through the Zip file entries using ZipFile
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@Test
|
||||
public void walkZipFileTest() throws IOException {
|
||||
try (ZipFile zip = new ZipFile(ZIP_FILE_NAME)) {
|
||||
zip.stream().forEach(z -> System.out.printf("%s, %s, %s%n",
|
||||
z.getName(), z.getMethod(), z.getLastModifiedTime()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the files that will be added to the ZIP file
|
||||
* @throws IOException if there is a problem creating the files
|
||||
*/
|
||||
private static void createFiles() throws IOException {
|
||||
try (RandomAccessFile file = new RandomAccessFile(LARGE_FILE_NAME, "rw")
|
||||
) {
|
||||
System.out.printf("Creating %s%n", LARGE_FILE_NAME);
|
||||
file.setLength(LARGE_FILE_SIZE);
|
||||
System.out.printf("Creating %s%n", SMALL_FILE_NAME);
|
||||
Files.writeString(Path.of(SMALL_FILE_NAME), "Hello");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to execute a ProcessBuilder command
|
||||
* @param pb ProcessBuilder to execute
|
||||
* @return The Result(s) from the ProcessBuilder execution
|
||||
*/
|
||||
private static Result run(ProcessBuilder pb) {
|
||||
Process p;
|
||||
System.out.printf("Running: %s%n", pb.command());
|
||||
try {
|
||||
p = pb.start();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
format("Couldn't start process '%s'", pb.command()), e);
|
||||
}
|
||||
|
||||
String output;
|
||||
try {
|
||||
output = toString(p.getInputStream(), p.getErrorStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
format("Couldn't read process output '%s'", pb.command()), e);
|
||||
}
|
||||
|
||||
try {
|
||||
p.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(
|
||||
format("Process hasn't finished '%s'", pb.command()), e);
|
||||
}
|
||||
return new Result(p.exitValue(), output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility Method for combining the output from a ProcessBuilder invocation
|
||||
* @param in1 ProccessBuilder.getInputStream
|
||||
* @param in2 ProcessBuilder.getErrorStream
|
||||
* @return The ProcessBuilder output
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
static String toString(InputStream in1, InputStream in2) throws IOException {
|
||||
try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
|
||||
InputStream concatenated = new SequenceInputStream(in1, in2)) {
|
||||
concatenated.transferTo(dst);
|
||||
return new String(dst.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class used to hold the results from a ProcessBuilder execution
|
||||
*/
|
||||
static class Result {
|
||||
final int ec;
|
||||
final String output;
|
||||
|
||||
private Result(int ec, String output) {
|
||||
this.ec = ec;
|
||||
this.output = output;
|
||||
}
|
||||
Result assertSuccess() {
|
||||
assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
static void assertTrue(boolean cond, Object ... failedArgs) {
|
||||
if (cond)
|
||||
return;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object o : failedArgs)
|
||||
sb.append(o);
|
||||
Assert.fail(sb.toString());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user