From 68e54e1d5d2680001d3c64b48e0f30ef58db4a78 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 9 Jul 2015 16:24:09 +0100 Subject: [PATCH] Favour entries in source jar over standard libraries when repackaging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When writing a jar, once an entry has been written it will never be overwritten, i.e. the first write of a given entry will win. Previously, when repackaging a jar, the existing contents were written followed by any libraries. This caused a problem when repackaged a WAR file and a library needed to be unpacked as the existing entry in WEB-INF/lib would prevent the library with the UNPACK comment from being written. This was addressed in f761916b by inverting the order so libraries would take precedence over entries in the source jar. It’s now become apparent that this change in the order causes a problem for users who are obfuscating their code. The obfuscated code exists in the source jar but is also provided to the repackager in its original form as a library. When libraries take precedence, this means that the code in its original form ends up in the repackaged war and the obfuscation is lost. This commit updates the repackager to write libraries that require unpacking first. This allows the UNPACK comment to be written even if there’s also a source entry for the library. Next, source entries are written. This allows obfuscated source entries to take precedence over any unobfuscated library equivalents. Lastly, standard libraries that do not require unpacking are written into the repackaged archive. Closes gh-3444 --- .../boot/loader/tools/Repackager.java | 42 ++++++++---- .../boot/loader/tools/RepackagerTests.java | 64 +++++++++++++++++++ 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index 97da6ffc71..0ff3f14a40 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -20,7 +20,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -154,29 +156,30 @@ public class Repackager { private void repackage(JarFile sourceJar, File destination, Libraries libraries) throws IOException { - final JarWriter writer = new JarWriter(destination); + JarWriter writer = new JarWriter(destination); try { - final Set seen = new HashSet(); - writer.writeManifest(buildManifest(sourceJar)); + final List unpackLibraries = new ArrayList(); + final List standardLibraries = new ArrayList(); + libraries.doWithLibraries(new LibraryCallback() { @Override public void library(Library library) throws IOException { File file = library.getFile(); if (isZip(file)) { - String destination = Repackager.this.layout - .getLibraryDestination(library.getName(), - library.getScope()); - if (destination != null) { - if (!seen.add(destination + library.getName())) { - throw new IllegalStateException("Duplicate library " - + library.getName()); - } - writer.writeNestedLibrary(destination, library); + if (library.isUnpackRequired()) { + unpackLibraries.add(library); + } + else { + standardLibraries.add(library); } } } }); + writer.writeManifest(buildManifest(sourceJar)); + Set seen = new HashSet(); + writeNestedLibraries(unpackLibraries, seen, writer); writer.writeEntries(sourceJar); + writeNestedLibraries(standardLibraries, seen, writer); if (this.layout.isExecutable()) { writer.writeLoaderClasses(); } @@ -191,6 +194,21 @@ public class Repackager { } } + private void writeNestedLibraries(List libraries, Set alreadySeen, + JarWriter writer) throws IOException { + for (Library library : libraries) { + String destination = Repackager.this.layout.getLibraryDestination( + library.getName(), library.getScope()); + if (destination != null) { + if (!alreadySeen.add(destination + library.getName())) { + throw new IllegalStateException("Duplicate library " + + library.getName()); + } + writer.writeNestedLibrary(destination, library); + } + } + } + private boolean isZip(File file) { try { FileInputStream fileInputStream = new FileInputStream(file); diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index a8a92de05e..8b3a91ad5d 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -32,6 +32,7 @@ import org.junit.rules.TemporaryFolder; import org.springframework.boot.loader.tools.sample.ClassWithMainMethod; import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod; import org.springframework.util.FileCopyUtils; +import org.zeroturnaround.zip.ZipUtil; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.startsWith; @@ -393,6 +394,69 @@ public class RepackagerTests { } } + @Test + public void unpackLibrariesTakePrecedenceOverExistingSourceEntries() throws Exception { + final TestJarFile nested = new TestJarFile(this.temporaryFolder); + nested.addClass("a/b/C.class", ClassWithoutMainMethod.class); + final File nestedFile = nested.getFile(); + this.testJarFile.addFile("lib/" + nestedFile.getName(), nested.getFile()); + this.testJarFile.addClass("A.class", ClassWithMainMethod.class); + File file = this.testJarFile.getFile(); + Repackager repackager = new Repackager(file); + repackager.repackage(new Libraries() { + + @Override + public void doWithLibraries(LibraryCallback callback) throws IOException { + callback.library(new Library(nestedFile, LibraryScope.COMPILE, true)); + } + + }); + + JarFile jarFile = new JarFile(file); + try { + assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getComment(), + startsWith("UNPACK:")); + } + finally { + jarFile.close(); + } + } + + @Test + public void existingSourceEntriesTakePrecedenceOverStandardLibraries() + throws Exception { + final TestJarFile nested = new TestJarFile(this.temporaryFolder); + nested.addClass("a/b/C.class", ClassWithoutMainMethod.class); + final File nestedFile = nested.getFile(); + this.testJarFile.addFile("lib/" + nestedFile.getName(), nested.getFile()); + this.testJarFile.addClass("A.class", ClassWithMainMethod.class); + File file = this.testJarFile.getFile(); + Repackager repackager = new Repackager(file); + + long sourceLength = nestedFile.length(); + + repackager.repackage(new Libraries() { + + @Override + public void doWithLibraries(LibraryCallback callback) throws IOException { + nestedFile.delete(); + File toZip = RepackagerTests.this.temporaryFolder.newFile(); + ZipUtil.packEntry(toZip, nestedFile); + callback.library(new Library(nestedFile, LibraryScope.COMPILE)); + } + + }); + + JarFile jarFile = new JarFile(file); + try { + assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getSize(), + equalTo(sourceLength)); + } + finally { + jarFile.close(); + } + } + private boolean hasLauncherClasses(File file) throws IOException { return hasEntry(file, "org/springframework/boot/") && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");