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