From 6220aba9835389d10ed81be664c8a7a9347f306f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 17 Oct 2013 17:19:51 -0700 Subject: [PATCH] Allow 'java -jar' to work with signed nested jars Fix RandomAccessJarFile to correctly read certificate information as jar entries are loaded. This change allows signed nested jars to be used as JCE providers. --- .../data/ByteArrayRandomAccessData.java | 60 ++++++++++++++++ ...try.java => RandomAccessDataJarEntry.java} | 14 ++-- ...va => RandomAccessDataJarInputStream.java} | 22 +++--- .../boot/loader/jar/RandomAccessJarFile.java | 72 +++++++++++-------- .../data/ByteArrayRandomAccessDataTest.java | 49 +++++++++++++ ... RandomAccessDataJarInputStreamTests.java} | 14 ++-- .../loader/jar/RandomAccessJarFileTests.java | 8 +++ 7 files changed, 184 insertions(+), 55 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java rename spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/{RandomAccessDataZipEntry.java => RandomAccessDataJarEntry.java} (75%) rename spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/{RandomAccessDataZipInputStream.java => RandomAccessDataJarInputStream.java} (82%) create mode 100644 spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTest.java rename spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/{RandomAccessDataZipInputStreamTests.java => RandomAccessDataJarInputStreamTests.java} (86%) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java new file mode 100644 index 00000000000..7697c272881 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/ByteArrayRandomAccessData.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader.data; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * {@link RandomAccessData} implementation backed by a byte array. + * + * @author Phillip Webb + */ +public class ByteArrayRandomAccessData implements RandomAccessData { + + private final byte[] bytes; + + private final long offset; + + private final long length; + + public ByteArrayRandomAccessData(byte[] bytes) { + this(bytes, 0, (bytes == null ? 0 : bytes.length)); + } + + public ByteArrayRandomAccessData(byte[] bytes, long offset, long length) { + this.bytes = (bytes == null ? new byte[0] : bytes); + this.offset = offset; + this.length = length; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(this.bytes, (int) this.offset, (int) this.length); + } + + @Override + public RandomAccessData getSubsection(long offset, long length) { + return new ByteArrayRandomAccessData(this.bytes, this.offset + offset, length); + } + + @Override + public long getSize() { + return this.length; + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipEntry.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarEntry.java similarity index 75% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipEntry.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarEntry.java index 939d18d68b2..6dfdfc708b7 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipEntry.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarEntry.java @@ -16,25 +16,25 @@ package org.springframework.boot.loader.jar; -import java.util.zip.ZipEntry; +import java.util.jar.JarEntry; import org.springframework.boot.loader.data.RandomAccessData; /** - * A {@link ZipEntry} returned from a {@link RandomAccessDataZipInputStream}. + * A {@link JarEntry} returned from a {@link RandomAccessDataJarInputStream}. * * @author Phillip Webb */ -public class RandomAccessDataZipEntry extends ZipEntry { +public class RandomAccessDataJarEntry extends JarEntry { private RandomAccessData data; /** - * Create new {@link RandomAccessDataZipEntry} instance. - * @param entry the underying {@link ZipEntry} + * Create new {@link RandomAccessDataJarEntry} instance. + * @param entry the underlying {@link JarEntry} * @param data the entry data */ - public RandomAccessDataZipEntry(ZipEntry entry, RandomAccessData data) { + public RandomAccessDataJarEntry(JarEntry entry, RandomAccessData data) { super(entry); this.data = data; } @@ -44,6 +44,6 @@ public class RandomAccessDataZipEntry extends ZipEntry { * @return the entry data */ public RandomAccessData getData() { - return data; + return this.data; } } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStream.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStream.java similarity index 82% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStream.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStream.java index f9b7f93512c..93ee5d22c73 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStream.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStream.java @@ -20,19 +20,19 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; import org.springframework.boot.loader.data.RandomAccessData; /** - * A {@link ZipInputStream} backed by {@link RandomAccessData}. Parsed entries provide + * A {@link JarInputStream} backed by {@link RandomAccessData}. Parsed entries provide * access to the underlying data {@link RandomAccessData#getSubsection(long, long) * subsection}. * * @author Phillip Webb */ -public class RandomAccessDataZipInputStream extends ZipInputStream { +public class RandomAccessDataJarInputStream extends JarInputStream { private RandomAccessData data; @@ -41,8 +41,9 @@ public class RandomAccessDataZipInputStream extends ZipInputStream { /** * Create a new {@link RandomAccessData} instance. * @param data the source of the zip stream + * @throws IOException */ - public RandomAccessDataZipInputStream(RandomAccessData data) { + public RandomAccessDataJarInputStream(RandomAccessData data) throws IOException { this(data, new TrackingInputStream(data.getInputStream())); } @@ -51,17 +52,18 @@ public class RandomAccessDataZipInputStream extends ZipInputStream { * {@link TrackingInputStream}. * @param data the source of the zip stream * @param trackingInputStream a tracking input stream + * @throws IOException */ - private RandomAccessDataZipInputStream(RandomAccessData data, - TrackingInputStream trackingInputStream) { + private RandomAccessDataJarInputStream(RandomAccessData data, + TrackingInputStream trackingInputStream) throws IOException { super(trackingInputStream); this.data = data; this.trackingInputStream = trackingInputStream; } @Override - public RandomAccessDataZipEntry getNextEntry() throws IOException { - ZipEntry entry = super.getNextEntry(); + public RandomAccessDataJarEntry getNextEntry() throws IOException { + JarEntry entry = (JarEntry) super.getNextEntry(); if (entry == null) { return null; } @@ -69,7 +71,7 @@ public class RandomAccessDataZipInputStream extends ZipInputStream { closeEntry(); int end = getPosition(); RandomAccessData entryData = this.data.getSubsection(start, end - start); - return new RandomAccessDataZipEntry(entry, entryData); + return new RandomAccessDataJarEntry(entry, entryData); } private int getPosition() throws IOException { diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessJarFile.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessJarFile.java index 2e5d2cac5c4..5d3eab1136d 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessJarFile.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/RandomAccessJarFile.java @@ -16,7 +16,7 @@ package org.springframework.boot.loader.jar; -import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; @@ -31,14 +31,15 @@ import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; -import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.springframework.boot.loader.data.ByteArrayRandomAccessData; import org.springframework.boot.loader.data.RandomAccessData; import org.springframework.boot.loader.data.RandomAccessDataFile; @@ -63,6 +64,9 @@ import org.springframework.boot.loader.data.RandomAccessDataFile; */ public class RandomAccessJarFile extends JarFile { + private static final RandomAccessData EMPTY_DATA = new ByteArrayRandomAccessData( + new byte[0]); + private final RandomAccessDataFile rootJarFile; private RandomAccessData data; @@ -113,19 +117,17 @@ public class RandomAccessJarFile extends JarFile { this.data = data; this.size = data.getSize(); - RandomAccessDataZipInputStream inputStream = new RandomAccessDataZipInputStream( + RandomAccessDataJarInputStream inputStream = new RandomAccessDataJarInputStream( data); try { - RandomAccessDataZipEntry zipEntry = inputStream.getNextEntry(); + RandomAccessDataJarEntry zipEntry = inputStream.getNextEntry(); while (zipEntry != null) { addJarEntry(zipEntry, filters); zipEntry = inputStream.getNextEntry(); } - this.manifest = findManifest(); + this.manifest = inputStream.getManifest(); if (this.manifest != null) { - for (JarEntry containedEntry : this.entries.values()) { - ((Entry) containedEntry).configure(this.manifest); - } + addManifestEntries(filters); } } finally { @@ -133,7 +135,36 @@ public class RandomAccessJarFile extends JarFile { } } - private void addJarEntry(RandomAccessDataZipEntry zipEntry, JarEntryFilter... filters) { + private void addManifestEntries(JarEntryFilter... filters) throws IOException { + + Map originalEntries = this.entries; + this.entries = new LinkedHashMap(); + + ZipInputStream zipInputStream = new ZipInputStream(this.data.getInputStream()); + try { + JarEntry entry; + do { + entry = new JarEntry(zipInputStream.getNextEntry()); + entry.setMethod(ZipEntry.STORED); + RandomAccessData data = EMPTY_DATA; + if (MANIFEST_NAME.equals(entry.getName())) { + ByteArrayOutputStream manifestBytes = new ByteArrayOutputStream(); + this.manifest.write(manifestBytes); + manifestBytes.close(); + data = new ByteArrayRandomAccessData(manifestBytes.toByteArray()); + } + addJarEntry(new RandomAccessDataJarEntry(entry, data), filters); + } + while (!MANIFEST_NAME.equals(entry.getName())); + + this.entries.putAll(originalEntries); + } + finally { + zipInputStream.close(); + } + } + + private void addJarEntry(RandomAccessDataJarEntry zipEntry, JarEntryFilter... filters) { Entry jarEntry = new Entry(zipEntry); String name = zipEntry.getName(); for (JarEntryFilter filter : filters) { @@ -145,16 +176,6 @@ public class RandomAccessJarFile extends JarFile { } } - private Manifest findManifest() throws IOException { - ZipEntry manifestEntry = getEntry(MANIFEST_NAME); - if (manifestEntry != null) { - BufferedInputStream inputStream = new BufferedInputStream( - getInputStream(manifestEntry)); - return new Manifest(inputStream); - } - return null; - } - protected final RandomAccessDataFile getRootJarFile() { return this.rootJarFile; } @@ -305,17 +326,11 @@ public class RandomAccessJarFile extends JarFile { private RandomAccessData entryData; - private Attributes attributes; - - public Entry(RandomAccessDataZipEntry entry) { + public Entry(RandomAccessDataJarEntry entry) { super(entry); this.entryData = entry.getData(); } - void configure(Manifest manifest) { - this.attributes = manifest.getAttributes(getName()); - } - void setName(String name) { this.name = name; } @@ -325,11 +340,6 @@ public class RandomAccessJarFile extends JarFile { return (this.name == null ? super.getName() : this.name); } - @Override - public Attributes getAttributes() throws IOException { - return this.attributes; - } - public RandomAccessData getData() { return this.entryData; } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTest.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTest.java new file mode 100644 index 00000000000..6bf2b8b9547 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/data/ByteArrayRandomAccessDataTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader.data; + +import org.junit.Test; +import org.springframework.util.FileCopyUtils; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link ByteArrayRandomAccessData}. + * + * @author Phillip Webb + */ +public class ByteArrayRandomAccessDataTest { + + @Test + public void testGetInputStream() throws Exception { + byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 }; + RandomAccessData data = new ByteArrayRandomAccessData(bytes); + assertThat(FileCopyUtils.copyToByteArray(data.getInputStream()), equalTo(bytes)); + assertThat(data.getSize(), equalTo((long) bytes.length)); + } + + @Test + public void testGetSubsection() throws Exception { + byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5 }; + RandomAccessData data = new ByteArrayRandomAccessData(bytes); + data = data.getSubsection(1, 4).getSubsection(1, 2); + assertThat(FileCopyUtils.copyToByteArray(data.getInputStream()), + equalTo(new byte[] { 2, 3 })); + assertThat(data.getSize(), equalTo(2L)); + } +} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStreamTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStreamTests.java similarity index 86% rename from spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStreamTests.java rename to spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStreamTests.java index bf0c1267058..ac73a0fd3cf 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataZipInputStreamTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessDataJarInputStreamTests.java @@ -32,15 +32,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.springframework.boot.loader.data.RandomAccessDataFile; -import org.springframework.boot.loader.jar.RandomAccessDataZipEntry; -import org.springframework.boot.loader.jar.RandomAccessDataZipInputStream; +import org.springframework.boot.loader.jar.RandomAccessDataJarEntry; +import org.springframework.boot.loader.jar.RandomAccessDataJarInputStream; /** - * Tests for {@link RandomAccessDataZipInputStream}. + * Tests for {@link RandomAccessDataJarInputStream}. * * @author Phillip Webb */ -public class RandomAccessDataZipInputStreamTests { +public class RandomAccessDataJarInputStreamTests { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -76,11 +76,11 @@ public class RandomAccessDataZipInputStreamTests { @Test public void entryData() throws Exception { - RandomAccessDataZipInputStream z = new RandomAccessDataZipInputStream( + RandomAccessDataJarInputStream z = new RandomAccessDataJarInputStream( new RandomAccessDataFile(file)); try { - RandomAccessDataZipEntry entry1 = z.getNextEntry(); - RandomAccessDataZipEntry entry2 = z.getNextEntry(); + RandomAccessDataJarEntry entry1 = z.getNextEntry(); + RandomAccessDataJarEntry entry2 = z.getNextEntry(); assertThat(entry1.getName(), equalTo("a")); assertThat(entry1.getData().getSize(), equalTo(10L)); assertThat(entry2.getName(), equalTo("b")); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessJarFileTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessJarFileTests.java index ce6d15462e1..660011b42a0 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessJarFileTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/RandomAccessJarFileTests.java @@ -25,6 +25,7 @@ import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.zip.ZipEntry; import org.junit.Before; @@ -91,6 +92,13 @@ public class RandomAccessJarFileTests { equalTo("j1")); } + @Test + public void getManifestEntry() throws Exception { + ZipEntry entry = this.jarFile.getJarEntry("META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(this.jarFile.getInputStream(entry)); + assertThat(manifest.getMainAttributes().getValue("Built-By"), equalTo("j1")); + } + @Test public void getEntries() throws Exception { Enumeration entries = this.jarFile.entries();