URLStreamHandler.java revision 5292410e4ebf7fb5149eefd2f52fcb94c46690a6
1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/*
2adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Licensed to the Apache Software Foundation (ASF) under one or more
3adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  contributor license agreements.  See the NOTICE file distributed with
4adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  this work for additional information regarding copyright ownership.
5adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  The ASF licenses this file to You under the Apache License, Version 2.0
6adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  (the "License"); you may not use this file except in compliance with
7adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  the License.  You may obtain a copy of the License at
8adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
9adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *     http://www.apache.org/licenses/LICENSE-2.0
10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Unless required by applicable law or agreed to in writing, software
12adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  distributed under the License is distributed on an "AS IS" BASIS,
13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  See the License for the specific language governing permissions and
15adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  limitations under the License.
16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
17adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
18adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpackage java.net;
19adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
20adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.IOException;
215292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilsonimport libcore.net.url.UrlUtils;
226186821cb13f4ac7ff50950c813394367e021eaeJesse Wilsonimport libcore.util.Objects;
23adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
24adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/**
25adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * The abstract class {@code URLStreamHandler} is the base for all classes which
26adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * can handle the communication with a URL object over a particular protocol
27adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * type.
28adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
29adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpublic abstract class URLStreamHandler {
30adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
31adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Establishes a new connection to the resource specified by the URL {@code
32adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * u}. Since different protocols also have unique ways of connecting, it
33adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * must be overwritten by the subclass.
34f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
35adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param u
36adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the URL to the resource where a connection has to be opened.
37adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return the opened URLConnection to the specified resource.
38adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws IOException
39adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if an I/O error occurs during opening the connection.
40adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
41adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected abstract URLConnection openConnection(URL u) throws IOException;
42adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
43adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
44adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Establishes a new connection to the resource specified by the URL {@code
45adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * u} using the given {@code proxy}. Since different protocols also have
46adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * unique ways of connecting, it must be overwritten by the subclass.
47f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
48adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param u
49adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the URL to the resource where a connection has to be opened.
50adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param proxy
51adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the proxy that is used to make the connection.
52adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return the opened URLConnection to the specified resource.
53adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws IOException
54adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if an I/O error occurs during opening the connection.
55adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws IllegalArgumentException
56adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if any argument is {@code null} or the type of proxy is
57adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             wrong.
58adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @throws UnsupportedOperationException
59adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             if the protocol handler doesn't support this method.
60adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
61b1396870f92135aa140bd2b86221768dea5bc11dElliott Hughes    protected URLConnection openConnection(URL u, Proxy proxy) throws IOException {
62b1396870f92135aa140bd2b86221768dea5bc11dElliott Hughes        throw new UnsupportedOperationException();
63adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
64adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
65adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
66adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Parses the clear text URL in {@code str} into a URL object. URL strings
67adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * generally have the following format:
68adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * <p>
69adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * http://www.company.com/java/file1.java#reference
70f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson     * <p>
71adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * The string is parsed in HTTP format. If the protocol has a different URL
72adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * format this method must be overridden.
73f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
745292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * @param url
75adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the URL to fill in the parsed clear text URL parts.
765292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * @param spec
77adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the URL string that is to be parsed.
78adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param start
79adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the string position from where to begin parsing.
80adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param end
81adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the string position to stop parsing.
82adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @see #toExternalForm
83adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @see URL
84adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
855292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    protected void parseURL(URL url, String spec, int start, int end) {
865292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (this != url.streamHandler) {
875292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            throw new SecurityException("Only a URL's stream handler is permitted to mutate it");
88adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
89adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (end < start) {
905292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            throw new StringIndexOutOfBoundsException(spec, start, end - start);
91adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
92adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
935292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        int fileStart;
945292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String authority;
955292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String userInfo;
965292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String host;
975292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        int port = -1;
985292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String path;
995292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String query;
1005292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String ref;
1015292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (spec.regionMatches(start, "//", 0, 2)) {
1025292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            // Parse the authority from the spec.
1035292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int authorityStart = start + 2;
1045292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            fileStart = findFirstOf(spec, "/?#", authorityStart, end);
1055292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            authority = spec.substring(authorityStart, fileStart);
1065292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int userInfoEnd = findFirstOf(spec, "@", authorityStart, fileStart);
1075292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int hostStart;
1085292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            if (userInfoEnd != fileStart) {
1095292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                userInfo = spec.substring(authorityStart, userInfoEnd);
1105292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                hostStart = userInfoEnd + 1;
1118fd0225a5c3918fe0cd4680258388985c25533e5Jesse Wilson            } else {
1125292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                userInfo = null;
1135292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                hostStart = authorityStart;
114adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
115adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1165292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            /*
1175292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson             * Extract the host and port. The host may be an IPv6 address with
1185292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson             * colons like "[::1]", in which case we look for the port delimiter
1195292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson             * colon after the ']' character.
1205292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson             */
1215292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int ipv6End = findFirstOf(spec, "]", hostStart, fileStart);
1225292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int colonSearchFrom = (ipv6End != fileStart) ? ipv6End : hostStart;
1235292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int hostEnd = findFirstOf(spec, ":", colonSearchFrom, fileStart);
1245292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            host = spec.substring(hostStart, hostEnd);
1255292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int portStart = hostEnd + 1;
1265292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            if (portStart < fileStart) {
1275292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                port = Integer.parseInt(spec.substring(portStart, fileStart));
1285292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                if (port < 0) {
1295292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                    throw new IllegalArgumentException("port < 0: " + port);
130adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                }
131adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
1325292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            path = null;
133adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            query = null;
1345292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            ref = null;
1355292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        } else {
1365292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            // Get the authority from the context URL.
1375292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            fileStart = start;
1385292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            authority = url.getAuthority();
1395292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            userInfo = url.getUserInfo();
1405292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            host = url.getHost();
1415292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            if (host == null) {
1425292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                host = "";
1435292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            }
1445292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            port = url.getPort();
1455292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            path = url.getPath();
1465292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            query = url.getQuery();
1475292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            ref = url.getRef();
148adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
149adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1505292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        /*
1515292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         * Extract the path, query and fragment. Each part has its own leading
1525292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         * delimiter character. The query can contain slashes and the fragment
1535292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         * can contain slashes and question marks.
1545292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         *    / path ? query # fragment
1555292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         */
1565292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        int pos = fileStart;
1575292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        while (pos < end) {
1585292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            int nextPos;
1595292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            switch (spec.charAt(pos)) {
1605292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            case '#':
1615292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                nextPos = end;
1625292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                ref = spec.substring(pos + 1, nextPos);
1635292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                break;
1645292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            case '?':
1655292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                nextPos = findFirstOf(spec, "#", pos, end);
1665292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                query = spec.substring(pos + 1, nextPos);
1675292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                ref = null;
1685292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                break;
1695292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            default:
1705292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                nextPos = findFirstOf(spec, "?#", pos, end);
1715292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                path = relativePath(path, spec.substring(pos, nextPos));
1725292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                query = null;
1735292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                ref = null;
1745292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                break;
175adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
1765292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            pos = nextPos;
177adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1785292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson
1795292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (path == null) {
1805292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            path = "";
181adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
182adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1835292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        /*
1845292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         * Force the path to start with a '/' if this URL has an authority.
1855292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         * Otherwise they run together like http://android.comindex.html.
1865292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson         */
1875292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (authority != null && !authority.isEmpty() && !path.startsWith("/") && !path.isEmpty()) {
1885292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            path = "/" + path;
189adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
190adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1915292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref);
1925292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    }
1935292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson
1945292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    /**
1955292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * Returns the index of the first char of {@code chars} in {@code string}
1965292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * bounded between {@code start} and {@code end}. This returns {@code end}
1975292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * if none of the characters exist in the requested range.
1985292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     */
1995292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    private static int findFirstOf(String string, String chars, int start, int end) {
2005292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        for (int i = start; i < end; i++) {
2015292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            char c = string.charAt(i);
2025292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            if (chars.indexOf(c) != -1) {
2035292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson                return i;
2045292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            }
205adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
2065292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        return end;
2075292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    }
208adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
2095292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    /**
2105292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     * Returns a new path by resolving {@code path} relative to {@code base}.
2115292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson     */
2125292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson    private static String relativePath(String base, String path) {
2135292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (path.startsWith("/")) {
2145292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            return UrlUtils.canonicalizePath(path);
2155292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        } else if (base != null) {
2165292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
2175292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            return UrlUtils.canonicalizePath(combined);
2185292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        } else {
2195292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            return path;
2205292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        }
221adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
222adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
223adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
224adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Sets the fields of the URL {@code u} to the values of the supplied
225adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * arguments.
226f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
227adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param u
228adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the non-null URL object to be set.
229adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param protocol
230adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the protocol.
231adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param host
232adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the host name.
233adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param port
234adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the port number.
235adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param file
236adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the file component.
237adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param ref
238adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the reference.
239adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @deprecated use setURL(URL, String String, int, String, String, String,
240adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *             String, String) instead.
241adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
242adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    @Deprecated
243adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected void setURL(URL u, String protocol, String host, int port,
244adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            String file, String ref) {
2458f99aa098c6a06b8be788abbca1c1d1060342709Jesse Wilson        if (this != u.streamHandler) {
246adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            throw new SecurityException();
247adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
248adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        u.set(protocol, host, port, file, ref);
249adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
250adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
251adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
252adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Sets the fields of the URL {@code u} to the values of the supplied
253adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * arguments.
254adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
255adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected void setURL(URL u, String protocol, String host, int port,
2565292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson            String authority, String userInfo, String path, String query,
257adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            String ref) {
2588f99aa098c6a06b8be788abbca1c1d1060342709Jesse Wilson        if (this != u.streamHandler) {
259adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            throw new SecurityException();
260adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
2615292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        u.set(protocol, host, port, authority, userInfo, path, query, ref);
262adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
263adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
264adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
265adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns the clear text representation of a given URL using HTTP format.
266f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
267adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @param url
268adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     *            the URL object to be converted.
269adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @return the clear text representation of the specified URL.
270adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @see #parseURL
271adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * @see URL#toExternalForm()
272adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
273adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected String toExternalForm(URL url) {
274d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        return toExternalForm(url, false);
275d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson    }
276d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson
277d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson    String toExternalForm(URL url, boolean escapeIllegalCharacters) {
278d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        StringBuilder result = new StringBuilder();
279d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        result.append(url.getProtocol());
280d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        result.append(':');
281d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson
282adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        String authority = url.getAuthority();
2835292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        if (authority != null) {
284d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            result.append("//");
285d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            if (escapeIllegalCharacters) {
28632559028b14b9b321b10eede050afd554a376569Jesse Wilson                URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
28732559028b14b9b321b10eede050afd554a376569Jesse Wilson            } else {
28832559028b14b9b321b10eede050afd554a376569Jesse Wilson                result.append(authority);
289d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            }
290adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
291adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
292d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        String fileAndQuery = url.getFile();
293d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        if (fileAndQuery != null) {
294d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            if (escapeIllegalCharacters) {
29532559028b14b9b321b10eede050afd554a376569Jesse Wilson                URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
29632559028b14b9b321b10eede050afd554a376569Jesse Wilson            } else {
29732559028b14b9b321b10eede050afd554a376569Jesse Wilson                result.append(fileAndQuery);
298d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            }
299a389b4a499f40379b0b204d7ba1c2057663d95c0Jesse Wilson        }
300d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson
301d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        String ref = url.getRef();
302adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (ref != null) {
303d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            result.append('#');
304d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            if (escapeIllegalCharacters) {
30532559028b14b9b321b10eede050afd554a376569Jesse Wilson                URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
30632559028b14b9b321b10eede050afd554a376569Jesse Wilson            } else {
30732559028b14b9b321b10eede050afd554a376569Jesse Wilson                result.append(ref);
308d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson            }
309adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
310d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson
311d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson        return result.toString();
312d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson    }
313d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson
314d1b5e5da828434388e486a388710d21e4306dae0Jesse Wilson    /**
3157e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * Returns true if {@code a} and {@code b} have the same protocol, host,
3167e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * port, file, and reference.
317adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
3187e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson    protected boolean equals(URL a, URL b) {
3197e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson        return sameFile(a, b)
3207e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson                && Objects.equal(a.getRef(), b.getRef())
3217e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson                && Objects.equal(a.getQuery(), b.getQuery());
322adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
323adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
324adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
325adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns the default port of the protocol used by the handled URL. The
3267e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * default implementation always returns {@code -1}.
327adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
328adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected int getDefaultPort() {
329adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return -1;
330adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
331adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
332adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
3337e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * Returns the host address of {@code url}.
334adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
335adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected InetAddress getHostAddress(URL url) {
336adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        try {
337adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            String host = url.getHost();
338adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (host == null || host.length() == 0) {
339adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                return null;
340adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
341adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return InetAddress.getByName(host);
342adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        } catch (UnknownHostException e) {
343adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return null;
344adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
345adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
346adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
347adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
3487e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * Returns the hash code of {@code url}.
349adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
350adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    protected int hashCode(URL url) {
351adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return toExternalForm(url).hashCode();
352adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
353adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
354adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
3557e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * Returns true if the hosts of {@code a} and {@code b} are equal.
356adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
35767db542e53c47a28e3f96beb7c9e46330483dc40Jesse Wilson    protected boolean hostsEqual(URL a, URL b) {
3587e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson        // URLs with the same case-insensitive host name have equal hosts
3595292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String aHost = a.getHost();
3605292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        String bHost = b.getHost();
3615292410e4ebf7fb5149eefd2f52fcb94c46690a6Jesse Wilson        return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
362adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
363adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
364adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
3657e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * Returns true if {@code a} and {@code b} have the same protocol, host,
3667e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson     * port and file.
367adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
3687e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson    protected boolean sameFile(URL a, URL b) {
3697e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson        return Objects.equal(a.getProtocol(), b.getProtocol())
3707e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson                && hostsEqual(a, b)
3717e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson                && a.getEffectivePort() == b.getEffectivePort()
3727e00db4156e50ce5f20fefb820dca339299134d3Jesse Wilson                && Objects.equal(a.getFile(), b.getFile());
373adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
374adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project}
375