From 888fa9026567da99ea8adfdc6212f38f5f1cd278 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 9 Oct 2015 14:37:33 +0100 Subject: [PATCH] Ensure that JarFileArchive unpacks entries to unique location Previously, JarFileArchive would always unpack any entries marked for unpacking to ${java.io.tmpdir}/spring-boot-libs. This could cause problems if multiple Spring Boot applications were running on the same host: - If the apps are run as different users the first application would create the spring-boot-libs directory and the second and subsequent applications may not have write permissions to that directory - Multiple apps may overwrite each others unpacked libs. At best this will mean one copy of a jar is overwritten with another identical copy. At worst the jars may have different contents so that some of the contents of the original jar disappear unexpectedly. This commit updates JarFileArchive to use an application-specific location when unpacking libs. A directory beneath ${java.io.tmpdir} is still used but it's now named -spring-boot-libs-. A loop, limited to 1000 attempts, is used to avoid problems caused by the uuid clashing. Closes gh-4124 --- .../boot/loader/archive/JarFileArchive.java | 29 ++++++++++++--- .../boot/loader/TestJarCreator.java | 36 +++++++++++-------- .../loader/archive/JarFileArchiveTests.java | 35 +++++++++++++++--- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java index d71f922293a..b31ebf59a50 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.Manifest; @@ -40,6 +41,7 @@ import org.springframework.boot.loader.util.AsciiBytes; * {@link Archive} implementation backed by a {@link JarFile}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class JarFileArchive extends Archive { @@ -53,6 +55,8 @@ public class JarFileArchive extends Archive { private URL url; + private File tempUnpackFolder; + public JarFileArchive(File file) throws IOException { this(file, null); } @@ -123,10 +127,25 @@ public class JarFileArchive extends Archive { } private File getTempUnpackFolder() { - File tempFolder = new File(System.getProperty("java.io.tmpdir")); - File unpackFolder = new File(tempFolder, "spring-boot-libs"); - unpackFolder.mkdirs(); - return unpackFolder; + if (this.tempUnpackFolder == null) { + File tempFolder = new File(System.getProperty("java.io.tmpdir")); + this.tempUnpackFolder = createUnpackFolder(tempFolder); + } + return this.tempUnpackFolder; + } + + private File createUnpackFolder(File parent) { + int attempts = 0; + while (attempts++ < 1000) { + String fileName = new File(this.jarFile.getName()).getName(); + File unpackFolder = new File(parent, + fileName + "-spring-boot-libs-" + UUID.randomUUID()); + if (unpackFolder.mkdirs()) { + return unpackFolder; + } + } + throw new IllegalStateException( + "Failed to create unpack folder in directory '" + parent + "'"); } private void unpack(JarEntryData data, File file) throws IOException { diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/TestJarCreator.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/TestJarCreator.java index 3ffa0acf963..a67fcb57958 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/TestJarCreator.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/TestJarCreator.java @@ -50,27 +50,33 @@ public abstract class TestJarCreator { writeDirEntry(jarOutputStream, "special/"); writeEntry(jarOutputStream, "special/\u00EB.dat", '\u00EB'); - JarEntry nestedEntry = new JarEntry("nested.jar"); - byte[] nestedJarData = getNestedJarData(); - nestedEntry.setSize(nestedJarData.length); - nestedEntry.setCompressedSize(nestedJarData.length); - if (unpackNested) { - nestedEntry.setComment("UNPACK:0000000000000000000000000000000000000000"); - } - CRC32 crc32 = new CRC32(); - crc32.update(nestedJarData); - nestedEntry.setCrc(crc32.getValue()); - - nestedEntry.setMethod(ZipEntry.STORED); - jarOutputStream.putNextEntry(nestedEntry); - jarOutputStream.write(nestedJarData); - jarOutputStream.closeEntry(); + writeNestedEntry("nested.jar", unpackNested, jarOutputStream); + writeNestedEntry("another-nested.jar", unpackNested, jarOutputStream); } finally { jarOutputStream.close(); } } + private static void writeNestedEntry(String name, boolean unpackNested, + JarOutputStream jarOutputStream) throws Exception, IOException { + JarEntry nestedEntry = new JarEntry(name); + byte[] nestedJarData = getNestedJarData(); + nestedEntry.setSize(nestedJarData.length); + nestedEntry.setCompressedSize(nestedJarData.length); + if (unpackNested) { + nestedEntry.setComment("UNPACK:0000000000000000000000000000000000000000"); + } + CRC32 crc32 = new CRC32(); + crc32.update(nestedJarData); + nestedEntry.setCrc(crc32.getValue()); + + nestedEntry.setMethod(ZipEntry.STORED); + jarOutputStream.putNextEntry(nestedEntry); + jarOutputStream.write(nestedJarData); + jarOutputStream.closeEntry(); + } + private static byte[] getNestedJarData() throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java index 3ebc190bb93..0f0994e532a 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ import org.springframework.boot.loader.util.AsciiBytes; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; @@ -38,6 +40,7 @@ import static org.junit.Assert.assertThat; * Tests for {@link JarFileArchive}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class JarFileArchiveTests { @@ -57,8 +60,7 @@ public class JarFileArchiveTests { private void setup(boolean unpackNested) throws Exception { this.rootJarFile = this.temporaryFolder.newFile(); - this.rootJarFileUrl = rootJarFile.toURI().toString(); - System.out.println(rootJarFileUrl); + this.rootJarFileUrl = this.rootJarFile.toURI().toString(); TestJarCreator.createTestJar(this.rootJarFile, unpackNested); this.archive = new JarFileArchive(this.rootJarFile); } @@ -72,7 +74,7 @@ public class JarFileArchiveTests { @Test public void getEntries() throws Exception { Map entries = getEntriesMap(this.archive); - assertThat(entries.size(), equalTo(9)); + assertThat(entries.size(), equalTo(10)); } @Test @@ -98,6 +100,31 @@ public class JarFileArchiveTests { assertThat(nested.getUrl().toString(), endsWith(".jar")); } + @Test + public void unpackedLocationsAreUniquePerArchive() throws Exception { + setup(true); + Entry entry = getEntriesMap(this.archive).get("nested.jar"); + URL firstNested = this.archive.getNestedArchive(entry).getUrl(); + setup(true); + entry = getEntriesMap(this.archive).get("nested.jar"); + URL secondNested = this.archive.getNestedArchive(entry).getUrl(); + assertThat(secondNested, is(not(equalTo(firstNested)))); + } + + @Test + public void unpackedLocationsFromSameArchiveShareSameParent() throws Exception { + setup(true); + File nested = new File(this.archive + .getNestedArchive(getEntriesMap(this.archive).get("nested.jar")).getUrl() + .toURI()); + File anotherNested = new File( + this.archive + .getNestedArchive( + getEntriesMap(this.archive).get("another-nested.jar")) + .getUrl().toURI()); + assertThat(nested.getParent(), is(equalTo(anotherNested.getParent()))); + } + @Test public void getFilteredArchive() throws Exception { Archive filteredArchive = this.archive