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