diff options
author | Jesse Wilson <jessewilson@google.com> | 2011-05-25 11:04:56 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-05-25 11:04:56 -0700 |
commit | 7a4511926c670efc527d352880c442ee3ce060e5 (patch) | |
tree | 8c90bb16b138baec430cf56af20f51cc76a2b140 /luni | |
parent | e19401333f608de6174e11a3bb266ee5394c7db7 (diff) | |
parent | 5292410e4ebf7fb5149eefd2f52fcb94c46690a6 (diff) | |
download | libcore-7a4511926c670efc527d352880c442ee3ce060e5.zip libcore-7a4511926c670efc527d352880c442ee3ce060e5.tar.gz libcore-7a4511926c670efc527d352880c442ee3ce060e5.tar.bz2 |
Merge "Rewrite parsing for java.net.URL." into dalvik-dev
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(":://")); + } +} |