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 138 boolean querySet = false; 139 // END Android-changed 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 152 querySet = true; 153 // END Android-changed 154 } 155 } 156 157 int i = 0; 158 // Parse the authority part if any 159 // BEGIN Android-changed 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 167 if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') && 168 (spec.charAt(start + 1) == '/')) { 169 start += 2; 170 i = spec.indexOf('/', start); 171 if (i < 0 || i > limit) { 172 i = spec.indexOf('?', start); 173 if (i < 0 || i > limit) 174 i = limit; 175 } 176 177 host = authority = spec.substring(start, i); 178 179 int ind = authority.indexOf('@'); 180 if (ind != -1) { 181 if (ind != authority.lastIndexOf('@')) { 182 // more than one '@' in authority. This is not server based 183 userInfo = null; 184 host = null; 185 } else { 186 userInfo = authority.substring(0, ind); 187 host = authority.substring(ind+1); 188 } 189 } else { 190 userInfo = null; 191 } 192 if (host != null) { 193 // If the host is surrounded by [ and ] then its an IPv6 194 // literal address as specified in RFC2732 195 if (host.length()>0 && (host.charAt(0) == '[')) { 196 if ((ind = host.indexOf(']')) > 2) { 197 198 String nhost = host ; 199 host = nhost.substring(0,ind+1); 200 if (!IPAddressUtil. 201 isIPv6LiteralAddress(host.substring(1, ind))) { 202 throw new IllegalArgumentException( 203 "Invalid host: "+ host); 204 } 205 206 port = -1 ; 207 if (nhost.length() > ind+1) { 208 if (nhost.charAt(ind+1) == ':') { 209 ++ind ; 210 // port can be null according to RFC2396 211 if (nhost.length() > (ind + 1)) { 212 port = Integer.parseInt(nhost.substring(ind+1)); 213 } 214 } else { 215 throw new IllegalArgumentException( 216 "Invalid authority field: " + authority); 217 } 218 } 219 } else { 220 throw new IllegalArgumentException( 221 "Invalid authority field: " + authority); 222 } 223 } else { 224 ind = host.indexOf(':'); 225 port = -1; 226 if (ind >= 0) { 227 // port can be null according to RFC2396 228 if (host.length() > (ind + 1)) { 229 // BEGIN Android-changed 230 // port = Integer.parseInt(host.substring(ind + 1)); 231 char firstPortChar = host.charAt(ind+1); 232 if (firstPortChar >= '0' && firstPortChar <= '9') { 233 port = Integer.parseInt(host.substring(ind + 1)); 234 } else { 235 throw new IllegalArgumentException("invalid port: " + 236 host.substring(ind + 1)); 237 } 238 // END Android-changed 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 251 // BEGIN Android-changed 252 // If the authority is defined then the path is defined by the 253 // spec only; See RFC 2396 Section 5.2.4. 254 // if (authority != null && authority.length() > 0) 255 // path = ""; 256 path = null; 257 if (!querySet) { 258 query = null; 259 } 260 // END Android-changed 261 } 262 263 if (host == null) { 264 host = ""; 265 } 266 267 // Parse the file path if any 268 if (start < limit) { 269 if (spec.charAt(start) == '/') { 270 path = spec.substring(start, limit); 271 } else if (path != null && path.length() > 0) { 272 isRelPath = true; 273 int ind = path.lastIndexOf('/'); 274 String seperator = ""; 275 if (ind == -1 && authority != null) 276 seperator = "/"; 277 path = path.substring(0, ind + 1) + seperator + 278 spec.substring(start, limit); 279 280 } else { 281 String seperator = (authority != null) ? "/" : ""; 282 path = seperator + spec.substring(start, limit); 283 } 284 } 285 // BEGIN Android-changed 286 //else if (queryOnly && path != null) { 287 // int ind = path.lastIndexOf('/'); 288 // if (ind < 0) 289 // ind = 0; 290 // path = path.substring(0, ind) + "/"; 291 //} 292 // END Android-changed 293 if (path == null) 294 path = ""; 295 296 // BEGIN Android-changed 297 //if (isRelPath) { 298 if (true) { 299 // END Android-changed 300 // Remove embedded /./ 301 while ((i = path.indexOf("/./")) >= 0) { 302 path = path.substring(0, i) + path.substring(i + 2); 303 } 304 // Remove embedded /../ if possible 305 i = 0; 306 while ((i = path.indexOf("/../", i)) >= 0) { 307 // BEGIN Android-changed 308 /* 309 * Trailing /../ 310 */ 311 if (i == 0) { 312 path = path.substring(i + 3); 313 i = 0; 314 // END Android-changed 315 /* 316 * A "/../" will cancel the previous segment and itself, 317 * unless that segment is a "/../" itself 318 * i.e. "/a/b/../c" becomes "/a/c" 319 * but "/../../a" should stay unchanged 320 */ 321 } else if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 && 322 (path.indexOf("/../", limit) != 0)) { 323 path = path.substring(0, limit) + path.substring(i + 3); 324 i = 0; 325 } else { 326 i = i + 3; 327 } 328 } 329 // Remove trailing .. if possible 330 while (path.endsWith("/..")) { 331 i = path.indexOf("/.."); 332 if ((limit = path.lastIndexOf('/', i - 1)) >= 0) { 333 path = path.substring(0, limit+1); 334 } else { 335 break; 336 } 337 } 338 // Remove starting . 339 if (path.startsWith("./") && path.length() > 2) 340 path = path.substring(2); 341 342 // Remove trailing . 343 if (path.endsWith("/.")) 344 path = path.substring(0, path.length() -1); 345 346 // Remove trailing ? 347 if (path.endsWith("?")) 348 path = path.substring(0, path.length() -1); 349 } 350 351 setURL(u, protocol, host, port, authority, userInfo, path, query, ref); 352 } 353 354 /** 355 * Returns the default port for a URL parsed by this handler. This method 356 * is meant to be overidden by handlers with default port numbers. 357 * @return the default port for a {@code URL} parsed by this handler. 358 * @since 1.3 359 */ getDefaultPort()360 protected int getDefaultPort() { 361 return -1; 362 } 363 364 /** 365 * Provides the default equals calculation. May be overidden by handlers 366 * for other protocols that have different requirements for equals(). 367 * This method requires that none of its arguments is null. This is 368 * guaranteed by the fact that it is only called by java.net.URL class. 369 * @param u1 a URL object 370 * @param u2 a URL object 371 * @return {@code true} if the two urls are 372 * considered equal, ie. they refer to the same 373 * fragment in the same file. 374 * @since 1.3 375 */ equals(URL u1, URL u2)376 protected boolean equals(URL u1, URL u2) { 377 return Objects.equals(u1.getRef(), u2.getRef()) && 378 Objects.equals(u1.getQuery(), u2.getQuery()) && 379 // sameFile compares the protocol, file, port & host components of 380 // the URLs. 381 sameFile(u1, u2); 382 } 383 384 /** 385 * Provides the default hash calculation. May be overidden by handlers for 386 * other protocols that have different requirements for hashCode 387 * calculation. 388 * @param u a URL object 389 * @return an {@code int} suitable for hash table indexing 390 * @since 1.3 391 */ hashCode(URL u)392 protected int hashCode(URL u) { 393 // Hash on the same set of fields that we compare in equals(). 394 return Objects.hash( 395 u.getRef(), 396 u.getQuery(), 397 u.getProtocol(), 398 u.getFile(), 399 u.getHost(), 400 u.getPort()); 401 } 402 403 /** 404 * Compare two urls to see whether they refer to the same file, 405 * i.e., having the same protocol, host, port, and path. 406 * This method requires that none of its arguments is null. This is 407 * guaranteed by the fact that it is only called indirectly 408 * by java.net.URL class. 409 * @param u1 a URL object 410 * @param u2 a URL object 411 * @return true if u1 and u2 refer to the same file 412 * @since 1.3 413 */ sameFile(URL u1, URL u2)414 protected boolean sameFile(URL u1, URL u2) { 415 // Compare the protocols. 416 if (!((u1.getProtocol() == u2.getProtocol()) || 417 (u1.getProtocol() != null && 418 u1.getProtocol().equalsIgnoreCase(u2.getProtocol())))) 419 return false; 420 421 // Compare the files. 422 if (!(u1.getFile() == u2.getFile() || 423 (u1.getFile() != null && u1.getFile().equals(u2.getFile())))) 424 return false; 425 426 // Compare the ports. 427 int port1, port2; 428 port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort(); 429 port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort(); 430 if (port1 != port2) 431 return false; 432 433 // Compare the hosts. 434 if (!hostsEqual(u1, u2)) 435 return false; 436 437 return true; 438 } 439 440 /** 441 * Get the IP address of our host. An empty host field or a DNS failure 442 * will result in a null return. 443 * 444 * @param u a URL object 445 * @return an {@code InetAddress} representing the host 446 * IP address. 447 * @since 1.3 448 */ getHostAddress(URL u)449 protected synchronized InetAddress getHostAddress(URL u) { 450 if (u.hostAddress != null) 451 return u.hostAddress; 452 453 String host = u.getHost(); 454 if (host == null || host.equals("")) { 455 return null; 456 } else { 457 try { 458 u.hostAddress = InetAddress.getByName(host); 459 } catch (UnknownHostException ex) { 460 return null; 461 } catch (SecurityException se) { 462 return null; 463 } 464 } 465 return u.hostAddress; 466 } 467 468 /** 469 * Compares the host components of two URLs. 470 * @param u1 the URL of the first host to compare 471 * @param u2 the URL of the second host to compare 472 * @return {@code true} if and only if they 473 * are equal, {@code false} otherwise. 474 * @since 1.3 475 */ hostsEqual(URL u1, URL u2)476 protected boolean hostsEqual(URL u1, URL u2) { 477 // Android-changed: Don't compare the InetAddresses of the hosts. 478 if (u1.getHost() != null && u2.getHost() != null) 479 return u1.getHost().equalsIgnoreCase(u2.getHost()); 480 else 481 return u1.getHost() == null && u2.getHost() == null; 482 } 483 484 /** 485 * Converts a {@code URL} of a specific protocol to a 486 * {@code String}. 487 * 488 * @param u the URL. 489 * @return a string representation of the {@code URL} argument. 490 */ toExternalForm(URL u)491 protected String toExternalForm(URL u) { 492 493 // pre-compute length of StringBuffer 494 int len = u.getProtocol().length() + 1; 495 if (u.getAuthority() != null && u.getAuthority().length() > 0) 496 len += 2 + u.getAuthority().length(); 497 if (u.getPath() != null) { 498 len += u.getPath().length(); 499 } 500 if (u.getQuery() != null) { 501 len += 1 + u.getQuery().length(); 502 } 503 if (u.getRef() != null) 504 len += 1 + u.getRef().length(); 505 506 StringBuilder result = new StringBuilder(len); 507 result.append(u.getProtocol()); 508 result.append(":"); 509 if (u.getAuthority() != null) {// ANDROID: && u.getAuthority().length() > 0) { 510 result.append("//"); 511 result.append(u.getAuthority()); 512 } 513 String fileAndQuery = u.getFile(); 514 if (fileAndQuery != null) { 515 result.append(fileAndQuery); 516 } 517 if (u.getRef() != null) { 518 result.append("#"); 519 result.append(u.getRef()); 520 } 521 return result.toString(); 522 } 523 524 /** 525 * Sets the fields of the {@code URL} argument to the indicated values. 526 * Only classes derived from URLStreamHandler are able 527 * to use this method to set the values of the URL fields. 528 * 529 * @param u the URL to modify. 530 * @param protocol the protocol name. 531 * @param host the remote host value for the URL. 532 * @param port the port on the remote machine. 533 * @param authority the authority part for the URL. 534 * @param userInfo the userInfo part of the URL. 535 * @param path the path component of the URL. 536 * @param query the query part for the URL. 537 * @param ref the reference. 538 * @exception SecurityException if the protocol handler of the URL is 539 * different from this one 540 * @see java.net.URL#set(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String) 541 * @since 1.3 542 */ setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)543 protected void setURL(URL u, String protocol, String host, int port, 544 String authority, String userInfo, String path, 545 String query, String ref) { 546 if (this != u.handler) { 547 throw new SecurityException("handler for url different from " + 548 "this handler"); 549 } 550 // ensure that no one can reset the protocol on a given URL. 551 u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref); 552 } 553 554 /** 555 * Sets the fields of the {@code URL} argument to the indicated values. 556 * Only classes derived from URLStreamHandler are able 557 * to use this method to set the values of the URL fields. 558 * 559 * @param u the URL to modify. 560 * @param protocol the protocol name. This value is ignored since 1.2. 561 * @param host the remote host value for the URL. 562 * @param port the port on the remote machine. 563 * @param file the file. 564 * @param ref the reference. 565 * @exception SecurityException if the protocol handler of the URL is 566 * different from this one 567 * @deprecated Use setURL(URL, String, String, int, String, String, String, 568 * String); 569 */ 570 @Deprecated setURL(URL u, String protocol, String host, int port, String file, String ref)571 protected void setURL(URL u, String protocol, String host, int port, 572 String file, String ref) { 573 /* 574 * Only old URL handlers call this, so assume that the host 575 * field might contain "user:passwd@host". Fix as necessary. 576 */ 577 String authority = null; 578 String userInfo = null; 579 if (host != null && host.length() != 0) { 580 authority = (port == -1) ? host : host + ":" + port; 581 int at = host.lastIndexOf('@'); 582 if (at != -1) { 583 userInfo = host.substring(0, at); 584 host = host.substring(at+1); 585 } 586 } 587 588 /* 589 * Assume file might contain query part. Fix as necessary. 590 */ 591 String path = null; 592 String query = null; 593 if (file != null) { 594 int q = file.lastIndexOf('?'); 595 if (q != -1) { 596 query = file.substring(q+1); 597 path = file.substring(0, q); 598 } else 599 path = file; 600 } 601 setURL(u, protocol, host, port, authority, userInfo, path, query, ref); 602 } 603 } 604