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