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 java.io.InputStream;
     22 import java.io.ObjectInputStream;
     23 import java.io.ObjectOutputStream;
     24 import java.io.Serializable;
     25 import java.util.Hashtable;
     26 import java.util.jar.JarFile;
     27 import libcore.net.http.HttpHandler;
     28 import libcore.net.http.HttpsHandler;
     29 import libcore.net.url.FileHandler;
     30 import libcore.net.url.FtpHandler;
     31 import libcore.net.url.JarHandler;
     32 import libcore.net.url.UrlUtils;
     33 
     34 /**
     35  * A Uniform Resource Locator that identifies the location of an Internet
     36  * resource as specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC
     37  * 1738</a>.
     38  *
     39  * <h3>Parts of a URL</h3>
     40  * A URL is composed of many parts. This class can both parse URL strings into
     41  * parts and compose URL strings from parts. For example, consider the parts of
     42  * this URL:
     43  * {@code http://username:password@host:8080/directory/file?query#ref}:
     44  * <table>
     45  * <tr><th>Component</th><th>Example value</th><th>Also known as</th></tr>
     46  * <tr><td>{@link #getProtocol() Protocol}</td><td>{@code http}</td><td>scheme</td></tr>
     47  * <tr><td>{@link #getAuthority() Authority}</td><td>{@code username:password@host:8080}</td><td></td></tr>
     48  * <tr><td>{@link #getUserInfo() User Info}</td><td>{@code username:password}</td><td></td></tr>
     49  * <tr><td>{@link #getHost() Host}</td><td>{@code host}</td><td></td></tr>
     50  * <tr><td>{@link #getPort() Port}</td><td>{@code 8080}</td><td></td></tr>
     51  * <tr><td>{@link #getFile() File}</td><td>{@code /directory/file?query}</td><td></td></tr>
     52  * <tr><td>{@link #getPath() Path}</td><td>{@code /directory/file}</td><td></td></tr>
     53  * <tr><td>{@link #getQuery() Query}</td><td>{@code query}</td><td></td></tr>
     54  * <tr><td>{@link #getRef() Ref}</td><td>{@code ref}</td><td>fragment</td></tr>
     55  * </table>
     56  *
     57  * <h3>Supported Protocols</h3>
     58  * This class may be used to construct URLs with the following protocols:
     59  * <ul>
     60  * <li><strong>file</strong>: read files from the local filesystem.
     61  * <li><strong>ftp</strong>: <a href="http://www.ietf.org/rfc/rfc959.txt">File
     62  *     Transfer Protocol</a>
     63  * <li><strong>http</strong>: <a href="http://www.ietf.org/rfc/rfc2616.txt">Hypertext
     64  *     Transfer Protocol</a>
     65  * <li><strong>https</strong>: <a href="http://www.ietf.org/rfc/rfc2818.txt">HTTP
     66  *     over TLS</a>
     67  * <li><strong>jar</strong>: read {@link JarFile Jar files} from the
     68  *     filesystem</li>
     69  * </ul>
     70  * In general, attempts to create URLs with any other protocol will fail with a
     71  * {@link MalformedURLException}. Applications may install handlers for other
     72  * schemes using {@link #setURLStreamHandlerFactory} or with the {@code
     73  * java.protocol.handler.pkgs} system property.
     74  *
     75  * <p>The {@link URI} class can be used to manipulate URLs of any protocol.
     76  */
     77 public final class URL implements Serializable {
     78     private static final long serialVersionUID = -7627629688361524110L;
     79 
     80     private static URLStreamHandlerFactory streamHandlerFactory;
     81 
     82     /** Cache of protocols to their handlers */
     83     private static final Hashtable<String, URLStreamHandler> streamHandlers
     84             = new Hashtable<String, URLStreamHandler>();
     85 
     86     private String protocol;
     87     private String authority;
     88     private String host;
     89     private int port = -1;
     90     private String file;
     91     private String ref;
     92 
     93     private transient String userInfo;
     94     private transient String path;
     95     private transient String query;
     96 
     97     transient URLStreamHandler streamHandler;
     98 
     99     /**
    100      * The cached hash code, or 0 if it hasn't been computed yet. Unlike the RI,
    101      * this implementation's hashCode is transient because the hash code is
    102      * unspecified and may vary between VMs or versions.
    103      */
    104     private transient int hashCode;
    105 
    106     /**
    107      * Sets the stream handler factory for this VM.
    108      *
    109      * @throws Error if a URLStreamHandlerFactory has already been installed
    110      *     for the current VM.
    111      */
    112     public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
    113         if (streamHandlerFactory != null) {
    114             throw new Error("Factory already set");
    115         }
    116         streamHandlers.clear();
    117         streamHandlerFactory = factory;
    118     }
    119 
    120     /**
    121      * Creates a new URL instance by parsing {@code spec}.
    122      *
    123      * @throws MalformedURLException if {@code spec} could not be parsed as a
    124      *     URL.
    125      */
    126     public URL(String spec) throws MalformedURLException {
    127         this((URL) null, spec, null);
    128     }
    129 
    130     /**
    131      * Creates a new URL by resolving {@code spec} relative to {@code context}.
    132      *
    133      * @param context the URL to which {@code spec} is relative, or null for
    134      *     no context in which case {@code spec} must be an absolute URL.
    135      * @throws MalformedURLException if {@code spec} could not be parsed as a
    136      *     URL or has an unsupported protocol.
    137      */
    138     public URL(URL context, String spec) throws MalformedURLException {
    139         this(context, spec, null);
    140     }
    141 
    142     /**
    143      * Creates a new URL by resolving {@code spec} relative to {@code context}.
    144      *
    145      * @param context the URL to which {@code spec} is relative, or null for
    146      *     no context in which case {@code spec} must be an absolute URL.
    147      * @param handler the stream handler for this URL, or null for the
    148      *     protocol's default stream handler.
    149      * @throws MalformedURLException if the given string {@code spec} could not
    150      *     be parsed as a URL or an invalid protocol has been found.
    151      */
    152     public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
    153         if (spec == null) {
    154             throw new MalformedURLException();
    155         }
    156         if (handler != null) {
    157             streamHandler = handler;
    158         }
    159         spec = spec.trim();
    160 
    161         protocol = UrlUtils.getSchemePrefix(spec);
    162         int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;
    163 
    164         // If the context URL has a different protocol, discard it because we can't use it.
    165         if (protocol != null && context != null && !protocol.equals(context.protocol)) {
    166             context = null;
    167         }
    168 
    169         // Inherit from the context URL if it exists.
    170         if (context != null) {
    171             set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),
    172                     context.getUserInfo(), context.getPath(), context.getQuery(),
    173                     context.getRef());
    174             if (streamHandler == null) {
    175                 streamHandler = context.streamHandler;
    176             }
    177         } else if (protocol == null) {
    178             throw new MalformedURLException("Protocol not found: " + spec);
    179         }
    180 
    181         if (streamHandler == null) {
    182             setupStreamHandler();
    183             if (streamHandler == null) {
    184                 throw new MalformedURLException("Unknown protocol: " + protocol);
    185             }
    186         }
    187 
    188         // Parse the URL. If the handler throws any exception, throw MalformedURLException instead.
    189         try {
    190             streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
    191         } catch (Exception e) {
    192             throw new MalformedURLException(e.toString());
    193         }
    194     }
    195 
    196     /**
    197      * Creates a new URL of the given component parts. The URL uses the
    198      * protocol's default port.
    199      *
    200      * @throws MalformedURLException if the combination of all arguments do not
    201      *     represent a valid URL or if the protocol is invalid.
    202      */
    203     public URL(String protocol, String host, String file) throws MalformedURLException {
    204         this(protocol, host, -1, file, null);
    205     }
    206 
    207     /**
    208      * Creates a new URL of the given component parts. The URL uses the
    209      * protocol's default port.
    210      *
    211      * @param host the host name or IP address of the new URL.
    212      * @param port the port, or {@code -1} for the protocol's default port.
    213      * @param file the name of the resource.
    214      * @throws MalformedURLException if the combination of all arguments do not
    215      *     represent a valid URL or if the protocol is invalid.
    216      */
    217     public URL(String protocol, String host, int port, String file) throws MalformedURLException {
    218         this(protocol, host, port, file, null);
    219     }
    220 
    221     /**
    222      * Creates a new URL of the given component parts. The URL uses the
    223      * protocol's default port.
    224      *
    225      * @param host the host name or IP address of the new URL.
    226      * @param port the port, or {@code -1} for the protocol's default port.
    227      * @param file the name of the resource.
    228      * @param handler the stream handler for this URL, or null for the
    229      *     protocol's default stream handler.
    230      * @throws MalformedURLException if the combination of all arguments do not
    231      *     represent a valid URL or if the protocol is invalid.
    232      */
    233     public URL(String protocol, String host, int port, String file,
    234             URLStreamHandler handler) throws MalformedURLException {
    235         if (port < -1) {
    236             throw new MalformedURLException("port < -1: " + port);
    237         }
    238         if (protocol == null) {
    239             throw new NullPointerException("protocol == null");
    240         }
    241 
    242         // Wrap IPv6 addresses in square brackets if they aren't already.
    243         if (host != null && host.contains(":") && host.charAt(0) != '[') {
    244             host = "[" + host + "]";
    245         }
    246 
    247         this.protocol = protocol;
    248         this.host = host;
    249         this.port = port;
    250 
    251         file = UrlUtils.authoritySafePath(host, file);
    252 
    253         // Set the fields from the arguments. Handle the case where the
    254         // passed in "file" includes both a file and a reference part.
    255         int hash = file.indexOf("#");
    256         if (hash != -1) {
    257             this.file = file.substring(0, hash);
    258             this.ref = file.substring(hash + 1);
    259         } else {
    260             this.file = file;
    261         }
    262         fixURL(false);
    263 
    264         // Set the stream handler for the URL either to the handler
    265         // argument if it was specified, or to the default for the
    266         // receiver's protocol if the handler was null.
    267         if (handler == null) {
    268             setupStreamHandler();
    269             if (streamHandler == null) {
    270                 throw new MalformedURLException("Unknown protocol: " + protocol);
    271             }
    272         } else {
    273             streamHandler = handler;
    274         }
    275     }
    276 
    277     void fixURL(boolean fixHost) {
    278         int index;
    279         if (host != null && host.length() > 0) {
    280             authority = host;
    281             if (port != -1) {
    282                 authority = authority + ":" + port;
    283             }
    284         }
    285         if (fixHost) {
    286             if (host != null && (index = host.lastIndexOf('@')) > -1) {
    287                 userInfo = host.substring(0, index);
    288                 host = host.substring(index + 1);
    289             } else {
    290                 userInfo = null;
    291             }
    292         }
    293         if (file != null && (index = file.indexOf('?')) > -1) {
    294             query = file.substring(index + 1);
    295             path = file.substring(0, index);
    296         } else {
    297             query = null;
    298             path = file;
    299         }
    300     }
    301 
    302     /**
    303      * Sets the properties of this URL using the provided arguments. Only a
    304      * {@code URLStreamHandler} can use this method to set fields of the
    305      * existing URL instance. A URL is generally constant.
    306      */
    307     protected void set(String protocol, String host, int port, String file, String ref) {
    308         if (this.protocol == null) {
    309             this.protocol = protocol;
    310         }
    311         this.host = host;
    312         this.file = file;
    313         this.port = port;
    314         this.ref = ref;
    315         hashCode = 0;
    316         fixURL(true);
    317     }
    318 
    319     /**
    320      * Returns true if this URL equals {@code o}. URLs are equal if they have
    321      * the same protocol, host, port, file, and reference.
    322      *
    323      * <h3>Network I/O Warning</h3>
    324      * <p>Some implementations of URL.equals() resolve host names over the
    325      * network. This is problematic:
    326      * <ul>
    327      * <li><strong>The network may be slow.</strong> Many classes, including
    328      * core collections like {@link java.util.Map Map} and {@link java.util.Set
    329      * Set} expect that {@code equals} and {@code hashCode} will return quickly.
    330      * By violating this assumption, this method posed potential performance
    331      * problems.
    332      * <li><strong>Equal IP addresses do not imply equal content.</strong>
    333      * Virtual hosting permits unrelated sites to share an IP address. This
    334      * method could report two otherwise unrelated URLs to be equal because
    335      * they're hosted on the same server.</li>
    336      * <li><strong>The network many not be available.</strong> Two URLs could be
    337      * equal when a network is available and unequal otherwise.</li>
    338      * <li><strong>The network may change.</strong> The IP address for a given
    339      * host name varies by network and over time. This is problematic for mobile
    340      * devices. Two URLs could be equal on some networks and unequal on
    341      * others.</li>
    342      * </ul>
    343      * <p>This problem is fixed in Android in the Ice Cream Sandwich release. In
    344      * that release, URLs are only equal if their host names are equal (ignoring
    345      * case).
    346      */
    347     @Override public boolean equals(Object o) {
    348         if (o == null) {
    349             return false;
    350         }
    351         if (this == o) {
    352             return true;
    353         }
    354         if (this.getClass() != o.getClass()) {
    355             return false;
    356         }
    357         return streamHandler.equals(this, (URL) o);
    358     }
    359 
    360     /**
    361      * Returns true if this URL refers to the same resource as {@code otherURL}.
    362      * All URL components except the reference field are compared.
    363      */
    364     public boolean sameFile(URL otherURL) {
    365         return streamHandler.sameFile(this, otherURL);
    366     }
    367 
    368     @Override public int hashCode() {
    369         if (hashCode == 0) {
    370             hashCode = streamHandler.hashCode(this);
    371         }
    372         return hashCode;
    373     }
    374 
    375     /**
    376      * Sets the receiver's stream handler to one which is appropriate for its
    377      * protocol.
    378      *
    379      * <p>Note that this will overwrite any existing stream handler with the new
    380      * one. Senders must check if the streamHandler is null before calling the
    381      * method if they do not want this behavior (a speed optimization).
    382      *
    383      * @throws MalformedURLException if no reasonable handler is available.
    384      */
    385     void setupStreamHandler() {
    386         // Check for a cached (previously looked up) handler for
    387         // the requested protocol.
    388         streamHandler = streamHandlers.get(protocol);
    389         if (streamHandler != null) {
    390             return;
    391         }
    392 
    393         // If there is a stream handler factory, then attempt to
    394         // use it to create the handler.
    395         if (streamHandlerFactory != null) {
    396             streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);
    397             if (streamHandler != null) {
    398                 streamHandlers.put(protocol, streamHandler);
    399                 return;
    400             }
    401         }
    402 
    403         // Check if there is a list of packages which can provide handlers.
    404         // If so, then walk this list looking for an applicable one.
    405         String packageList = System.getProperty("java.protocol.handler.pkgs");
    406         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    407         if (packageList != null && contextClassLoader != null) {
    408             for (String packageName : packageList.split("\\|")) {
    409                 String className = packageName + "." + protocol + ".Handler";
    410                 try {
    411                     Class<?> c = contextClassLoader.loadClass(className);
    412                     streamHandler = (URLStreamHandler) c.newInstance();
    413                     if (streamHandler != null) {
    414                         streamHandlers.put(protocol, streamHandler);
    415                     }
    416                     return;
    417                 } catch (IllegalAccessException ignored) {
    418                 } catch (InstantiationException ignored) {
    419                 } catch (ClassNotFoundException ignored) {
    420                 }
    421             }
    422         }
    423 
    424         // Fall back to a built-in stream handler if the user didn't supply one
    425         if (protocol.equals("file")) {
    426             streamHandler = new FileHandler();
    427         } else if (protocol.equals("ftp")) {
    428             streamHandler = new FtpHandler();
    429         } else if (protocol.equals("http")) {
    430             streamHandler = new HttpHandler();
    431         } else if (protocol.equals("https")) {
    432             streamHandler = new HttpsHandler();
    433         } else if (protocol.equals("jar")) {
    434             streamHandler = new JarHandler();
    435         }
    436         if (streamHandler != null) {
    437             streamHandlers.put(protocol, streamHandler);
    438         }
    439     }
    440 
    441     /**
    442      * Returns the content of the resource which is referred by this URL. By
    443      * default this returns an {@code InputStream}, or null if the content type
    444      * of the response is unknown.
    445      */
    446     public final Object getContent() throws IOException {
    447         return openConnection().getContent();
    448     }
    449 
    450     /**
    451      * Equivalent to {@code openConnection().getContent(types)}.
    452      */
    453     @SuppressWarnings("unchecked") // Param not generic in spec
    454     public final Object getContent(Class[] types) throws IOException {
    455         return openConnection().getContent(types);
    456     }
    457 
    458     /**
    459      * Equivalent to {@code openConnection().getInputStream(types)}.
    460      */
    461     public final InputStream openStream() throws IOException {
    462         return openConnection().getInputStream();
    463     }
    464 
    465     /**
    466      * Returns a new connection to the resource referred to by this URL.
    467      *
    468      * @throws IOException if an error occurs while opening the connection.
    469      */
    470     public URLConnection openConnection() throws IOException {
    471         return streamHandler.openConnection(this);
    472     }
    473 
    474     /**
    475      * Returns a new connection to the resource referred to by this URL.
    476      *
    477      * @param proxy the proxy through which the connection will be established.
    478      * @throws IOException if an I/O error occurs while opening the connection.
    479      * @throws IllegalArgumentException if the argument proxy is null or of is
    480      *     an invalid type.
    481      * @throws UnsupportedOperationException if the protocol handler does not
    482      *     support opening connections through proxies.
    483      */
    484     public URLConnection openConnection(Proxy proxy) throws IOException {
    485         if (proxy == null) {
    486             throw new IllegalArgumentException("proxy == null");
    487         }
    488         return streamHandler.openConnection(this, proxy);
    489     }
    490 
    491     /**
    492      * Returns the URI equivalent to this URL.
    493      *
    494      * @throws URISyntaxException if this URL cannot be converted into a URI.
    495      */
    496     public URI toURI() throws URISyntaxException {
    497         return new URI(toExternalForm());
    498     }
    499 
    500     /**
    501      * Encodes this URL to the equivalent URI after escaping characters that are
    502      * not permitted by URI.
    503      *
    504      * @hide
    505      */
    506     public URI toURILenient() throws URISyntaxException {
    507         if (streamHandler == null) {
    508             throw new IllegalStateException(protocol);
    509         }
    510         return new URI(streamHandler.toExternalForm(this, true));
    511     }
    512 
    513     /**
    514      * Returns a string containing a concise, human-readable representation of
    515      * this URL. The returned string is the same as the result of the method
    516      * {@code toExternalForm()}.
    517      */
    518     @Override public String toString() {
    519         return toExternalForm();
    520     }
    521 
    522     /**
    523      * Returns a string containing a concise, human-readable representation of
    524      * this URL.
    525      */
    526     public String toExternalForm() {
    527         if (streamHandler == null) {
    528             return "unknown protocol(" + protocol + ")://" + host + file;
    529         }
    530         return streamHandler.toExternalForm(this);
    531     }
    532 
    533     private void readObject(ObjectInputStream stream) throws IOException {
    534         try {
    535             stream.defaultReadObject();
    536             if (host != null && authority == null) {
    537                 fixURL(true);
    538             } else if (authority != null) {
    539                 int index;
    540                 if ((index = authority.lastIndexOf('@')) > -1) {
    541                     userInfo = authority.substring(0, index);
    542                 }
    543                 if (file != null && (index = file.indexOf('?')) > -1) {
    544                     query = file.substring(index + 1);
    545                     path = file.substring(0, index);
    546                 } else {
    547                     path = file;
    548                 }
    549             }
    550             setupStreamHandler();
    551             if (streamHandler == null) {
    552                 throw new IOException("Unknown protocol: " + protocol);
    553             }
    554             hashCode = 0; // necessary until http://b/4471249 is fixed
    555         } catch (ClassNotFoundException e) {
    556             throw new IOException(e);
    557         }
    558     }
    559 
    560     private void writeObject(ObjectOutputStream s) throws IOException {
    561         s.defaultWriteObject();
    562     }
    563 
    564     /** @hide */
    565     public int getEffectivePort() {
    566         return URI.getEffectivePort(protocol, port);
    567     }
    568 
    569     /**
    570      * Returns the protocol of this URL like "http" or "file". This is also
    571      * known as the scheme. The returned string is lower case.
    572      */
    573     public String getProtocol() {
    574         return protocol;
    575     }
    576 
    577     /**
    578      * Returns the authority part of this URL, or null if this URL has no
    579      * authority.
    580      */
    581     public String getAuthority() {
    582         return authority;
    583     }
    584 
    585     /**
    586      * Returns the user info of this URL, or null if this URL has no user info.
    587      */
    588     public String getUserInfo() {
    589         return userInfo;
    590     }
    591 
    592     /**
    593      * Returns the host name or IP address of this URL.
    594      */
    595     public String getHost() {
    596         return host;
    597     }
    598 
    599     /**
    600      * Returns the port number of this URL or {@code -1} if this URL has no
    601      * explicit port.
    602      *
    603      * <p>If this URL has no explicit port, connections opened using this URL
    604      * will use its {@link #getDefaultPort() default port}.
    605      */
    606     public int getPort() {
    607         return port;
    608     }
    609 
    610     /**
    611      * Returns the default port number of the protocol used by this URL. If no
    612      * default port is defined by the protocol or the {@code URLStreamHandler},
    613      * {@code -1} will be returned.
    614      *
    615      * @see URLStreamHandler#getDefaultPort
    616      */
    617     public int getDefaultPort() {
    618         return streamHandler.getDefaultPort();
    619     }
    620 
    621     /**
    622      * Returns the file of this URL.
    623      */
    624     public String getFile() {
    625         return file;
    626     }
    627 
    628     /**
    629      * Returns the path part of this URL.
    630      */
    631     public String getPath() {
    632         return path;
    633     }
    634 
    635     /**
    636      * Returns the query part of this URL, or null if this URL has no query.
    637      */
    638     public String getQuery() {
    639         return query;
    640     }
    641 
    642     /**
    643      * Returns the value of the reference part of this URL, or null if this URL
    644      * has no reference part. This is also known as the fragment.
    645      */
    646     public String getRef() {
    647         return ref;
    648     }
    649 
    650     /**
    651      * Sets the properties of this URL using the provided arguments. Only a
    652      * {@code URLStreamHandler} can use this method to set fields of the
    653      * existing URL instance. A URL is generally constant.
    654      */
    655     protected void set(String protocol, String host, int port, String authority, String userInfo,
    656             String path, String query, String ref) {
    657         String file = path;
    658         if (query != null && !query.isEmpty()) {
    659             file += "?" + query;
    660         }
    661         set(protocol, host, port, file, ref);
    662         this.authority = authority;
    663         this.userInfo = userInfo;
    664         this.path = path;
    665         this.query = query;
    666     }
    667 }
    668