From ba78355bd678a4355ca1250923f2caa43d23be62 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 18 Sep 2020 15:29:02 -0500 Subject: [PATCH] Issue #5264 - Supporting extract of maven archive to destination Signed-off-by: Joakim Erdfelt --- .../main/java/org/eclipse/jetty/start/FS.java | 52 +++++++++++++ .../eclipse/jetty/start/FileInitializer.java | 21 +---- .../MavenLocalRepoFileInitializer.java | 78 +++++++++++++++---- .../MavenLocalRepoFileInitializerTest.java | 51 ++++++++++-- 4 files changed, 160 insertions(+), 42 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index cd6a8ba33fc..856c2ab8b9a 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -21,11 +21,15 @@ package org.eclipse.jetty.start; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; +import java.util.Enumeration; import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; public class FS { @@ -168,4 +172,52 @@ public class FS { return path.toRealPath(); } + + public static void extract(Path archive, Path destination) throws IOException + { + String filename = archive.getFileName().toString().toLowerCase(Locale.US); + + if (filename.endsWith(".jar") || filename.endsWith(".zip")) + { + extractZip(archive, destination); + } + else + { + throw new IOException("Unable to extract unsupported archive format: " + archive); + } + } + + public static void extractZip(Path archive, Path destination) throws IOException + { + try (ZipFile zip = new ZipFile(archive.toFile())) + { + StartLog.info("extract %s to %s", archive, destination); + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) + { + ZipEntry entry = entries.nextElement(); + + if (entry.isDirectory() || entry.getName().startsWith("/META-INF")) + { + // skip + continue; + } + + Path destFile = destination.resolve(entry.getName()); + if (!Files.exists(destFile)) + { + FS.ensureDirectoryExists(destFile.getParent()); + try (InputStream input = zip.getInputStream(entry)) + { + StartLog.debug("extracting %s", destFile); + Files.copy(input, destFile); + } + } + else + { + StartLog.debug("skipping extract (file exists) %s", destFile); + } + } + } + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java index 4f6cfa4eda8..7e0c0233eb3 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FileInitializer.java @@ -20,13 +20,12 @@ package org.eclipse.jetty.start; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URI; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; +import java.nio.file.StandardCopyOption; import java.util.HashSet; import java.util.Set; @@ -143,23 +142,9 @@ public abstract class FileInitializer throw new IOException("URL GET Failure [" + status + "/" + http.getResponseMessage() + "] on " + uri); } - byte[] buf = new byte[8192]; - try (InputStream in = http.getInputStream(); - OutputStream out = Files.newOutputStream(destination, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) + try (InputStream in = http.getInputStream()) { - while (true) - { - int len = in.read(buf); - - if (len > 0) - { - out.write(buf, 0, len); - } - if (len < 0) - { - break; - } - } + Files.copy(in, destination, StandardCopyOption.REPLACE_EXISTING); } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java index 602fcd84b2e..b163d046417 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.jetty.start.BaseHome; @@ -105,7 +106,7 @@ public class MavenLocalRepoFileInitializer extends FileInitializer } } - private Path localRepositoryDir; + private final Path localRepositoryDir; private final boolean readonly; private String mavenRepoUri; @@ -116,19 +117,24 @@ public class MavenLocalRepoFileInitializer extends FileInitializer public MavenLocalRepoFileInitializer(BaseHome baseHome, Path localRepoDir, boolean readonly) { - super(baseHome, "maven"); - this.localRepositoryDir = localRepoDir; - this.readonly = readonly; + this(baseHome, localRepoDir, readonly, null); } public MavenLocalRepoFileInitializer(BaseHome baseHome, Path localRepoDir, boolean readonly, String mavenRepoUri) { super(baseHome, "maven"); - this.localRepositoryDir = localRepoDir; + this.localRepositoryDir = localRepoDir != null ? localRepoDir : newTempRepo(); this.readonly = readonly; this.mavenRepoUri = mavenRepoUri; } + private static Path newTempRepo() + { + Path javaTempDir = Paths.get(System.getProperty("java.io.tmpdir")); + // Simple return here, don't create the directory, unless it's being used. + return javaTempDir.resolve("jetty-start-downloads"); + } + @Override public boolean create(URI uri, String location) throws IOException { @@ -139,16 +145,55 @@ public class MavenLocalRepoFileInitializer extends FileInitializer return false; } - Path destination = getDestination(uri, location); - - if (isFilePresent(destination)) - return false; - - // If using local repository - if (this.localRepositoryDir != null) + URI destURI = URI.create(location); + if (destURI.isAbsolute() && destURI.getScheme().equals("extract")) { - // Grab copy from local repository (download if needed to local - // repository) + // Extract Flow + + // Download to local repo. + Path localFile = localRepositoryDir.resolve(coords.toPath()); + if (!FS.canReadFile(localFile)) + { + if (FS.ensureDirectoryExists(localFile.getParent())) + StartLog.info("mkdir " + _basehome.toShortForm(localFile.getParent())); + download(coords, localFile); + if (!FS.canReadFile(localFile)) + { + throw new IOException("Unable to establish temp copy of file to extract: " + localFile); + } + } + + // Destination Directory + Path destination; + String extractLocation = destURI.getSchemeSpecificPart(); + if (extractLocation.equals("/")) + { + destination = _basehome.getBasePath(); + } + else + { + extractLocation = extractLocation.replaceFirst("^[/\\\\]*", ""); + if (!extractLocation.endsWith("/")) + throw new IOException("Extract mode can only unpack to a directory, end your URL with a slash: " + location); + destination = _basehome.getBasePath().resolve(extractLocation); + + if (Files.exists(destination) && !Files.isDirectory(destination)) + throw new IOException("Destination already exists, and is not a directory: " + destination); + + if (!destination.startsWith(_basehome.getBasePath())) + throw new IOException("For security reasons, Jetty start is unable to extract outside of the ${jetty.base} - " + location); + } + + FS.extract(localFile, destination); + } + else + { + // Copy Flow + Path destination = getDestination(uri, location); + if (isFilePresent(destination)) + return false; + + // Grab copy from local repository (download if needed to local repository) Path localRepoFile = getLocalRepoFile(coords); if (localRepoFile != null) @@ -159,10 +204,11 @@ public class MavenLocalRepoFileInitializer extends FileInitializer Files.copy(localRepoFile, destination); return true; } + + // normal non-local repo version + download(coords, destination); } - // normal non-local repo version - download(coords, destination); return true; } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializerTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializerTest.java index 84db30df01f..ef1cd1b5df0 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializerTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializerTest.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.eclipse.jetty.start.BaseHome; import org.eclipse.jetty.start.config.ConfigSources; @@ -30,7 +29,6 @@ import org.eclipse.jetty.start.config.JettyBaseConfigSource; import org.eclipse.jetty.start.config.JettyHomeConfigSource; import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer.Coordinates; import org.eclipse.jetty.toolchain.test.FS; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.junit.jupiter.api.BeforeEach; @@ -55,11 +53,15 @@ public class MavenLocalRepoFileInitializerTest @BeforeEach public void setupBaseHome() throws IOException { - Path homeDir = testdir.getEmptyPathDir(); + Path homeDir = testdir.getEmptyPathDir().resolve("home"); + Path baseDir = testdir.getEmptyPathDir().resolve("base"); + + FS.ensureDirExists(homeDir); + FS.ensureDirExists(baseDir); ConfigSources config = new ConfigSources(); config.add(new JettyHomeConfigSource(homeDir)); - config.add(new JettyBaseConfigSource(homeDir)); + config.add(new JettyBaseConfigSource(baseDir)); this.baseHome = new BaseHome(config); } @@ -193,7 +195,7 @@ public class MavenLocalRepoFileInitializerTest assertThat("coords.toCentralURI", coords.toCentralURI().toASCIIString(), is("https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-http/9.4.31.v20200723/jetty-http-9.4.31.v20200723-tests.jar")); - Path destination = Paths.get(System.getProperty("java.io.tmpdir"), "jetty-http-9.4.31.v20200723-tests.jar"); + Path destination = testdir.getEmptyPathDir().resolve("jetty-http-9.4.31.v20200723-tests.jar"); Files.deleteIfExists(destination); repo.download(coords.toCentralURI(), destination); assertThat(Files.exists(destination), is(true)); @@ -204,7 +206,7 @@ public class MavenLocalRepoFileInitializerTest public void testDownloadSnapshotRepo() throws Exception { - Path snapshotLocalRepoDir = MavenTestingUtils.getTargetTestingPath("snapshot-repo"); + Path snapshotLocalRepoDir = testdir.getPath().resolve("snapshot-repo"); FS.ensureEmpty(snapshotLocalRepoDir); MavenLocalRepoFileInitializer repo = @@ -222,10 +224,43 @@ public class MavenLocalRepoFileInitializerTest assertThat("coords.toCentralURI", coords.toCentralURI().toASCIIString(), is("https://oss.sonatype.org/content/repositories/jetty-snapshots/org/eclipse/jetty/jetty-rewrite/10.0.0-SNAPSHOT/jetty-rewrite-10.0.0-SNAPSHOT.jar")); - Path destination = Paths.get(System.getProperty("java.io.tmpdir"), "jetty-rewrite-10.0.0-SNAPSHOT.jar"); - Files.deleteIfExists(destination); + Path destination = baseHome.getBasePath().resolve("jetty-rewrite-10.0.0-SNAPSHOT.jar"); repo.download(coords, destination); assertThat(Files.exists(destination), is(true)); assertThat("Snapshot File size", destination.toFile().length(), greaterThan(10_000L)); } + + @Test + public void testDownloadSnapshotRepoWithExtractDeep() + throws Exception + { + Path snapshotLocalRepoDir = testdir.getPath().resolve("snapshot-repo"); + FS.ensureEmpty(snapshotLocalRepoDir); + + MavenLocalRepoFileInitializer repo = + new MavenLocalRepoFileInitializer(baseHome, snapshotLocalRepoDir, false, + "https://oss.sonatype.org/content/repositories/jetty-snapshots/"); + String ref = "maven://org.eclipse.jetty/test-jetty-webapp/10.0.0-SNAPSHOT/jar/config"; + Path baseDir = baseHome.getBasePath(); + repo.create(URI.create(ref), "extract:company/"); + + assertThat(Files.exists(baseDir.resolve("company/webapps/test.d/override-web.xml")), is(true)); + } + + @Test + public void testDownloadSnapshotRepoWithExtractDefault() + throws Exception + { + Path snapshotLocalRepoDir = testdir.getPath().resolve("snapshot-repo"); + FS.ensureEmpty(snapshotLocalRepoDir); + + MavenLocalRepoFileInitializer repo = + new MavenLocalRepoFileInitializer(baseHome, snapshotLocalRepoDir, false, + "https://oss.sonatype.org/content/repositories/jetty-snapshots/"); + String ref = "maven://org.eclipse.jetty/test-jetty-webapp/10.0.0-SNAPSHOT/jar/config"; + Path baseDir = baseHome.getBasePath(); + repo.create(URI.create(ref), "extract:/"); + + assertThat(Files.exists(baseDir.resolve("webapps/test.d/override-web.xml")), is(true)); + } }