diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java index 4637c65a32..d4c1841656 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java @@ -34,6 +34,7 @@ import java.util.logging.Logger; * {@link URLStreamHandler} for Spring Boot loader {@link JarFile}s. * * @author Phillip Webb + * @author Andy Wilkinson * @see JarFile#registerUrlProtocolHandler() */ public class Handler extends URLStreamHandler { @@ -41,6 +42,8 @@ public class Handler extends URLStreamHandler { // NOTE: in order to be found as a URL protocol handler, this class must be public, // must be named Handler and must be in a package ending '.jar' + private static final String JAR_PROTOCOL = "jar:"; + private static final String FILE_PROTOCOL = "file:"; private static final String SEPARATOR = "!/"; @@ -140,6 +143,85 @@ public class Handler extends URLStreamHandler { return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url); } + @Override + protected void parseURL(URL context, String spec, int start, int limit) { + if (spec.toLowerCase().startsWith(JAR_PROTOCOL)) { + setFile(context, getFileFromSpec(spec.substring(start, limit))); + } + else { + setFile(context, getFileFromContext(context, spec.substring(start, limit))); + } + } + + private String getFileFromSpec(String spec) { + int separatorIndex = spec.lastIndexOf("!/"); + if (separatorIndex == -1) { + throw new IllegalArgumentException("No !/ in spec '" + spec + "'"); + } + try { + new URL(spec.substring(0, separatorIndex)); + return spec; + } + catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid spec URL '" + spec + "'", ex); + } + } + + private String getFileFromContext(URL context, String spec) { + String file = context.getFile(); + if (spec.startsWith("/")) { + return trimToJarRoot(file) + SEPARATOR + spec.substring(1); + } + if (file.endsWith("/")) { + return file + spec; + } + int lastSlashIndex = file.lastIndexOf('/'); + if (lastSlashIndex == -1) { + throw new IllegalArgumentException( + "No / found in context URL's file '" + file + "'"); + } + return file.substring(0, lastSlashIndex + 1) + spec; + } + + private String trimToJarRoot(String file) { + int lastSeparatorIndex = file.lastIndexOf(SEPARATOR); + if (lastSeparatorIndex == -1) { + throw new IllegalArgumentException( + "No !/ found in context URL's file '" + file + "'"); + } + return file.substring(0, lastSeparatorIndex); + } + + private void setFile(URL context, String file) { + setURL(context, JAR_PROTOCOL, null, -1, null, null, file, null, null); + } + + @Override + protected boolean sameFile(URL u1, URL u2) { + if (!u1.getProtocol().equals("jar") || u2.getProtocol().equals("jar")) { + return super.sameFile(u1, u2); + } + int separator1 = u1.getFile().indexOf(SEPARATOR); + int separator2 = u1.getFile().indexOf(SEPARATOR); + if (separator1 < 0 || separator2 < 0) { + return super.sameFile(u1, u2); + } + String root1 = u1.getFile().substring(separator1 + SEPARATOR.length()); + String root2 = u2.getFile().substring(separator2 + SEPARATOR.length()); + if (!root1.equals(root2)) { + return super.sameFile(u1, u2); + } + String nested1 = u1.getFile().substring(0, separator1); + String nested2 = u1.getFile().substring(0, separator2); + try { + return super.sameFile(new URL(nested1), new URL(nested2)); + } + catch (MalformedURLException ex) { + // Continue + } + return super.sameFile(u1, u2); + } + public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); int separatorIndex = spec.indexOf(SEPARATOR); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java new file mode 100644 index 0000000000..8b5dbe31b1 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2016 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.jar; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Handler}. + * + * @author Andy Wilkinson + */ +public class HandlerTests { + + private final Handler handler = new Handler(); + + @Test + public void parseUrlWithJarRootContextAndAbsoluteSpecThatUsesContext() + throws MalformedURLException { + String spec = "/entry.txt"; + URL context = createUrl("file:example.jar!/"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); + } + + @Test + public void parseUrlWithDirectoryEntryContextAndAbsoluteSpecThatUsesContext() + throws MalformedURLException { + String spec = "/entry.txt"; + URL context = createUrl("file:example.jar!/dir/"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); + } + + @Test + public void parseUrlWithJarRootContextAndRelativeSpecThatUsesContext() + throws MalformedURLException { + String spec = "entry.txt"; + URL context = createUrl("file:example.jar!/"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); + } + + @Test + public void parseUrlWithDirectoryEntryContextAndRelativeSpecThatUsesContext() + throws MalformedURLException { + String spec = "entry.txt"; + URL context = createUrl("file:example.jar!/dir/"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()) + .isEqualTo("jar:file:example.jar!/dir/entry.txt"); + } + + @Test + public void parseUrlWithFileEntryContextAndRelativeSpecThatUsesContext() + throws MalformedURLException { + String spec = "entry.txt"; + URL context = createUrl("file:example.jar!/dir/file"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()) + .isEqualTo("jar:file:example.jar!/dir/entry.txt"); + } + + @Test + public void parseUrlWithSpecThatIgnoresContext() throws MalformedURLException { + JarFile.registerUrlProtocolHandler(); + String spec = "jar:file:/other.jar!/nested!/entry.txt"; + URL context = createUrl("file:example.jar!/dir/file"); + this.handler.parseURL(context, spec, 0, spec.length()); + assertThat(context.toExternalForm()) + .isEqualTo("jar:jar:file:/other.jar!/nested!/entry.txt"); + } + + private URL createUrl(String file) throws MalformedURLException { + return new URL("jar", null, -1, file, this.handler); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java index 32a38cb2ee..ef98792d9f 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java @@ -18,7 +18,6 @@ package org.springframework.boot.loader.jar; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileNotFoundException; import java.net.URL; import org.junit.Before; @@ -127,15 +126,14 @@ public class JarURLConnectionTests { } @Test - public void nestedJarNotFound() throws Exception { - URL url = new URL( - "jar:file:" + getAbsolutePath() + "!/nested.jar!/missing.jar!/1.dat"); + public void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() + throws Exception { + URL url = new URL(new URL("jar", null, -1, + "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), "/3.dat"); JarFile nested = this.jarFile .getNestedJarFile(this.jarFile.getEntry("nested.jar")); - JarURLConnection connection = JarURLConnection.get(url, nested); - this.thrown.expect(FileNotFoundException.class); - this.thrown.expectMessage("JAR entry missing.jar not found in"); - connection.connect(); + assertThat(JarURLConnection.get(url, nested).getInputStream()) + .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); } private String getAbsolutePath() {