Skip to content

Commit 6606e09

Browse files
author
Lance Andersen
committed
8255380: (zipfs) ZipFileSystem::readExtra can fail if zipinfo-time is not set to false
Reviewed-by: redestad
1 parent 88ee973 commit 6606e09

File tree

2 files changed

+282
-39
lines changed

2 files changed

+282
-39
lines changed

src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,11 +2907,16 @@ private int writeEXT(OutputStream os) throws IOException {
29072907

29082908
// read NTFS, UNIX and ZIP64 data from cen.extra
29092909
private void readExtra(ZipFileSystem zipfs) throws IOException {
2910+
// Note that Section 4.5, Extensible data fields, of the PKWARE ZIP File
2911+
// Format Specification does not mandate a specific order for the
2912+
// data in the extra field, therefore Zip FS cannot assume the data
2913+
// is written in the same order by Zip libraries as Zip FS.
29102914
if (extra == null)
29112915
return;
29122916
int elen = extra.length;
29132917
int off = 0;
29142918
int newOff = 0;
2919+
boolean hasZip64LocOffset = false;
29152920
while (off + 4 < elen) {
29162921
// extra spec: HeaderID+DataSize+Data
29172922
int pos = off;
@@ -2955,7 +2960,7 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
29552960
ctime = winToJavaTime(LL(extra, pos + 20));
29562961
break;
29572962
case EXTID_EXTT:
2958-
// spec says the Extened timestamp in cen only has mtime
2963+
// spec says the Extended timestamp in cen only has mtime
29592964
// need to read the loc to get the extra a/ctime, if flag
29602965
// "zipinfo-time" is not specified to false;
29612966
// there is performance cost (move up to loc and read) to
@@ -2965,44 +2970,15 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
29652970
mtime = unixToJavaTime(LG(extra, pos + 1));
29662971
break;
29672972
}
2968-
byte[] buf = new byte[LOCHDR];
2969-
if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
2970-
!= buf.length)
2971-
throw new ZipException("loc: reading failed");
2972-
if (!locSigAt(buf, 0))
2973-
throw new ZipException("loc: wrong sig ->"
2974-
+ Long.toString(getSig(buf, 0), 16));
2975-
int locElen = LOCEXT(buf);
2976-
if (locElen < 9) // EXTT is at least 9 bytes
2977-
break;
2978-
int locNlen = LOCNAM(buf);
2979-
buf = new byte[locElen];
2980-
if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
2981-
!= buf.length)
2982-
throw new ZipException("loc extra: reading failed");
2983-
int locPos = 0;
2984-
while (locPos + 4 < buf.length) {
2985-
int locTag = SH(buf, locPos);
2986-
int locSZ = SH(buf, locPos + 2);
2987-
locPos += 4;
2988-
if (locTag != EXTID_EXTT) {
2989-
locPos += locSZ;
2990-
continue;
2991-
}
2992-
int end = locPos + locSZ - 4;
2993-
int flag = CH(buf, locPos++);
2994-
if ((flag & 0x1) != 0 && locPos <= end) {
2995-
mtime = unixToJavaTime(LG(buf, locPos));
2996-
locPos += 4;
2997-
}
2998-
if ((flag & 0x2) != 0 && locPos <= end) {
2999-
atime = unixToJavaTime(LG(buf, locPos));
3000-
locPos += 4;
3001-
}
3002-
if ((flag & 0x4) != 0 && locPos <= end) {
3003-
ctime = unixToJavaTime(LG(buf, locPos));
3004-
}
3005-
break;
2973+
// If the LOC offset is 0xFFFFFFFF, then we need to read the
2974+
// LOC offset from the EXTID_ZIP64 extra data. Therefore
2975+
// wait until all of the CEN extra data fields have been processed
2976+
// prior to reading the LOC extra data field in order to obtain
2977+
// the Info-ZIP Extended Timestamp.
2978+
if (locoff != ZIP64_MINVAL) {
2979+
readLocEXTT(zipfs);
2980+
} else {
2981+
hasZip64LocOffset = true;
30062982
}
30072983
break;
30082984
default: // unknown tag
@@ -3011,12 +2987,66 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
30112987
}
30122988
off += (sz + 4);
30132989
}
2990+
2991+
// We need to read the LOC extra data and the LOC offset was obtained
2992+
// from the EXTID_ZIP64 field.
2993+
if (hasZip64LocOffset) {
2994+
readLocEXTT(zipfs);
2995+
}
2996+
30142997
if (newOff != 0 && newOff != extra.length)
30152998
extra = Arrays.copyOf(extra, newOff);
30162999
else
30173000
extra = null;
30183001
}
30193002

3003+
/**
3004+
* Read the LOC extra field to obtain the Info-ZIP Extended Timestamp fields
3005+
* @param zipfs The Zip FS to use
3006+
* @throws IOException If an error occurs
3007+
*/
3008+
private void readLocEXTT(ZipFileSystem zipfs) throws IOException {
3009+
byte[] buf = new byte[LOCHDR];
3010+
if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
3011+
!= buf.length)
3012+
throw new ZipException("loc: reading failed");
3013+
if (!locSigAt(buf, 0))
3014+
throw new ZipException("R"
3015+
+ Long.toString(getSig(buf, 0), 16));
3016+
int locElen = LOCEXT(buf);
3017+
if (locElen < 9) // EXTT is at least 9 bytes
3018+
return;
3019+
int locNlen = LOCNAM(buf);
3020+
buf = new byte[locElen];
3021+
if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
3022+
!= buf.length)
3023+
throw new ZipException("loc extra: reading failed");
3024+
int locPos = 0;
3025+
while (locPos + 4 < buf.length) {
3026+
int locTag = SH(buf, locPos);
3027+
int locSZ = SH(buf, locPos + 2);
3028+
locPos += 4;
3029+
if (locTag != EXTID_EXTT) {
3030+
locPos += locSZ;
3031+
continue;
3032+
}
3033+
int end = locPos + locSZ - 4;
3034+
int flag = CH(buf, locPos++);
3035+
if ((flag & 0x1) != 0 && locPos <= end) {
3036+
mtime = unixToJavaTime(LG(buf, locPos));
3037+
locPos += 4;
3038+
}
3039+
if ((flag & 0x2) != 0 && locPos <= end) {
3040+
atime = unixToJavaTime(LG(buf, locPos));
3041+
locPos += 4;
3042+
}
3043+
if ((flag & 0x4) != 0 && locPos <= end) {
3044+
ctime = unixToJavaTime(LG(buf, locPos));
3045+
}
3046+
break;
3047+
}
3048+
}
3049+
30203050
@Override
30213051
public String toString() {
30223052
StringBuilder sb = new StringBuilder(1024);
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright (c) 2020, 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+
import org.testng.Assert;
25+
import org.testng.annotations.AfterClass;
26+
import org.testng.annotations.BeforeClass;
27+
import org.testng.annotations.Test;
28+
29+
import java.io.*;
30+
import java.nio.charset.StandardCharsets;
31+
import java.nio.file.*;
32+
import java.nio.file.attribute.BasicFileAttributes;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.zip.ZipFile;
36+
37+
import static java.lang.String.format;
38+
39+
/**
40+
* @test
41+
* @bug 8255380
42+
* @summary Test that Zip FS can access the LOC offset from the Zip64 extra field
43+
* @modules jdk.zipfs
44+
* @requires (os.family == "linux") | (os.family == "mac")
45+
* @run testng/manual TestLocOffsetFromZip64EF
46+
*/
47+
public class TestLocOffsetFromZip64EF {
48+
49+
private static final String ZIP_FILE_NAME = "LargeZipTest.zip";
50+
// File that will be created with a size greater than 0xFFFFFFFF
51+
private static final String LARGE_FILE_NAME = "LargeZipEntry.txt";
52+
// File that will be created with a size less than 0xFFFFFFFF
53+
private static final String SMALL_FILE_NAME = "SmallZipEntry.txt";
54+
// The size (4GB) of the large file to be created
55+
private static final long LARGE_FILE_SIZE = 4L * 1024L * 1024L * 1024L;
56+
57+
/**
58+
* Create the files used by this test
59+
* @throws IOException if an error occurs
60+
*/
61+
@BeforeClass
62+
public void setUp() throws IOException {
63+
System.out.println("In setup");
64+
cleanup();
65+
createFiles();
66+
createZipWithZip64Ext();
67+
}
68+
69+
/**
70+
* Delete files used by this test
71+
* @throws IOException if an error occurs
72+
*/
73+
@AfterClass
74+
public void cleanup() throws IOException {
75+
System.out.println("In cleanup");
76+
Files.deleteIfExists(Path.of(ZIP_FILE_NAME));
77+
Files.deleteIfExists(Path.of(LARGE_FILE_NAME));
78+
Files.deleteIfExists(Path.of(SMALL_FILE_NAME));
79+
}
80+
81+
/**
82+
* Create a Zip file that will result in the an Zip64 Extra (EXT) header
83+
* being added to the CEN entry in order to find the LOC offset for
84+
* SMALL_FILE_NAME.
85+
*/
86+
public static void createZipWithZip64Ext() {
87+
System.out.println("Executing zip...");
88+
List<String> commands = List.of("zip", "-0", ZIP_FILE_NAME,
89+
LARGE_FILE_NAME, SMALL_FILE_NAME);
90+
Result rc = run(new ProcessBuilder(commands));
91+
rc.assertSuccess();
92+
}
93+
94+
/**
95+
* Navigate through the Zip file entries using Zip FS
96+
* @throws IOException if an error occurs
97+
*/
98+
@Test
99+
public void walkZipFSTest() throws IOException {
100+
try (FileSystem fs =
101+
FileSystems.newFileSystem(Paths.get(ZIP_FILE_NAME), Map.of("zipinfo-time", "False"))) {
102+
for (Path root : fs.getRootDirectories()) {
103+
Files.walkFileTree(root, new SimpleFileVisitor<>() {
104+
@Override
105+
public FileVisitResult visitFile(Path file, BasicFileAttributes
106+
attrs) throws IOException {
107+
System.out.println(Files.readAttributes(file,
108+
BasicFileAttributes.class).toString());
109+
return FileVisitResult.CONTINUE;
110+
}
111+
});
112+
}
113+
}
114+
}
115+
116+
/**
117+
* Navigate through the Zip file entries using ZipFile
118+
* @throws IOException if an error occurs
119+
*/
120+
@Test
121+
public void walkZipFileTest() throws IOException {
122+
try (ZipFile zip = new ZipFile(ZIP_FILE_NAME)) {
123+
zip.stream().forEach(z -> System.out.printf("%s, %s, %s%n",
124+
z.getName(), z.getMethod(), z.getLastModifiedTime()));
125+
}
126+
}
127+
128+
/**
129+
* Create the files that will be added to the ZIP file
130+
* @throws IOException if there is a problem creating the files
131+
*/
132+
private static void createFiles() throws IOException {
133+
try (RandomAccessFile file = new RandomAccessFile(LARGE_FILE_NAME, "rw")
134+
) {
135+
System.out.printf("Creating %s%n", LARGE_FILE_NAME);
136+
file.setLength(LARGE_FILE_SIZE);
137+
System.out.printf("Creating %s%n", SMALL_FILE_NAME);
138+
Files.writeString(Path.of(SMALL_FILE_NAME), "Hello");
139+
}
140+
}
141+
142+
/**
143+
* Utility method to execute a ProcessBuilder command
144+
* @param pb ProcessBuilder to execute
145+
* @return The Result(s) from the ProcessBuilder execution
146+
*/
147+
private static Result run(ProcessBuilder pb) {
148+
Process p;
149+
System.out.printf("Running: %s%n", pb.command());
150+
try {
151+
p = pb.start();
152+
} catch (IOException e) {
153+
throw new RuntimeException(
154+
format("Couldn't start process '%s'", pb.command()), e);
155+
}
156+
157+
String output;
158+
try {
159+
output = toString(p.getInputStream(), p.getErrorStream());
160+
} catch (IOException e) {
161+
throw new RuntimeException(
162+
format("Couldn't read process output '%s'", pb.command()), e);
163+
}
164+
165+
try {
166+
p.waitFor();
167+
} catch (InterruptedException e) {
168+
throw new RuntimeException(
169+
format("Process hasn't finished '%s'", pb.command()), e);
170+
}
171+
return new Result(p.exitValue(), output);
172+
}
173+
174+
/**
175+
* Utility Method for combining the output from a ProcessBuilder invocation
176+
* @param in1 ProccessBuilder.getInputStream
177+
* @param in2 ProcessBuilder.getErrorStream
178+
* @return The ProcessBuilder output
179+
* @throws IOException if an error occurs
180+
*/
181+
static String toString(InputStream in1, InputStream in2) throws IOException {
182+
try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
183+
InputStream concatenated = new SequenceInputStream(in1, in2)) {
184+
concatenated.transferTo(dst);
185+
return new String(dst.toByteArray(), StandardCharsets.UTF_8);
186+
}
187+
}
188+
189+
/**
190+
* Utility class used to hold the results from a ProcessBuilder execution
191+
*/
192+
static class Result {
193+
final int ec;
194+
final String output;
195+
196+
private Result(int ec, String output) {
197+
this.ec = ec;
198+
this.output = output;
199+
}
200+
Result assertSuccess() {
201+
assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]");
202+
return this;
203+
}
204+
}
205+
static void assertTrue(boolean cond, Object ... failedArgs) {
206+
if (cond)
207+
return;
208+
StringBuilder sb = new StringBuilder();
209+
for (Object o : failedArgs)
210+
sb.append(o);
211+
Assert.fail(sb.toString());
212+
}
213+
}

0 commit comments

Comments
 (0)