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