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