• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
setURLStreamHandlerFactory(URLStreamHandlerFactory factory)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      */
URL(String spec)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      */
URL(URL context, String spec)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      */
URL(URL context, String spec, URLStreamHandler handler)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      */
URL(String protocol, String host, String file)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      */
URL(String protocol, String host, int port, String file)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      */
URL(String protocol, String host, int port, String file, URLStreamHandler handler)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 
fixURL(boolean fixHost)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      */
set(String protocol, String host, int port, String file, String ref)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 4.0 (Ice Cream Sandwich). In that
344      * release, URLs are only equal if their host names are equal (ignoring
345      * case).
346      */
equals(Object o)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      */
sameFile(URL otherURL)364     public boolean sameFile(URL otherURL) {
365         return streamHandler.sameFile(this, otherURL);
366     }
367 
hashCode()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      */
setupStreamHandler()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      */
getContent()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
getContent(Class[] types)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      */
openStream()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      */
openConnection()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      */
openConnection(Proxy proxy)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      */
toURI()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      */
toURILenient()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      */
toString()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      */
toExternalForm()526     public String toExternalForm() {
527         if (streamHandler == null) {
528             return "unknown protocol(" + protocol + ")://" + host + file;
529         }
530         return streamHandler.toExternalForm(this);
531     }
532 
readObject(ObjectInputStream stream)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 
writeObject(ObjectOutputStream s)560     private void writeObject(ObjectOutputStream s) throws IOException {
561         s.defaultWriteObject();
562     }
563 
564     /** @hide */
getEffectivePort()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      */
getProtocol()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      */
getAuthority()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      */
getUserInfo()588     public String getUserInfo() {
589         return userInfo;
590     }
591 
592     /**
593      * Returns the host name or IP address of this URL.
594      */
getHost()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      */
getPort()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      */
getDefaultPort()617     public int getDefaultPort() {
618         return streamHandler.getDefaultPort();
619     }
620 
621     /**
622      * Returns the file of this URL.
623      */
getFile()624     public String getFile() {
625         return file;
626     }
627 
628     /**
629      * Returns the path part of this URL.
630      */
getPath()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      */
getQuery()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      */
getRef()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      */
set(String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)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