Handle mixture of encoded and unencoded chars in JarURLConnection

Previously, JarURLConnection would corrupt a URL that contained a
mixture of encoded and unencoded double-byte characters. URLs that
only contained unencoded double-byte characters were not affected as
they are passed through as-is.

This commit updates JarURLConnection.JarEntryName to correctly handle
characters with a value that won't fit in a single signed byte (a
value greater than 127). Such characters are now URL encoded and then
written to the output stream as multiple bytes.

Closes gh-5194
This commit is contained in:
Andy Wilkinson 2016-02-23 11:36:59 +00:00
parent 4d208c7717
commit d9382244d8
2 changed files with 92 additions and 13 deletions

View File

@ -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() {

View File

@ -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");
}
}