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 */ setURLStreamHandlerFactory(URLStreamHandlerFactory factory)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 */ URL(String spec)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 */ URL(URL context, String spec)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 */ URL(URL context, String spec, URLStreamHandler handler)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 */ URL(String protocol, String host, String file)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 */ URL(String protocol, String host, int port, String file)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 */ URL(String protocol, String host, int port, String file, URLStreamHandler handler)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 fixURL(boolean fixHost)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 */ set(String protocol, String host, int port, String file, String ref)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 4.0 (Ice Cream Sandwich). In that 344 * release, URLs are only equal if their host names are equal (ignoring 345 * case). 346 */ equals(Object o)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 */ sameFile(URL otherURL)364 public boolean sameFile(URL otherURL) { 365 return streamHandler.sameFile(this, otherURL); 366 } 367 hashCode()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 */ setupStreamHandler()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 */ getContent()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 getContent(Class[] types)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 */ openStream()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 */ openConnection()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 */ openConnection(Proxy proxy)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 */ toURI()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 */ toURILenient()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 */ toString()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 */ toExternalForm()526 public String toExternalForm() { 527 if (streamHandler == null) { 528 return "unknown protocol(" + protocol + ")://" + host + file; 529 } 530 return streamHandler.toExternalForm(this); 531 } 532 readObject(ObjectInputStream stream)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 writeObject(ObjectOutputStream s)560 private void writeObject(ObjectOutputStream s) throws IOException { 561 s.defaultWriteObject(); 562 } 563 564 /** @hide */ getEffectivePort()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 */ getProtocol()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 */ getAuthority()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 */ getUserInfo()588 public String getUserInfo() { 589 return userInfo; 590 } 591 592 /** 593 * Returns the host name or IP address of this URL. 594 */ getHost()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 */ getPort()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 */ getDefaultPort()617 public int getDefaultPort() { 618 return streamHandler.getDefaultPort(); 619 } 620 621 /** 622 * Returns the file of this URL. 623 */ getFile()624 public String getFile() { 625 return file; 626 } 627 628 /** 629 * Returns the path part of this URL. 630 */ getPath()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 */ getQuery()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 */ getRef()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 */ set(String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)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