1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.net; 19 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.ObjectInputStream; 23 import java.io.ObjectOutputStream; 24 import java.io.Serializable; 25 import java.util.Hashtable; 26 import java.util.jar.JarFile; 27 import libcore.net.http.HttpHandler; 28 import libcore.net.http.HttpsHandler; 29 import libcore.net.url.FileHandler; 30 import libcore.net.url.FtpHandler; 31 import libcore.net.url.JarHandler; 32 import libcore.net.url.UrlUtils; 33 34 /** 35 * A Uniform Resource Locator that identifies the location of an Internet 36 * resource as specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC 37 * 1738</a>. 38 * 39 * <h3>Parts of a URL</h3> 40 * A URL is composed of many parts. This class can both parse URL strings into 41 * parts and compose URL strings from parts. For example, consider the parts of 42 * this URL: 43 * {@code http://username:password@host:8080/directory/file?query#ref}: 44 * <table> 45 * <tr><th>Component</th><th>Example value</th><th>Also known as</th></tr> 46 * <tr><td>{@link #getProtocol() Protocol}</td><td>{@code http}</td><td>scheme</td></tr> 47 * <tr><td>{@link #getAuthority() Authority}</td><td>{@code username:password@host:8080}</td><td></td></tr> 48 * <tr><td>{@link #getUserInfo() User Info}</td><td>{@code username:password}</td><td></td></tr> 49 * <tr><td>{@link #getHost() Host}</td><td>{@code host}</td><td></td></tr> 50 * <tr><td>{@link #getPort() Port}</td><td>{@code 8080}</td><td></td></tr> 51 * <tr><td>{@link #getFile() File}</td><td>{@code /directory/file?query}</td><td></td></tr> 52 * <tr><td>{@link #getPath() Path}</td><td>{@code /directory/file}</td><td></td></tr> 53 * <tr><td>{@link #getQuery() Query}</td><td>{@code query}</td><td></td></tr> 54 * <tr><td>{@link #getRef() Ref}</td><td>{@code ref}</td><td>fragment</td></tr> 55 * </table> 56 * 57 * <h3>Supported Protocols</h3> 58 * This class may be used to construct URLs with the following protocols: 59 * <ul> 60 * <li><strong>file</strong>: read files from the local filesystem. 61 * <li><strong>ftp</strong>: <a href="http://www.ietf.org/rfc/rfc959.txt">File 62 * Transfer Protocol</a> 63 * <li><strong>http</strong>: <a href="http://www.ietf.org/rfc/rfc2616.txt">Hypertext 64 * Transfer Protocol</a> 65 * <li><strong>https</strong>: <a href="http://www.ietf.org/rfc/rfc2818.txt">HTTP 66 * over TLS</a> 67 * <li><strong>jar</strong>: read {@link JarFile Jar files} from the 68 * filesystem</li> 69 * </ul> 70 * In general, attempts to create URLs with any other protocol will fail with a 71 * {@link MalformedURLException}. Applications may install handlers for other 72 * schemes using {@link #setURLStreamHandlerFactory} or with the {@code 73 * java.protocol.handler.pkgs} system property. 74 * 75 * <p>The {@link URI} class can be used to manipulate URLs of any protocol. 76 */ 77 public final class URL implements Serializable { 78 private static final long serialVersionUID = -7627629688361524110L; 79 80 private static URLStreamHandlerFactory streamHandlerFactory; 81 82 /** Cache of protocols to their handlers */ 83 private static final Hashtable<String, URLStreamHandler> streamHandlers 84 = new Hashtable<String, URLStreamHandler>(); 85 86 private String protocol; 87 private String authority; 88 private String host; 89 private int port = -1; 90 private String file; 91 private String ref; 92 93 private transient String userInfo; 94 private transient String path; 95 private transient String query; 96 97 transient URLStreamHandler streamHandler; 98 99 /** 100 * The cached hash code, or 0 if it hasn't been computed yet. Unlike the RI, 101 * this implementation's hashCode is transient because the hash code is 102 * unspecified and may vary between VMs or versions. 103 */ 104 private transient int hashCode; 105 106 /** 107 * Sets the stream handler factory for this VM. 108 * 109 * @throws Error if a URLStreamHandlerFactory has already been installed 110 * for the current VM. 111 */ 112 public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory factory) { 113 if (streamHandlerFactory != null) { 114 throw new Error("Factory already set"); 115 } 116 streamHandlers.clear(); 117 streamHandlerFactory = factory; 118 } 119 120 /** 121 * Creates a new URL instance by parsing {@code spec}. 122 * 123 * @throws MalformedURLException if {@code spec} could not be parsed as a 124 * URL. 125 */ 126 public URL(String spec) throws MalformedURLException { 127 this((URL) null, spec, null); 128 } 129 130 /** 131 * Creates a new URL by resolving {@code spec} relative to {@code context}. 132 * 133 * @param context the URL to which {@code spec} is relative, or null for 134 * no context in which case {@code spec} must be an absolute URL. 135 * @throws MalformedURLException if {@code spec} could not be parsed as a 136 * URL or has an unsupported protocol. 137 */ 138 public URL(URL context, String spec) throws MalformedURLException { 139 this(context, spec, null); 140 } 141 142 /** 143 * Creates a new URL by resolving {@code spec} relative to {@code context}. 144 * 145 * @param context the URL to which {@code spec} is relative, or null for 146 * no context in which case {@code spec} must be an absolute URL. 147 * @param handler the stream handler for this URL, or null for the 148 * protocol's default stream handler. 149 * @throws MalformedURLException if the given string {@code spec} could not 150 * be parsed as a URL or an invalid protocol has been found. 151 */ 152 public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { 153 if (spec == null) { 154 throw new MalformedURLException(); 155 } 156 if (handler != null) { 157 streamHandler = handler; 158 } 159 spec = spec.trim(); 160 161 protocol = UrlUtils.getSchemePrefix(spec); 162 int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0; 163 164 // If the context URL has a different protocol, discard it because we can't use it. 165 if (protocol != null && context != null && !protocol.equals(context.protocol)) { 166 context = null; 167 } 168 169 // Inherit from the context URL if it exists. 170 if (context != null) { 171 set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(), 172 context.getUserInfo(), context.getPath(), context.getQuery(), 173 context.getRef()); 174 if (streamHandler == null) { 175 streamHandler = context.streamHandler; 176 } 177 } else if (protocol == null) { 178 throw new MalformedURLException("Protocol not found: " + spec); 179 } 180 181 if (streamHandler == null) { 182 setupStreamHandler(); 183 if (streamHandler == null) { 184 throw new MalformedURLException("Unknown protocol: " + protocol); 185 } 186 } 187 188 // Parse the URL. If the handler throws any exception, throw MalformedURLException instead. 189 try { 190 streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length()); 191 } catch (Exception e) { 192 throw new MalformedURLException(e.toString()); 193 } 194 } 195 196 /** 197 * Creates a new URL of the given component parts. The URL uses the 198 * protocol's default port. 199 * 200 * @throws MalformedURLException if the combination of all arguments do not 201 * represent a valid URL or if the protocol is invalid. 202 */ 203 public URL(String protocol, String host, String file) throws MalformedURLException { 204 this(protocol, host, -1, file, null); 205 } 206 207 /** 208 * Creates a new URL of the given component parts. The URL uses the 209 * protocol's default port. 210 * 211 * @param host the host name or IP address of the new URL. 212 * @param port the port, or {@code -1} for the protocol's default port. 213 * @param file the name of the resource. 214 * @throws MalformedURLException if the combination of all arguments do not 215 * represent a valid URL or if the protocol is invalid. 216 */ 217 public URL(String protocol, String host, int port, String file) throws MalformedURLException { 218 this(protocol, host, port, file, null); 219 } 220 221 /** 222 * Creates a new URL of the given component parts. The URL uses the 223 * protocol's default port. 224 * 225 * @param host the host name or IP address of the new URL. 226 * @param port the port, or {@code -1} for the protocol's default port. 227 * @param file the name of the resource. 228 * @param handler the stream handler for this URL, or null for the 229 * protocol's default stream handler. 230 * @throws MalformedURLException if the combination of all arguments do not 231 * represent a valid URL or if the protocol is invalid. 232 */ 233 public URL(String protocol, String host, int port, String file, 234 URLStreamHandler handler) throws MalformedURLException { 235 if (port < -1) { 236 throw new MalformedURLException("port < -1: " + port); 237 } 238 if (protocol == null) { 239 throw new NullPointerException("protocol == null"); 240 } 241 242 // Wrap IPv6 addresses in square brackets if they aren't already. 243 if (host != null && host.contains(":") && host.charAt(0) != '[') { 244 host = "[" + host + "]"; 245 } 246 247 this.protocol = protocol; 248 this.host = host; 249 this.port = port; 250 251 file = UrlUtils.authoritySafePath(host, file); 252 253 // Set the fields from the arguments. Handle the case where the 254 // passed in "file" includes both a file and a reference part. 255 int hash = file.indexOf("#"); 256 if (hash != -1) { 257 this.file = file.substring(0, hash); 258 this.ref = file.substring(hash + 1); 259 } else { 260 this.file = file; 261 } 262 fixURL(false); 263 264 // Set the stream handler for the URL either to the handler 265 // argument if it was specified, or to the default for the 266 // receiver's protocol if the handler was null. 267 if (handler == null) { 268 setupStreamHandler(); 269 if (streamHandler == null) { 270 throw new MalformedURLException("Unknown protocol: " + protocol); 271 } 272 } else { 273 streamHandler = handler; 274 } 275 } 276 277 void fixURL(boolean fixHost) { 278 int index; 279 if (host != null && host.length() > 0) { 280 authority = host; 281 if (port != -1) { 282 authority = authority + ":" + port; 283 } 284 } 285 if (fixHost) { 286 if (host != null && (index = host.lastIndexOf('@')) > -1) { 287 userInfo = host.substring(0, index); 288 host = host.substring(index + 1); 289 } else { 290 userInfo = null; 291 } 292 } 293 if (file != null && (index = file.indexOf('?')) > -1) { 294 query = file.substring(index + 1); 295 path = file.substring(0, index); 296 } else { 297 query = null; 298 path = file; 299 } 300 } 301 302 /** 303 * Sets the properties of this URL using the provided arguments. Only a 304 * {@code URLStreamHandler} can use this method to set fields of the 305 * existing URL instance. A URL is generally constant. 306 */ 307 protected void set(String protocol, String host, int port, String file, String ref) { 308 if (this.protocol == null) { 309 this.protocol = protocol; 310 } 311 this.host = host; 312 this.file = file; 313 this.port = port; 314 this.ref = ref; 315 hashCode = 0; 316 fixURL(true); 317 } 318 319 /** 320 * Returns true if this URL equals {@code o}. URLs are equal if they have 321 * the same protocol, host, port, file, and reference. 322 * 323 * <h3>Network I/O Warning</h3> 324 * <p>Some implementations of URL.equals() resolve host names over the 325 * network. This is problematic: 326 * <ul> 327 * <li><strong>The network may be slow.</strong> Many classes, including 328 * core collections like {@link java.util.Map Map} and {@link java.util.Set 329 * Set} expect that {@code equals} and {@code hashCode} will return quickly. 330 * By violating this assumption, this method posed potential performance 331 * problems. 332 * <li><strong>Equal IP addresses do not imply equal content.</strong> 333 * Virtual hosting permits unrelated sites to share an IP address. This 334 * method could report two otherwise unrelated URLs to be equal because 335 * they're hosted on the same server.</li> 336 * <li><strong>The network many not be available.</strong> Two URLs could be 337 * equal when a network is available and unequal otherwise.</li> 338 * <li><strong>The network may change.</strong> The IP address for a given 339 * host name varies by network and over time. This is problematic for mobile 340 * devices. Two URLs could be equal on some networks and unequal on 341 * others.</li> 342 * </ul> 343 * <p>This problem is fixed in Android in the Ice Cream Sandwich release. In 344 * that release, URLs are only equal if their host names are equal (ignoring 345 * case). 346 */ 347 @Override public boolean equals(Object o) { 348 if (o == null) { 349 return false; 350 } 351 if (this == o) { 352 return true; 353 } 354 if (this.getClass() != o.getClass()) { 355 return false; 356 } 357 return streamHandler.equals(this, (URL) o); 358 } 359 360 /** 361 * Returns true if this URL refers to the same resource as {@code otherURL}. 362 * All URL components except the reference field are compared. 363 */ 364 public boolean sameFile(URL otherURL) { 365 return streamHandler.sameFile(this, otherURL); 366 } 367 368 @Override public int hashCode() { 369 if (hashCode == 0) { 370 hashCode = streamHandler.hashCode(this); 371 } 372 return hashCode; 373 } 374 375 /** 376 * Sets the receiver's stream handler to one which is appropriate for its 377 * protocol. 378 * 379 * <p>Note that this will overwrite any existing stream handler with the new 380 * one. Senders must check if the streamHandler is null before calling the 381 * method if they do not want this behavior (a speed optimization). 382 * 383 * @throws MalformedURLException if no reasonable handler is available. 384 */ 385 void setupStreamHandler() { 386 // Check for a cached (previously looked up) handler for 387 // the requested protocol. 388 streamHandler = streamHandlers.get(protocol); 389 if (streamHandler != null) { 390 return; 391 } 392 393 // If there is a stream handler factory, then attempt to 394 // use it to create the handler. 395 if (streamHandlerFactory != null) { 396 streamHandler = streamHandlerFactory.createURLStreamHandler(protocol); 397 if (streamHandler != null) { 398 streamHandlers.put(protocol, streamHandler); 399 return; 400 } 401 } 402 403 // Check if there is a list of packages which can provide handlers. 404 // If so, then walk this list looking for an applicable one. 405 String packageList = System.getProperty("java.protocol.handler.pkgs"); 406 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 407 if (packageList != null && contextClassLoader != null) { 408 for (String packageName : packageList.split("\\|")) { 409 String className = packageName + "." + protocol + ".Handler"; 410 try { 411 Class<?> c = contextClassLoader.loadClass(className); 412 streamHandler = (URLStreamHandler) c.newInstance(); 413 if (streamHandler != null) { 414 streamHandlers.put(protocol, streamHandler); 415 } 416 return; 417 } catch (IllegalAccessException ignored) { 418 } catch (InstantiationException ignored) { 419 } catch (ClassNotFoundException ignored) { 420 } 421 } 422 } 423 424 // Fall back to a built-in stream handler if the user didn't supply one 425 if (protocol.equals("file")) { 426 streamHandler = new FileHandler(); 427 } else if (protocol.equals("ftp")) { 428 streamHandler = new FtpHandler(); 429 } else if (protocol.equals("http")) { 430 streamHandler = new HttpHandler(); 431 } else if (protocol.equals("https")) { 432 streamHandler = new HttpsHandler(); 433 } else if (protocol.equals("jar")) { 434 streamHandler = new JarHandler(); 435 } 436 if (streamHandler != null) { 437 streamHandlers.put(protocol, streamHandler); 438 } 439 } 440 441 /** 442 * Returns the content of the resource which is referred by this URL. By 443 * default this returns an {@code InputStream}, or null if the content type 444 * of the response is unknown. 445 */ 446 public final Object getContent() throws IOException { 447 return openConnection().getContent(); 448 } 449 450 /** 451 * Equivalent to {@code openConnection().getContent(types)}. 452 */ 453 @SuppressWarnings("unchecked") // Param not generic in spec 454 public final Object getContent(Class[] types) throws IOException { 455 return openConnection().getContent(types); 456 } 457 458 /** 459 * Equivalent to {@code openConnection().getInputStream(types)}. 460 */ 461 public final InputStream openStream() throws IOException { 462 return openConnection().getInputStream(); 463 } 464 465 /** 466 * Returns a new connection to the resource referred to by this URL. 467 * 468 * @throws IOException if an error occurs while opening the connection. 469 */ 470 public URLConnection openConnection() throws IOException { 471 return streamHandler.openConnection(this); 472 } 473 474 /** 475 * Returns a new connection to the resource referred to by this URL. 476 * 477 * @param proxy the proxy through which the connection will be established. 478 * @throws IOException if an I/O error occurs while opening the connection. 479 * @throws IllegalArgumentException if the argument proxy is null or of is 480 * an invalid type. 481 * @throws UnsupportedOperationException if the protocol handler does not 482 * support opening connections through proxies. 483 */ 484 public URLConnection openConnection(Proxy proxy) throws IOException { 485 if (proxy == null) { 486 throw new IllegalArgumentException("proxy == null"); 487 } 488 return streamHandler.openConnection(this, proxy); 489 } 490 491 /** 492 * Returns the URI equivalent to this URL. 493 * 494 * @throws URISyntaxException if this URL cannot be converted into a URI. 495 */ 496 public URI toURI() throws URISyntaxException { 497 return new URI(toExternalForm()); 498 } 499 500 /** 501 * Encodes this URL to the equivalent URI after escaping characters that are 502 * not permitted by URI. 503 * 504 * @hide 505 */ 506 public URI toURILenient() throws URISyntaxException { 507 if (streamHandler == null) { 508 throw new IllegalStateException(protocol); 509 } 510 return new URI(streamHandler.toExternalForm(this, true)); 511 } 512 513 /** 514 * Returns a string containing a concise, human-readable representation of 515 * this URL. The returned string is the same as the result of the method 516 * {@code toExternalForm()}. 517 */ 518 @Override public String toString() { 519 return toExternalForm(); 520 } 521 522 /** 523 * Returns a string containing a concise, human-readable representation of 524 * this URL. 525 */ 526 public String toExternalForm() { 527 if (streamHandler == null) { 528 return "unknown protocol(" + protocol + ")://" + host + file; 529 } 530 return streamHandler.toExternalForm(this); 531 } 532 533 private void readObject(ObjectInputStream stream) throws IOException { 534 try { 535 stream.defaultReadObject(); 536 if (host != null && authority == null) { 537 fixURL(true); 538 } else if (authority != null) { 539 int index; 540 if ((index = authority.lastIndexOf('@')) > -1) { 541 userInfo = authority.substring(0, index); 542 } 543 if (file != null && (index = file.indexOf('?')) > -1) { 544 query = file.substring(index + 1); 545 path = file.substring(0, index); 546 } else { 547 path = file; 548 } 549 } 550 setupStreamHandler(); 551 if (streamHandler == null) { 552 throw new IOException("Unknown protocol: " + protocol); 553 } 554 hashCode = 0; // necessary until http://b/4471249 is fixed 555 } catch (ClassNotFoundException e) { 556 throw new IOException(e); 557 } 558 } 559 560 private void writeObject(ObjectOutputStream s) throws IOException { 561 s.defaultWriteObject(); 562 } 563 564 /** @hide */ 565 public int getEffectivePort() { 566 return URI.getEffectivePort(protocol, port); 567 } 568 569 /** 570 * Returns the protocol of this URL like "http" or "file". This is also 571 * known as the scheme. The returned string is lower case. 572 */ 573 public String getProtocol() { 574 return protocol; 575 } 576 577 /** 578 * Returns the authority part of this URL, or null if this URL has no 579 * authority. 580 */ 581 public String getAuthority() { 582 return authority; 583 } 584 585 /** 586 * Returns the user info of this URL, or null if this URL has no user info. 587 */ 588 public String getUserInfo() { 589 return userInfo; 590 } 591 592 /** 593 * Returns the host name or IP address of this URL. 594 */ 595 public String getHost() { 596 return host; 597 } 598 599 /** 600 * Returns the port number of this URL or {@code -1} if this URL has no 601 * explicit port. 602 * 603 * <p>If this URL has no explicit port, connections opened using this URL 604 * will use its {@link #getDefaultPort() default port}. 605 */ 606 public int getPort() { 607 return port; 608 } 609 610 /** 611 * Returns the default port number of the protocol used by this URL. If no 612 * default port is defined by the protocol or the {@code URLStreamHandler}, 613 * {@code -1} will be returned. 614 * 615 * @see URLStreamHandler#getDefaultPort 616 */ 617 public int getDefaultPort() { 618 return streamHandler.getDefaultPort(); 619 } 620 621 /** 622 * Returns the file of this URL. 623 */ 624 public String getFile() { 625 return file; 626 } 627 628 /** 629 * Returns the path part of this URL. 630 */ 631 public String getPath() { 632 return path; 633 } 634 635 /** 636 * Returns the query part of this URL, or null if this URL has no query. 637 */ 638 public String getQuery() { 639 return query; 640 } 641 642 /** 643 * Returns the value of the reference part of this URL, or null if this URL 644 * has no reference part. This is also known as the fragment. 645 */ 646 public String getRef() { 647 return ref; 648 } 649 650 /** 651 * Sets the properties of this URL using the provided arguments. Only a 652 * {@code URLStreamHandler} can use this method to set fields of the 653 * existing URL instance. A URL is generally constant. 654 */ 655 protected void set(String protocol, String host, int port, String authority, String userInfo, 656 String path, String query, String ref) { 657 String file = path; 658 if (query != null && !query.isEmpty()) { 659 file += "?" + query; 660 } 661 set(protocol, host, port, file, ref); 662 this.authority = authority; 663 this.userInfo = userInfo; 664 this.path = path; 665 this.query = query; 666 } 667 } 668