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