From 2a5586fbcf0bdec4ebfe9313152a946498d2493a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 17 Jan 2017 15:40:53 +0000 Subject: [PATCH] Ensure that JarResourceManager correctly handles path without leading / Previously, JarResourceManager assumed that the path would begin with a / but this isn't always the case. For example, it may be an empty string. This could lead to a malformed jar:file: URL that used ! as the separator rather than the required !/. This commit updates JarResourceManager to prepend / to any path that does not being with one before using it to construct the URL. Closes gh-7717 --- .../embedded/undertow/JarResourceManager.java | 79 +++++++++++++++++ ...dertowEmbeddedServletContainerFactory.java | 53 +----------- .../undertow/JarResourceManagerTests.java | 86 +++++++++++++++++++ 3 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/JarResourceManager.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/JarResourceManagerTests.java diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/JarResourceManager.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/JarResourceManager.java new file mode 100644 index 0000000000..040d4531da --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/JarResourceManager.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2017 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.context.embedded.undertow; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import io.undertow.UndertowMessages; +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceChangeListener; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.server.handlers.resource.URLResource; + +/** + * {@link ResourceManager} for JAR resources. + * + * @author Ivan Sopov + * @author Andy Wilkinson + */ +class JarResourceManager implements ResourceManager { + + private final String jarPath; + + JarResourceManager(File jarFile) { + this(jarFile.getAbsolutePath()); + } + + JarResourceManager(String jarPath) { + this.jarPath = jarPath; + } + + @Override + public Resource getResource(String path) throws IOException { + URL url = new URL("jar:file:" + this.jarPath + "!" + + (path.startsWith("/") ? path : "/" + path)); + URLResource resource = new URLResource(url, url.openConnection(), path); + if (resource.getContentLength() < 0) { + return null; + } + return resource; + } + + @Override + public boolean isResourceChangeListenerSupported() { + return false; + } + + @Override + public void registerResourceChangeListener(ResourceChangeListener listener) { + throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); + + } + + @Override + public void removeResourceChangeListener(ResourceChangeListener listener) { + throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); + } + + @Override + public void close() throws IOException { + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index fd06f59390..c264f996be 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -43,17 +43,13 @@ import javax.servlet.ServletException; import io.undertow.Undertow; import io.undertow.Undertow.Builder; -import io.undertow.UndertowMessages; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.accesslog.AccessLogHandler; import io.undertow.server.handlers.accesslog.AccessLogReceiver; import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver; import io.undertow.server.handlers.resource.FileResourceManager; -import io.undertow.server.handlers.resource.Resource; -import io.undertow.server.handlers.resource.ResourceChangeListener; import io.undertow.server.handlers.resource.ResourceManager; -import io.undertow.server.handlers.resource.URLResource; import io.undertow.server.session.SessionManager; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; @@ -601,53 +597,6 @@ public class UndertowEmbeddedServletContainerFactory this.useForwardHeaders = useForwardHeaders; } - /** - * Undertow {@link ResourceManager} for JAR resources. - */ - private static class JarResourceManager implements ResourceManager { - - private final String jarPath; - - JarResourceManager(File jarFile) { - this(jarFile.getAbsolutePath()); - } - - JarResourceManager(String jarPath) { - this.jarPath = jarPath; - } - - @Override - public Resource getResource(String path) throws IOException { - URL url = new URL("jar:file:" + this.jarPath + "!" + path); - URLResource resource = new URLResource(url, url.openConnection(), path); - if (resource.getContentLength() < 0) { - return null; - } - return resource; - } - - @Override - public boolean isResourceChangeListenerSupported() { - return false; - } - - @Override - public void registerResourceChangeListener(ResourceChangeListener listener) { - throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); - - } - - @Override - public void removeResourceChangeListener(ResourceChangeListener listener) { - throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); - } - - @Override - public void close() throws IOException { - } - - } - /** * {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer * ServletContextInitializers}. diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/JarResourceManagerTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/JarResourceManagerTests.java new file mode 100644 index 0000000000..d967478392 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/JarResourceManagerTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2017 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.context.embedded.undertow; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceManager; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JarResourceManager}. + * + * @author Andy Wilkinson + */ +public class JarResourceManagerTests { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private ResourceManager resourceManager; + + @Before + public void createJar() throws IOException { + File jar = this.temp.newFile(); + JarOutputStream out = new JarOutputStream(new FileOutputStream(jar)); + out.putNextEntry(new ZipEntry("hello.txt")); + out.write("hello".getBytes()); + out.close(); + this.resourceManager = new JarResourceManager(jar); + } + + @Test + public void emptyPathIsHandledCorrectly() throws IOException { + Resource resource = this.resourceManager.getResource(""); + assertThat(resource).isNotNull(); + assertThat(resource.isDirectory()).isTrue(); + } + + @Test + public void rootPathIsHandledCorrectly() throws IOException { + Resource resource = this.resourceManager.getResource("/"); + assertThat(resource).isNotNull(); + assertThat(resource.isDirectory()).isTrue(); + } + + @Test + public void resourceIsFoundInJarFile() throws IOException { + Resource resource = this.resourceManager.getResource("/hello.txt"); + assertThat(resource).isNotNull(); + assertThat(resource.isDirectory()).isFalse(); + assertThat(resource.getContentLength()).isEqualTo(5); + } + + @Test + public void resourceIsFoundInJarFileWithoutLeadingSlash() throws IOException { + Resource resource = this.resourceManager.getResource("hello.txt"); + assertThat(resource).isNotNull(); + assertThat(resource.isDirectory()).isFalse(); + assertThat(resource.getContentLength()).isEqualTo(5); + } + +}