| /* |
| * 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) { |
| port = Integer.parseInt(spec.substring(portStart, fileStart)); |
| if (port < 0) { |
| throw new IllegalArgumentException("port < 0: " + 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()); |
| } |
| } |