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                 char firstPortChar = spec.charAt(portStart);
    135                 if (firstPortChar >= '0' && firstPortChar <= '9') {
    136                     port = Integer.parseInt(spec.substring(portStart, fileStart));
    137                 } else {
    138                     throw new IllegalArgumentException("invalid port: " + port);
    139                 }
    140             }
    141             path = null;
    142             query = null;
    143             ref = null;
    144         } else {
    145             // Get the authority from the context URL.
    146             fileStart = start;
    147             authority = url.getAuthority();
    148             userInfo = url.getUserInfo();
    149             host = url.getHost();
    150             if (host == null) {
    151                 host = "";
    152             }
    153             port = url.getPort();
    154             path = url.getPath();
    155             query = url.getQuery();
    156             ref = url.getRef();
    157         }
    158 
    159         /*
    160          * Extract the path, query and fragment. Each part has its own leading
    161          * delimiter character. The query can contain slashes and the fragment
    162          * can contain slashes and question marks.
    163          *    / path ? query # fragment
    164          */
    165         int pos = fileStart;
    166         while (pos < end) {
    167             int nextPos;
    168             switch (spec.charAt(pos)) {
    169             case '#':
    170                 nextPos = end;
    171                 ref = spec.substring(pos + 1, nextPos);
    172                 break;
    173             case '?':
    174                 nextPos = UrlUtils.findFirstOf(spec, "#", pos, end);
    175                 query = spec.substring(pos + 1, nextPos);
    176                 ref = null;
    177                 break;
    178             default:
    179                 nextPos = UrlUtils.findFirstOf(spec, "?#", pos, end);
    180                 path = relativePath(path, spec.substring(pos, nextPos));
    181                 query = null;
    182                 ref = null;
    183                 break;
    184             }
    185             pos = nextPos;
    186         }
    187 
    188         if (path == null) {
    189             path = "";
    190         }
    191 
    192         path = UrlUtils.authoritySafePath(authority, path);
    193 
    194         setURL(url, url.getProtocol(), host, port, authority, userInfo, path, query, ref);
    195     }
    196 
    197     /**
    198      * Returns a new path by resolving {@code path} relative to {@code base}.
    199      */
    200     private static String relativePath(String base, String path) {
    201         if (path.startsWith("/")) {
    202             return UrlUtils.canonicalizePath(path, true);
    203         } else if (base != null) {
    204             String combined = base.substring(0, base.lastIndexOf('/') + 1) + path;
    205             return UrlUtils.canonicalizePath(combined, true);
    206         } else {
    207             return path;
    208         }
    209     }
    210 
    211     /**
    212      * Sets the fields of the URL {@code u} to the values of the supplied
    213      * arguments.
    214      *
    215      * @param u
    216      *            the non-null URL object to be set.
    217      * @param protocol
    218      *            the protocol.
    219      * @param host
    220      *            the host name.
    221      * @param port
    222      *            the port number.
    223      * @param file
    224      *            the file component.
    225      * @param ref
    226      *            the reference.
    227      * @deprecated Use setURL(URL, String String, int, String, String, String,
    228      *             String, String) instead.
    229      */
    230     @Deprecated
    231     protected void setURL(URL u, String protocol, String host, int port,
    232             String file, String ref) {
    233         if (this != u.streamHandler) {
    234             throw new SecurityException();
    235         }
    236         u.set(protocol, host, port, file, ref);
    237     }
    238 
    239     /**
    240      * Sets the fields of the URL {@code u} to the values of the supplied
    241      * arguments.
    242      */
    243     protected void setURL(URL u, String protocol, String host, int port,
    244             String authority, String userInfo, String path, String query,
    245             String ref) {
    246         if (this != u.streamHandler) {
    247             throw new SecurityException();
    248         }
    249         u.set(protocol, host, port, authority, userInfo, path, query, ref);
    250     }
    251 
    252     /**
    253      * Returns the clear text representation of a given URL using HTTP format.
    254      *
    255      * @param url
    256      *            the URL object to be converted.
    257      * @return the clear text representation of the specified URL.
    258      * @see #parseURL
    259      * @see URL#toExternalForm()
    260      */
    261     protected String toExternalForm(URL url) {
    262         return toExternalForm(url, false);
    263     }
    264 
    265     String toExternalForm(URL url, boolean escapeIllegalCharacters) {
    266         StringBuilder result = new StringBuilder();
    267         result.append(url.getProtocol());
    268         result.append(':');
    269 
    270         String authority = url.getAuthority();
    271         if (authority != null) {
    272             result.append("//");
    273             if (escapeIllegalCharacters) {
    274                 URI.AUTHORITY_ENCODER.appendPartiallyEncoded(result, authority);
    275             } else {
    276                 result.append(authority);
    277             }
    278         }
    279 
    280         String fileAndQuery = url.getFile();
    281         if (fileAndQuery != null) {
    282             if (escapeIllegalCharacters) {
    283                 URI.FILE_AND_QUERY_ENCODER.appendPartiallyEncoded(result, fileAndQuery);
    284             } else {
    285                 result.append(fileAndQuery);
    286             }
    287         }
    288 
    289         String ref = url.getRef();
    290         if (ref != null) {
    291             result.append('#');
    292             if (escapeIllegalCharacters) {
    293                 URI.ALL_LEGAL_ENCODER.appendPartiallyEncoded(result, ref);
    294             } else {
    295                 result.append(ref);
    296             }
    297         }
    298 
    299         return result.toString();
    300     }
    301 
    302     /**
    303      * Returns true if {@code a} and {@code b} have the same protocol, host,
    304      * port, file, and reference.
    305      */
    306     protected boolean equals(URL a, URL b) {
    307         return sameFile(a, b)
    308                 && Objects.equal(a.getRef(), b.getRef())
    309                 && Objects.equal(a.getQuery(), b.getQuery());
    310     }
    311 
    312     /**
    313      * Returns the default port of the protocol used by the handled URL. The
    314      * default implementation always returns {@code -1}.
    315      */
    316     protected int getDefaultPort() {
    317         return -1;
    318     }
    319 
    320     /**
    321      * Returns the host address of {@code url}.
    322      */
    323     protected InetAddress getHostAddress(URL url) {
    324         try {
    325             String host = url.getHost();
    326             if (host == null || host.length() == 0) {
    327                 return null;
    328             }
    329             return InetAddress.getByName(host);
    330         } catch (UnknownHostException e) {
    331             return null;
    332         }
    333     }
    334 
    335     /**
    336      * Returns the hash code of {@code url}.
    337      */
    338     protected int hashCode(URL url) {
    339         return toExternalForm(url).hashCode();
    340     }
    341 
    342     /**
    343      * Returns true if the hosts of {@code a} and {@code b} are equal.
    344      */
    345     protected boolean hostsEqual(URL a, URL b) {
    346         // URLs with the same case-insensitive host name have equal hosts
    347         String aHost = a.getHost();
    348         String bHost = b.getHost();
    349         return (aHost == bHost) || aHost != null && aHost.equalsIgnoreCase(bHost);
    350     }
    351 
    352     /**
    353      * Returns true if {@code a} and {@code b} have the same protocol, host,
    354      * port and file.
    355      */
    356     protected boolean sameFile(URL a, URL b) {
    357         return Objects.equal(a.getProtocol(), b.getProtocol())
    358                 && hostsEqual(a, b)
    359                 && a.getEffectivePort() == b.getEffectivePort()
    360                 && Objects.equal(a.getFile(), b.getFile());
    361     }
    362 }
    363