8251329: (zipfs) Files.walkFileTree walks infinitely if zip has dir named "." inside
Reviewed-by: alanb, naoto
This commit is contained in:
parent
7a4c754e5d
commit
3e3051e2ee
@ -1575,6 +1575,10 @@ class ZipFileSystem extends FileSystem {
|
||||
throw new ZipException("invalid CEN header (bad header size)");
|
||||
}
|
||||
IndexNode inode = new IndexNode(cen, pos, nlen);
|
||||
if (hasDotOrDotDot(inode.name)) {
|
||||
throw new ZipException("ZIP file can't be opened as a file system " +
|
||||
"because an entry has a '.' or '..' element in its name");
|
||||
}
|
||||
inodes.put(inode, inode);
|
||||
if (zc.isUTF8() || (flag & FLAG_USE_UTF8) != 0) {
|
||||
checkUTF8(inode.name);
|
||||
@ -1591,6 +1595,44 @@ class ZipFileSystem extends FileSystem {
|
||||
return cen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Inode.name to see if it includes a "." or ".." in the name array
|
||||
* @param path the path as stored in Inode.name to verify
|
||||
* @return true if the path contains a "." or ".." entry; false otherwise
|
||||
*/
|
||||
private boolean hasDotOrDotDot(byte[] path) {
|
||||
// Inode.name always includes "/" in path[0]
|
||||
assert path[0] == '/';
|
||||
if (path.length == 1) {
|
||||
return false;
|
||||
}
|
||||
int index = 1;
|
||||
while (index < path.length) {
|
||||
int starting = index;
|
||||
while (index < path.length && path[index] != '/') {
|
||||
index++;
|
||||
}
|
||||
// Check the path snippet for a "." or ".."
|
||||
if (isDotOrDotDotPath(path, starting, index)) {
|
||||
return true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the path to see if it includes a "." or ".."
|
||||
* @param path the path to check
|
||||
* @return true if the path contains a "." or ".." entry; false otherwise
|
||||
*/
|
||||
private boolean isDotOrDotDotPath(byte[] path, int start, int index) {
|
||||
int pathLen = index - start;
|
||||
if ((pathLen == 1 && path[start] == '.'))
|
||||
return true;
|
||||
return (pathLen == 2 && path[start] == '.') && path[start + 1] == '.';
|
||||
}
|
||||
|
||||
private final void checkUTF8(byte[] a) throws ZipException {
|
||||
try {
|
||||
int end = a.length;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 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
|
||||
@ -45,6 +45,9 @@ import java.util.Set;
|
||||
* <li>Open an existing file as a Zip file system</li>
|
||||
* </ul>
|
||||
*
|
||||
* The Zip file system provider does not support opening an existing Zip file
|
||||
* that contains entries with "." or ".." in its name elements.
|
||||
*
|
||||
* <h2>URI Scheme Used to Identify the Zip File System</h2>
|
||||
*
|
||||
* The URI {@link java.net.URI#getScheme scheme} that identifies the ZIP file system is {@code jar}.
|
||||
|
155
test/jdk/jdk/nio/zipfs/HasDotDotTest.java
Normal file
155
test/jdk/jdk/nio/zipfs/HasDotDotTest.java
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
/**
|
||||
* @test
|
||||
* @bug 8251329
|
||||
* @summary Excercise Zip FS with "." or ".." in a Zip Entry name
|
||||
* @modules jdk.zipfs
|
||||
* @run testng/othervm HasDotDotTest
|
||||
*/
|
||||
public class HasDotDotTest {
|
||||
// Zip file to be created
|
||||
private static final Path ZIPFILE = Path.of("zipfsDotDotTest.zip");
|
||||
// Data for Zip entries
|
||||
private static final byte[] ENTRY_DATA =
|
||||
"Tennis Anyone".getBytes(StandardCharsets.UTF_8);
|
||||
// Display output
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* DataProvider containing Zip entry names which should result in an IOException
|
||||
* @return Array of Zip entry names
|
||||
*/
|
||||
@DataProvider
|
||||
private Object[][] checkForDotOrDotDotPaths() {
|
||||
return new Object[][]{
|
||||
{"/./foo"},
|
||||
{"/../foo"},
|
||||
{"/../foo/.."},
|
||||
{"/foo/.."},
|
||||
{"/foo/."},
|
||||
{"/.."},
|
||||
{"/."},
|
||||
{"/.foo/./"},
|
||||
{"/.././"},
|
||||
};
|
||||
}
|
||||
|
||||
// Zip entry names to create a Zip file with for validating they are not
|
||||
// interpreted as a "." or ".." entry
|
||||
private final String[] VALID_PATHS =
|
||||
{"/foo.txt", "/..foo.txt", "/.foo.txt", "/.foo/bar.txt", "/foo/bar.txt"};
|
||||
// Paths to be returned from Files::walk via Zip FS
|
||||
private final String[] EXPECTED_PATHS =
|
||||
{"/", "/..foo.txt", "/foo.txt", "/.foo.txt", "/.foo",
|
||||
"/.foo/bar.txt", "/foo/bar.txt", "/foo"};
|
||||
|
||||
/**
|
||||
* Creates a Zip file
|
||||
* @param zip path for Zip to be created
|
||||
* @param entries the entries to add to the Zip file
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
private static void createZip(Path zip, String... entries) throws IOException {
|
||||
try (var os = Files.newOutputStream(zip);
|
||||
ZipOutputStream zos = new ZipOutputStream(os)) {
|
||||
for (var e : entries) {
|
||||
var ze = new ZipEntry(e);
|
||||
var crc = new CRC32();
|
||||
ze.setMethod(ZipEntry.STORED);
|
||||
crc.update(ENTRY_DATA);
|
||||
ze.setCrc(crc.getValue());
|
||||
ze.setSize(ENTRY_DATA.length);
|
||||
zos.putNextEntry(ze);
|
||||
zos.write(ENTRY_DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to validate an IOException is thrown when opening a Zip file using
|
||||
* Zip FS and the path contains a "." or ".."
|
||||
* @param path
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test(dataProvider = "checkForDotOrDotDotPaths")
|
||||
public void hasDotOrDotDotTest(String path) throws IOException {
|
||||
if (DEBUG) {
|
||||
System.out.printf("Validating entry: %s%n", path);
|
||||
}
|
||||
Files.deleteIfExists(ZIPFILE);
|
||||
createZip(ZIPFILE, path);
|
||||
assertThrows(IOException.class, () ->
|
||||
FileSystems.newFileSystem(ZIPFILE, Map.of()));
|
||||
Files.deleteIfExists(ZIPFILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that an entry with a name containing a "." or ".." can be
|
||||
* accessed via Files::walk
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@Test
|
||||
public void validPaths() throws IOException {
|
||||
Files.deleteIfExists(ZIPFILE);
|
||||
createZip(ZIPFILE, VALID_PATHS);
|
||||
/*
|
||||
Walk through the Zip file and collect the Zip FS entries
|
||||
*/
|
||||
try (FileSystem zipfs = FileSystems.newFileSystem(ZIPFILE)) {
|
||||
Path zipRoot = zipfs.getPath("/");
|
||||
try (Stream<Path> files = Files.walk(zipRoot, Integer.MAX_VALUE)) {
|
||||
var entries = files.map(Path::toString)
|
||||
.sorted()
|
||||
.toArray(String[]::new);
|
||||
if (DEBUG) {
|
||||
for (String zipEntry : entries) {
|
||||
System.out.println(zipEntry);
|
||||
}
|
||||
}
|
||||
Arrays.sort(EXPECTED_PATHS);
|
||||
assertTrue(Arrays.equals(entries, EXPECTED_PATHS));
|
||||
}
|
||||
}
|
||||
Files.deleteIfExists(ZIPFILE);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user