Improve handling of reserved characters in MetaInfResourceManager

Previously, MetaInfResourceManager that we use with Undertow to serve
static resources from jar's META-INF/resources did not correctly
handle characters in the path that should be percent-encoded when
used in a URL.

This commit updates MetaInfResourceManager to encode the path before
it is used to create a URL. Prior to this encoding, encoded slashes
(%2F) are decoded as, unlike other encoded characters in the request's
URL, encoded slashes are not decoded prior to calling the
ResourceManager.

Fixes gh-17853
This commit is contained in:
Andy Wilkinson 2019-08-29 11:58:21 +01:00
parent 674f2f5a6c
commit 9961647c7f
6 changed files with 47 additions and 3 deletions

View File

@ -18,8 +18,8 @@ package org.springframework.boot.web.embedded.undertow;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@ -32,6 +32,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
@ -94,6 +95,8 @@ import org.springframework.util.Assert;
public class UndertowServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableUndertowWebServerFactory, ResourceLoaderAware {
private static final Pattern ENCODED_SLASH = Pattern.compile("%2F", Pattern.LITERAL);
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
private List<UndertowBuilderCustomizer> builderCustomizers = new ArrayList<>();
@ -578,14 +581,15 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
private URLResource getMetaInfResource(URL resourceJar, String path) {
try {
URL resourceUrl = new URL(resourceJar + "META-INF/resources" + path);
String urlPath = URLEncoder.encode(ENCODED_SLASH.matcher(path).replaceAll("/"), "UTF-8");
URL resourceUrl = new URL(resourceJar + "META-INF/resources" + urlPath);
URLResource resource = new URLResource(resourceUrl, path);
if (resource.getContentLength() < 0) {
return null;
}
return resource;
}
catch (MalformedURLException ex) {
catch (Exception ex) {
return null;
}
}

View File

@ -90,6 +90,10 @@ class ApplicationBuilder {
resourcesJarStream.putNextEntry(new ZipEntry("META-INF/resources/nested-meta-inf-resource.txt"));
resourcesJarStream.write("nested".getBytes());
resourcesJarStream.closeEntry();
resourcesJarStream.putNextEntry(
new ZipEntry("META-INF/resources/nested-reserved-!#$%&()*+,:=?@[]-meta-inf-resource.txt"));
resourcesJarStream.write("encoded-name".getBytes());
resourcesJarStream.closeEntry();
return resourcesJar;
}
}

View File

@ -54,6 +54,15 @@ public class EmbeddedServletContainerJarDevelopmentIntegrationTests
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp() {
ResponseEntity<String> entity = this.rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaServletContext() {
ResponseEntity<String> entity = this.rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",

View File

@ -54,6 +54,15 @@ public class EmbeddedServletContainerJarPackagingIntegrationTests
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp() {
ResponseEntity<String> entity = this.rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() {
ResponseEntity<String> entity = this.rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",

View File

@ -60,6 +60,15 @@ public class EmbeddedServletContainerWarDevelopmentIntegrationTests
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp() {
ResponseEntity<String> entity = this.rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaServletContext() {
ResponseEntity<String> entity = this.rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",

View File

@ -60,6 +60,15 @@ public class EmbeddedServletContainerWarPackagingIntegrationTests
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp() {
ResponseEntity<String> entity = this.rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() {
ResponseEntity<String> entity = this.rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",