blob: dae193bdb90c53cd007298ad218f38dfa23744e4 [file] [log] [blame]
/*
* 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.base.Objects;
import org.apache.harmony.luni.util.URLUtil;
/**
* 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 u
* the URL to fill in the parsed clear text URL parts.
* @param str
* 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 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);
}
if (end < start) {
if (this != u.strmHandler) {
throw new SecurityException();
}
return;
}
String parseString = "";
if (start < end) {
parseString = str.substring(start, end);
}
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, portIdx = -1;
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;
}
int userIdx = parseString.lastIndexOf('@', hostEnd);
authority = parseString.substring(hostIdx, hostEnd);
if (userIdx > -1) {
userInfo = parseString.substring(hostIdx, userIdx);
hostIdx = userIdx + 1;
}
portIdx = parseString.indexOf(':', userIdx == -1 ? hostIdx
: userIdx);
int endOfIPv6Addr = parseString.indexOf(']');
// if there are square braces, ie. IPv6 address, use last ':'
if (endOfIPv6Addr != -1) {
try {
if (parseString.length() > endOfIPv6Addr + 1) {
char c = parseString.charAt(endOfIPv6Addr + 1);
if (c == ':') {
portIdx = endOfIPv6Addr + 1;
} else {
portIdx = -1;
}
} else {
portIdx = -1;
}
} catch (Exception e) {
// Ignored
}
}
if (portIdx == -1 || portIdx > fileIdx) {
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;
}
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) {
query = null;
}
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);
}
}
}
if (file == null) {
file = "";
}
if (host == null) {
host = "";
}
if (canonicalize) {
// modify file if there's any relative referencing
file = URLUtil.canonicalizePath(file);
}
setURL(u, u.getProtocol(), host, port, authority, userInfo, file,
query, ref);
}
/**
* 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.strmHandler) {
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.
*
* @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 ref) {
if (this != u.strmHandler) {
throw new SecurityException();
}
u.set(protocol, host, port, authority, userInfo, file, 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) {
StringBuilder answer = new StringBuilder();
answer.append(url.getProtocol());
answer.append(':');
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
answer.append("//");
answer.append(url.getAuthority());
}
String file = url.getFile();
String ref = url.getRef();
if (file != null) {
answer.append(file);
}
if (ref != null) {
answer.append('#');
answer.append(ref);
}
return answer.toString();
}
/**
* Compares two URL objects whether they represent the same URL. Two URLs
* are equal if they have the same file, host, port, protocol, query, and
* reference components.
*
* @param url1
* the first URL to compare.
* @param url2
* the second URL to compare.
* @return {@code true} if the URLs are the same, {@code false} otherwise.
* @see #hashCode
*/
protected boolean equals(URL url1, URL url2) {
if (!sameFile(url1, url2)) {
return false;
}
return Objects.equal(url1.getRef(), url2.getRef())
&& Objects.equal(url1.getQuery(), url2.getQuery());
}
/**
* Returns the default port of the protocol used by the handled URL. The
* current implementation returns always {@code -1}.
*
* @return the appropriate default port number of the protocol.
*/
protected int getDefaultPort() {
return -1;
}
/**
* Returns the host address of the given URL.
*
* @param url
* the URL object where to read the host address from.
* @return the host address of the specified 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 hashcode value for the given URL object.
*
* @param url
* the URL to determine the hashcode.
* @return the hashcode of the given URL.
*/
protected int hashCode(URL url) {
return toExternalForm(url).hashCode();
}
/**
* Compares two URL objects whether they refer to the same host.
*
* @param a the first URL to be compared.
* @param b the second URL to be compared.
* @return {@code true} if both URLs refer to the same host, {@code false}
* otherwise.
*/
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);
if (aHost != null && aHost.equalsIgnoreCase(bHost)) {
return true;
}
/*
* Call out to DNS to resolve the host addresses. If this succeeds for
* both addresses and both addresses yield the same InetAddress, report
* equality.
*
* Although it's consistent with historical behavior of the RI, this
* approach is fundamentally broken. In particular, acting upon this
* result is bogus because a single server may serve content for many
* unrelated host names.
*/
InetAddress aResolved = getHostAddress(a);
return aResolved != null && aResolved.equals(getHostAddress(b));
}
/**
* Compares two URL objects whether they refer to the same file. In the
* comparison included are the URL components protocol, host, port and file.
*
* @param url1
* the first URL to be compared.
* @param url2
* the second URL to be compared.
* @return {@code true} if both URLs refer to the same file, {@code false}
* otherwise.
*/
protected boolean sameFile(URL url1, URL url2) {
return Objects.equal(url1.getProtocol(), url2.getProtocol())
&& Objects.equal(url1.getFile(), url2.getFile())
&& hostsEqual(url1, url2)
&& url1.getEffectivePort() == url2.getEffectivePort();
}
/*
* If the URL host is empty while protocal is file, the host is regarded as
* localhost.
*/
private static String getHost(URL url) {
String host = url.getHost();
if ("file".equals(url.getProtocol()) && host.isEmpty()) {
host = "localhost";
}
return host;
}
}