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.url.FileHandler; 28 import libcore.net.url.FtpHandler; 29 import libcore.net.url.JarHandler; 30 import 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 */ 75 public 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 */ setURLStreamHandlerFactory(URLStreamHandlerFactory factory)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 */ URL(String spec)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 */ URL(URL context, String spec)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 */ URL(URL context, String spec, URLStreamHandler handler)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 */ URL(String protocol, String host, String file)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 */ URL(String protocol, String host, int port, String file)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 */ URL(String protocol, String host, int port, String file, URLStreamHandler handler)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 fixURL(boolean fixHost)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 */ set(String protocol, String host, int port, String file, String ref)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 many 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 */ equals(Object o)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 */ sameFile(URL otherURL)362 public boolean sameFile(URL otherURL) { 363 return streamHandler.sameFile(this, otherURL); 364 } 365 hashCode()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 */ setupStreamHandler()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 */ getContent()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 getContent(Class[] types)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 */ openStream()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 */ openConnection()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 */ openConnection(Proxy proxy)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 */ toURI()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 */ toURILenient()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 */ toString()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 */ toExternalForm()534 public String toExternalForm() { 535 if (streamHandler == null) { 536 return "unknown protocol(" + protocol + ")://" + host + file; 537 } 538 return streamHandler.toExternalForm(this); 539 } 540 readObject(ObjectInputStream stream)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 writeObject(ObjectOutputStream s)568 private void writeObject(ObjectOutputStream s) throws IOException { 569 s.defaultWriteObject(); 570 } 571 572 /** @hide */ getEffectivePort()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 */ getProtocol()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 */ getAuthority()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 */ getUserInfo()596 public String getUserInfo() { 597 return userInfo; 598 } 599 600 /** 601 * Returns the host name or IP address of this URL. 602 */ getHost()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 */ getPort()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 */ getDefaultPort()625 public int getDefaultPort() { 626 return streamHandler.getDefaultPort(); 627 } 628 629 /** 630 * Returns the file of this URL. 631 */ getFile()632 public String getFile() { 633 return file; 634 } 635 636 /** 637 * Returns the path part of this URL. 638 */ getPath()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 */ getQuery()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 */ getRef()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 */ set(String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)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