Skip to content

Commit

Permalink
Fixes #2619: properly handle plus signs when resolving paths (#2623)
Browse files Browse the repository at this point in the history
* Fixes #2619: properly handle plus signs when resolving paths

Signed-off-by: Paulo Lopes <[email protected]>

* Fixes #2619: properly handle plus signs when resolving paths

Signed-off-by: Paulo Lopes <[email protected]>

* updates based on review

Signed-off-by: Paulo Lopes <[email protected]>

* Delete URIDecoder.java

Signed-off-by: Paulo Lopes <[email protected]>

* Updates based on the review

Signed-off-by: Paulo Lopes <[email protected]>
  • Loading branch information
pmlopes authored and vietj committed Sep 25, 2018
1 parent 83e94f6 commit fcef656
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 25 deletions.
13 changes: 4 additions & 9 deletions src/main/java/io/vertx/core/impl/FileResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
Expand All @@ -36,6 +34,8 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static io.vertx.core.net.impl.URIDecoder.*;

/**
* Sometimes the file resources of an application are bundled into jars, or are somewhere on the classpath but not
* available on the file system, e.g. in the case of a Vert.x webapp bundled as a fat jar.
Expand Down Expand Up @@ -162,12 +162,7 @@ private File unpackUrlResource(URL url, String fileName, ClassLoader cl, boolean


private synchronized File unpackFromFileURL(URL url, String fileName, ClassLoader cl) {
File resource;
try {
resource = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new VertxException(e);
}
final File resource = new File(decodeURIComponent(url.getPath(), false));
boolean isDirectory = resource.isDirectory();
File cacheFile = new File(cacheDir, fileName);
if (!isDirectory) {
Expand Down Expand Up @@ -207,7 +202,7 @@ private synchronized File unpackFromJarURL(URL url, String fileName, ClassLoader
idx2 = path.lastIndexOf(".zip!", idx1 - 1);
}
if (idx2 == -1) {
File file = new File(URLDecoder.decode(path.substring(5, idx1 + 4), "UTF-8"));
File file = new File(decodeURIComponent(path.substring(5, idx1 + 4), false));
zip = new ZipFile(file);
} else {
String s = path.substring(idx2 + 6, idx1 + 4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static io.vertx.core.net.impl.URIDecoder.decodeURIComponent;

/**
*
* Classloader for dynamic .java source file compilation and loading.
Expand Down Expand Up @@ -68,12 +68,7 @@ public CompilingClassLoader(ClassLoader loader, String sourceName) {
throw new RuntimeException("Resource not found: " + sourceName);
}
//Need to urldecode it too, since bug in JDK URL class which does not url decode it, so if it contains spaces you are screwed
File sourceFile;
try {
sourceFile = new File(URLDecoder.decode(resource.getFile(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Failed to decode " + e.getMessage());
}
final File sourceFile = new File(decodeURIComponent(resource.getFile(), false));
if (!sourceFile.canRead()) {
throw new RuntimeException("File not found: " + sourceFile.getAbsolutePath() + " current dir is: " + new File(".").getAbsolutePath());
}
Expand Down
11 changes: 3 additions & 8 deletions src/main/java/io/vertx/core/impl/verticle/PackageHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
import javax.tools.JavaFileObject;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;

import static io.vertx.core.net.impl.URIDecoder.decodeURIComponent;

/**
* @author Janne Hietam&auml;ki
*/
Expand All @@ -46,12 +46,7 @@ public List<JavaFileObject> find(String packageName) throws IOException {
while (urlEnumeration.hasMoreElements()) {
URL resource = urlEnumeration.nextElement();
//Need to urldecode it too, since bug in JDK URL class which does not url decode it, so if it contains spaces you are screwed
File directory;
try {
directory = new File(URLDecoder.decode(resource.getFile(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Failed to decode " + e.getMessage());
}
final File directory = new File(decodeURIComponent(resource.getFile(), false));
if (directory.isDirectory()) {
result.addAll(browseDir(packageName, directory));
} else {
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/io/vertx/core/net/impl/URIDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/

package io.vertx.core.net.impl;

import java.nio.charset.StandardCharsets;

/**
* @author <a href="mailto:[email protected]">Paulo Lopes</a>
*/
public final class URIDecoder {

private URIDecoder() {
throw new RuntimeException("Static Class");
}

/**
* Decodes a segment of an URI encoded by a browser.
*
* The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* encodeURI and encodeURIComponent, but not escape. For example in this encoding, é (in Unicode U+00E9 or in
* UTF-8 0xC3 0xA9) is encoded as %C3%A9 or %c3%a9.
*
* Plus signs '+' will be handled as spaces and encoded using the default JDK URLEncoder class.
*
* @param s string to decode
*
* @return decoded string
*/
public static String decodeURIComponent(String s) {
return decodeURIComponent(s, true);
}

/**
* Decodes a segment of an URI encoded by a browser.
*
* The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* encodeURI and encodeURIComponent, but not escape. For example in this encoding, é (in Unicode U+00E9 or in
* UTF-8 0xC3 0xA9) is encoded as %C3%A9 or %c3%a9.
*
* @param s string to decode
* @param plus weather or not to transform plus signs into spaces
*
* @return decoded string
*/
public static String decodeURIComponent(String s, boolean plus) {
if (s == null) {
return null;
}

final int size = s.length();
boolean modified = false;
int i;
for (i = 0; i < size; i++) {
final char c = s.charAt(i);
if (c == '%' || (plus && c == '+')) {
modified = true;
break;
}
}
if (!modified) {
return s;
}
final byte[] buf = s.getBytes(StandardCharsets.UTF_8);
int pos = i; // position in `buf'.
for (; i < size; i++) {
char c = s.charAt(i);
if (c == '%') {
if (i == size - 1) {
throw new IllegalArgumentException("unterminated escape"
+ " sequence at end of string: " + s);
}
c = s.charAt(++i);
if (c == '%') {
buf[pos++] = '%'; // "%%" -> "%"
break;
}
if (i >= size - 1) {
throw new IllegalArgumentException("partial escape"
+ " sequence at end of string: " + s);
}
c = decodeHexNibble(c);
final char c2 = decodeHexNibble(s.charAt(++i));
if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) {
throw new IllegalArgumentException(
"invalid escape sequence `%" + s.charAt(i - 1)
+ s.charAt(i) + "' at index " + (i - 2)
+ " of: " + s);
}
c = (char) (c * 16 + c2);
// shouldn't check for plus since it would be a double decoding
buf[pos++] = (byte) c;
} else {
buf[pos++] = (byte) (plus && c == '+' ? ' ' : c);
}
}
return new String(buf, 0, pos, StandardCharsets.UTF_8);
}

/**
* Helper to decode half of a hexadecimal number from a string.
* @param c The ASCII character of the hexadecimal number to decode.
* Must be in the range {@code [0-9a-fA-F]}.
* @return The hexadecimal value represented in the ASCII character
* given, or {@link Character#MAX_VALUE} if the character is invalid.
*/
private static char decodeHexNibble(final char c) {
if ('0' <= c && c <= '9') {
return (char) (c - '0');
} else if ('a' <= c && c <= 'f') {
return (char) (c - 'a' + 10);
} else if ('A' <= c && c <= 'F') {
return (char) (c - 'A' + 10);
} else {
return Character.MAX_VALUE;
}
}
}
81 changes: 81 additions & 0 deletions src/test/java/io/vertx/core/net/impl/URIDecoderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2014 Red Hat, Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/

package io.vertx.core.net.impl;

import org.junit.Test;

import java.net.URLEncoder;

import static io.vertx.core.net.impl.URIDecoder.decodeURIComponent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
* @author <a href="mailto:[email protected]">Paulo Lopes</a>
*/
public class URIDecoderTest {

@Test
public void testDecode() throws Exception {
String original = "ein verr+++ückter text mit Leerzeichen, Plus und Umlauten";
String encoded = URLEncoder.encode(original, "UTF-8");
assertEquals(original, decodeURIComponent(encoded, true));
}

@Test
public void testPlusAsSpace() {
assertEquals("foo bar", decodeURIComponent("foo+bar"));
}

@Test
public void testPlusAsPlus() {
assertEquals("foo+bar", decodeURIComponent("foo+bar", false));
}

@Test
public void testSpaces() {
assertEquals("foo bar", decodeURIComponent("foo%20bar"));
}

@Test
public void testSingleDecode() {
assertEquals("../blah", decodeURIComponent("%2E%2E%2Fblah"));
assertEquals("%20", decodeURIComponent("%2520"));
}

@Test
public void testFromRFC() {
assertEquals("/ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", decodeURIComponent("/%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E", false));
}

@Test
public void testNonLatin() {
assertEquals("/foo/ñ/blah/婴儿服饰/eek/ฌ", decodeURIComponent("/foo/%C3%B1/blah/%E5%A9%B4%E5%84%BF%E6%9C%8D%E9%A5%B0/eek/%E0%B8%8C"));
assertEquals("/foo/\u00F1/blah/\u5a74\u513f\u670d\u9970/eek/\u0E0C", decodeURIComponent("/foo/%C3%B1/blah/%E5%A9%B4%E5%84%BF%E6%9C%8D%E9%A5%B0/eek/%E0%B8%8C", false));
}

@Test
public void testIncomplete() {
try {
decodeURIComponent("a%");
fail("should fail");
} catch (RuntimeException e) {
// expected
}
}

@Test
public void testCaseInsensitive() {
assertEquals("../blah", decodeURIComponent("%2e%2e%2fblah"));
}

}
14 changes: 14 additions & 0 deletions src/test/java/io/vertx/test/core/FileSystemFileResolverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@

package io.vertx.test.core;

import org.junit.Test;

import java.io.File;

import org.junit.Test;

import java.io.File;

/**
* @author <a href="http://tfox.org">Tim Fox</a>
*/
Expand All @@ -22,4 +30,10 @@ public void setUp() throws Exception {
webRoot = "webroot";
}

@Test
public void testResolvePlusSignsOnName() {
File file = resolver.resolveFile("this+that");
assertFalse(file.exists());
assertEquals("this+that", file.getPath());
}
}
1 change: 1 addition & 0 deletions src/test/resources/webroot/this+that
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hi!

0 comments on commit fcef656

Please sign in to comment.