/*
* 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 java.net;
import java.io.IOException;
import libcore.net.url.UrlUtils;
import libcore.util.Objects;
/**
* The abstract class {@code URLStreamHandler} is the base for all classes which
* can handle the communication with a URL object over a particular protocol
* type.
*/
public abstract class URLStreamHandler {
/**
* Establishes a new connection to the resource specified by the URL {@code
* u}. Since different protocols also have unique ways of connecting, it
* must be overwritten by the subclass.
*
* @param u
* the URL to the resource where a connection has to be opened.
* @return the opened URLConnection to the specified resource.
* @throws IOException
* if an I/O error occurs during opening the connection.
*/
protected abstract URLConnection openConnection(URL u) throws IOException;
/**
* Establishes a new connection to the resource specified by the URL {@code
* u} using the given {@code proxy}. Since different protocols also have
* unique ways of connecting, it must be overwritten by the subclass.
*
* @param u
* the URL to the resource where a connection has to be opened.
* @param proxy
* the proxy that is used to make the connection.
* @return the opened URLConnection to the specified resource.
* @throws IOException
* if an I/O error occurs during opening the connection.
* @throws IllegalArgumentException
* if any argument is {@code null} or the type of proxy is
* wrong.
* @throws UnsupportedOperationException
* if the protocol handler doesn't support this method.
*/
protected URLConnection openConnection(URL u, Proxy proxy) throws IOException {
throw new UnsupportedOperationException();
}
/**
* Parses the clear text URL in {@code str} into a URL object. URL strings
* generally have the following format:
* <p>
* http://www.company.com/java/file1.java#reference
* <p>
* The string is parsed in HTTP format. If the protocol has a different URL
* format this method must be overridden.
*
* @param url
* the URL to fill in the parsed clear text URL parts.
* @param spec
* the URL string that is to be parsed.
* @param start
* the string position from where to begin parsing.
* @param end
* the string position to stop parsing.
* @see #toExternalForm
* @see URL
*/
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) {
throw new StringIndexOutOfBoundsException(spec, start, end - start);
}
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 = UrlUtils.findFirstOf(spec, "/?#", authorityStart, end);
authority = spec.substring(authorityStart, fileStart);
int userInfoEnd = UrlUtils.findFirstOf(spec, "@", authorityStart, fileStart);
int hostStart;
if (userInfoEnd != fileStart) {
userInfo = spec.substring(authorityStart, userInfoEnd);
hostStart = userInfoEnd + 1;
} else {
userInfo = null;
hostStart = authorityStart;
}
/*
* 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 colonSearchFrom = hostStart;
int ipv6End = UrlUtils.findFirstOf(spec, "]", hostStart, fileStart);
if (ipv6End != fileStart) {
if (UrlUtils.findFirstOf(spec, ":", hostStart, ipv6End) == ipv6End) {
throw new IllegalArgumentException("Expected an IPv6 address: "
+ spec.substring(hostStart, ipv6End + 1));
}
colonSearchFrom = ipv6End;
}
int hostEnd = UrlUtils.findFirstOf(spec, ":", colonSearchFrom, fileStart);
host = spec.substring(hostStart, hostEnd);
int portStart = hostEnd + 1;
if (portStart < fileStart) {
char firstPortChar = spec.charAt(portStart);
if (firstPortChar >= '0' && firstPortChar <= '9') {
port = Integer.parseInt(spec.substring(portStart, fileStart));
} else {
throw new IllegalArgumentException("invalid port: " + port);
}
}
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();
}
/*
* 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 = UrlUtils.findFirstOf(spec, "#", pos, end);
query = spec.substring(pos + 1, nextPos);
ref = null;
break;
default:
nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end);
path = relativePath(path, spec.substring(pos, nextPos));
query = null;
ref = null;
break;
}
pos = nextPos;
}
if (path == null) {
path = "";
}
path = UrlUtils.authoritySafePath(authority, path);
setURL(url, url.getProtocol(), host, port, authority, userInfo, path, 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, true);
} else if (base != null) {
String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
return UrlUtils.canonicalizePath(combined, true);
} else {
return path;
}
}
/**
* 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 file
* the file component.
* @param ref
* the reference.
* @deprecated Use setURL(URL, String String, int, String, String, String,
* String, String) instead.
*/
@Deprecated
protected void setURL(URL u, String protocol, String host, int port,
String file, String ref) {
if (this != u.streamHandler) {
throw new SecurityException();
}
u.set(protocol, host, port, file, ref);
}
/**
* Sets the fields of the URL {@code u} to the values of the supplied
* arguments.
*/
protected void setURL(URL u, String protocol, String host, int port,
String authority, String userInfo, String path, String query,
String ref) {
if (this != u.streamHandler) {
throw new SecurityException();
}
u.set(protocol, host, port, authority, userInfo, path, query, ref);
}
/**
* Returns the clear text representation of a given URL using HTTP format.
*
* @param url
* the URL object to be converted.
* @return the clear text representation of the specified URL.
* @see #parseURL
* @see URL#toExternalForm()
*/
protected String toExternalForm(URL url) {
return toExternalForm(url, false);
}
String toExternalForm(URL url, boolean escapeIllegalCharacters) {
StringBuilder result = new StringBuilder();
result.append(url.getProtocol());
result.append(':');
String authority = url.getAuthority();
if (authority != null) {
result.append("//");
if (escapeIllegalCharacters) {
URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
} else {
result.append(authority);
}
}
String fileAndQuery = url.getFile();
if (fileAndQuery != null) {
if (escapeIllegalCharacters) {
URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
} else {
result.append(fileAndQuery);
}
}
String ref = url.getRef();
if (ref != null) {
result.append('#');
if (escapeIllegalCharacters) {
URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
} else {
result.append(ref);
}
}
return result.toString();
}
/**
* Returns true if {@code a} and {@code b} have the same protocol, host,
* port, file, and reference.
*/
protected boolean equals(URL a, URL b) {
return sameFile(a, b)
&& Objects.equal(a.getRef(), b.getRef())
&& Objects.equal(a.getQuery(), b.getQuery());
}
/**
* Returns the default port of the protocol used by the handled URL. The
* default implementation always returns {@code -1}.
*/
protected int getDefaultPort() {
return -1;
}
/**
* Returns the host address of {@code url}.
*/
protected InetAddress getHostAddress(URL url) {
try {
String host = url.getHost();
if (host == null || host.length() == 0) {
return null;
}
return InetAddress.getByName(host);
} catch (UnknownHostException e) {
return null;
}
}
/**
* Returns the hash code of {@code url}.
*/
protected int hashCode(URL url) {
return toExternalForm(url).hashCode();
}
/**
* Returns true if the hosts of {@code a} and {@code b} are equal.
*/
protected boolean hostsEqual(URL a, URL b) {
// URLs with the same case-insensitive host name have equal hosts
String aHost = a.getHost();
String bHost = b.getHost();
return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
}
/**
* Returns true if {@code a} and {@code b} have the same protocol, host,
* port and file.
*/
protected boolean sameFile(URL a, URL b) {
return Objects.equal(a.getProtocol(), b.getProtocol())
&& hostsEqual(a, b)
&& a.getEffectivePort() == b.getEffectivePort()
&& Objects.equal(a.getFile(), b.getFile());
}
}