8173970: jar tool should have a way to extract to a directory
Reviewed-by: lancea, cstein
This commit is contained in:
parent
2b03dbdac4
commit
ffe60919df
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 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
|
||||
@ -30,6 +30,8 @@ import java.io.PrintWriter;
|
||||
import java.lang.module.ModuleDescriptor.Version;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import jdk.internal.module.ModulePath;
|
||||
@ -245,6 +247,14 @@ class GNUStyleOptions {
|
||||
if (jartool.info == null)
|
||||
jartool.info = GNUStyleOptions::printVersion;
|
||||
}
|
||||
},
|
||||
new Option(true, true, OptionType.EXTRACT, "--dir") {
|
||||
void process(Main jartool, String opt, String arg) throws BadArgs {
|
||||
if (jartool.xdestDir != null) {
|
||||
throw new BadArgs("error.extract.multiple.dest.dir").showUsage(true);
|
||||
}
|
||||
jartool.xdestDir = arg;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -254,6 +264,7 @@ class GNUStyleOptions {
|
||||
CREATE("create"),
|
||||
CREATE_UPDATE("create.update"),
|
||||
CREATE_UPDATE_INDEX("create.update.index"),
|
||||
EXTRACT("extract"),
|
||||
OTHER("other");
|
||||
|
||||
/** Resource lookup section prefix. */
|
||||
|
@ -160,6 +160,9 @@ public class Main {
|
||||
|
||||
boolean suppressDeprecateMsg = false;
|
||||
|
||||
// destination directory for extraction
|
||||
String xdestDir = null;
|
||||
|
||||
/* To support additional GNU Style informational options */
|
||||
Consumer<PrintWriter> info;
|
||||
|
||||
@ -372,6 +375,15 @@ public class Main {
|
||||
}
|
||||
}
|
||||
} else if (xflag) {
|
||||
if (xdestDir != null) {
|
||||
final Path destPath = Paths.get(xdestDir);
|
||||
try {
|
||||
Files.createDirectories(destPath);
|
||||
} catch (IOException ioe) {
|
||||
throw new IOException(formatMsg("error.create.dir",
|
||||
destPath.toString()), ioe);
|
||||
}
|
||||
}
|
||||
replaceFSC(filesMap);
|
||||
// For the extract action, when extracting all the entries,
|
||||
// access using the ZipInputStream class is most efficient,
|
||||
@ -631,6 +643,11 @@ public class Main {
|
||||
}
|
||||
/* change the directory */
|
||||
String dir = args[++i];
|
||||
if (xflag && xdestDir != null) {
|
||||
// extract option doesn't allow more than one destination directory
|
||||
usageError(getMsg("error.extract.multiple.dest.dir"));
|
||||
return false;
|
||||
}
|
||||
dir = (dir.endsWith(File.separator) ?
|
||||
dir : (dir + File.separator));
|
||||
dir = dir.replace(File.separatorChar, '/');
|
||||
@ -642,8 +659,12 @@ public class Main {
|
||||
if (hasUNC) { // Restore Windows UNC path.
|
||||
dir = "/" + dir;
|
||||
}
|
||||
pathsMap.get(version).add(dir);
|
||||
nameBuf[k++] = dir + args[++i];
|
||||
if (xflag) {
|
||||
xdestDir = dir;
|
||||
} else {
|
||||
pathsMap.get(version).add(dir);
|
||||
nameBuf[k++] = dir + args[++i];
|
||||
}
|
||||
} else if (args[i].startsWith("--release")) {
|
||||
int v = BASE_VERSION;
|
||||
try {
|
||||
@ -702,6 +723,10 @@ public class Main {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (xflag && pflag && xdestDir != null) {
|
||||
usageError(getMsg("error.extract.pflag.not.allowed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1355,7 +1380,7 @@ public class Main {
|
||||
if (lastModified != -1) {
|
||||
String name = safeName(ze.getName().replace(File.separatorChar, '/'));
|
||||
if (name.length() != 0) {
|
||||
File f = new File(name.replace('/', File.separatorChar));
|
||||
File f = new File(xdestDir, name.replace('/', File.separatorChar));
|
||||
f.setLastModified(lastModified);
|
||||
}
|
||||
}
|
||||
@ -1366,6 +1391,10 @@ public class Main {
|
||||
* Extracts specified entries from JAR file.
|
||||
*/
|
||||
void extract(InputStream in, String[] files) throws IOException {
|
||||
if (vflag) {
|
||||
output(formatMsg("out.extract.dir", Path.of(xdestDir == null ? "." : xdestDir).normalize()
|
||||
.toAbsolutePath().toString()));
|
||||
}
|
||||
ZipInputStream zis = new ZipInputStream(in);
|
||||
ZipEntry e;
|
||||
Set<ZipEntry> dirs = newDirSet();
|
||||
@ -1394,6 +1423,10 @@ public class Main {
|
||||
* Extracts specified entries from JAR file, via ZipFile.
|
||||
*/
|
||||
void extract(String fname, String[] files) throws IOException {
|
||||
if (vflag) {
|
||||
output(formatMsg("out.extract.dir", Path.of(xdestDir == null ? "." : xdestDir).normalize()
|
||||
.toAbsolutePath().toString()));
|
||||
}
|
||||
final Set<ZipEntry> dirs;
|
||||
try (ZipFile zf = new ZipFile(fname)) {
|
||||
dirs = newDirSet();
|
||||
@ -1423,16 +1456,24 @@ public class Main {
|
||||
*/
|
||||
ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException {
|
||||
ZipEntry rc = null;
|
||||
// The spec requres all slashes MUST be forward '/', it is possible
|
||||
// The spec requires all slashes MUST be forward '/', it is possible
|
||||
// an offending zip/jar entry may uses the backwards slash in its
|
||||
// name. It might cause problem on Windows platform as it skips
|
||||
// our "safe" check for leading slahs and dot-dot. So replace them
|
||||
// our "safe" check for leading slash and dot-dot. So replace them
|
||||
// with '/'.
|
||||
String name = safeName(e.getName().replace(File.separatorChar, '/'));
|
||||
if (name.length() == 0) {
|
||||
return rc; // leading '/' or 'dot-dot' only path
|
||||
}
|
||||
File f = new File(name.replace('/', File.separatorChar));
|
||||
// the xdestDir points to the user specified location where the jar needs to
|
||||
// be extracted. By default xdestDir is null and represents current working
|
||||
// directory.
|
||||
// jar extraction using -P option is only allowed when the destination
|
||||
// directory isn't specified (and hence defaults to current working directory).
|
||||
// In such cases using this java.io.File constructor which accepts a null parent path
|
||||
// allows us to extract entries that may have leading slashes and hence may need
|
||||
// to be extracted outside of the current directory.
|
||||
File f = new File(xdestDir, name.replace('/', File.separatorChar));
|
||||
if (e.isDirectory()) {
|
||||
if (f.exists()) {
|
||||
if (!f.isDirectory()) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
# 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
|
||||
@ -62,6 +62,10 @@ error.incorrect.length=\
|
||||
incorrect length while processing: {0}
|
||||
error.create.tempfile=\
|
||||
Could not create a temporary file
|
||||
error.extract.multiple.dest.dir=\
|
||||
You may not specify the '-C' or '--dir' option more than once with the '-x' option
|
||||
error.extract.pflag.not.allowed=\
|
||||
You may not specify '-Px' with the '-C' or '--dir' options
|
||||
error.hash.dep=\
|
||||
Hashing module {0} dependences, unable to find module {1} on module path
|
||||
error.module.options.without.info=\
|
||||
@ -169,6 +173,8 @@ out.inflated=\
|
||||
\ inflated: {0}
|
||||
out.size=\
|
||||
(in = {0}) (out= {1})
|
||||
out.extract.dir=\
|
||||
extracting to directory: {0}
|
||||
|
||||
usage.compat=\
|
||||
\Compatibility Interface:\
|
||||
@ -190,6 +196,7 @@ Options:\n\
|
||||
\ \ -i generate index information for the specified jar files\n\
|
||||
\ \ -C change to the specified directory and include the following file\n\
|
||||
If any file is a directory then it is processed recursively.\n\
|
||||
When used in extract mode, extracts the jar to the specified directory\n\
|
||||
The manifest file name, the archive file name and the entry point name are\n\
|
||||
specified in the same order as the 'm', 'f' and 'e' flags.\n\n\
|
||||
Example 1: to archive two class files into an archive called classes.jar: \n\
|
||||
@ -257,7 +264,8 @@ main.help.opt.any=\
|
||||
\ Operation modifiers valid in any mode:\n\
|
||||
\n\
|
||||
\ -C DIR Change to the specified directory and include the\n\
|
||||
\ following file
|
||||
\ following file. When used in extract mode, extracts\n\
|
||||
\ the jar to the specified directory
|
||||
main.help.opt.any.file=\
|
||||
\ -f, --file=FILE The archive file name. When omitted, either stdin or\n\
|
||||
\ stdout is used based on the operation\n\
|
||||
@ -324,3 +332,7 @@ main.help.postopt=\
|
||||
\n\
|
||||
\ Mandatory or optional arguments to long options are also mandatory or optional\n\
|
||||
\ for any corresponding short options.
|
||||
main.help.opt.extract=\
|
||||
\ Operation modifiers valid only in extract mode:\n
|
||||
main.help.opt.extract.dir=\
|
||||
\ --dir Directory into which the jar will be extracted
|
||||
|
@ -1,4 +1,4 @@
|
||||
.\" Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
.\" Copyright (c) 1997, 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
|
||||
@ -127,13 +127,19 @@ You can use the following options to customize the actions of any
|
||||
operation mode included in the \f[V]jar\f[R] command.
|
||||
.TP
|
||||
\f[V]-C\f[R] \f[I]DIR\f[R]
|
||||
Changes the specified directory and includes the \f[I]files\f[R]
|
||||
specified at the end of the command line.
|
||||
When used with the create operation mode, changes the specified
|
||||
directory and includes the \f[I]files\f[R] specified at the end of the
|
||||
command line.
|
||||
.RS
|
||||
.PP
|
||||
\f[V]jar\f[R] [\f[I]OPTION\f[R] ...]
|
||||
[ [\f[V]--release\f[R] \f[I]VERSION\f[R]] [\f[V]-C\f[R] \f[I]dir\f[R]]
|
||||
\f[I]files\f[R]]
|
||||
.PP
|
||||
When used with the extract operation mode, specifies the destination
|
||||
directory where the JAR file will be extracted.
|
||||
Unlike with the create operation mode, this option can be specified only
|
||||
once with the extract operation mode.
|
||||
.RE
|
||||
.TP
|
||||
\f[V]-f\f[R] \f[I]FILE\f[R] or \f[V]--file=\f[R]\f[I]FILE\f[R]
|
||||
@ -202,6 +208,10 @@ Stores without using ZIP compression.
|
||||
The timestamp in ISO-8601 extended offset date-time with optional
|
||||
time-zone format, to use for the timestamp of the entries, e.g.
|
||||
\[dq]2022-02-12T12:30:00-05:00\[dq].
|
||||
.SH OPERATION MODIFIERS VALID ONLY IN EXTRACT MODE
|
||||
.TP
|
||||
\f[V]--dir\f[R] \f[I]DIR\f[R]
|
||||
Directory into which the JAR file will be extracted.
|
||||
.SH OTHER OPTIONS
|
||||
.PP
|
||||
The following options are recognized by the \f[V]jar\f[R] command and
|
||||
@ -342,3 +352,17 @@ file that lists the files to include in the JAR file and pass it to the
|
||||
If one or more entries in the arg file cannot be found then the jar
|
||||
command fails without creating the JAR file.
|
||||
.RE
|
||||
.IP \[bu] 2
|
||||
Extract the JAR file \f[V]foo.jar\f[R] to \f[V]/tmp/bar/\f[R] directory:
|
||||
.RS 2
|
||||
.RS
|
||||
.PP
|
||||
\f[V]jar -xf foo.jar -C /tmp/bar/\f[R]
|
||||
.RE
|
||||
.PP
|
||||
Alternatively, you can also do:
|
||||
.RS
|
||||
.PP
|
||||
\f[V]jar --extract --file foo.jar --dir /tmp/bar/\f[R]
|
||||
.RE
|
||||
.RE
|
||||
|
517
test/jdk/tools/jar/JarExtractTest.java
Normal file
517
test/jdk/tools/jar/JarExtractTest.java
Normal file
@ -0,0 +1,517 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.util.JarBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8173970
|
||||
* @summary jar tool should allow extracting to specific directory
|
||||
* @library /test/lib
|
||||
* @comment The test relies on verification of error messages generated by jar tool, so we use
|
||||
* a fixed en_US locale for this test.
|
||||
* @run junit/othervm -Duser.language=en -Duser.country=US JarExtractTest
|
||||
*/
|
||||
public class JarExtractTest {
|
||||
private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
|
||||
.orElseThrow(() ->
|
||||
new RuntimeException("jar tool not found")
|
||||
);
|
||||
|
||||
private static final byte[] FILE_CONTENT = "Hello world!!!".getBytes(StandardCharsets.UTF_8);
|
||||
// the jar that will get extracted in the tests
|
||||
private Path testJarPath;
|
||||
private static Collection<Path> filesToDelete = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
public void createTestJar() throws Exception {
|
||||
final String tmpDir = Files.createTempDirectory("8173970-").toString();
|
||||
testJarPath = Path.of(tmpDir, "8173970-test.jar");
|
||||
final JarBuilder builder = new JarBuilder(testJarPath.toString());
|
||||
// d1
|
||||
// |--- d2
|
||||
// | |--- d3
|
||||
// | | |--- f2.txt
|
||||
// |
|
||||
// |--- d4
|
||||
// ...
|
||||
// f1.txt
|
||||
|
||||
builder.addEntry("d1/", new byte[0]);
|
||||
builder.addEntry("f1.txt", FILE_CONTENT);
|
||||
builder.addEntry("d1/d2/d3/f2.txt", FILE_CONTENT);
|
||||
builder.addEntry("d1/d4/", new byte[0]);
|
||||
builder.build();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
for (final Path p : filesToDelete) {
|
||||
try {
|
||||
System.out.println("Deleting file/dir " + p);
|
||||
Files.delete(p);
|
||||
} catch (IOException ioe) {
|
||||
//ignore
|
||||
System.err.println("ignoring exception: " + ioe
|
||||
+ " that happened when deleting: " + p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns various relative paths, to which the jar will be extracted in the tests
|
||||
*/
|
||||
static Stream<Arguments> provideRelativeExtractLocations() throws Exception {
|
||||
// create some dirs so that they already exist when the jar is being extracted
|
||||
final String existing1 = "." + File.separator + "8173970-existing-1";
|
||||
Files.createDirectories(Path.of(existing1));
|
||||
final String existing2 = "." + File.separator + "foo" + File.separator + "8173970-existing-2";
|
||||
Files.createDirectories(Path.of(existing2));
|
||||
final Path dirOutsideScratchDir = Files.createTempDirectory(Path.of(".."), "8173970");
|
||||
// we need to explicitly delete this dir after the tests end
|
||||
filesToDelete.add(dirOutsideScratchDir);
|
||||
final String existing3 = dirOutsideScratchDir.toString() + File.separator + "8173970-existing-3";
|
||||
Files.createDirectories(Path.of(existing3));
|
||||
|
||||
final String anotherDirOutsideScratchDir = ".." + File.separator + "8173970-non-existent";
|
||||
filesToDelete.add(Path.of(anotherDirOutsideScratchDir));
|
||||
|
||||
final List<Arguments> args = new ArrayList<>();
|
||||
args.add(Arguments.of(".")); // current dir
|
||||
// (explicitly) relative to current dir
|
||||
args.add(Arguments.of("." + File.separator + "8173970-extract-1"));
|
||||
// (implicitly) relative to current dir
|
||||
args.add(Arguments.of("8173970-extract-2"));
|
||||
// sibling to current dir
|
||||
args.add(Arguments.of(anotherDirOutsideScratchDir));
|
||||
// some existing dirs
|
||||
args.add(Arguments.of(existing1));
|
||||
args.add(Arguments.of(existing2));
|
||||
args.add(Arguments.of(existing3));
|
||||
// a non-existent dir within an existing dir
|
||||
args.add(Arguments.of(existing1 + File.separator
|
||||
+ "non-existing" + File.separator + "foo"));
|
||||
return args.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns various absolute paths, to which the jar will be extracted in the tests
|
||||
*/
|
||||
static Stream<Arguments> provideAbsoluteExtractLocations() throws Exception {
|
||||
final Stream<Arguments> relative = provideRelativeExtractLocations();
|
||||
return relative.map((arg) -> {
|
||||
final String relPath = (String) arg.get()[0];
|
||||
return Arguments.of(Path.of(relPath).toAbsolutePath().toString());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns various normalized paths, to which the jar will be extracted in the tests
|
||||
*/
|
||||
static Stream<Arguments> provideAbsoluteNormalizedExtractLocations() throws Exception {
|
||||
final Stream<Arguments> relative = provideRelativeExtractLocations();
|
||||
return relative.map((arg) -> {
|
||||
final String relPath = (String) arg.get()[0];
|
||||
return Arguments.of(Path.of(relPath).toAbsolutePath().normalize().toString());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a jar to various relative paths, using the -C/--dir option and then
|
||||
* verifies that the extracted content is at the expected locations with the correct
|
||||
* content
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideRelativeExtractLocations")
|
||||
public void testExtractToRelativeDir(final String dest) throws Exception {
|
||||
testLongFormExtract(dest);
|
||||
testExtract(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a jar to various absolute paths, using the -C/--dir option and then
|
||||
* verifies that the extracted content is at the expected locations with the correct
|
||||
* content
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideAbsoluteExtractLocations")
|
||||
public void testExtractToAbsoluteDir(final String dest) throws Exception {
|
||||
testExtract(dest);
|
||||
testLongFormExtract(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a jar to various normalized paths (i.e. no {@code .} or @{code ..} in the path components),
|
||||
* using the -C/--dir option and then verifies that the extracted content is at the expected locations
|
||||
* with the correct content
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideAbsoluteNormalizedExtractLocations")
|
||||
public void testExtractToAbsoluteNormalizedDir(final String dest) throws Exception {
|
||||
testExtract(dest);
|
||||
testLongFormExtract(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that extracting a jar with {@code jar -x -f --dir} works as expected
|
||||
*/
|
||||
@Test
|
||||
public void testExtractLongFormDir() throws Exception {
|
||||
final String dest = "foo-bar";
|
||||
System.out.println("Extracting " + testJarPath + " to " + dest);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, "-x", "-f", testJarPath.toString(),
|
||||
"--dir", dest);
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath + " to " + dest);
|
||||
verifyExtractedContent(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the {@code jar --help} output contains the --dir option
|
||||
*/
|
||||
@Test
|
||||
public void testHelpOutput() {
|
||||
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
final int exitCode = JAR_TOOL.run(new PrintStream(outStream), System.err, "--help");
|
||||
assertEquals(0, exitCode, "jar --help command failed");
|
||||
final String output = outStream.toString();
|
||||
// this message is expected to be the one from the jar --help output which is sourced from
|
||||
// jar.properties
|
||||
final String expectedMsg = "--dir Directory into which the jar will be extracted";
|
||||
assertTrue(output.contains(expectedMsg), "jar --help didn't contain --dir option");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@code jar -x -f} command works fine even when the -C or --dir option
|
||||
* isn't specified
|
||||
*/
|
||||
@Test
|
||||
public void testExtractWithoutOutputDir() throws Exception {
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, "-x", "-f", testJarPath.toString());
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath);
|
||||
// the content would have been extracted to current dir
|
||||
verifyExtractedContent(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@code jar --extract -f} command works fine even when the -C or --dir option
|
||||
* isn't specified
|
||||
*/
|
||||
@Test
|
||||
public void testLongFormExtractWithoutOutputDir() throws Exception {
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, "--extract", "-f", testJarPath.toString());
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath);
|
||||
// the content would have been extracted to current dir
|
||||
verifyExtractedContent(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when the destination directory specified for jar extract is actually a file
|
||||
* or one of the path component in the specified destination path is a file, then the
|
||||
* extraction fails.
|
||||
*/
|
||||
@Test
|
||||
public void testExtractToNonDirectory() throws Exception {
|
||||
final String expectedErrMsg = "could not create directory";
|
||||
final Path notADir1 = Files.createTempFile(Path.of("."), "8173970", ".txt");
|
||||
final Path notADir2 = notADir1.resolve("foobar");
|
||||
for (final Path dest : List.of(notADir1, notADir2)) {
|
||||
final String[] args = {"-x", "-f", testJarPath.toString(), "-C", dest.toString()};
|
||||
final ByteArrayOutputStream err = new ByteArrayOutputStream();
|
||||
printJarCommand(args);
|
||||
int exitCode = JAR_TOOL.run(System.out, new PrintStream(err), args);
|
||||
assertNotEquals(0, exitCode, "jar extraction was expected to fail but didn't");
|
||||
// verify it did indeed fail due to the right reason
|
||||
assertTrue(err.toString(StandardCharsets.UTF_8).contains(expectedErrMsg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that extracting a jar using {@code -P} flag and without any explicit destination
|
||||
* directory works correctly if the jar contains entries with leading slashes and/or {@code ..}
|
||||
* parts preserved.
|
||||
*/
|
||||
@Test
|
||||
public void testExtractNoDestDirWithPFlag() throws Exception {
|
||||
// run this test only on those systems where "/tmp" directory is available and we
|
||||
// can write to it
|
||||
Assumptions.assumeTrue(Files.isDirectory(Path.of("/tmp")),
|
||||
"skipping test, since /tmp isn't a directory");
|
||||
// try and write into "/tmp"
|
||||
final Path tmpDir;
|
||||
try {
|
||||
tmpDir = Files.createTempDirectory(Path.of("/tmp"), "8173970-").toAbsolutePath();
|
||||
} catch (IOException ioe) {
|
||||
Assumptions.abort("skipping test, since /tmp cannot be written to: " + ioe);
|
||||
return;
|
||||
}
|
||||
final String leadingSlashEntryName = tmpDir.toString() + "/foo/f1.txt";
|
||||
// create a jar which has leading slash (/) and dot-dot (..) preserved in entry names
|
||||
final Path jarPath = createJarWithPFlagSemantics(leadingSlashEntryName);
|
||||
final List<String[]> cmdArgs = new ArrayList<>();
|
||||
cmdArgs.add(new String[]{"-xvfP", jarPath.toString()});
|
||||
cmdArgs.add(new String[]{"--extract", "-v", "-P", "-f", jarPath.toString()});
|
||||
try {
|
||||
for (final String[] args : cmdArgs) {
|
||||
printJarCommand(args);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, args);
|
||||
assertEquals(0, exitCode, "Failed to extract " + jarPath);
|
||||
final String dest = ".";
|
||||
assertTrue(Files.isDirectory(Path.of(dest)), dest + " is not a directory");
|
||||
final Path d1 = Path.of(dest, "d1");
|
||||
assertTrue(Files.isDirectory(d1), d1 + " directory is missing or not a directory");
|
||||
final Path d2 = Path.of(dest, "d1", "d2");
|
||||
assertTrue(Files.isDirectory(d2), d2 + " directory is missing or not a directory");
|
||||
final Path f1 = Path.of(leadingSlashEntryName);
|
||||
assertTrue(Files.isRegularFile(f1), f1 + " is missing or not a file");
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f1),
|
||||
"Unexpected content in file " + f1);
|
||||
final Path f2 = Path.of("d1/d2/../f2.txt");
|
||||
assertTrue(Files.isRegularFile(f2), f2 + " is missing or not a file");
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f2),
|
||||
"Unexpected content in file " + f2);
|
||||
}
|
||||
} finally {
|
||||
// clean up the file that might have been extracted into "/tmp/...." directory
|
||||
Files.deleteIfExists(Path.of(leadingSlashEntryName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the {@code -P} option cannot be used during jar extraction when the {@code -C} and/or
|
||||
* {@code --dir} option is used
|
||||
*/
|
||||
@Test
|
||||
public void testExtractWithDirPFlagNotAllowed() throws Exception {
|
||||
// this error message is expected to be the one from the jar --help output which is sourced from
|
||||
// jar.properties
|
||||
final String expectedErrMsg = "You may not specify '-Px' with the '-C' or '--dir' options";
|
||||
final String tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toString();
|
||||
final List<String[]> cmdArgs = new ArrayList<>();
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "-P", "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "-P", "--dir", tmpDir});
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "-P", "-C", "."});
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "-P", "--dir", "."});
|
||||
cmdArgs.add(new String[]{"-xvfP", testJarPath.toString(), "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "-P", "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "-P", "--dir", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "-P", "-C", "."});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "-P", "--dir", "."});
|
||||
for (final String[] args : cmdArgs) {
|
||||
final ByteArrayOutputStream err = new ByteArrayOutputStream();
|
||||
printJarCommand(args);
|
||||
int exitCode = JAR_TOOL.run(System.out, new PrintStream(err), args);
|
||||
assertNotEquals(0, exitCode, "jar extraction was expected to fail but didn't");
|
||||
// verify it did indeed fail due to the right reason
|
||||
assertTrue(err.toString(StandardCharsets.UTF_8).contains(expectedErrMsg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@code jar -xvf <jarname> -C <dir>} works fine too
|
||||
*/
|
||||
@Test
|
||||
public void testLegacyCompatibilityMode() throws Exception {
|
||||
final String tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toString();
|
||||
final String[] args = new String[]{"-xvf", testJarPath.toString(), "-C", tmpDir};
|
||||
printJarCommand(args);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, args);
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath);
|
||||
verifyExtractedContent(tmpDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when multiple directories are specified for extracting the jar, the jar extraction
|
||||
* fails
|
||||
*/
|
||||
@Test
|
||||
public void testExtractFailWithMultipleDir() throws Exception {
|
||||
// this error message is expected to be the one from the jar --help output which is sourced from
|
||||
// jar.properties
|
||||
final String expectedErrMsg = "You may not specify the '-C' or '--dir' option more than once with the '-x' option";
|
||||
final String tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toString();
|
||||
final List<String[]> cmdArgs = new ArrayList<>();
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "-C", tmpDir, "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "--dir", tmpDir, "--dir", tmpDir});
|
||||
cmdArgs.add(new String[]{"-x", "-f", testJarPath.toString(), "--dir", tmpDir, "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "-C", tmpDir, "-C", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "--dir", tmpDir, "--dir", tmpDir});
|
||||
cmdArgs.add(new String[]{"--extract", "-f", testJarPath.toString(), "--dir", tmpDir, "-C", tmpDir});
|
||||
for (final String[] args : cmdArgs) {
|
||||
final ByteArrayOutputStream err = new ByteArrayOutputStream();
|
||||
printJarCommand(args);
|
||||
int exitCode = JAR_TOOL.run(System.out, new PrintStream(err), args);
|
||||
assertNotEquals(0, exitCode, "jar extraction was expected to fail but didn't");
|
||||
// verify it did indeed fail due to the right reason
|
||||
assertTrue(err.toString(StandardCharsets.UTF_8).contains(expectedErrMsg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that extracting only specific files from a jar, into a specific destination directory,
|
||||
* works as expected
|
||||
*/
|
||||
@Test
|
||||
public void testExtractPartialContent() throws Exception {
|
||||
String tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toString();
|
||||
String[] cmdArgs = new String[]{"-x", "-f", testJarPath.toString(), "--dir", tmpDir,
|
||||
"f1.txt", "d1/d2/d3/f2.txt"};
|
||||
testExtractPartialContent(tmpDir, cmdArgs);
|
||||
|
||||
tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toString();
|
||||
cmdArgs = new String[]{"--extract", "-f", testJarPath.toString(), "--dir", tmpDir,
|
||||
"f1.txt", "d1/d2/d3/f2.txt"};
|
||||
testExtractPartialContent(tmpDir, cmdArgs);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract to destDir using the passed command arguments and verify the extracted content
|
||||
*/
|
||||
private void testExtractPartialContent(final String destDir, final String[] extractArgs) throws Exception {
|
||||
printJarCommand(extractArgs);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, extractArgs);
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath);
|
||||
// make sure only the specific files were extracted
|
||||
final Stream<Path> paths = Files.walk(Path.of(destDir));
|
||||
// files/dirs count expected to be found when the location to which the jar was extracted
|
||||
// is walked.
|
||||
// 1) The top level dir being walked 2) f1.txt file 3) d1 dir 4) d1/d2 dir
|
||||
// 5) d1/d2/d3 dir 6) d1/d2/d3/f2.txt file
|
||||
final int numExpectedFiles = 6;
|
||||
assertEquals(numExpectedFiles, paths.count(), "Unexpected number of files/dirs in " + destDir);
|
||||
final Path f1 = Path.of(destDir, "f1.txt");
|
||||
assertTrue(Files.isRegularFile(f1), f1.toString() + " wasn't extracted from " + testJarPath);
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f1), "Unexpected content in file " + f1);
|
||||
final Path d1 = Path.of(destDir, "d1");
|
||||
assertTrue(Files.isDirectory(d1), d1.toString() + " wasn't extracted from " + testJarPath);
|
||||
assertEquals(2, Files.walk(d1, 1).count(), "Unexpected number " +
|
||||
"of files/dirs in " + d1);
|
||||
final Path d2 = Path.of(d1.toString(), "d2");
|
||||
assertTrue(Files.isDirectory(d2), d2.toString() + " wasn't extracted from " + testJarPath);
|
||||
assertEquals(2, Files.walk(d2, 1).count(), "Unexpected number " +
|
||||
"of files/dirs in " + d2);
|
||||
final Path d3 = Path.of(d2.toString(), "d3");
|
||||
assertTrue(Files.isDirectory(d3), d3.toString() + " wasn't extracted from " + testJarPath);
|
||||
assertEquals(2, Files.walk(d3, 1).count(), "Unexpected number " +
|
||||
"of files/dirs in " + d3);
|
||||
final Path f2 = Path.of(d3.toString(), "f2.txt");
|
||||
assertTrue(Files.isRegularFile(f2), f2.toString() + " wasn't extracted from " + testJarPath);
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f2), "Unexpected content in file " + f2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the jar file using {@code jar -x -f <jarfile> -C <dest>} and verifies the extracted content
|
||||
*/
|
||||
private void testExtract(final String dest) throws Exception {
|
||||
final String[] args = new String[]{"-x", "-f", testJarPath.toString(), "-C", dest};
|
||||
printJarCommand(args);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, args);
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath + " to " + dest);
|
||||
verifyExtractedContent(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the jar file using {@code jar --extract -f <jarfile> -C <dest>} and verifies the
|
||||
* extracted content
|
||||
*/
|
||||
private void testLongFormExtract(final String dest) throws Exception {
|
||||
final String[] args = new String[]{"--extract", "-f", testJarPath.toString(), "-C", dest};
|
||||
printJarCommand(args);
|
||||
final int exitCode = JAR_TOOL.run(System.out, System.err, args);
|
||||
assertEquals(0, exitCode, "Failed to extract " + testJarPath + " to " + dest);
|
||||
verifyExtractedContent(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the extracted jar content matches what was present in the original jar
|
||||
*/
|
||||
private void verifyExtractedContent(final String dest) throws IOException {
|
||||
assertTrue(Files.isDirectory(Path.of(dest)), dest + " is not a directory");
|
||||
final Path d1 = Path.of(dest, "d1");
|
||||
assertTrue(Files.isDirectory(d1), d1 + " directory is missing or not a directory");
|
||||
final Path d2 = Path.of(dest, "d1", "d2");
|
||||
assertTrue(Files.isDirectory(d2), d2 + " directory is missing or not a directory");
|
||||
final Path d3 = Path.of(dest, "d1", "d2", "d3");
|
||||
assertTrue(Files.isDirectory(d3), d3 + " directory is missing or not a directory");
|
||||
final Path d4 = Path.of(dest, "d1", "d4");
|
||||
assertTrue(Files.isDirectory(d4), d4 + " directory is missing or not a directory");
|
||||
// d1/d4 is expected to be empty directory
|
||||
final List<Path> d4Children;
|
||||
try (final Stream<Path> s = Files.walk(d4, 1)) {
|
||||
d4Children = s.toList();
|
||||
}
|
||||
assertEquals(1, d4Children.size(), "Directory " + d4
|
||||
+ " has unexpected files/dirs: " + d4Children);
|
||||
final Path f1 = Path.of(dest, "f1.txt");
|
||||
assertTrue(Files.isRegularFile(f1), f1 + " is missing or not a file");
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f1), "Unexpected content in file " + f1);
|
||||
final Path f2 = Path.of(d3.toString(), "f2.txt");
|
||||
assertTrue(Files.isRegularFile(f2), f2 + " is missing or not a file");
|
||||
assertArrayEquals(FILE_CONTENT, Files.readAllBytes(f2), "Unexpected content in file " + f2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a jar whose entries have a leading slash and the dot-dot character preserved.
|
||||
* This is the same as creating a jar using {@code jar -cfP somejar.jar <file1> <file2> ...}
|
||||
*/
|
||||
private static Path createJarWithPFlagSemantics(String leadingSlashEntryName)
|
||||
throws IOException {
|
||||
final Path tmpDir = Files.createTempDirectory(Path.of("."), "8173970-").toAbsolutePath();
|
||||
final Path jarPath = tmpDir.resolve("8173970-test-withpflag.jar");
|
||||
final JarBuilder builder = new JarBuilder(jarPath.toString());
|
||||
builder.addEntry("d1/", new byte[0]);
|
||||
builder.addEntry("d1/d2/", new byte[0]);
|
||||
builder.addEntry(leadingSlashEntryName, FILE_CONTENT);
|
||||
builder.addEntry("d1/d2/../f2.txt", FILE_CONTENT);
|
||||
builder.build();
|
||||
return jarPath;
|
||||
}
|
||||
|
||||
private static void printJarCommand(final String[] cmdArgs) {
|
||||
System.out.println("Running 'jar " + String.join(" ", cmdArgs) + "'");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user