1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package java.net;
28 
29 import java.io.IOException;
30 import java.util.Objects;
31 
32 import sun.net.util.IPAddressUtil;
33 
34 /**
35  * The abstract class {@code URLStreamHandler} is the common
36  * superclass for all stream protocol handlers. A stream protocol
37  * handler knows how to make a connection for a particular protocol
38  * type, such as {@code http} or {@code https}.
39  * <p>
40  * In most cases, an instance of a {@code URLStreamHandler}
41  * subclass is not created directly by an application. Rather, the
42  * first time a protocol name is encountered when constructing a
43  * {@code URL}, the appropriate stream protocol handler is
44  * automatically loaded.
45  *
46  * @author  James Gosling
47  * @see     java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
48  * @since   JDK1.0
49  */
50 public abstract class URLStreamHandler {
51     /**
52      * Opens a connection to the object referenced by the
53      * {@code URL} argument.
54      * This method should be overridden by a subclass.
55      *
56      * <p>If for the handler's protocol (such as HTTP or JAR), there
57      * exists a public, specialized URLConnection subclass belonging
58      * to one of the following packages or one of their subpackages:
59      * java.lang, java.io, java.util, java.net, the connection
60      * returned will be of that subclass. For example, for HTTP an
61      * HttpURLConnection will be returned, and for JAR a
62      * JarURLConnection will be returned.
63      *
64      * @param      u   the URL that this connects to.
65      * @return     a {@code URLConnection} object for the {@code URL}.
66      * @exception  IOException  if an I/O error occurs while opening the
67      *               connection.
68      */
openConnection(URL u)69     abstract protected URLConnection openConnection(URL u) throws IOException;
70 
71     /**
72      * Same as openConnection(URL), except that the connection will be
73      * made through the specified proxy; Protocol handlers that do not
74      * support proxying will ignore the proxy parameter and make a
75      * normal connection.
76      *
77      * Calling this method preempts the system's default ProxySelector
78      * settings.
79      *
80      * @param      u   the URL that this connects to.
81      * @param      p   the proxy through which the connection will be made.
82      *                 If direct connection is desired, Proxy.NO_PROXY
83      *                 should be specified.
84      * @return     a {@code URLConnection} object for the {@code URL}.
85      * @exception  IOException  if an I/O error occurs while opening the
86      *               connection.
87      * @exception  IllegalArgumentException if either u or p is null,
88      *               or p has the wrong type.
89      * @exception  UnsupportedOperationException if the subclass that
90      *               implements the protocol doesn't support this method.
91      * @since      1.5
92      */
openConnection(URL u, Proxy p)93     protected URLConnection openConnection(URL u, Proxy p) throws IOException {
94         throw new UnsupportedOperationException("Method not implemented.");
95     }
96 
97     /**
98      * Parses the string representation of a {@code URL} into a
99      * {@code URL} object.
100      * <p>
101      * If there is any inherited context, then it has already been
102      * copied into the {@code URL} argument.
103      * <p>
104      * The {@code parseURL} method of {@code URLStreamHandler}
105      * parses the string representation as if it were an
106      * {@code http} specification. Most URL protocol families have a
107      * similar parsing. A stream protocol handler for a protocol that has
108      * a different syntax must override this routine.
109      *
110      * @param   u       the {@code URL} to receive the result of parsing
111      *                  the spec.
112      * @param   spec    the {@code String} representing the URL that
113      *                  must be parsed.
114      * @param   start   the character index at which to begin parsing. This is
115      *                  just past the '{@code :}' (if there is one) that
116      *                  specifies the determination of the protocol name.
117      * @param   limit   the character position to stop parsing at. This is the
118      *                  end of the string or the position of the
119      *                  "{@code #}" character, if present. All information
120      *                  after the sharp sign indicates an anchor.
121      */
parseURL(URL u, String spec, int start, int limit)122     protected void parseURL(URL u, String spec, int start, int limit) {
123         // These fields may receive context content if this was relative URL
124         String protocol = u.getProtocol();
125         String authority = u.getAuthority();
126         String userInfo = u.getUserInfo();
127         String host = u.getHost();
128         int port = u.getPort();
129         String path = u.getPath();
130         String query = u.getQuery();
131 
132         // This field has already been parsed
133         String ref = u.getRef();
134 
135         boolean isRelPath = false;
136         boolean queryOnly = false;
137         // BEGIN Android-changed: App compat.
138         boolean querySet = false;
139         // END Android-changed: App compat.
140 
141 // FIX: should not assume query if opaque
142         // Strip off the query part
143         if (start < limit) {
144             int queryStart = spec.indexOf('?');
145             queryOnly = queryStart == start;
146             if ((queryStart != -1) && (queryStart < limit)) {
147                 query = spec.substring(queryStart+1, limit);
148                 if (limit > queryStart)
149                     limit = queryStart;
150                 spec = spec.substring(0, queryStart);
151                 // BEGIN Android-changed: App compat.
152                 querySet = true;
153                 // END Android-changed: App compat.
154             }
155         }
156 
157         int i = 0;
158         // Parse the authority part if any
159         // BEGIN Android-changed: App compat.
160         // boolean isUNCName = (start <= limit - 4) &&
161         //                 (spec.charAt(start) == '/') &&
162         //                 (spec.charAt(start + 1) == '/') &&
163         //                 (spec.charAt(start + 2) == '/') &&
164         //                 (spec.charAt(start + 3) == '/');
165         boolean isUNCName = false;
166         // END Android-changed: App compat.
167         if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
168             (spec.charAt(start + 1) == '/')) {
169             start += 2;
170             // BEGIN Android-changed: Check for all hostname termination chars. http://b/110955991
171             /*
172             i = spec.indexOf('/', start);
173             if (i < 0 || i > limit) {
174                 i = spec.indexOf('?', start);
175                 if (i < 0 || i > limit)
176                     i = limit;
177             }
178             */
179             LOOP: for (i = start; i < limit; i++) {
180                 switch (spec.charAt(i)) {
181                     case '/':  // Start of path
182                     case '\\': // Start of path - see https://url.spec.whatwg.org/#host-state
183                     case '?':  // Start of query
184                     case '#':  // Start of fragment
185                         break LOOP;
186                 }
187             }
188             // END Android-changed: Check for all hostname termination chars. http://b/110955991
189 
190             host = authority = spec.substring(start, i);
191 
192             int ind = authority.indexOf('@');
193             if (ind != -1) {
194                 if (ind != authority.lastIndexOf('@')) {
195                     // more than one '@' in authority. This is not server based
196                     userInfo = null;
197                     host = null;
198                 } else {
199                     userInfo = authority.substring(0, ind);
200                     host = authority.substring(ind+1);
201                 }
202             } else {
203                 userInfo = null;
204             }
205             if (host != null) {
206                 // If the host is surrounded by [ and ] then its an IPv6
207                 // literal address as specified in RFC2732
208                 if (host.length()>0 && (host.charAt(0) == '[')) {
209                     if ((ind = host.indexOf(']')) > 2) {
210 
211                         String nhost = host ;
212                         host = nhost.substring(0,ind+1);
213                         if (!IPAddressUtil.
214                             isIPv6LiteralAddress(host.substring(1, ind))) {
215                             throw new IllegalArgumentException(
216                                 "Invalid host: "+ host);
217                         }
218 
219                         port = -1 ;
220                         if (nhost.length() > ind+1) {
221                             if (nhost.charAt(ind+1) == ':') {
222                                 ++ind ;
223                                 // port can be null according to RFC2396
224                                 if (nhost.length() > (ind + 1)) {
225                                     port = Integer.parseInt(nhost.substring(ind+1));
226                                 }
227                             } else {
228                                 throw new IllegalArgumentException(
229                                     "Invalid authority field: " + authority);
230                             }
231                         }
232                     } else {
233                         throw new IllegalArgumentException(
234                             "Invalid authority field: " + authority);
235                     }
236                 } else {
237                     ind = host.indexOf(':');
238                     port = -1;
239                     if (ind >= 0) {
240                         // port can be null according to RFC2396
241                         if (host.length() > (ind + 1)) {
242                             // BEGIN Android-changed: App compat.
243                             // port = Integer.parseInt(host.substring(ind + 1));
244                             char firstPortChar = host.charAt(ind+1);
245                             if (firstPortChar >= '0' && firstPortChar <= '9') {
246                                 port = Integer.parseInt(host.substring(ind + 1));
247                             } else {
248                                 throw new IllegalArgumentException("invalid port: " +
249                                                                    host.substring(ind + 1));
250                             }
251                             // END Android-changed: App compat.
252                         }
253                         host = host.substring(0, ind);
254                     }
255                 }
256             } else {
257                 host = "";
258             }
259             if (port < -1)
260                 throw new IllegalArgumentException("Invalid port number :" +
261                                                    port);
262             start = i;
263 
264             // If the authority is defined then the path is defined by the
265             // spec only; See RFC 2396 Section 5.2.4.
266             // BEGIN Android-changed: App compat.
267             // if (authority != null && authority.length() > 0)
268             //   path = "";
269             path = null;
270             if (!querySet) {
271                 query = null;
272             }
273             // END Android-changed: App compat.
274         }
275 
276         if (host == null) {
277             host = "";
278         }
279 
280         // Parse the file path if any
281         if (start < limit) {
282             // Android-changed: Check for all hostname termination chars. http://b/110955991
283             // if (spec.charAt(start) == '/') {
284             if (spec.charAt(start) == '/' || spec.charAt(start) == '\\') {
285                 path = spec.substring(start, limit);
286             } else if (path != null && path.length() > 0) {
287                 isRelPath = true;
288                 int ind = path.lastIndexOf('/');
289                 String seperator = "";
290                 if (ind == -1 && authority != null)
291                     seperator = "/";
292                 path = path.substring(0, ind + 1) + seperator +
293                          spec.substring(start, limit);
294 
295             } else {
296                 String seperator = (authority != null) ? "/" : "";
297                 path = seperator + spec.substring(start, limit);
298             }
299         }
300         // BEGIN Android-changed: App compat.
301         //else if (queryOnly && path != null) {
302         //    int ind = path.lastIndexOf('/');
303         //    if (ind < 0)
304         //        ind = 0;
305         //    path = path.substring(0, ind) + "/";
306         //}
307         // END Android-changed: App compat.
308         if (path == null)
309             path = "";
310 
311         // BEGIN Android-changed: always assume isRelPath is true.
312         //if (isRelPath) {
313         if (true) {
314         // END Android-changed: always assume isRelPath is true.
315             // Remove embedded /./
316             while ((i = path.indexOf("/./")) >= 0) {
317                 path = path.substring(0, i) + path.substring(i + 2);
318             }
319             // Remove embedded /../ if possible
320             i = 0;
321             while ((i = path.indexOf("/../", i)) >= 0) {
322                 // BEGIN Android-changed: App compat.
323                 /*
324                  * Trailing /../
325                  */
326                 if (i == 0) {
327                     path = path.substring(i + 3);
328                     i = 0;
329                 // END Android-changed: App compat.
330                 /*
331                  * A "/../" will cancel the previous segment and itself,
332                  * unless that segment is a "/../" itself
333                  * i.e. "/a/b/../c" becomes "/a/c"
334                  * but "/../../a" should stay unchanged
335                  */
336                 // Android-changed: App compat.
337                 // if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
338                 } else if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
339                     (path.indexOf("/../", limit) != 0)) {
340                     path = path.substring(0, limit) + path.substring(i + 3);
341                     i = 0;
342                 } else {
343                     i = i + 3;
344                 }
345             }
346             // Remove trailing .. if possible
347             while (path.endsWith("/..")) {
348                 i = path.indexOf("/..");
349                 if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
350                     path = path.substring(0, limit+1);
351                 } else {
352                     break;
353                 }
354             }
355             // Remove starting .
356             if (path.startsWith("./") && path.length() > 2)
357                 path = path.substring(2);
358 
359             // Remove trailing .
360             if (path.endsWith("/."))
361                 path = path.substring(0, path.length() -1);
362 
363             // Android-changed: App compat: Remove trailing '?'.
364             if (path.endsWith("?"))
365                 path = path.substring(0, path.length() -1);
366         }
367 
368         setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
369     }
370 
371     /**
372      * Returns the default port for a URL parsed by this handler. This method
373      * is meant to be overidden by handlers with default port numbers.
374      * @return the default port for a {@code URL} parsed by this handler.
375      * @since 1.3
376      */
getDefaultPort()377     protected int getDefaultPort() {
378         return -1;
379     }
380 
381     /**
382      * Provides the default equals calculation. May be overidden by handlers
383      * for other protocols that have different requirements for equals().
384      * This method requires that none of its arguments is null. This is
385      * guaranteed by the fact that it is only called by java.net.URL class.
386      * @param u1 a URL object
387      * @param u2 a URL object
388      * @return {@code true} if the two urls are
389      * considered equal, ie. they refer to the same
390      * fragment in the same file.
391      * @since 1.3
392      */
equals(URL u1, URL u2)393     protected boolean equals(URL u1, URL u2) {
394         // Android-changed: Avoid network I/O.
395         return Objects.equals(u1.getRef(), u2.getRef()) &&
396                Objects.equals(u1.getQuery(), u2.getQuery()) &&
397                // sameFile compares the protocol, file, port & host components of
398                // the URLs.
399                sameFile(u1, u2);
400     }
401 
402     /**
403      * Provides the default hash calculation. May be overidden by handlers for
404      * other protocols that have different requirements for hashCode
405      * calculation.
406      * @param u a URL object
407      * @return an {@code int} suitable for hash table indexing
408      * @since 1.3
409      */
hashCode(URL u)410     protected int hashCode(URL u) {
411         // Android-changed: Avoid network I/O.
412         // Hash on the same set of fields that we compare in equals().
413         return Objects.hash(
414                 u.getRef(),
415                 u.getQuery(),
416                 u.getProtocol(),
417                 u.getFile(),
418                 u.getHost(),
419                 u.getPort());
420     }
421 
422     /**
423      * Compare two urls to see whether they refer to the same file,
424      * i.e., having the same protocol, host, port, and path.
425      * This method requires that none of its arguments is null. This is
426      * guaranteed by the fact that it is only called indirectly
427      * by java.net.URL class.
428      * @param u1 a URL object
429      * @param u2 a URL object
430      * @return true if u1 and u2 refer to the same file
431      * @since 1.3
432      */
sameFile(URL u1, URL u2)433     protected boolean sameFile(URL u1, URL u2) {
434         // Compare the protocols.
435         if (!((u1.getProtocol() == u2.getProtocol()) ||
436               (u1.getProtocol() != null &&
437                u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
438             return false;
439 
440         // Compare the files.
441         if (!(u1.getFile() == u2.getFile() ||
442               (u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
443             return false;
444 
445         // Compare the ports.
446         int port1, port2;
447         port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
448         port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
449         if (port1 != port2)
450             return false;
451 
452         // Compare the hosts.
453         if (!hostsEqual(u1, u2))
454             return false;
455 
456         return true;
457     }
458 
459     /**
460      * Get the IP address of our host. An empty host field or a DNS failure
461      * will result in a null return.
462      *
463      * @param u a URL object
464      * @return an {@code InetAddress} representing the host
465      * IP address.
466      * @since 1.3
467      */
getHostAddress(URL u)468     protected synchronized InetAddress getHostAddress(URL u) {
469         if (u.hostAddress != null)
470             return u.hostAddress;
471 
472         String host = u.getHost();
473         if (host == null || host.equals("")) {
474             return null;
475         } else {
476             try {
477                 u.hostAddress = InetAddress.getByName(host);
478             } catch (UnknownHostException ex) {
479                 return null;
480             } catch (SecurityException se) {
481                 return null;
482             }
483         }
484         return u.hostAddress;
485     }
486 
487     /**
488      * Compares the host components of two URLs.
489      * @param u1 the URL of the first host to compare
490      * @param u2 the URL of the second host to compare
491      * @return  {@code true} if and only if they
492      * are equal, {@code false} otherwise.
493      * @since 1.3
494      */
hostsEqual(URL u1, URL u2)495     protected boolean hostsEqual(URL u1, URL u2) {
496         // Android-changed: Don't compare the InetAddresses of the hosts.
497         if (u1.getHost() != null && u2.getHost() != null)
498             return u1.getHost().equalsIgnoreCase(u2.getHost());
499          else
500             return u1.getHost() == null && u2.getHost() == null;
501     }
502 
503     /**
504      * Converts a {@code URL} of a specific protocol to a
505      * {@code String}.
506      *
507      * @param   u   the URL.
508      * @return  a string representation of the {@code URL} argument.
509      */
toExternalForm(URL u)510     protected String toExternalForm(URL u) {
511 
512         // pre-compute length of StringBuffer
513         int len = u.getProtocol().length() + 1;
514         if (u.getAuthority() != null && u.getAuthority().length() > 0)
515             len += 2 + u.getAuthority().length();
516         if (u.getPath() != null) {
517             len += u.getPath().length();
518         }
519         if (u.getQuery() != null) {
520             len += 1 + u.getQuery().length();
521         }
522         if (u.getRef() != null)
523             len += 1 + u.getRef().length();
524 
525         // BEGIN Android-changed: New toExternalForm variant that optionally escapes illegal chars.
526         // TODO: The variant has been removed. We can potentially revert the change
527         StringBuilder result = new StringBuilder(len);
528         result.append(u.getProtocol());
529         result.append(":");
530         if (u.getAuthority() != null) {// ANDROID: && u.getAuthority().length() > 0) {
531             result.append("//");
532             result.append(u.getAuthority());
533         }
534         String fileAndQuery = u.getFile();
535         if (fileAndQuery != null) {
536             result.append(fileAndQuery);
537         }
538         // END Android-changed: New toExternalForm variant that optionally escapes illegal chars.
539         if (u.getRef() != null) {
540             result.append("#");
541             result.append(u.getRef());
542         }
543         return result.toString();
544     }
545 
546     // Android-changed: Removed @see tag (target is package-private).
547     // @see     java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String)
548     /**
549      * Sets the fields of the {@code URL} argument to the indicated values.
550      * Only classes derived from URLStreamHandler are able
551      * to use this method to set the values of the URL fields.
552      *
553      * @param   u         the URL to modify.
554      * @param   protocol  the protocol name.
555      * @param   host      the remote host value for the URL.
556      * @param   port      the port on the remote machine.
557      * @param   authority the authority part for the URL.
558      * @param   userInfo the userInfo part of the URL.
559      * @param   path      the path component of the URL.
560      * @param   query     the query part for the URL.
561      * @param   ref       the reference.
562      * @exception       SecurityException       if the protocol handler of the URL is
563      *                                  different from this one
564      * @since 1.3
565      */
setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)566        protected void setURL(URL u, String protocol, String host, int port,
567                              String authority, String userInfo, String path,
568                              String query, String ref) {
569         if (this != u.handler) {
570             throw new SecurityException("handler for url different from " +
571                                         "this handler");
572         }
573         // ensure that no one can reset the protocol on a given URL.
574         u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
575     }
576 
577     /**
578      * Sets the fields of the {@code URL} argument to the indicated values.
579      * Only classes derived from URLStreamHandler are able
580      * to use this method to set the values of the URL fields.
581      *
582      * @param   u         the URL to modify.
583      * @param   protocol  the protocol name. This value is ignored since 1.2.
584      * @param   host      the remote host value for the URL.
585      * @param   port      the port on the remote machine.
586      * @param   file      the file.
587      * @param   ref       the reference.
588      * @exception       SecurityException       if the protocol handler of the URL is
589      *                                  different from this one
590      * @deprecated Use setURL(URL, String, String, int, String, String, String,
591      *             String);
592      */
593     @Deprecated
setURL(URL u, String protocol, String host, int port, String file, String ref)594     protected void setURL(URL u, String protocol, String host, int port,
595                           String file, String ref) {
596         /*
597          * Only old URL handlers call this, so assume that the host
598          * field might contain "user:passwd@host". Fix as necessary.
599          */
600         String authority = null;
601         String userInfo = null;
602         if (host != null && host.length() != 0) {
603             authority = (port == -1) ? host : host + ":" + port;
604             int at = host.lastIndexOf('@');
605             if (at != -1) {
606                 userInfo = host.substring(0, at);
607                 host = host.substring(at+1);
608             }
609         }
610 
611         /*
612          * Assume file might contain query part. Fix as necessary.
613          */
614         String path = null;
615         String query = null;
616         if (file != null) {
617             int q = file.lastIndexOf('?');
618             if (q != -1) {
619                 query = file.substring(q+1);
620                 path = file.substring(0, q);
621             } else
622                 path = file;
623         }
624         setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
625     }
626 }
627