Home | History | Annotate | Download | only in net
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package java.net;
     19 
     20 import java.io.IOException;
     21 import libcore.net.url.UrlUtils;
     22 import libcore.util.Objects;
     23 
     24 /**
     25  * The abstract class {@code URLStreamHandler} is the base for all classes which
     26  * can handle the communication with a URL object over a particular protocol
     27  * type.
     28  */
     29 public abstract class URLStreamHandler {
     30     /**
     31      * Establishes a new connection to the resource specified by the URL {@code
     32      * u}. Since different protocols also have unique ways of connecting, it
     33      * must be overwritten by the subclass.
     34      *
     35      * @param u
     36      *            the URL to the resource where a connection has to be opened.
     37      * @return the opened URLConnection to the specified resource.
     38      * @throws IOException
     39      *             if an I/O error occurs during opening the connection.
     40      */
     41     protected abstract URLConnection openConnection(URL u) throws IOException;
     42 
     43     /**
     44      * Establishes a new connection to the resource specified by the URL {@code
     45      * u} using the given {@code proxy}. Since different protocols also have
     46      * unique ways of connecting, it must be overwritten by the subclass.
     47      *
     48      * @param u
     49      *            the URL to the resource where a connection has to be opened.
     50      * @param proxy
     51      *            the proxy that is used to make the connection.
     52      * @return the opened URLConnection to the specified resource.
     53      * @throws IOException
     54      *             if an I/O error occurs during opening the connection.
     55      * @throws IllegalArgumentException
     56      *             if any argument is {@code null} or the type of proxy is
     57      *             wrong.
     58      * @throws UnsupportedOperationException
     59      *             if the protocol handler doesn't support this method.
     60      */
     61     protected URLConnection openConnection(URL u, Proxy proxy) throws IOException {
     62         throw new UnsupportedOperationException();
     63     }
     64 
     65     /**
     66      * Parses the clear text URL in {@code str} into a URL object. URL strings
     67      * generally have the following format:
     68      * <p>
     69      * http://www.company.com/java/file1.java#reference
     70      * <p>
     71      * The string is parsed in HTTP format. If the protocol has a different URL
     72      * format this method must be overridden.
     73      *
     74      * @param url
     75      *            the URL to fill in the parsed clear text URL parts.
     76      * @param spec
     77      *            the URL string that is to be parsed.
     78      * @param start
     79      *            the string position from where to begin parsing.
     80      * @param end
     81      *            the string position to stop parsing.
     82      * @see #toExternalForm
     83      * @see URL
     84      */
     85     protected void parseURL(URL url, String spec, int start, int end) {
     86         if (this != url.streamHandler) {
     87             throw new SecurityException("Only a URL's stream handler is permitted to mutate it");
     88         }
     89         if (end < start) {
     90             throw new StringIndexOutOfBoundsException(spec, start, end - start);
     91         }
     92 
     93         int fileStart;
     94         String authority;
     95         String userInfo;
     96         String host;
     97         int port = -1;
     98         String path;
     99         String query;
    100         String ref;
    101         if (spec.regionMatches(start, "//", 0, 2)) {
    102             // Parse the authority from the spec.
    103             int authorityStart = start + 2;
    104             fileStart = UrlUtils.findFirstOf(spec, "/?#", authorityStart, end);
    105             authority = spec.substring(authorityStart, fileStart);
    106             int userInfoEnd = UrlUtils.findFirstOf(spec, "@", authorityStart, fileStart);
    107             int hostStart;
    108             if (userInfoEnd != fileStart) {
    109                 userInfo = spec.substring(authorityStart, userInfoEnd);
    110                 hostStart = userInfoEnd + 1;
    111             } else {
    112                 userInfo = null;
    113                 hostStart = authorityStart;
    114             }
    115 
    116             /*
    117              * Extract the host and port. The host may be an IPv6 address with
    118              * colons like "[::1]", in which case we look for the port delimiter
    119              * colon after the ']' character.
    120              */
    121             int colonSearchFrom = hostStart;
    122             int ipv6End = UrlUtils.findFirstOf(spec, "]", hostStart, fileStart);
    123             if (ipv6End != fileStart) {
    124                 if (UrlUtils.findFirstOf(spec, ":", hostStart, ipv6End) == ipv6End) {
    125                     throw new IllegalArgumentException("Expected an IPv6 address: "
    126                             + spec.substring(hostStart, ipv6End + 1));
    127                 }
    128                 colonSearchFrom = ipv6End;
    129             }
    130             int hostEnd = UrlUtils.findFirstOf(spec, ":", colonSearchFrom, fileStart);
    131             host = spec.substring(hostStart, hostEnd);
    132             int portStart = hostEnd + 1;
    133             if (portStart < fileStart) {
    134                 port = Integer.parseInt(spec.substring(portStart, fileStart));
    135                 if (port < 0) {
    136                     throw new IllegalArgumentException("port < 0: " + port);
    137                 }
    138             }
    139             path = null;
    140             query = null;
    141             ref = null;
    142         } else {
    143             // Get the authority from the context URL.
    144             fileStart = start;
    145             authority = url.getAuthority();
    146             userInfo = url.getUserInfo();
    147             host = url.getHost();
    148             if (host == null) {
    149                 host = "";
    150             }
    151             port = url.getPort();
    152             path = url.getPath();
    153             query = url.getQuery();
    154             ref = url.getRef();
    155         }
    156 
    157         /*
    158          * Extract the path, query and fragment. Each part has its own leading
    159          * delimiter character. The query can contain slashes and the fragment
    160          * can contain slashes and question marks.
    161          *    / path ? query # fragment
    162          */
    163         int pos = fileStart;
    164         while (pos < end) {
    165             int nextPos;
    166             switch (spec.charAt(pos)) {
    167             case '#':
    168                 nextPos = end;
    169                 ref = spec.substring(pos + 1, nextPos);
    170                 break;
    171             case '?':
    172                 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end);
    173                 query = spec.substring(pos + 1, nextPos);
    174                 ref = null;
    175                 break;
    176             default:
    177                 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end);
    178                 path = relativePath(path, spec.substring(pos, nextPos));
    179                 query = null;
    180                 ref = null;
    181                 break;
    182             }
    183             pos = nextPos;
    184         }
    185 
    186         if (path == null) {
    187             path = "";
    188         }
    189 
    190         path = UrlUtils.authoritySafePath(authority, path);
    191 
    192         setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref);
    193     }
    194 
    195     /**
    196      * Returns a new path by resolving {@code path} relative to {@code base}.
    197      */
    198     private static String relativePath(String base, String path) {
    199         if (path.startsWith("/")) {
    200             return UrlUtils.canonicalizePath(path, true);
    201         } else if (base != null) {
    202             String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
    203             return UrlUtils.canonicalizePath(combined, true);
    204         } else {
    205             return path;
    206         }
    207     }
    208 
    209     /**
    210      * Sets the fields of the URL {@code u} to the values of the supplied
    211      * arguments.
    212      *
    213      * @param u
    214      *            the non-null URL object to be set.
    215      * @param protocol
    216      *            the protocol.
    217      * @param host
    218      *            the host name.
    219      * @param port
    220      *            the port number.
    221      * @param file
    222      *            the file component.
    223      * @param ref
    224      *            the reference.
    225      * @deprecated Use setURL(URL, String String, int, String, String, String,
    226      *             String, String) instead.
    227      */
    228     @Deprecated
    229     protected void setURL(URL u, String protocol, String host, int port,
    230             String file, String ref) {
    231         if (this != u.streamHandler) {
    232             throw new SecurityException();
    233         }
    234         u.set(protocol, host, port, file, ref);
    235     }
    236 
    237     /**
    238      * Sets the fields of the URL {@code u} to the values of the supplied
    239      * arguments.
    240      */
    241     protected void setURL(URL u, String protocol, String host, int port,
    242             String authority, String userInfo, String path, String query,
    243             String ref) {
    244         if (this != u.streamHandler) {
    245             throw new SecurityException();
    246         }
    247         u.set(protocol, host, port, authority, userInfo, path, query, ref);
    248     }
    249 
    250     /**
    251      * Returns the clear text representation of a given URL using HTTP format.
    252      *
    253      * @param url
    254      *            the URL object to be converted.
    255      * @return the clear text representation of the specified URL.
    256      * @see #parseURL
    257      * @see URL#toExternalForm()
    258      */
    259     protected String toExternalForm(URL url) {
    260         return toExternalForm(url, false);
    261     }
    262 
    263     String toExternalForm(URL url, boolean escapeIllegalCharacters) {
    264         StringBuilder result = new StringBuilder();
    265         result.append(url.getProtocol());
    266         result.append(':');
    267 
    268         String authority = url.getAuthority();
    269         if (authority != null) {
    270             result.append("//");
    271             if (escapeIllegalCharacters) {
    272                 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
    273             } else {
    274                 result.append(authority);
    275             }
    276         }
    277 
    278         String fileAndQuery = url.getFile();
    279         if (fileAndQuery != null) {
    280             if (escapeIllegalCharacters) {
    281                 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
    282             } else {
    283                 result.append(fileAndQuery);
    284             }
    285         }
    286 
    287         String ref = url.getRef();
    288         if (ref != null) {
    289             result.append('#');
    290             if (escapeIllegalCharacters) {
    291                 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
    292             } else {
    293                 result.append(ref);
    294             }
    295         }
    296 
    297         return result.toString();
    298     }
    299 
    300     /**
    301      * Returns true if {@code a} and {@code b} have the same protocol, host,
    302      * port, file, and reference.
    303      */
    304     protected boolean equals(URL a, URL b) {
    305         return sameFile(a, b)
    306                 && Objects.equal(a.getRef(), b.getRef())
    307                 && Objects.equal(a.getQuery(), b.getQuery());
    308     }
    309 
    310     /**
    311      * Returns the default port of the protocol used by the handled URL. The
    312      * default implementation always returns {@code -1}.
    313      */
    314     protected int getDefaultPort() {
    315         return -1;
    316     }
    317 
    318     /**
    319      * Returns the host address of {@code url}.
    320      */
    321     protected InetAddress getHostAddress(URL url) {
    322         try {
    323             String host = url.getHost();
    324             if (host == null || host.length() == 0) {
    325                 return null;
    326             }
    327             return InetAddress.getByName(host);
    328         } catch (UnknownHostException e) {
    329             return null;
    330         }
    331     }
    332 
    333     /**
    334      * Returns the hash code of {@code url}.
    335      */
    336     protected int hashCode(URL url) {
    337         return toExternalForm(url).hashCode();
    338     }
    339 
    340     /**
    341      * Returns true if the hosts of {@code a} and {@code b} are equal.
    342      */
    343     protected boolean hostsEqual(URL a, URL b) {
    344         // URLs with the same case-insensitive host name have equal hosts
    345         String aHost = a.getHost();
    346         String bHost = b.getHost();
    347         return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
    348     }
    349 
    350     /**
    351      * Returns true if {@code a} and {@code b} have the same protocol, host,
    352      * port and file.
    353      */
    354     protected boolean sameFile(URL a, URL b) {
    355         return Objects.equal(a.getProtocol(), b.getProtocol())
    356                 && hostsEqual(a, b)
    357                 && a.getEffectivePort() == b.getEffectivePort()
    358                 && Objects.equal(a.getFile(), b.getFile());
    359     }
    360 }
    361