diff options
author | Jesse Wilson <jessewilson@google.com> | 2011-05-23 18:38:38 -0700 |
---|---|---|
committer | Jesse Wilson <jessewilson@google.com> | 2011-05-25 11:04:27 -0700 |
commit | 5292410e4ebf7fb5149eefd2f52fcb94c46690a6 (patch) | |
tree | 32b183965fa6ebff9f42dc92e213bbf100b4ff39 /luni | |
parent | 23c3b7f76a8ec59d5329417d53d83b14242441de (diff) | |
download | libcore-5292410e4ebf7fb5149eefd2f52fcb94c46690a6.zip libcore-5292410e4ebf7fb5149eefd2f52fcb94c46690a6.tar.gz libcore-5292410e4ebf7fb5149eefd2f52fcb94c46690a6.tar.bz2 |
Rewrite parsing for java.net.URL.
This fixes many broken cases on handling relative URLs.
We normalize all URLs by default. This will result
in more URL equality than before. Previously the URLs
http://android.com/a/../ and http://android.com/ were not
equal; now they are equal.
Change-Id: I8cf7be2e42eeb1386520be2698d8f14e0a55decb
http://b/4361656
Diffstat (limited to 'luni')
-rw-r--r-- | luni/src/main/java/java/net/URL.java | 146 | ||||
-rw-r--r-- | luni/src/main/java/java/net/URLStreamHandler.java | 286 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/http/HttpEngine.java | 4 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/url/FileHandler.java | 10 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/url/JarHandler.java | 3 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/url/UrlUtils.java | 98 | ||||
-rw-r--r-- | luni/src/main/java/org/apache/harmony/luni/util/URLUtil.java | 56 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java | 4 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/net/OldURLTest.java | 15 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/net/URLConnectionTest.java | 10 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/net/URLTest.java | 481 | ||||
-rw-r--r-- | luni/src/test/java/libcore/net/url/UrlUtilsTest.java | 89 |
12 files changed, 842 insertions, 360 deletions
diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java index 4e68b39..91a398e 100644 --- a/luni/src/main/java/java/net/URL.java +++ b/luni/src/main/java/java/net/URL.java @@ -23,13 +23,13 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Hashtable; -import java.util.Locale; import java.util.jar.JarFile; import libcore.net.http.HttpHandler; import libcore.net.http.HttpsHandler; import libcore.net.url.FileHandler; import libcore.net.url.FtpHandler; import libcore.net.url.JarHandler; +import libcore.net.url.UrlUtils; /** * A Uniform Resource Locator that identifies the location of an Internet @@ -150,81 +150,34 @@ public final class URL implements Serializable { * be parsed as a URL or an invalid protocol has been found. */ public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { - if (handler != null) { - streamHandler = handler; - } - if (spec == null) { throw new MalformedURLException(); } + if (handler != null) { + streamHandler = handler; + } spec = spec.trim(); - // The spec includes a protocol if it includes a colon character - // before the first occurrence of a slash character. Note that, - // "protocol" is the field which holds this URLs protocol. - int index = spec.indexOf(':'); - int startIpv6Address = spec.indexOf('['); - if (index >= 0) { - if ((startIpv6Address == -1) || (index < startIpv6Address)) { - protocol = spec.substring(0, index); - // According to RFC 2396 scheme part should match - // the following expression: - // alpha *( alpha | digit | "+" | "-" | "." ) - char c = protocol.charAt(0); - boolean valid = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); - for (int i = 1; valid && (i < protocol.length()); i++) { - c = protocol.charAt(i); - valid = ('a' <= c && c <= 'z') || - ('A' <= c && c <= 'Z') || - ('0' <= c && c <= '9') || - (c == '+') || - (c == '-') || - (c == '.'); - } - if (!valid) { - protocol = null; - index = -1; - } else { - // Ignore case in protocol names. Scheme is defined by ASCII characters. - protocol = protocol.toLowerCase(Locale.US); - } - } + protocol = UrlUtils.getSchemePrefix(spec); + int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0; + + // If the context URL has a different protocol, discard it because we can't use it. + if (protocol != null && context != null && !protocol.equals(context.protocol)) { + context = null; } - if (protocol != null) { - // If the context was specified, and it had the same protocol - // as the spec, then fill in the receiver's slots from the values - // in the context but still allow them to be over-ridden later - // by the values in the spec. - if (context != null && protocol.equals(context.getProtocol())) { - String cPath = context.getPath(); - if (cPath != null && cPath.startsWith("/")) { - set(protocol, context.getHost(), context.getPort(), context - .getAuthority(), context.getUserInfo(), cPath, - context.getQuery(), null); - } - if (streamHandler == null) { - streamHandler = context.streamHandler; - } - } - } else { - // If the spec did not include a protocol, then the context - // *must* be specified. Fill in the receiver's slots from the - // values in the context, but still allow them to be over-ridden - // by the values in the ("relative") spec. - if (context == null) { - throw new MalformedURLException("Protocol not found: " + spec); - } - set(context.getProtocol(), context.getHost(), context.getPort(), - context.getAuthority(), context.getUserInfo(), context - .getPath(), context.getQuery(), null); + // Inherit from the context URL if it exists. + if (context != null) { + set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(), + context.getUserInfo(), context.getPath(), context.getQuery(), + context.getRef()); if (streamHandler == null) { streamHandler = context.streamHandler; } + } else if (protocol == null) { + throw new MalformedURLException("Protocol not found: " + spec); } - // If the stream handler has not been determined, set it - // to the default for the specified protocol. if (streamHandler == null) { setupStreamHandler(); if (streamHandler == null) { @@ -232,23 +185,12 @@ public final class URL implements Serializable { } } - // Let the handler parse the URL. If the handler throws - // any exception, throw MalformedURLException instead. - // - // Note: We want "index" to be the index of the start of the scheme - // specific part of the URL. At this point, it will be either - // -1 or the index of the colon after the protocol, so we - // increment it to point at either character 0 or the character - // after the colon. + // Parse the URL. If the handler throws any exception, throw MalformedURLException instead. try { - streamHandler.parseURL(this, spec, ++index, spec.length()); + streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length()); } catch (Exception e) { throw new MalformedURLException(e.toString()); } - - if (port < -1) { - throw new MalformedURLException("Port out of range: " + port); - } } /** @@ -291,26 +233,33 @@ public final class URL implements Serializable { public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException { if (port < -1) { - throw new MalformedURLException("Port out of range: " + port); + throw new MalformedURLException("port < -1: " + port); + } + if (protocol == null) { + throw new NullPointerException("protocol == null"); } + // Wrap IPv6 addresses in square brackets if they aren't already. if (host != null && host.contains(":") && host.charAt(0) != '[') { host = "[" + host + "]"; } - if (protocol == null) { - throw new NullPointerException("Unknown protocol: null"); - } - this.protocol = protocol; this.host = host; this.port = port; + /* + * Force the path to start with a '/' if this URL has an authority. + * Otherwise they blend together like http://android.comindex.html. + */ + if (host != null && !host.isEmpty() && !file.startsWith("/")) { + file = "/" + file; + } + // Set the fields from the arguments. Handle the case where the // passed in "file" includes both a file and a reference part. - int index = -1; - index = file.indexOf("#", file.lastIndexOf("/")); - if (index >= 0) { + int index = file.indexOf("#", file.lastIndexOf("/")); + if (index != -1) { this.file = file.substring(0, index); ref = file.substring(index + 1); } else { @@ -496,7 +445,7 @@ public final class URL implements Serializable { /** * Returns the content of the resource which is referred by this URL. By - * default, this returns an {@code InputStream} or null if the content type + * default this returns an {@code InputStream}, or null if the content type * of the response is unknown. */ public final Object getContent() throws IOException { @@ -631,14 +580,15 @@ public final class URL implements Serializable { } /** - * Returns the authority part of this URL. + * Returns the authority part of this URL, or null if this URL has no + * authority. */ public String getAuthority() { return authority; } /** - * Returns the user-info part of this URL. + * Returns the user info of this URL, or null if this URL has no user info. */ public String getUserInfo() { return userInfo; @@ -652,7 +602,7 @@ public final class URL implements Serializable { } /** - * Returns the port number of this URL, or {@code -1} if this URL has no + * Returns the port number of this URL or {@code -1} if this URL has no * explicit port. * * <p>If this URL has no explicit port, connections opened using this URL @@ -674,7 +624,7 @@ public final class URL implements Serializable { } /** - * Returns the file of this URL, or an empty string if the file is not set. + * Returns the file of this URL. */ public String getFile() { return file; @@ -688,15 +638,15 @@ public final class URL implements Serializable { } /** - * Returns the query part of this URL. + * Returns the query part of this URL, or null if this URL has no query. */ public String getQuery() { return query; } /** - * Returns the value of the reference part of this URL. This is also known - * as the fragment. + * Returns the value of the reference part of this URL, or null if this URL + * has no reference part. This is also known as the fragment. */ public String getRef() { return ref; @@ -709,15 +659,11 @@ public final class URL implements Serializable { */ protected void set(String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref) { - String filePart = path; + String file = path; if (query != null && !query.isEmpty()) { - if (filePart != null) { - filePart = filePart + "?" + query; - } else { - filePart = "?" + query; - } + file += "?" + query; } - set(protocol, host, port, filePart, ref); + set(protocol, host, port, file, ref); this.authority = authority; this.userInfo = userInfo; this.path = path; diff --git a/luni/src/main/java/java/net/URLStreamHandler.java b/luni/src/main/java/java/net/URLStreamHandler.java index dc1c680..528c4eb 100644 --- a/luni/src/main/java/java/net/URLStreamHandler.java +++ b/luni/src/main/java/java/net/URLStreamHandler.java @@ -18,8 +18,8 @@ package java.net; import java.io.IOException; +import libcore.net.url.UrlUtils; import libcore.util.Objects; -import org.apache.harmony.luni.util.URLUtil; /** * The abstract class {@code URLStreamHandler} is the base for all classes which @@ -71,9 +71,9 @@ public abstract class URLStreamHandler { * The string is parsed in HTTP format. If the protocol has a different URL * format this method must be overridden. * - * @param u + * @param url * the URL to fill in the parsed clear text URL parts. - * @param str + * @param spec * the URL string that is to be parsed. * @param start * the string position from where to begin parsing. @@ -82,151 +82,142 @@ public abstract class URLStreamHandler { * @see #toExternalForm * @see URL */ - protected void parseURL(URL u, String str, int start, int end) { - // For compatibility, refer to Harmony-2941 - if (str.startsWith("//", start) - && str.indexOf('/', start + 2) == -1 - && end <= Integer.MIN_VALUE + 1) { - throw new StringIndexOutOfBoundsException(end - 2 - start); + protected void parseURL(URL url, String spec, int start, int end) { + if (this != url.streamHandler) { + throw new SecurityException("Only a URL's stream handler is permitted to mutate it"); } if (end < start) { - if (this != u.streamHandler) { - throw new SecurityException(); - } - return; - } - String parseString = ""; - if (start < end) { - parseString = str.substring(start, end); + throw new StringIndexOutOfBoundsException(spec, start, end - start); } - end -= start; - int fileIdx = 0; - // Default is to use info from context - String host = u.getHost(); - int port = u.getPort(); - String ref = u.getRef(); - String file = u.getPath(); - String query = u.getQuery(); - String authority = u.getAuthority(); - String userInfo = u.getUserInfo(); - - int refIdx = parseString.indexOf('#', 0); - if (parseString.startsWith("//")) { - int hostIdx = 2; - port = -1; - fileIdx = parseString.indexOf('/', hostIdx); - int questionMarkIndex = parseString.indexOf('?', hostIdx); - if (questionMarkIndex != -1 && (fileIdx == -1 || fileIdx > questionMarkIndex)) { - fileIdx = questionMarkIndex; - } - if (fileIdx == -1) { - fileIdx = end; - // Use default - file = ""; - } - int hostEnd = fileIdx; - if (refIdx != -1 && refIdx < fileIdx) { - hostEnd = refIdx; - fileIdx = refIdx; - file = ""; - } - int userIdx = parseString.lastIndexOf('@', hostEnd); - authority = parseString.substring(hostIdx, hostEnd); - if (userIdx != -1) { - userInfo = parseString.substring(hostIdx, userIdx); - hostIdx = userIdx + 1; - } - - int endOfIPv6Addr = parseString.indexOf(']', hostIdx); - if (endOfIPv6Addr >= hostEnd) { - endOfIPv6Addr = -1; - } - - // the port separator must be immediately after an IPv6 address "http://[::1]:80/" - int portIdx = -1; - if (endOfIPv6Addr != -1) { - int maybeColon = endOfIPv6Addr + 1; - if (maybeColon < hostEnd && parseString.charAt(maybeColon) == ':') { - portIdx = maybeColon; - } + int fileStart; + String authority; + String userInfo; + String host; + int port = -1; + String path; + String query; + String ref; + if (spec.regionMatches(start, "//", 0, 2)) { + // Parse the authority from the spec. + int authorityStart = start + 2; + fileStart = findFirstOf(spec, "/?#", authorityStart, end); + authority = spec.substring(authorityStart, fileStart); + int userInfoEnd = findFirstOf(spec, "@", authorityStart, fileStart); + int hostStart; + if (userInfoEnd != fileStart) { + userInfo = spec.substring(authorityStart, userInfoEnd); + hostStart = userInfoEnd + 1; } else { - portIdx = parseString.indexOf(':', hostIdx); + userInfo = null; + hostStart = authorityStart; } - if (portIdx == -1 || portIdx > hostEnd) { - host = parseString.substring(hostIdx, hostEnd); - } else { - host = parseString.substring(hostIdx, portIdx); - String portString = parseString.substring(portIdx + 1, hostEnd); - if (portString.length() == 0) { - port = -1; - } else { - port = Integer.parseInt(portString); - } - } - } - - if (refIdx > -1) { - ref = parseString.substring(refIdx + 1, end); - } - int fileEnd = (refIdx == -1 ? end : refIdx); - - int queryIdx = parseString.lastIndexOf('?', fileEnd); - boolean canonicalize = false; - if (queryIdx > -1) { - query = parseString.substring(queryIdx + 1, fileEnd); - if (queryIdx == 0 && file != null) { - if (file.isEmpty()) { - file = "/"; - } else if (file.startsWith("/")) { - canonicalize = true; + /* + * Extract the host and port. The host may be an IPv6 address with + * colons like "[::1]", in which case we look for the port delimiter + * colon after the ']' character. + */ + int ipv6End = findFirstOf(spec, "]", hostStart, fileStart); + int colonSearchFrom = (ipv6End != fileStart) ? ipv6End : hostStart; + int hostEnd = findFirstOf(spec, ":", colonSearchFrom, fileStart); + host = spec.substring(hostStart, hostEnd); + int portStart = hostEnd + 1; + if (portStart < fileStart) { + port = Integer.parseInt(spec.substring(portStart, fileStart)); + if (port < 0) { + throw new IllegalArgumentException("port < 0: " + port); } - int last = file.lastIndexOf('/') + 1; - file = file.substring(0, last); } - fileEnd = queryIdx; - } else - // Don't inherit query unless only the ref is changed - if (refIdx != 0) { + path = null; query = null; + ref = null; + } else { + // Get the authority from the context URL. + fileStart = start; + authority = url.getAuthority(); + userInfo = url.getUserInfo(); + host = url.getHost(); + if (host == null) { + host = ""; + } + port = url.getPort(); + path = url.getPath(); + query = url.getQuery(); + ref = url.getRef(); } - if (fileIdx > -1) { - if (fileIdx < end && parseString.charAt(fileIdx) == '/') { - file = parseString.substring(fileIdx, fileEnd); - } else if (fileEnd > fileIdx) { - if (file == null) { - file = ""; - } else if (file.isEmpty()) { - file = "/"; - } else if (file.startsWith("/")) { - canonicalize = true; - } - int last = file.lastIndexOf('/') + 1; - if (last == 0) { - file = parseString.substring(fileIdx, fileEnd); - } else { - file = file.substring(0, last) - + parseString.substring(fileIdx, fileEnd); - } + /* + * Extract the path, query and fragment. Each part has its own leading + * delimiter character. The query can contain slashes and the fragment + * can contain slashes and question marks. + * / path ? query # fragment + */ + int pos = fileStart; + while (pos < end) { + int nextPos; + switch (spec.charAt(pos)) { + case '#': + nextPos = end; + ref = spec.substring(pos + 1, nextPos); + break; + case '?': + nextPos = findFirstOf(spec, "#", pos, end); + query = spec.substring(pos + 1, nextPos); + ref = null; + break; + default: + nextPos = findFirstOf(spec, "?#", pos, end); + path = relativePath(path, spec.substring(pos, nextPos)); + query = null; + ref = null; + break; } + pos = nextPos; } - if (file == null) { - file = ""; + + if (path == null) { + path = ""; } - if (host == null) { - host = ""; + /* + * Force the path to start with a '/' if this URL has an authority. + * Otherwise they run together like http://android.comindex.html. + */ + if (authority != null && !authority.isEmpty() && !path.startsWith("/") && !path.isEmpty()) { + path = "/" + path; } - if (canonicalize) { - // modify file if there's any relative referencing - file = URLUtil.canonicalizePath(file); + setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref); + } + + /** + * Returns the index of the first char of {@code chars} in {@code string} + * bounded between {@code start} and {@code end}. This returns {@code end} + * if none of the characters exist in the requested range. + */ + private static int findFirstOf(String string, String chars, int start, int end) { + for (int i = start; i < end; i++) { + char c = string.charAt(i); + if (chars.indexOf(c) != -1) { + return i; + } } + return end; + } - setURL(u, u.getProtocol(), host, port, authority, userInfo, file, - query, ref); + /** + * Returns a new path by resolving {@code path} relative to {@code base}. + */ + private static String relativePath(String base, String path) { + if (path.startsWith("/")) { + return UrlUtils.canonicalizePath(path); + } else if (base != null) { + String combined = base.substring(0, base.lastIndexOf('/') + 1) + path; + return UrlUtils.canonicalizePath(combined); + } else { + return path; + } } /** @@ -260,33 +251,14 @@ public abstract class URLStreamHandler { /** * Sets the fields of the URL {@code u} to the values of the supplied * arguments. - * - * @param u - * the non-null URL object to be set. - * @param protocol - * the protocol. - * @param host - * the host name. - * @param port - * the port number. - * @param authority - * the authority. - * @param userInfo - * the user info. - * @param file - * the file component. - * @param query - * the query. - * @param ref - * the reference. */ protected void setURL(URL u, String protocol, String host, int port, - String authority, String userInfo, String file, String query, + String authority, String userInfo, String path, String query, String ref) { if (this != u.streamHandler) { throw new SecurityException(); } - u.set(protocol, host, port, authority, userInfo, file, query, ref); + u.set(protocol, host, port, authority, userInfo, path, query, ref); } /** @@ -308,7 +280,7 @@ public abstract class URLStreamHandler { result.append(':'); String authority = url.getAuthority(); - if (authority != null && !authority.isEmpty()) { + if (authority != null) { result.append("//"); if (escapeIllegalCharacters) { URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority); @@ -384,9 +356,9 @@ public abstract class URLStreamHandler { */ protected boolean hostsEqual(URL a, URL b) { // URLs with the same case-insensitive host name have equal hosts - String aHost = getHost(a); - String bHost = getHost(b); - return aHost != null && aHost.equalsIgnoreCase(bHost); + String aHost = a.getHost(); + String bHost = b.getHost(); + return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost); } /** @@ -399,12 +371,4 @@ public abstract class URLStreamHandler { && a.getEffectivePort() == b.getEffectivePort() && Objects.equal(a.getFile(), b.getFile()); } - - private static String getHost(URL url) { - String host = url.getHost(); - if ("file".equals(url.getProtocol()) && host != null && host.isEmpty()) { - host = "localhost"; - } - return host; - } } diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java index d95e21c..9dad90f 100644 --- a/luni/src/main/java/libcore/net/http/HttpEngine.java +++ b/luni/src/main/java/libcore/net/http/HttpEngine.java @@ -705,8 +705,10 @@ public class HttpEngine { return url.toString(); } else { String fileOnly = url.getFile(); - if (fileOnly == null || fileOnly.isEmpty()) { + if (fileOnly == null) { fileOnly = "/"; + } else if (!fileOnly.startsWith("/")) { + fileOnly = "/" + fileOnly; } return fileOnly; } diff --git a/luni/src/main/java/libcore/net/url/FileHandler.java b/luni/src/main/java/libcore/net/url/FileHandler.java index a1d73c3..d921490 100644 --- a/luni/src/main/java/libcore/net/url/FileHandler.java +++ b/luni/src/main/java/libcore/net/url/FileHandler.java @@ -81,9 +81,9 @@ public class FileHandler extends URLStreamHandler { * already have the context properties. The string generally have the * following format: <code><center>/c:/windows/win.ini</center></code>. * - * @param u + * @param url * The URL object that's parsed into - * @param str + * @param spec * The string equivalent of the specification URL * @param start * The index in the spec string from which to begin parsing @@ -94,14 +94,14 @@ public class FileHandler extends URLStreamHandler { * @see java.net.URL */ @Override - protected void parseURL(URL u, String str, int start, int end) { + protected void parseURL(URL url, String spec, int start, int end) { if (end < start) { return; } String parseString = ""; if (start < end) { - parseString = str.substring(start, end).replace('\\', '/'); + parseString = spec.substring(start, end).replace('\\', '/'); } - super.parseURL(u, parseString, 0, parseString.length()); + super.parseURL(url, parseString, 0, parseString.length()); } } diff --git a/luni/src/main/java/libcore/net/url/JarHandler.java b/luni/src/main/java/libcore/net/url/JarHandler.java index cee517e..97267f2 100644 --- a/luni/src/main/java/libcore/net/url/JarHandler.java +++ b/luni/src/main/java/libcore/net/url/JarHandler.java @@ -22,7 +22,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import org.apache.harmony.luni.util.URLUtil; public class JarHandler extends URLStreamHandler { /** @@ -76,7 +75,7 @@ public class JarHandler extends URLStreamHandler { int idx = file.indexOf('!'); String tmpFile = file.substring(idx + 1, file.lastIndexOf('/') + 1) + spec; - tmpFile = URLUtil.canonicalizePath(tmpFile); + tmpFile = UrlUtils.canonicalizePath(tmpFile); file = file.substring(0, idx + 1) + tmpFile; } try { diff --git a/luni/src/main/java/libcore/net/url/UrlUtils.java b/luni/src/main/java/libcore/net/url/UrlUtils.java new file mode 100644 index 0000000..28825f5 --- /dev/null +++ b/luni/src/main/java/libcore/net/url/UrlUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 libcore.net.url; + +import java.util.Locale; + +public final class UrlUtils { + private UrlUtils() { + } + + /** + * Returns the path will relative path segments like ".." and "." resolved. + * The returned path will not necessarily start with a "/" character. This + * handles ".." and "." segments at both the beginning and end of the path. + */ + public static String canonicalizePath(String path) { + // the first character of the current path segment + int segmentStart = 0; + + for (int i = 0; i <= path.length(); ) { + int nextSegmentStart; + if (i == path.length()) { + nextSegmentStart = i; + } else if (path.charAt(i) == '/') { + nextSegmentStart = i + 1; + } else { + i++; + continue; + } + + /* + * We've encountered either the end of a segment or the end of the + * complete path. If the final segment was "." or "..", remove the + * appropriate segments of the path. + */ + if (i == segmentStart + 1 && path.regionMatches(segmentStart, ".", 0, 1)) { + // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". + path = path.substring(0, segmentStart) + path.substring(nextSegmentStart); + i = segmentStart; + } else if (i == segmentStart + 2 && path.regionMatches(segmentStart, "..", 0, 2)) { + // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". + int prevSegmentStart = path.lastIndexOf('/', segmentStart - 2) + 1; + path = path.substring(0, prevSegmentStart) + path.substring(nextSegmentStart); + i = segmentStart = prevSegmentStart; + } else { + i++; + segmentStart = i; + } + } + return path; + } + + /** + * Returns the scheme prefix like "http" from the URL spec, or null if the + * spec doesn't start with a scheme. Scheme prefixes match this pattern: + * {@code alpha ( alpha | digit | '+' | '-' | '.' )* ':'} + */ + public static String getSchemePrefix(String spec) { + int colon = spec.indexOf(':'); + + if (colon < 1) { + return null; + } + + for (int i = 0; i < colon; i++) { + char c = spec.charAt(i); + if (!isValidSchemeChar(i, c)) { + return null; + } + } + + return spec.substring(0, colon).toLowerCase(Locale.US); + } + + private static boolean isValidSchemeChar(int index, char c) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + return true; + } + if (index > 0 && ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')) { + return true; + } + return false; + } +} diff --git a/luni/src/main/java/org/apache/harmony/luni/util/URLUtil.java b/luni/src/main/java/org/apache/harmony/luni/util/URLUtil.java deleted file mode 100644 index 341637d..0000000 --- a/luni/src/main/java/org/apache/harmony/luni/util/URLUtil.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.harmony.luni.util; - -public final class URLUtil { - - /** - * Canonicalize the path, i.e. remove ".." and "." occurences. - * - * @param path the path to be canonicalized - * @return the canonicalized path - */ - public static String canonicalizePath(String path) { - int dirIndex; - - while ((dirIndex = path.indexOf("/./")) >= 0) { - path = path.substring(0, dirIndex + 1) - + path.substring(dirIndex + 3); - } - - if (path.endsWith("/.")) { - path = path.substring(0, path.length() - 1); - } - - while ((dirIndex = path.indexOf("/../")) >= 0) { - if (dirIndex != 0) { - path = path.substring(0, path - .lastIndexOf('/', dirIndex - 1)) - + path.substring(dirIndex + 3); - } else { - path = path.substring(dirIndex + 3); - } - } - - if (path.endsWith("/..") && path.length() > 3) { - path = path.substring(0, path.lastIndexOf('/', - path.length() - 4) + 1); - } - return path; - } -} diff --git a/luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java b/luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java index a47d833..6738420 100644 --- a/luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java +++ b/luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java @@ -201,8 +201,8 @@ public class OldURLStreamHandlerTest extends TestCase { return super.openConnection(u, p); } - @Override public void parseURL(URL u, String spec, int start, int limit) { - super.parseURL(u, spec, start, limit); + @Override public void parseURL(URL url, String spec, int start, int limit) { + super.parseURL(url, spec, start, limit); } @Override public boolean sameFile(URL a, URL b) { diff --git a/luni/src/test/java/libcore/java/net/OldURLTest.java b/luni/src/test/java/libcore/java/net/OldURLTest.java index 58caf2e..fa4f4cb 100644 --- a/luni/src/test/java/libcore/java/net/OldURLTest.java +++ b/luni/src/test/java/libcore/java/net/OldURLTest.java @@ -70,7 +70,7 @@ public class OldURLTest extends TestCase { assertEquals("Assert 0: wrong protocol", "http", testURL.getProtocol()); assertEquals("Assert 1: wrong host", "[www.apache.org:8082]", testURL.getHost()); assertEquals("Assert 2: wrong port", -1, testURL.getPort()); - assertEquals("Assert 3: wrong file", "test.html", testURL.getFile()); + assertEquals("Assert 3: wrong file", "/test.html", testURL.getFile()); assertEquals("Assert 4: wrong anchor", "anch", testURL.getRef()); try { @@ -225,7 +225,7 @@ public class OldURLTest extends TestCase { assertFalse("Assert 1: error in equals: not same", testURL1.equals(wrongPort)); assertFalse("Assert 2: error in equals: not same", testURL1.equals(wrongHost)); assertFalse("Assert 3: error in equals: not same", testURL1.equals(wrongRef)); - assertFalse("Assert 4: error in equals: not same", testURL1.equals(testURL2)); + assertEquals(testURL1, testURL2); URL testURL3 = new URL("http", "www.apache.org", "/test.html"); URL testURL4 = new URL("http://www.apache.org/test.html"); @@ -658,7 +658,7 @@ public class OldURLTest extends TestCase { assertEquals("SSISH1 returns a wrong host", "www.yahoo.com", u .getHost()); assertEquals("SSISH1 returns a wrong port", 8080, u.getPort()); - assertEquals("SSISH1 returns a wrong file", "test.html", u.getFile()); + assertEquals("SSISH1 returns a wrong file", "/test.html", u.getFile()); assertTrue("SSISH1 returns a wrong anchor: " + u.getRef(), u.getRef() .equals("foo")); @@ -668,7 +668,7 @@ public class OldURLTest extends TestCase { assertEquals("SSISH2 returns a wrong host", "www.yahoo.com", u .getHost()); assertEquals("SSISH2 returns a wrong port", 8080, u.getPort()); - assertEquals("SSISH2 returns a wrong file", "test.html", u.getFile()); + assertEquals("SSISH2 returns a wrong file", "/test.html", u.getFile()); assertTrue("SSISH2 returns a wrong anchor: " + u.getRef(), u.getRef() .equals("foo")); @@ -748,8 +748,7 @@ public class OldURLTest extends TestCase { assertEquals("3 returns a wrong protocol", "http", u1.getProtocol()); assertEquals("3 returns a wrong host", "www.yahoo.com", u1.getHost()); assertEquals("3 returns a wrong port", -1, u1.getPort()); - assertEquals("3 returns a wrong file", "/dir1/dir2/../file.java", u1 - .getFile()); + assertEquals("3 returns a wrong file", "/dir1/file.java", u1.getFile()); assertNull("3 returns a wrong anchor", u1.getRef()); // test for question mark processing @@ -762,7 +761,7 @@ public class OldURLTest extends TestCase { // test for absolute and relative file processing u1 = new URL(u, "/../dir1/dir2/../file.java", null); - assertEquals("B) returns a wrong file", "/../dir1/dir2/../file.java", + assertEquals("B) returns a wrong file", "/dir1/file.java", u1.getFile()); URL one; @@ -785,7 +784,7 @@ public class OldURLTest extends TestCase { String strURL = "http://a/b/c/d;p?q"; String ref = "?y"; URL url = new URL(new URL(strURL), ref); - assertEquals("http://a/b/c/?y", url.toExternalForm()); + assertEquals("http://a/b/c/d;p?y", url.toExternalForm()); } public void test_toExternalForm_Absolute() throws MalformedURLException { diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index 6393e02..f0ec4d0 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -1856,6 +1856,16 @@ public final class URLConnectionTest extends TestCase { connection.disconnect(); } + // http://b/4361656 + public void testUrlContainsQueryButNoPath() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); + assertEquals("A", readAscii(url.openConnection().getInputStream(), Integer.MAX_VALUE)); + RecordedRequest request = server.takeRequest(); + assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); + } + /** * Returns a gzipped copy of {@code bytes}. */ diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java index f00cb25..871db3b 100644 --- a/luni/src/test/java/libcore/java/net/URLTest.java +++ b/luni/src/test/java/libcore/java/net/URLTest.java @@ -23,22 +23,20 @@ import java.net.URL; import junit.framework.TestCase; import libcore.java.util.SerializableTester; -public class URLTest extends TestCase { +public final class URLTest extends TestCase { public void testUrlParts() throws Exception { URL url = new URL("http://username:password@host:8080/directory/file?query#ref"); assertEquals("http", url.getProtocol()); assertEquals("username:password@host:8080", url.getAuthority()); - assertEquals("/directory/file", url.getPath()); - assertEquals("ref", url.getRef()); - assertEquals("username:password", url.getUserInfo()); assertEquals("host", url.getHost()); assertEquals(8080, url.getPort()); + assertEquals(80, url.getDefaultPort()); assertEquals("/directory/file?query", url.getFile()); + assertEquals("/directory/file", url.getPath()); assertEquals("query", url.getQuery()); - - assertEquals(80, url.getDefaultPort()); + assertEquals("ref", url.getRef()); } // http://code.google.com/p/android/issues/detail?id=12724 public void testExplicitPort() throws Exception { @@ -47,20 +45,6 @@ public class URLTest extends TestCase { assertEquals(80, url.getPort()); } - public void testHostWithSlashInFragment() throws Exception { - URL url = new URL("http://www.google.com#foo/bar"); - assertEquals("www.google.com", url.getHost()); - assertEquals("foo/bar", url.getRef()); - assertEquals(-1, url.getPort()); - } - - public void testHostWithColonAndSlashInFragment() throws Exception { - URL url = new URL("http://www.google.com#foo:bar/baz"); - assertEquals("www.google.com", url.getHost()); - assertEquals("foo:bar/baz", url.getRef()); - assertEquals(-1, url.getPort()); - } - /** * Android's URL.equals() works as if the network is down. This is different * from the RI, which does potentially slow and inconsistent DNS lookups in @@ -75,7 +59,7 @@ public class URLTest extends TestCase { URL urlByHostName = new URL("http://localhost/foo?bar=baz#quux"); URL urlByAddress = new URL("http://" + address + "/foo?bar=baz#quux"); assertFalse("Expected " + urlByHostName + " to not equal " + urlByAddress, - urlByHostName.equals(urlByAddress)); + urlByHostName.equals(urlByAddress)); // fails on RI } } @@ -92,9 +76,24 @@ public class URLTest extends TestCase { new URL("http://localhost/foo?bar=baz#QUUX"))); } - public void testEqualsWithNullHost() throws Exception { - assertFalse(new URL("file", null, -1, "/a/").equals(new URL("file:/a/"))); - assertFalse(new URL("http", null, 80, "/a/").equals(new URL("http:/a/"))); + public void testFileEqualsWithEmptyHost() throws Exception { + assertEquals(new URL("file", "", -1, "/a/"), new URL("file:/a/")); + } + + public void testHttpEqualsWithEmptyHost() throws Exception { + assertEquals(new URL("http", "", 80, "/a/"), new URL("http:/a/")); + assertFalse(new URL("http", "", 80, "/a/").equals(new URL("http://host/a/"))); + } + + public void testFileEquals() throws Exception { + assertEquals(new URL("file", null, -1, "/a"), new URL("file", null, -1, "/a")); + assertFalse(new URL("file", null, -1, "/a").equals(new URL("file", null, -1, "/A"))); + } + + public void testJarEquals() throws Exception { + assertEquals(new URL("jar", null, -1, "/a!b"), new URL("jar", null, -1, "/a!b")); + assertFalse(new URL("jar", null, -1, "/a!b").equals(new URL("jar", null, -1, "/a!B"))); + assertFalse(new URL("jar", null, -1, "/a!b").equals(new URL("jar", null, -1, "/A!b"))); } public void testUrlSerialization() throws Exception { @@ -135,5 +134,437 @@ public class URLTest extends TestCase { } } - // TODO: test resolve relative URL + public void testOmittedHost() throws Exception { + URL url = new URL("http:///path"); + assertEquals("", url.getHost()); + assertEquals("/path", url.getFile()); + assertEquals("/path", url.getPath()); + } + + public void testNoHost() throws Exception { + URL url = new URL("http:/path"); + assertEquals("http", url.getProtocol()); + assertEquals(null, url.getAuthority()); + assertEquals(null, url.getUserInfo()); + assertEquals("", url.getHost()); + assertEquals(-1, url.getPort()); + assertEquals(80, url.getDefaultPort()); + assertEquals("/path", url.getFile()); + assertEquals("/path", url.getPath()); + assertEquals(null, url.getQuery()); + assertEquals(null, url.getRef()); + } + + public void testNoPath() throws Exception { + URL url = new URL("http://host"); + assertEquals("host", url.getHost()); + assertEquals("", url.getFile()); + assertEquals("", url.getPath()); + } + + public void testEmptyHostAndNoPath() throws Exception { + URL url = new URL("http://"); + assertEquals("http", url.getProtocol()); + assertEquals("", url.getAuthority()); + assertEquals(null, url.getUserInfo()); + assertEquals("", url.getHost()); + assertEquals(-1, url.getPort()); + assertEquals(80, url.getDefaultPort()); + assertEquals("", url.getFile()); + assertEquals("", url.getPath()); + assertEquals(null, url.getQuery()); + assertEquals(null, url.getRef()); + } + + public void testNoHostAndNoPath() throws Exception { + URL url = new URL("http:"); + assertEquals("http", url.getProtocol()); + assertEquals(null, url.getAuthority()); + assertEquals(null, url.getUserInfo()); + assertEquals("", url.getHost()); + assertEquals(-1, url.getPort()); + assertEquals(80, url.getDefaultPort()); + assertEquals("", url.getFile()); + assertEquals("", url.getPath()); + assertEquals(null, url.getQuery()); + assertEquals(null, url.getRef()); + } + + public void testAtSignInUserInfo() throws Exception { + try { + new URL("http://user@userhost.com:password@host"); + fail(); + } catch (MalformedURLException expected) { + } + } + + public void testUserNoPassword() throws Exception { + URL url = new URL("http://user@host"); + assertEquals("user@host", url.getAuthority()); + assertEquals("user", url.getUserInfo()); + assertEquals("host", url.getHost()); + } + + public void testUserNoPasswordExplicitPort() throws Exception { + URL url = new URL("http://user@host:8080"); + assertEquals("user@host:8080", url.getAuthority()); + assertEquals("user", url.getUserInfo()); + assertEquals("host", url.getHost()); + assertEquals(8080, url.getPort()); + } + + public void testUserPasswordHostPort() throws Exception { + URL url = new URL("http://user:password@host:8080"); + assertEquals("user:password@host:8080", url.getAuthority()); + assertEquals("user:password", url.getUserInfo()); + assertEquals("host", url.getHost()); + assertEquals(8080, url.getPort()); + } + + public void testUserPasswordEmptyHostPort() throws Exception { + URL url = new URL("http://user:password@:8080"); + assertEquals("user:password@:8080", url.getAuthority()); + assertEquals("user:password", url.getUserInfo()); + assertEquals("", url.getHost()); + assertEquals(8080, url.getPort()); + } + + public void testUserPasswordEmptyHostEmptyPort() throws Exception { + URL url = new URL("http://user:password@"); + assertEquals("user:password@", url.getAuthority()); + assertEquals("user:password", url.getUserInfo()); + assertEquals("", url.getHost()); + assertEquals(-1, url.getPort()); + } + + public void testPathOnly() throws Exception { + URL url = new URL("http://host/path"); + assertEquals("/path", url.getFile()); + assertEquals("/path", url.getPath()); + } + + public void testQueryOnly() throws Exception { + URL url = new URL("http://host?query"); + assertEquals("?query", url.getFile()); + assertEquals("", url.getPath()); + assertEquals("query", url.getQuery()); + } + + public void testFragmentOnly() throws Exception { + URL url = new URL("http://host#fragment"); + assertEquals("", url.getFile()); + assertEquals("", url.getPath()); + assertEquals("fragment", url.getRef()); + } + + public void testAtSignInPath() throws Exception { + URL url = new URL("http://host/file@foo"); + assertEquals("/file@foo", url.getFile()); + assertEquals("/file@foo", url.getPath()); + assertEquals(null, url.getUserInfo()); + } + + public void testColonInPath() throws Exception { + URL url = new URL("http://host/file:colon"); + assertEquals("/file:colon", url.getFile()); + assertEquals("/file:colon", url.getPath()); + } + + public void testSlashInQuery() throws Exception { + URL url = new URL("http://host/file?query/path"); + assertEquals("/file?query/path", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("query/path", url.getQuery()); + } + + public void testQuestionMarkInQuery() throws Exception { + URL url = new URL("http://host/file?query?another"); + assertEquals("/file?query?another", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("query?another", url.getQuery()); + } + + public void testAtSignInQuery() throws Exception { + URL url = new URL("http://host/file?query@at"); + assertEquals("/file?query@at", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("query@at", url.getQuery()); + } + + public void testColonInQuery() throws Exception { + URL url = new URL("http://host/file?query:colon"); + assertEquals("/file?query:colon", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("query:colon", url.getQuery()); + } + + public void testQuestionMarkInFragment() throws Exception { + URL url = new URL("http://host/file#fragment?query"); + assertEquals("/file", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals(null, url.getQuery()); + assertEquals("fragment?query", url.getRef()); + } + + public void testColonInFragment() throws Exception { + URL url = new URL("http://host/file#fragment:80"); + assertEquals("/file", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals(-1, url.getPort()); + assertEquals("fragment:80", url.getRef()); + } + + public void testSlashInFragment() throws Exception { + URL url = new URL("http://host/file#fragment/path"); + assertEquals("/file", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("fragment/path", url.getRef()); + } + + public void testHashInFragment() throws Exception { + URL url = new URL("http://host/file#fragment#another"); + assertEquals("/file", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("fragment#another", url.getRef()); + } + + public void testEmptyPort() throws Exception { + URL url = new URL("http://host:/"); + assertEquals(-1, url.getPort()); + } + + public void testNonNumericPort() throws Exception { + try { + new URL("http://host:x/"); + fail(); + } catch (MalformedURLException expected) { + } + } + + public void testNegativePort() throws Exception { + try { + new URL("http://host:-2/"); + fail(); + } catch (MalformedURLException expected) { + } + } + + public void testNegativePortEqualsPlaceholder() throws Exception { + try { + new URL("http://host:-1/"); + fail(); // RI fails this + } catch (MalformedURLException expected) { + } + } + + public void testRelativePathOnQuery() throws Exception { + URL base = new URL("http://host/file?query/x"); + URL url = new URL(base, "another"); + assertEquals("http://host/another", url.toString()); + assertEquals("/another", url.getFile()); + assertEquals("/another", url.getPath()); + assertEquals(null, url.getQuery()); + assertEquals(null, url.getRef()); + } + + public void testRelativeFragmentOnQuery() throws Exception { + URL base = new URL("http://host/file?query/x#fragment"); + URL url = new URL(base, "#another"); + assertEquals("http://host/file?query/x#another", url.toString()); + assertEquals("/file?query/x", url.getFile()); + assertEquals("/file", url.getPath()); + assertEquals("query/x", url.getQuery()); + assertEquals("another", url.getRef()); + } + + public void testPathContainsRelativeParts() throws Exception { + URL url = new URL("http://host/a/b/../c"); + assertEquals("http://host/a/c", url.toString()); // RI doesn't canonicalize + } + + public void testRelativePathAndFragment() throws Exception { + URL base = new URL("http://host/file"); + assertEquals("http://host/another#fragment", new URL(base, "another#fragment").toString()); + } + + public void testRelativeParentDirectory() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://host/a/d", new URL(base, "../d").toString()); + } + + public void testRelativeChildDirectory() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://host/a/b/d/e", new URL(base, "d/e").toString()); + } + + public void testRelativeRootDirectory() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://host/d", new URL(base, "/d").toString()); + } + + public void testRelativeFullUrl() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://host2/d/e", new URL(base, "http://host2/d/e").toString()); + assertEquals("https://host2/d/e", new URL(base, "https://host2/d/e").toString()); + } + + public void testRelativeDifferentScheme() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("https://host2/d/e", new URL(base, "https://host2/d/e").toString()); + } + + public void testRelativeDifferentAuthority() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://another/d/e", new URL(base, "//another/d/e").toString()); + } + + public void testRelativeWithScheme() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://host/a/b/c", new URL(base, "http:").toString()); + assertEquals("http://host/", new URL(base, "http:/").toString()); + } + + public void testMalformedUrlsRefusedByFirefoxAndChrome() throws Exception { + URL base = new URL("http://host/a/b/c"); + assertEquals("http://", new URL(base, "http://").toString()); // fails on RI + assertEquals("http://", new URL(base, "//").toString()); // fails on RI + assertEquals("https:", new URL(base, "https:").toString()); + assertEquals("https:/", new URL(base, "https:/").toString()); + assertEquals("https://", new URL(base, "https://").toString()); + } + + public void testRfc1808NormalExamples() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + assertEquals("https:h", new URL(base, "https:h").toString()); + assertEquals("http://a/b/c/g", new URL(base, "g").toString()); + assertEquals("http://a/b/c/g", new URL(base, "./g").toString()); + assertEquals("http://a/b/c/g/", new URL(base, "g/").toString()); + assertEquals("http://a/g", new URL(base, "/g").toString()); + assertEquals("http://g", new URL(base, "//g").toString()); + assertEquals("http://a/b/c/d;p?y", new URL(base, "?y").toString()); // fails on RI + assertEquals("http://a/b/c/g?y", new URL(base, "g?y").toString()); + assertEquals("http://a/b/c/d;p?q#s", new URL(base, "#s").toString()); + assertEquals("http://a/b/c/g#s", new URL(base, "g#s").toString()); + assertEquals("http://a/b/c/g?y#s", new URL(base, "g?y#s").toString()); + assertEquals("http://a/b/c/;x", new URL(base, ";x").toString()); + assertEquals("http://a/b/c/g;x", new URL(base, "g;x").toString()); + assertEquals("http://a/b/c/g;x?y#s", new URL(base, "g;x?y#s").toString()); + assertEquals("http://a/b/c/d;p?q", new URL(base, "").toString()); + assertEquals("http://a/b/c/", new URL(base, ".").toString()); + assertEquals("http://a/b/c/", new URL(base, "./").toString()); + assertEquals("http://a/b/", new URL(base, "..").toString()); + assertEquals("http://a/b/", new URL(base, "../").toString()); + assertEquals("http://a/b/g", new URL(base, "../g").toString()); + assertEquals("http://a/", new URL(base, "../..").toString()); + assertEquals("http://a/", new URL(base, "../../").toString()); + assertEquals("http://a/g", new URL(base, "../../g").toString()); + } + + public void testRfc1808AbnormalExampleTooManyDotDotSequences() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + assertEquals("http://a/g", new URL(base, "../../../g").toString()); // fails on RI + assertEquals("http://a/g", new URL(base, "../../../../g").toString()); // fails on RI + } + + public void testRfc1808AbnormalExampleRemoveDotSegments() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + assertEquals("http://a/g", new URL(base, "/./g").toString()); // fails on RI + assertEquals("http://a/g", new URL(base, "/../g").toString()); // fails on RI + assertEquals("http://a/b/c/g.", new URL(base, "g.").toString()); + assertEquals("http://a/b/c/.g", new URL(base, ".g").toString()); + assertEquals("http://a/b/c/g..", new URL(base, "g..").toString()); + assertEquals("http://a/b/c/..g", new URL(base, "..g").toString()); + } + + public void testRfc1808AbnormalExampleNonsensicalDots() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + assertEquals("http://a/b/g", new URL(base, "./../g").toString()); + assertEquals("http://a/b/c/g/", new URL(base, "./g/.").toString()); + assertEquals("http://a/b/c/g/h", new URL(base, "g/./h").toString()); + assertEquals("http://a/b/c/h", new URL(base, "g/../h").toString()); + assertEquals("http://a/b/c/g;x=1/y", new URL(base, "g;x=1/./y").toString()); + assertEquals("http://a/b/c/y", new URL(base, "g;x=1/../y").toString()); + } + + public void testRfc1808AbnormalExampleRelativeScheme() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + // this result is permitted; strict parsers prefer "http:g" + assertEquals("http://a/b/c/g", new URL(base, "http:g").toString()); + } + + public void testRfc1808AbnormalExampleQueryOrFragmentDots() throws Exception { + URL base = new URL("http://a/b/c/d;p?q"); + assertEquals("http://a/b/c/g?y/./x", new URL(base, "g?y/./x").toString()); + assertEquals("http://a/b/c/g?y/../x", new URL(base, "g?y/../x").toString()); + assertEquals("http://a/b/c/g#s/./x", new URL(base, "g#s/./x").toString()); + assertEquals("http://a/b/c/g#s/../x", new URL(base, "g#s/../x").toString()); + } + + public void testSquareBracketsInUserInfo() throws Exception { + URL url = new URL("http://user:[::1]@host"); + assertEquals("user:[::1]", url.getUserInfo()); + assertEquals("host", url.getHost()); + } + + public void testComposeUrl() throws Exception { + URL url = new URL("http", "host", "a"); + assertEquals("http", url.getProtocol()); + assertEquals("host", url.getAuthority()); + assertEquals("host", url.getHost()); + assertEquals("/a", url.getFile()); // fails on RI + assertEquals("http://host/a", url.toString()); // fails on RI + } + + public void testComposeUrlWithNullHost() throws Exception { + URL url = new URL("http", null, "a"); + assertEquals("http", url.getProtocol()); + assertEquals(null, url.getAuthority()); + assertEquals(null, url.getHost()); + assertEquals("a", url.getFile()); + assertEquals("http:a", url.toString()); // fails on RI + } + + public void testFileUrlExtraLeadingSlashes() throws Exception { + URL url = new URL("file:////foo"); + assertEquals("", url.getAuthority()); + assertEquals("//foo", url.getPath()); + assertEquals("file:////foo", url.toString()); + } + + public void testFileUrlWithAuthority() throws Exception { + URL url = new URL("file://x/foo"); + assertEquals("x", url.getAuthority()); + assertEquals("/foo", url.getPath()); + assertEquals("file://x/foo", url.toString()); + } + + /** + * The RI is not self-consistent on missing authorities, returning either + * null or the empty string depending on the number of slashes in the path. + * We always treat '//' as the beginning of an authority. + */ + public void testEmptyAuthority() throws Exception { + URL url = new URL("http:///foo"); + assertEquals("", url.getAuthority()); + assertEquals("/foo", url.getPath()); + assertEquals("http:///foo", url.toString()); + } + + public void testHttpUrlExtraLeadingSlashes() throws Exception { + URL url = new URL("http:////foo"); + assertEquals("", url.getAuthority()); + assertEquals("//foo", url.getPath()); + assertEquals("http:////foo", url.toString()); + } + + public void testFileUrlRelativePath() throws Exception { + URL base = new URL("file:a/b/c"); + assertEquals("file:a/b/d", new URL(base, "d").toString()); + } + + public void testFileUrlDottedPath() throws Exception { + URL url = new URL("file:../a/b"); + assertEquals("../a/b", url.getPath()); + assertEquals("file:../a/b", url.toString()); + } } diff --git a/luni/src/test/java/libcore/net/url/UrlUtilsTest.java b/luni/src/test/java/libcore/net/url/UrlUtilsTest.java new file mode 100644 index 0000000..936c20e --- /dev/null +++ b/luni/src/test/java/libcore/net/url/UrlUtilsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 libcore.net.url; + +import junit.framework.TestCase; + +public final class UrlUtilsTest extends TestCase { + public void testCanonicalizePath() { + assertEquals("", UrlUtils.canonicalizePath("")); + assertEquals("", UrlUtils.canonicalizePath(".")); + assertEquals("", UrlUtils.canonicalizePath("..")); + assertEquals("...", UrlUtils.canonicalizePath("...")); + assertEquals("", UrlUtils.canonicalizePath("./")); + assertEquals("", UrlUtils.canonicalizePath("../")); + assertEquals("a", UrlUtils.canonicalizePath("../a")); + assertEquals("a", UrlUtils.canonicalizePath("a")); + assertEquals("a/", UrlUtils.canonicalizePath("a/")); + assertEquals("a/", UrlUtils.canonicalizePath("a/.")); + assertEquals("a/b", UrlUtils.canonicalizePath("a/./b")); + assertEquals("", UrlUtils.canonicalizePath("a/..")); + assertEquals("b", UrlUtils.canonicalizePath("a/../b")); + assertEquals("a/.../b", UrlUtils.canonicalizePath("a/.../b")); + assertEquals("a/b", UrlUtils.canonicalizePath("a/b")); + assertEquals("a/b/", UrlUtils.canonicalizePath("a/b/.")); + assertEquals("a/b/", UrlUtils.canonicalizePath("a/b/./")); + assertEquals("a/b/c", UrlUtils.canonicalizePath("a/b/./c")); + assertEquals("a/", UrlUtils.canonicalizePath("a/b/..")); + assertEquals("a/", UrlUtils.canonicalizePath("a/b/../")); + assertEquals("a//", UrlUtils.canonicalizePath("a/b/..//")); + assertEquals("a/c", UrlUtils.canonicalizePath("a/b/../c")); + assertEquals("a//c", UrlUtils.canonicalizePath("a/b/..//c")); + assertEquals("c", UrlUtils.canonicalizePath("a/b/../../c")); + assertEquals("/", UrlUtils.canonicalizePath("/")); + assertEquals("//", UrlUtils.canonicalizePath("//")); + assertEquals("/", UrlUtils.canonicalizePath("/.")); + assertEquals("/", UrlUtils.canonicalizePath("/./")); + assertEquals("", UrlUtils.canonicalizePath("/..")); + assertEquals("c", UrlUtils.canonicalizePath("/../c")); + assertEquals("/a/b/c", UrlUtils.canonicalizePath("/a/b/c")); + } + + public void testGetProtocolPrefix() { + assertEquals("http", UrlUtils.getSchemePrefix("http:")); + assertEquals("http", UrlUtils.getSchemePrefix("HTTP:")); + assertEquals("http", UrlUtils.getSchemePrefix("http:x")); + assertEquals("a", UrlUtils.getSchemePrefix("a:")); + assertEquals("z", UrlUtils.getSchemePrefix("z:")); + assertEquals("a", UrlUtils.getSchemePrefix("A:")); + assertEquals("z", UrlUtils.getSchemePrefix("Z:")); + assertEquals("h0", UrlUtils.getSchemePrefix("h0:")); + assertEquals("h5", UrlUtils.getSchemePrefix("h5:")); + assertEquals("h9", UrlUtils.getSchemePrefix("h9:")); + assertEquals("h+", UrlUtils.getSchemePrefix("h+:")); + assertEquals("h-", UrlUtils.getSchemePrefix("h-:")); + assertEquals("h.", UrlUtils.getSchemePrefix("h.:")); + } + + public void testGetProtocolPrefixInvalidScheme() { + assertNull(UrlUtils.getSchemePrefix("")); + assertNull(UrlUtils.getSchemePrefix("http")); + assertNull(UrlUtils.getSchemePrefix(":")); + assertNull(UrlUtils.getSchemePrefix("+:")); + assertNull(UrlUtils.getSchemePrefix("-:")); + assertNull(UrlUtils.getSchemePrefix(".:")); + assertNull(UrlUtils.getSchemePrefix("0:")); + assertNull(UrlUtils.getSchemePrefix("5:")); + assertNull(UrlUtils.getSchemePrefix("9:")); + assertNull(UrlUtils.getSchemePrefix("http//")); + assertNull(UrlUtils.getSchemePrefix("http/:")); + assertNull(UrlUtils.getSchemePrefix("ht tp://")); + assertNull(UrlUtils.getSchemePrefix(" http://")); + assertNull(UrlUtils.getSchemePrefix("http ://")); + assertNull(UrlUtils.getSchemePrefix(":://")); + } +} |