summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2011-05-25 11:04:56 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-05-25 11:04:56 -0700
commit7a4511926c670efc527d352880c442ee3ce060e5 (patch)
tree8c90bb16b138baec430cf56af20f51cc76a2b140 /luni
parente19401333f608de6174e11a3bb266ee5394c7db7 (diff)
parent5292410e4ebf7fb5149eefd2f52fcb94c46690a6 (diff)
downloadlibcore-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.java146
-rw-r--r--luni/src/main/java/java/net/URLStreamHandler.java286
-rw-r--r--luni/src/main/java/libcore/net/http/HttpEngine.java4
-rw-r--r--luni/src/main/java/libcore/net/url/FileHandler.java10
-rw-r--r--luni/src/main/java/libcore/net/url/JarHandler.java3
-rw-r--r--luni/src/main/java/libcore/net/url/UrlUtils.java98
-rw-r--r--luni/src/main/java/org/apache/harmony/luni/util/URLUtil.java56
-rw-r--r--luni/src/test/java/libcore/java/net/OldURLStreamHandlerTest.java4
-rw-r--r--luni/src/test/java/libcore/java/net/OldURLTest.java15
-rw-r--r--luni/src/test/java/libcore/java/net/URLConnectionTest.java10
-rw-r--r--luni/src/test/java/libcore/java/net/URLTest.java481
-rw-r--r--luni/src/test/java/libcore/net/url/UrlUtilsTest.java89
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(":://"));
+ }
+}