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