1 /* 2 * Copyright (c) 1995, 2021, 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 */ openConnection(URL u)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 */ openConnection(URL u, Proxy p)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 */ parseURL(URL u, String spec, int start, int limit)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.isEmpty()) 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.isEmpty()) { 251 isRelPath = true; 252 int ind = path.lastIndexOf('/'); 253 String seperator = ""; 254 if (ind == -1 && authority != null) 255 seperator = "/"; 256 path = path.substring(0, ind + 1) + seperator + 257 spec.substring(start, limit); 258 259 } else { 260 String seperator = (authority != null) ? "/" : ""; 261 path = seperator + 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 overidden by handlers with default port numbers. 318 * @return the default port for a {@code URL} parsed by this handler. 319 * @since 1.3 320 */ getDefaultPort()321 protected int getDefaultPort() { 322 return -1; 323 } 324 325 /** 326 * Provides the default equals calculation. May be overidden 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, ie. they refer to the same 334 * fragment in the same file. 335 * @since 1.3 336 */ equals(URL u1, URL u2)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 overidden 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 */ hashCode(URL u)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 */ sameFile(URL u1, URL u2)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 */ getHostAddress(URL u)435 protected InetAddress getHostAddress(URL u) { 436 return u.getHostAddress(); 437 } 438 439 /** 440 * Compares the host components of two URLs. 441 * @param u1 the URL of the first host to compare 442 * @param u2 the URL of the second host to compare 443 * @return {@code true} if and only if they 444 * are equal, {@code false} otherwise. 445 * @since 1.3 446 */ hostsEqual(URL u1, URL u2)447 protected boolean hostsEqual(URL u1, URL u2) { 448 InetAddress a1 = getHostAddress(u1); 449 InetAddress a2 = getHostAddress(u2); 450 // if we have internet address for both, compare them 451 if (a1 != null && a2 != null) { 452 return a1.equals(a2); 453 // else, if both have host names, compare them 454 } else if (u1.getHost() != null && u2.getHost() != null) 455 return u1.getHost().equalsIgnoreCase(u2.getHost()); 456 else 457 return u1.getHost() == null && u2.getHost() == null; 458 } 459 460 /** 461 * Converts a {@code URL} of a specific protocol to a 462 * {@code String}. 463 * 464 * @param u the URL. 465 * @return a string representation of the {@code URL} argument. 466 */ toExternalForm(URL u)467 protected String toExternalForm(URL u) { 468 String s; 469 return u.getProtocol() 470 + ':' 471 + ((s = u.getAuthority()) != null && !s.isEmpty() 472 ? "//" + s : "") 473 + ((s = u.getPath()) != null ? s : "") 474 + ((s = u.getQuery()) != null ? '?' + s : "") 475 + ((s = u.getRef()) != null ? '#' + s : ""); 476 } 477 478 /** 479 * Sets the fields of the {@code URL} argument to the indicated values. 480 * Only classes derived from URLStreamHandler are able 481 * to use this method to set the values of the URL fields. 482 * 483 * @param u the URL to modify. 484 * @param protocol the protocol name. 485 * @param host the remote host value for the URL. 486 * @param port the port on the remote machine. 487 * @param authority the authority part for the URL. 488 * @param userInfo the userInfo part of the URL. 489 * @param path the path component of the URL. 490 * @param query the query part for the URL. 491 * @param ref the reference. 492 * @exception SecurityException if the protocol handler of the URL is 493 * different from this one 494 * @since 1.3 495 */ setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)496 protected void setURL(URL u, String protocol, String host, int port, 497 String authority, String userInfo, String path, 498 String query, String ref) { 499 if (this != u.handler) { 500 throw new SecurityException("handler for url different from " + 501 "this handler"); 502 } else if (host != null && u.isBuiltinStreamHandler(this)) { 503 String s = IPAddressUtil.checkHostString(host); 504 if (s != null) throw new IllegalArgumentException(s); 505 } 506 // ensure that no one can reset the protocol on a given URL. 507 u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref); 508 } 509 510 /** 511 * Sets the fields of the {@code URL} argument to the indicated values. 512 * Only classes derived from URLStreamHandler are able 513 * to use this method to set the values of the URL fields. 514 * 515 * @param u the URL to modify. 516 * @param protocol the protocol name. This value is ignored since 1.2. 517 * @param host the remote host value for the URL. 518 * @param port the port on the remote machine. 519 * @param file the file. 520 * @param ref the reference. 521 * @exception SecurityException if the protocol handler of the URL is 522 * different from this one 523 * @deprecated Use setURL(URL, String, String, int, String, String, String, 524 * String); 525 */ 526 @Deprecated setURL(URL u, String protocol, String host, int port, String file, String ref)527 protected void setURL(URL u, String protocol, String host, int port, 528 String file, String ref) { 529 /* 530 * Only old URL handlers call this, so assume that the host 531 * field might contain "user:passwd@host". Fix as necessary. 532 */ 533 String authority = null; 534 String userInfo = null; 535 if (host != null && !host.isEmpty()) { 536 authority = (port == -1) ? host : host + ":" + port; 537 int at = host.lastIndexOf('@'); 538 if (at != -1) { 539 userInfo = host.substring(0, at); 540 host = host.substring(at+1); 541 } 542 } 543 544 /* 545 * Assume file might contain query part. Fix as necessary. 546 */ 547 String path = null; 548 String query = null; 549 if (file != null) { 550 int q = file.lastIndexOf('?'); 551 if (q != -1) { 552 query = file.substring(q+1); 553 path = file.substring(0, q); 554 } else 555 path = file; 556 } 557 setURL(u, protocol, host, port, authority, userInfo, path, query, ref); 558 } 559 } 560