diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java index 4e8e330af4..45da271918 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -20,15 +20,18 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.net.URLEncoder; import java.net.URLStreamHandler; /** * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * * @author Phillip Webb + * @author Andy Wilkinson */ class JarURLConnection extends java.net.JarURLConnection { @@ -204,7 +207,7 @@ class JarURLConnection extends java.net.JarURLConnection { /** * A JarEntryName parsed from a URL String. */ - private static class JarEntryName { + static class JarEntryName { private final String name; @@ -219,21 +222,39 @@ class JarURLConnection extends java.net.JarURLConnection { if ((length == 0) || (source.indexOf('%') < 0)) { return new AsciiBytes(source).toString(); } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(length); + ByteArrayOutputStream bos = new ByteArrayOutputStream(length); + write(source, bos); + // AsciiBytes is what is used to store the JarEntries so make it symmetric + return new AsciiBytes(bos.toByteArray()).toString(); + } + + private void write(String source, ByteArrayOutputStream outputStream) { + int length = source.length(); for (int i = 0; i < length; i++) { int c = source.charAt(i); - if (c == '%') { - if ((i + 2) >= length) { - throw new IllegalArgumentException("Invalid encoded sequence \"" - + source.substring(i) + "\""); + if (c > 127) { + try { + String encoded = URLEncoder.encode(String.valueOf((char) c), + "UTF-8"); + write(encoded, outputStream); + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); } - c = decodeEscapeSequence(source, i); - i += 2; } - outputStream.write(c); + else { + if (c == '%') { + if ((i + 2) >= length) { + throw new IllegalArgumentException( + "Invalid encoded sequence \"" + source.substring(i) + + "\""); + } + c = decodeEscapeSequence(source, i); + i += 2; + } + outputStream.write(c); + } } - // AsciiBytes is what is used to store the JarEntries so make it symmetric - return new AsciiBytes(outputStream.toByteArray()).toString(); } private char decodeEscapeSequence(String source, int i) { @@ -248,7 +269,7 @@ class JarURLConnection extends java.net.JarURLConnection { @Override public String toString() { - return this.name.toString(); + return this.name; } public boolean isEmpty() { diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarEntryNameTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarEntryNameTests.java new file mode 100644 index 0000000000..4cff3f083a --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarEntryNameTests.java @@ -0,0 +1,58 @@ +/* + * 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.io.UnsupportedEncodingException; + +import org.junit.Test; + +import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JarEntryName}. + * + * @author Andy Wilkinson + */ +public class JarEntryNameTests { + + @Test + public void basicName() { + assertThat(new JarEntryName("a/b/C.class").toString()).isEqualTo("a/b/C.class"); + } + + @Test + public void nameWithSingleByteEncodedCharacters() { + assertThat(new JarEntryName("%61/%62/%43.class").toString()) + .isEqualTo("a/b/C.class"); + } + + @Test + public void nameWithDoubleByteEncodedCharacters() { + assertThat(new JarEntryName("%c3%a1/b/C.class").toString()) + .isEqualTo("\u00e1/b/C.class"); + } + + @Test + public void nameWithMixtureOfEncodedAndUnencodedDoubleByteCharacters() + throws UnsupportedEncodingException { + assertThat(new JarEntryName("%c3%a1/b/\u00c7.class").toString()) + .isEqualTo("\u00e1/b/\u00c7.class"); + } + +}