1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net; 18 19 import android.os.Parcel; 20 import android.os.Parcelable; 21 import android.util.Log; 22 import java.io.File; 23 import java.io.UnsupportedEncodingException; 24 import java.net.URLEncoder; 25 import java.nio.charset.Charsets; 26 import java.util.AbstractList; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.LinkedHashSet; 30 import java.util.List; 31 import java.util.Locale; 32 import java.util.RandomAccess; 33 import java.util.Set; 34 import libcore.net.UriCodec; 35 36 /** 37 * Immutable URI reference. A URI reference includes a URI and a fragment, the 38 * component of the URI following a '#'. Builds and parses URI references 39 * which conform to 40 * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>. 41 * 42 * <p>In the interest of performance, this class performs little to no 43 * validation. Behavior is undefined for invalid input. This class is very 44 * forgiving--in the face of invalid input, it will return garbage 45 * rather than throw an exception unless otherwise specified. 46 */ 47 public abstract class Uri implements Parcelable, Comparable<Uri> { 48 49 /* 50 51 This class aims to do as little up front work as possible. To accomplish 52 that, we vary the implementation depending on what the user passes in. 53 For example, we have one implementation if the user passes in a 54 URI string (StringUri) and another if the user passes in the 55 individual components (OpaqueUri). 56 57 *Concurrency notes*: Like any truly immutable object, this class is safe 58 for concurrent use. This class uses a caching pattern in some places where 59 it doesn't use volatile or synchronized. This is safe to do with ints 60 because getting or setting an int is atomic. It's safe to do with a String 61 because the internal fields are final and the memory model guarantees other 62 threads won't see a partially initialized instance. We are not guaranteed 63 that some threads will immediately see changes from other threads on 64 certain platforms, but we don't mind if those threads reconstruct the 65 cached result. As a result, we get thread safe caching with no concurrency 66 overhead, which means the most common case, access from a single thread, 67 is as fast as possible. 68 69 From the Java Language spec.: 70 71 "17.5 Final Field Semantics 72 73 ... when the object is seen by another thread, that thread will always 74 see the correctly constructed version of that object's final fields. 75 It will also see versions of any object or array referenced by 76 those final fields that are at least as up-to-date as the final fields 77 are." 78 79 In that same vein, all non-transient fields within Uri 80 implementations should be final and immutable so as to ensure true 81 immutability for clients even when they don't use proper concurrency 82 control. 83 84 For reference, from RFC 2396: 85 86 "4.3. Parsing a URI Reference 87 88 A URI reference is typically parsed according to the four main 89 components and fragment identifier in order to determine what 90 components are present and whether the reference is relative or 91 absolute. The individual components are then parsed for their 92 subparts and, if not opaque, to verify their validity. 93 94 Although the BNF defines what is allowed in each component, it is 95 ambiguous in terms of differentiating between an authority component 96 and a path component that begins with two slash characters. The 97 greedy algorithm is used for disambiguation: the left-most matching 98 rule soaks up as much of the URI reference string as it is capable of 99 matching. In other words, the authority component wins." 100 101 The "four main components" of a hierarchical URI consist of 102 <scheme>://<authority><path>?<query> 103 104 */ 105 106 /** Log tag. */ 107 private static final String LOG = Uri.class.getSimpleName(); 108 109 /** 110 * NOTE: EMPTY accesses this field during its own initialization, so this 111 * field *must* be initialized first, or else EMPTY will see a null value! 112 * 113 * Placeholder for strings which haven't been cached. This enables us 114 * to cache null. We intentionally create a new String instance so we can 115 * compare its identity and there is no chance we will confuse it with 116 * user data. 117 */ 118 @SuppressWarnings("RedundantStringConstructorCall") 119 private static final String NOT_CACHED = new String("NOT CACHED"); 120 121 /** 122 * The empty URI, equivalent to "". 123 */ 124 public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL, 125 PathPart.EMPTY, Part.NULL, Part.NULL); 126 127 /** 128 * Prevents external subclassing. 129 */ Uri()130 private Uri() {} 131 132 /** 133 * Returns true if this URI is hierarchical like "http://google.com". 134 * Absolute URIs are hierarchical if the scheme-specific part starts with 135 * a '/'. Relative URIs are always hierarchical. 136 */ isHierarchical()137 public abstract boolean isHierarchical(); 138 139 /** 140 * Returns true if this URI is opaque like "mailto:nobody@google.com". The 141 * scheme-specific part of an opaque URI cannot start with a '/'. 142 */ isOpaque()143 public boolean isOpaque() { 144 return !isHierarchical(); 145 } 146 147 /** 148 * Returns true if this URI is relative, i.e. if it doesn't contain an 149 * explicit scheme. 150 * 151 * @return true if this URI is relative, false if it's absolute 152 */ isRelative()153 public abstract boolean isRelative(); 154 155 /** 156 * Returns true if this URI is absolute, i.e. if it contains an 157 * explicit scheme. 158 * 159 * @return true if this URI is absolute, false if it's relative 160 */ isAbsolute()161 public boolean isAbsolute() { 162 return !isRelative(); 163 } 164 165 /** 166 * Gets the scheme of this URI. Example: "http" 167 * 168 * @return the scheme or null if this is a relative URI 169 */ getScheme()170 public abstract String getScheme(); 171 172 /** 173 * Gets the scheme-specific part of this URI, i.e. everything between the 174 * scheme separator ':' and the fragment separator '#'. If this is a 175 * relative URI, this method returns the entire URI. Decodes escaped octets. 176 * 177 * <p>Example: "//www.google.com/search?q=android" 178 * 179 * @return the decoded scheme-specific-part 180 */ getSchemeSpecificPart()181 public abstract String getSchemeSpecificPart(); 182 183 /** 184 * Gets the scheme-specific part of this URI, i.e. everything between the 185 * scheme separator ':' and the fragment separator '#'. If this is a 186 * relative URI, this method returns the entire URI. Leaves escaped octets 187 * intact. 188 * 189 * <p>Example: "//www.google.com/search?q=android" 190 * 191 * @return the decoded scheme-specific-part 192 */ getEncodedSchemeSpecificPart()193 public abstract String getEncodedSchemeSpecificPart(); 194 195 /** 196 * Gets the decoded authority part of this URI. For 197 * server addresses, the authority is structured as follows: 198 * {@code [ userinfo '@' ] host [ ':' port ]} 199 * 200 * <p>Examples: "google.com", "bob@google.com:80" 201 * 202 * @return the authority for this URI or null if not present 203 */ getAuthority()204 public abstract String getAuthority(); 205 206 /** 207 * Gets the encoded authority part of this URI. For 208 * server addresses, the authority is structured as follows: 209 * {@code [ userinfo '@' ] host [ ':' port ]} 210 * 211 * <p>Examples: "google.com", "bob@google.com:80" 212 * 213 * @return the authority for this URI or null if not present 214 */ getEncodedAuthority()215 public abstract String getEncodedAuthority(); 216 217 /** 218 * Gets the decoded user information from the authority. 219 * For example, if the authority is "nobody@google.com", this method will 220 * return "nobody". 221 * 222 * @return the user info for this URI or null if not present 223 */ getUserInfo()224 public abstract String getUserInfo(); 225 226 /** 227 * Gets the encoded user information from the authority. 228 * For example, if the authority is "nobody@google.com", this method will 229 * return "nobody". 230 * 231 * @return the user info for this URI or null if not present 232 */ getEncodedUserInfo()233 public abstract String getEncodedUserInfo(); 234 235 /** 236 * Gets the encoded host from the authority for this URI. For example, 237 * if the authority is "bob@google.com", this method will return 238 * "google.com". 239 * 240 * @return the host for this URI or null if not present 241 */ getHost()242 public abstract String getHost(); 243 244 /** 245 * Gets the port from the authority for this URI. For example, 246 * if the authority is "google.com:80", this method will return 80. 247 * 248 * @return the port for this URI or -1 if invalid or not present 249 */ getPort()250 public abstract int getPort(); 251 252 /** 253 * Gets the decoded path. 254 * 255 * @return the decoded path, or null if this is not a hierarchical URI 256 * (like "mailto:nobody@google.com") or the URI is invalid 257 */ getPath()258 public abstract String getPath(); 259 260 /** 261 * Gets the encoded path. 262 * 263 * @return the encoded path, or null if this is not a hierarchical URI 264 * (like "mailto:nobody@google.com") or the URI is invalid 265 */ getEncodedPath()266 public abstract String getEncodedPath(); 267 268 /** 269 * Gets the decoded query component from this URI. The query comes after 270 * the query separator ('?') and before the fragment separator ('#'). This 271 * method would return "q=android" for 272 * "http://www.google.com/search?q=android". 273 * 274 * @return the decoded query or null if there isn't one 275 */ getQuery()276 public abstract String getQuery(); 277 278 /** 279 * Gets the encoded query component from this URI. The query comes after 280 * the query separator ('?') and before the fragment separator ('#'). This 281 * method would return "q=android" for 282 * "http://www.google.com/search?q=android". 283 * 284 * @return the encoded query or null if there isn't one 285 */ getEncodedQuery()286 public abstract String getEncodedQuery(); 287 288 /** 289 * Gets the decoded fragment part of this URI, everything after the '#'. 290 * 291 * @return the decoded fragment or null if there isn't one 292 */ getFragment()293 public abstract String getFragment(); 294 295 /** 296 * Gets the encoded fragment part of this URI, everything after the '#'. 297 * 298 * @return the encoded fragment or null if there isn't one 299 */ getEncodedFragment()300 public abstract String getEncodedFragment(); 301 302 /** 303 * Gets the decoded path segments. 304 * 305 * @return decoded path segments, each without a leading or trailing '/' 306 */ getPathSegments()307 public abstract List<String> getPathSegments(); 308 309 /** 310 * Gets the decoded last segment in the path. 311 * 312 * @return the decoded last segment or null if the path is empty 313 */ getLastPathSegment()314 public abstract String getLastPathSegment(); 315 316 /** 317 * Compares this Uri to another object for equality. Returns true if the 318 * encoded string representations of this Uri and the given Uri are 319 * equal. Case counts. Paths are not normalized. If one Uri specifies a 320 * default port explicitly and the other leaves it implicit, they will not 321 * be considered equal. 322 */ equals(Object o)323 public boolean equals(Object o) { 324 if (!(o instanceof Uri)) { 325 return false; 326 } 327 328 Uri other = (Uri) o; 329 330 return toString().equals(other.toString()); 331 } 332 333 /** 334 * Hashes the encoded string represention of this Uri consistently with 335 * {@link #equals(Object)}. 336 */ hashCode()337 public int hashCode() { 338 return toString().hashCode(); 339 } 340 341 /** 342 * Compares the string representation of this Uri with that of 343 * another. 344 */ compareTo(Uri other)345 public int compareTo(Uri other) { 346 return toString().compareTo(other.toString()); 347 } 348 349 /** 350 * Returns the encoded string representation of this URI. 351 * Example: "http://google.com/" 352 */ toString()353 public abstract String toString(); 354 355 /** 356 * Return a string representation of the URI that is safe to print 357 * to logs and other places where PII should be avoided. 358 * @hide 359 */ toSafeString()360 public String toSafeString() { 361 String scheme = getScheme(); 362 String ssp = getSchemeSpecificPart(); 363 if (scheme != null) { 364 if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") 365 || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") 366 || scheme.equalsIgnoreCase("mailto")) { 367 StringBuilder builder = new StringBuilder(64); 368 builder.append(scheme); 369 builder.append(':'); 370 if (ssp != null) { 371 for (int i=0; i<ssp.length(); i++) { 372 char c = ssp.charAt(i); 373 if (c == '-' || c == '@' || c == '.') { 374 builder.append(c); 375 } else { 376 builder.append('x'); 377 } 378 } 379 } 380 return builder.toString(); 381 } 382 } 383 // Not a sensitive scheme, but let's still be conservative about 384 // the data we include -- only the ssp, not the query params or 385 // fragment, because those can often have sensitive info. 386 StringBuilder builder = new StringBuilder(64); 387 if (scheme != null) { 388 builder.append(scheme); 389 builder.append(':'); 390 } 391 if (ssp != null) { 392 builder.append(ssp); 393 } 394 return builder.toString(); 395 } 396 397 /** 398 * Constructs a new builder, copying the attributes from this Uri. 399 */ buildUpon()400 public abstract Builder buildUpon(); 401 402 /** Index of a component which was not found. */ 403 private final static int NOT_FOUND = -1; 404 405 /** Placeholder value for an index which hasn't been calculated yet. */ 406 private final static int NOT_CALCULATED = -2; 407 408 /** 409 * Error message presented when a user tries to treat an opaque URI as 410 * hierarchical. 411 */ 412 private static final String NOT_HIERARCHICAL 413 = "This isn't a hierarchical URI."; 414 415 /** Default encoding. */ 416 private static final String DEFAULT_ENCODING = "UTF-8"; 417 418 /** 419 * Creates a Uri which parses the given encoded URI string. 420 * 421 * @param uriString an RFC 2396-compliant, encoded URI 422 * @throws NullPointerException if uriString is null 423 * @return Uri for this given uri string 424 */ parse(String uriString)425 public static Uri parse(String uriString) { 426 return new StringUri(uriString); 427 } 428 429 /** 430 * Creates a Uri from a file. The URI has the form 431 * "file://<absolute path>". Encodes path characters with the exception of 432 * '/'. 433 * 434 * <p>Example: "file:///tmp/android.txt" 435 * 436 * @throws NullPointerException if file is null 437 * @return a Uri for the given file 438 */ fromFile(File file)439 public static Uri fromFile(File file) { 440 if (file == null) { 441 throw new NullPointerException("file"); 442 } 443 444 PathPart path = PathPart.fromDecoded(file.getAbsolutePath()); 445 return new HierarchicalUri( 446 "file", Part.EMPTY, path, Part.NULL, Part.NULL); 447 } 448 449 /** 450 * An implementation which wraps a String URI. This URI can be opaque or 451 * hierarchical, but we extend AbstractHierarchicalUri in case we need 452 * the hierarchical functionality. 453 */ 454 private static class StringUri extends AbstractHierarchicalUri { 455 456 /** Used in parcelling. */ 457 static final int TYPE_ID = 1; 458 459 /** URI string representation. */ 460 private final String uriString; 461 StringUri(String uriString)462 private StringUri(String uriString) { 463 if (uriString == null) { 464 throw new NullPointerException("uriString"); 465 } 466 467 this.uriString = uriString; 468 } 469 readFrom(Parcel parcel)470 static Uri readFrom(Parcel parcel) { 471 return new StringUri(parcel.readString()); 472 } 473 describeContents()474 public int describeContents() { 475 return 0; 476 } 477 writeToParcel(Parcel parcel, int flags)478 public void writeToParcel(Parcel parcel, int flags) { 479 parcel.writeInt(TYPE_ID); 480 parcel.writeString(uriString); 481 } 482 483 /** Cached scheme separator index. */ 484 private volatile int cachedSsi = NOT_CALCULATED; 485 486 /** Finds the first ':'. Returns -1 if none found. */ findSchemeSeparator()487 private int findSchemeSeparator() { 488 return cachedSsi == NOT_CALCULATED 489 ? cachedSsi = uriString.indexOf(':') 490 : cachedSsi; 491 } 492 493 /** Cached fragment separator index. */ 494 private volatile int cachedFsi = NOT_CALCULATED; 495 496 /** Finds the first '#'. Returns -1 if none found. */ findFragmentSeparator()497 private int findFragmentSeparator() { 498 return cachedFsi == NOT_CALCULATED 499 ? cachedFsi = uriString.indexOf('#', findSchemeSeparator()) 500 : cachedFsi; 501 } 502 isHierarchical()503 public boolean isHierarchical() { 504 int ssi = findSchemeSeparator(); 505 506 if (ssi == NOT_FOUND) { 507 // All relative URIs are hierarchical. 508 return true; 509 } 510 511 if (uriString.length() == ssi + 1) { 512 // No ssp. 513 return false; 514 } 515 516 // If the ssp starts with a '/', this is hierarchical. 517 return uriString.charAt(ssi + 1) == '/'; 518 } 519 isRelative()520 public boolean isRelative() { 521 // Note: We return true if the index is 0 522 return findSchemeSeparator() == NOT_FOUND; 523 } 524 525 private volatile String scheme = NOT_CACHED; 526 getScheme()527 public String getScheme() { 528 @SuppressWarnings("StringEquality") 529 boolean cached = (scheme != NOT_CACHED); 530 return cached ? scheme : (scheme = parseScheme()); 531 } 532 parseScheme()533 private String parseScheme() { 534 int ssi = findSchemeSeparator(); 535 return ssi == NOT_FOUND ? null : uriString.substring(0, ssi); 536 } 537 538 private Part ssp; 539 getSsp()540 private Part getSsp() { 541 return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp; 542 } 543 getEncodedSchemeSpecificPart()544 public String getEncodedSchemeSpecificPart() { 545 return getSsp().getEncoded(); 546 } 547 getSchemeSpecificPart()548 public String getSchemeSpecificPart() { 549 return getSsp().getDecoded(); 550 } 551 parseSsp()552 private String parseSsp() { 553 int ssi = findSchemeSeparator(); 554 int fsi = findFragmentSeparator(); 555 556 // Return everything between ssi and fsi. 557 return fsi == NOT_FOUND 558 ? uriString.substring(ssi + 1) 559 : uriString.substring(ssi + 1, fsi); 560 } 561 562 private Part authority; 563 getAuthorityPart()564 private Part getAuthorityPart() { 565 if (authority == null) { 566 String encodedAuthority 567 = parseAuthority(this.uriString, findSchemeSeparator()); 568 return authority = Part.fromEncoded(encodedAuthority); 569 } 570 571 return authority; 572 } 573 getEncodedAuthority()574 public String getEncodedAuthority() { 575 return getAuthorityPart().getEncoded(); 576 } 577 getAuthority()578 public String getAuthority() { 579 return getAuthorityPart().getDecoded(); 580 } 581 582 private PathPart path; 583 getPathPart()584 private PathPart getPathPart() { 585 return path == null 586 ? path = PathPart.fromEncoded(parsePath()) 587 : path; 588 } 589 getPath()590 public String getPath() { 591 return getPathPart().getDecoded(); 592 } 593 getEncodedPath()594 public String getEncodedPath() { 595 return getPathPart().getEncoded(); 596 } 597 getPathSegments()598 public List<String> getPathSegments() { 599 return getPathPart().getPathSegments(); 600 } 601 parsePath()602 private String parsePath() { 603 String uriString = this.uriString; 604 int ssi = findSchemeSeparator(); 605 606 // If the URI is absolute. 607 if (ssi > -1) { 608 // Is there anything after the ':'? 609 boolean schemeOnly = ssi + 1 == uriString.length(); 610 if (schemeOnly) { 611 // Opaque URI. 612 return null; 613 } 614 615 // A '/' after the ':' means this is hierarchical. 616 if (uriString.charAt(ssi + 1) != '/') { 617 // Opaque URI. 618 return null; 619 } 620 } else { 621 // All relative URIs are hierarchical. 622 } 623 624 return parsePath(uriString, ssi); 625 } 626 627 private Part query; 628 getQueryPart()629 private Part getQueryPart() { 630 return query == null 631 ? query = Part.fromEncoded(parseQuery()) : query; 632 } 633 getEncodedQuery()634 public String getEncodedQuery() { 635 return getQueryPart().getEncoded(); 636 } 637 parseQuery()638 private String parseQuery() { 639 // It doesn't make sense to cache this index. We only ever 640 // calculate it once. 641 int qsi = uriString.indexOf('?', findSchemeSeparator()); 642 if (qsi == NOT_FOUND) { 643 return null; 644 } 645 646 int fsi = findFragmentSeparator(); 647 648 if (fsi == NOT_FOUND) { 649 return uriString.substring(qsi + 1); 650 } 651 652 if (fsi < qsi) { 653 // Invalid. 654 return null; 655 } 656 657 return uriString.substring(qsi + 1, fsi); 658 } 659 getQuery()660 public String getQuery() { 661 return getQueryPart().getDecoded(); 662 } 663 664 private Part fragment; 665 getFragmentPart()666 private Part getFragmentPart() { 667 return fragment == null 668 ? fragment = Part.fromEncoded(parseFragment()) : fragment; 669 } 670 getEncodedFragment()671 public String getEncodedFragment() { 672 return getFragmentPart().getEncoded(); 673 } 674 parseFragment()675 private String parseFragment() { 676 int fsi = findFragmentSeparator(); 677 return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1); 678 } 679 getFragment()680 public String getFragment() { 681 return getFragmentPart().getDecoded(); 682 } 683 toString()684 public String toString() { 685 return uriString; 686 } 687 688 /** 689 * Parses an authority out of the given URI string. 690 * 691 * @param uriString URI string 692 * @param ssi scheme separator index, -1 for a relative URI 693 * 694 * @return the authority or null if none is found 695 */ parseAuthority(String uriString, int ssi)696 static String parseAuthority(String uriString, int ssi) { 697 int length = uriString.length(); 698 699 // If "//" follows the scheme separator, we have an authority. 700 if (length > ssi + 2 701 && uriString.charAt(ssi + 1) == '/' 702 && uriString.charAt(ssi + 2) == '/') { 703 // We have an authority. 704 705 // Look for the start of the path, query, or fragment, or the 706 // end of the string. 707 int end = ssi + 3; 708 LOOP: while (end < length) { 709 switch (uriString.charAt(end)) { 710 case '/': // Start of path 711 case '?': // Start of query 712 case '#': // Start of fragment 713 break LOOP; 714 } 715 end++; 716 } 717 718 return uriString.substring(ssi + 3, end); 719 } else { 720 return null; 721 } 722 723 } 724 725 /** 726 * Parses a path out of this given URI string. 727 * 728 * @param uriString URI string 729 * @param ssi scheme separator index, -1 for a relative URI 730 * 731 * @return the path 732 */ parsePath(String uriString, int ssi)733 static String parsePath(String uriString, int ssi) { 734 int length = uriString.length(); 735 736 // Find start of path. 737 int pathStart; 738 if (length > ssi + 2 739 && uriString.charAt(ssi + 1) == '/' 740 && uriString.charAt(ssi + 2) == '/') { 741 // Skip over authority to path. 742 pathStart = ssi + 3; 743 LOOP: while (pathStart < length) { 744 switch (uriString.charAt(pathStart)) { 745 case '?': // Start of query 746 case '#': // Start of fragment 747 return ""; // Empty path. 748 case '/': // Start of path! 749 break LOOP; 750 } 751 pathStart++; 752 } 753 } else { 754 // Path starts immediately after scheme separator. 755 pathStart = ssi + 1; 756 } 757 758 // Find end of path. 759 int pathEnd = pathStart; 760 LOOP: while (pathEnd < length) { 761 switch (uriString.charAt(pathEnd)) { 762 case '?': // Start of query 763 case '#': // Start of fragment 764 break LOOP; 765 } 766 pathEnd++; 767 } 768 769 return uriString.substring(pathStart, pathEnd); 770 } 771 buildUpon()772 public Builder buildUpon() { 773 if (isHierarchical()) { 774 return new Builder() 775 .scheme(getScheme()) 776 .authority(getAuthorityPart()) 777 .path(getPathPart()) 778 .query(getQueryPart()) 779 .fragment(getFragmentPart()); 780 } else { 781 return new Builder() 782 .scheme(getScheme()) 783 .opaquePart(getSsp()) 784 .fragment(getFragmentPart()); 785 } 786 } 787 } 788 789 /** 790 * Creates an opaque Uri from the given components. Encodes the ssp 791 * which means this method cannot be used to create hierarchical URIs. 792 * 793 * @param scheme of the URI 794 * @param ssp scheme-specific-part, everything between the 795 * scheme separator (':') and the fragment separator ('#'), which will 796 * get encoded 797 * @param fragment fragment, everything after the '#', null if undefined, 798 * will get encoded 799 * 800 * @throws NullPointerException if scheme or ssp is null 801 * @return Uri composed of the given scheme, ssp, and fragment 802 * 803 * @see Builder if you don't want the ssp and fragment to be encoded 804 */ fromParts(String scheme, String ssp, String fragment)805 public static Uri fromParts(String scheme, String ssp, 806 String fragment) { 807 if (scheme == null) { 808 throw new NullPointerException("scheme"); 809 } 810 if (ssp == null) { 811 throw new NullPointerException("ssp"); 812 } 813 814 return new OpaqueUri(scheme, Part.fromDecoded(ssp), 815 Part.fromDecoded(fragment)); 816 } 817 818 /** 819 * Opaque URI. 820 */ 821 private static class OpaqueUri extends Uri { 822 823 /** Used in parcelling. */ 824 static final int TYPE_ID = 2; 825 826 private final String scheme; 827 private final Part ssp; 828 private final Part fragment; 829 OpaqueUri(String scheme, Part ssp, Part fragment)830 private OpaqueUri(String scheme, Part ssp, Part fragment) { 831 this.scheme = scheme; 832 this.ssp = ssp; 833 this.fragment = fragment == null ? Part.NULL : fragment; 834 } 835 readFrom(Parcel parcel)836 static Uri readFrom(Parcel parcel) { 837 return new OpaqueUri( 838 parcel.readString(), 839 Part.readFrom(parcel), 840 Part.readFrom(parcel) 841 ); 842 } 843 describeContents()844 public int describeContents() { 845 return 0; 846 } 847 writeToParcel(Parcel parcel, int flags)848 public void writeToParcel(Parcel parcel, int flags) { 849 parcel.writeInt(TYPE_ID); 850 parcel.writeString(scheme); 851 ssp.writeTo(parcel); 852 fragment.writeTo(parcel); 853 } 854 isHierarchical()855 public boolean isHierarchical() { 856 return false; 857 } 858 isRelative()859 public boolean isRelative() { 860 return scheme == null; 861 } 862 getScheme()863 public String getScheme() { 864 return this.scheme; 865 } 866 getEncodedSchemeSpecificPart()867 public String getEncodedSchemeSpecificPart() { 868 return ssp.getEncoded(); 869 } 870 getSchemeSpecificPart()871 public String getSchemeSpecificPart() { 872 return ssp.getDecoded(); 873 } 874 getAuthority()875 public String getAuthority() { 876 return null; 877 } 878 getEncodedAuthority()879 public String getEncodedAuthority() { 880 return null; 881 } 882 getPath()883 public String getPath() { 884 return null; 885 } 886 getEncodedPath()887 public String getEncodedPath() { 888 return null; 889 } 890 getQuery()891 public String getQuery() { 892 return null; 893 } 894 getEncodedQuery()895 public String getEncodedQuery() { 896 return null; 897 } 898 getFragment()899 public String getFragment() { 900 return fragment.getDecoded(); 901 } 902 getEncodedFragment()903 public String getEncodedFragment() { 904 return fragment.getEncoded(); 905 } 906 getPathSegments()907 public List<String> getPathSegments() { 908 return Collections.emptyList(); 909 } 910 getLastPathSegment()911 public String getLastPathSegment() { 912 return null; 913 } 914 getUserInfo()915 public String getUserInfo() { 916 return null; 917 } 918 getEncodedUserInfo()919 public String getEncodedUserInfo() { 920 return null; 921 } 922 getHost()923 public String getHost() { 924 return null; 925 } 926 getPort()927 public int getPort() { 928 return -1; 929 } 930 931 private volatile String cachedString = NOT_CACHED; 932 toString()933 public String toString() { 934 @SuppressWarnings("StringEquality") 935 boolean cached = cachedString != NOT_CACHED; 936 if (cached) { 937 return cachedString; 938 } 939 940 StringBuilder sb = new StringBuilder(); 941 942 sb.append(scheme).append(':'); 943 sb.append(getEncodedSchemeSpecificPart()); 944 945 if (!fragment.isEmpty()) { 946 sb.append('#').append(fragment.getEncoded()); 947 } 948 949 return cachedString = sb.toString(); 950 } 951 buildUpon()952 public Builder buildUpon() { 953 return new Builder() 954 .scheme(this.scheme) 955 .opaquePart(this.ssp) 956 .fragment(this.fragment); 957 } 958 } 959 960 /** 961 * Wrapper for path segment array. 962 */ 963 static class PathSegments extends AbstractList<String> 964 implements RandomAccess { 965 966 static final PathSegments EMPTY = new PathSegments(null, 0); 967 968 final String[] segments; 969 final int size; 970 PathSegments(String[] segments, int size)971 PathSegments(String[] segments, int size) { 972 this.segments = segments; 973 this.size = size; 974 } 975 get(int index)976 public String get(int index) { 977 if (index >= size) { 978 throw new IndexOutOfBoundsException(); 979 } 980 981 return segments[index]; 982 } 983 size()984 public int size() { 985 return this.size; 986 } 987 } 988 989 /** 990 * Builds PathSegments. 991 */ 992 static class PathSegmentsBuilder { 993 994 String[] segments; 995 int size = 0; 996 add(String segment)997 void add(String segment) { 998 if (segments == null) { 999 segments = new String[4]; 1000 } else if (size + 1 == segments.length) { 1001 String[] expanded = new String[segments.length * 2]; 1002 System.arraycopy(segments, 0, expanded, 0, segments.length); 1003 segments = expanded; 1004 } 1005 1006 segments[size++] = segment; 1007 } 1008 build()1009 PathSegments build() { 1010 if (segments == null) { 1011 return PathSegments.EMPTY; 1012 } 1013 1014 try { 1015 return new PathSegments(segments, size); 1016 } finally { 1017 // Makes sure this doesn't get reused. 1018 segments = null; 1019 } 1020 } 1021 } 1022 1023 /** 1024 * Support for hierarchical URIs. 1025 */ 1026 private abstract static class AbstractHierarchicalUri extends Uri { 1027 getLastPathSegment()1028 public String getLastPathSegment() { 1029 // TODO: If we haven't parsed all of the segments already, just 1030 // grab the last one directly so we only allocate one string. 1031 1032 List<String> segments = getPathSegments(); 1033 int size = segments.size(); 1034 if (size == 0) { 1035 return null; 1036 } 1037 return segments.get(size - 1); 1038 } 1039 1040 private Part userInfo; 1041 getUserInfoPart()1042 private Part getUserInfoPart() { 1043 return userInfo == null 1044 ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo; 1045 } 1046 getEncodedUserInfo()1047 public final String getEncodedUserInfo() { 1048 return getUserInfoPart().getEncoded(); 1049 } 1050 parseUserInfo()1051 private String parseUserInfo() { 1052 String authority = getEncodedAuthority(); 1053 if (authority == null) { 1054 return null; 1055 } 1056 1057 int end = authority.indexOf('@'); 1058 return end == NOT_FOUND ? null : authority.substring(0, end); 1059 } 1060 getUserInfo()1061 public String getUserInfo() { 1062 return getUserInfoPart().getDecoded(); 1063 } 1064 1065 private volatile String host = NOT_CACHED; 1066 getHost()1067 public String getHost() { 1068 @SuppressWarnings("StringEquality") 1069 boolean cached = (host != NOT_CACHED); 1070 return cached ? host 1071 : (host = parseHost()); 1072 } 1073 parseHost()1074 private String parseHost() { 1075 String authority = getEncodedAuthority(); 1076 if (authority == null) { 1077 return null; 1078 } 1079 1080 // Parse out user info and then port. 1081 int userInfoSeparator = authority.indexOf('@'); 1082 int portSeparator = authority.indexOf(':', userInfoSeparator); 1083 1084 String encodedHost = portSeparator == NOT_FOUND 1085 ? authority.substring(userInfoSeparator + 1) 1086 : authority.substring(userInfoSeparator + 1, portSeparator); 1087 1088 return decode(encodedHost); 1089 } 1090 1091 private volatile int port = NOT_CALCULATED; 1092 getPort()1093 public int getPort() { 1094 return port == NOT_CALCULATED 1095 ? port = parsePort() 1096 : port; 1097 } 1098 parsePort()1099 private int parsePort() { 1100 String authority = getEncodedAuthority(); 1101 if (authority == null) { 1102 return -1; 1103 } 1104 1105 // Make sure we look for the port separtor *after* the user info 1106 // separator. We have URLs with a ':' in the user info. 1107 int userInfoSeparator = authority.indexOf('@'); 1108 int portSeparator = authority.indexOf(':', userInfoSeparator); 1109 1110 if (portSeparator == NOT_FOUND) { 1111 return -1; 1112 } 1113 1114 String portString = decode(authority.substring(portSeparator + 1)); 1115 try { 1116 return Integer.parseInt(portString); 1117 } catch (NumberFormatException e) { 1118 Log.w(LOG, "Error parsing port string.", e); 1119 return -1; 1120 } 1121 } 1122 } 1123 1124 /** 1125 * Hierarchical Uri. 1126 */ 1127 private static class HierarchicalUri extends AbstractHierarchicalUri { 1128 1129 /** Used in parcelling. */ 1130 static final int TYPE_ID = 3; 1131 1132 private final String scheme; // can be null 1133 private final Part authority; 1134 private final PathPart path; 1135 private final Part query; 1136 private final Part fragment; 1137 HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment)1138 private HierarchicalUri(String scheme, Part authority, PathPart path, 1139 Part query, Part fragment) { 1140 this.scheme = scheme; 1141 this.authority = Part.nonNull(authority); 1142 this.path = path == null ? PathPart.NULL : path; 1143 this.query = Part.nonNull(query); 1144 this.fragment = Part.nonNull(fragment); 1145 } 1146 readFrom(Parcel parcel)1147 static Uri readFrom(Parcel parcel) { 1148 return new HierarchicalUri( 1149 parcel.readString(), 1150 Part.readFrom(parcel), 1151 PathPart.readFrom(parcel), 1152 Part.readFrom(parcel), 1153 Part.readFrom(parcel) 1154 ); 1155 } 1156 describeContents()1157 public int describeContents() { 1158 return 0; 1159 } 1160 writeToParcel(Parcel parcel, int flags)1161 public void writeToParcel(Parcel parcel, int flags) { 1162 parcel.writeInt(TYPE_ID); 1163 parcel.writeString(scheme); 1164 authority.writeTo(parcel); 1165 path.writeTo(parcel); 1166 query.writeTo(parcel); 1167 fragment.writeTo(parcel); 1168 } 1169 isHierarchical()1170 public boolean isHierarchical() { 1171 return true; 1172 } 1173 isRelative()1174 public boolean isRelative() { 1175 return scheme == null; 1176 } 1177 getScheme()1178 public String getScheme() { 1179 return scheme; 1180 } 1181 1182 private Part ssp; 1183 getSsp()1184 private Part getSsp() { 1185 return ssp == null 1186 ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp; 1187 } 1188 getEncodedSchemeSpecificPart()1189 public String getEncodedSchemeSpecificPart() { 1190 return getSsp().getEncoded(); 1191 } 1192 getSchemeSpecificPart()1193 public String getSchemeSpecificPart() { 1194 return getSsp().getDecoded(); 1195 } 1196 1197 /** 1198 * Creates the encoded scheme-specific part from its sub parts. 1199 */ makeSchemeSpecificPart()1200 private String makeSchemeSpecificPart() { 1201 StringBuilder builder = new StringBuilder(); 1202 appendSspTo(builder); 1203 return builder.toString(); 1204 } 1205 appendSspTo(StringBuilder builder)1206 private void appendSspTo(StringBuilder builder) { 1207 String encodedAuthority = authority.getEncoded(); 1208 if (encodedAuthority != null) { 1209 // Even if the authority is "", we still want to append "//". 1210 builder.append("//").append(encodedAuthority); 1211 } 1212 1213 String encodedPath = path.getEncoded(); 1214 if (encodedPath != null) { 1215 builder.append(encodedPath); 1216 } 1217 1218 if (!query.isEmpty()) { 1219 builder.append('?').append(query.getEncoded()); 1220 } 1221 } 1222 getAuthority()1223 public String getAuthority() { 1224 return this.authority.getDecoded(); 1225 } 1226 getEncodedAuthority()1227 public String getEncodedAuthority() { 1228 return this.authority.getEncoded(); 1229 } 1230 getEncodedPath()1231 public String getEncodedPath() { 1232 return this.path.getEncoded(); 1233 } 1234 getPath()1235 public String getPath() { 1236 return this.path.getDecoded(); 1237 } 1238 getQuery()1239 public String getQuery() { 1240 return this.query.getDecoded(); 1241 } 1242 getEncodedQuery()1243 public String getEncodedQuery() { 1244 return this.query.getEncoded(); 1245 } 1246 getFragment()1247 public String getFragment() { 1248 return this.fragment.getDecoded(); 1249 } 1250 getEncodedFragment()1251 public String getEncodedFragment() { 1252 return this.fragment.getEncoded(); 1253 } 1254 getPathSegments()1255 public List<String> getPathSegments() { 1256 return this.path.getPathSegments(); 1257 } 1258 1259 private volatile String uriString = NOT_CACHED; 1260 1261 @Override toString()1262 public String toString() { 1263 @SuppressWarnings("StringEquality") 1264 boolean cached = (uriString != NOT_CACHED); 1265 return cached ? uriString 1266 : (uriString = makeUriString()); 1267 } 1268 makeUriString()1269 private String makeUriString() { 1270 StringBuilder builder = new StringBuilder(); 1271 1272 if (scheme != null) { 1273 builder.append(scheme).append(':'); 1274 } 1275 1276 appendSspTo(builder); 1277 1278 if (!fragment.isEmpty()) { 1279 builder.append('#').append(fragment.getEncoded()); 1280 } 1281 1282 return builder.toString(); 1283 } 1284 buildUpon()1285 public Builder buildUpon() { 1286 return new Builder() 1287 .scheme(scheme) 1288 .authority(authority) 1289 .path(path) 1290 .query(query) 1291 .fragment(fragment); 1292 } 1293 } 1294 1295 /** 1296 * Helper class for building or manipulating URI references. Not safe for 1297 * concurrent use. 1298 * 1299 * <p>An absolute hierarchical URI reference follows the pattern: 1300 * {@code <scheme>://<authority><absolute path>?<query>#<fragment>} 1301 * 1302 * <p>Relative URI references (which are always hierarchical) follow one 1303 * of two patterns: {@code <relative or absolute path>?<query>#<fragment>} 1304 * or {@code //<authority><absolute path>?<query>#<fragment>} 1305 * 1306 * <p>An opaque URI follows this pattern: 1307 * {@code <scheme>:<opaque part>#<fragment>} 1308 * 1309 * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI. 1310 */ 1311 public static final class Builder { 1312 1313 private String scheme; 1314 private Part opaquePart; 1315 private Part authority; 1316 private PathPart path; 1317 private Part query; 1318 private Part fragment; 1319 1320 /** 1321 * Constructs a new Builder. 1322 */ Builder()1323 public Builder() {} 1324 1325 /** 1326 * Sets the scheme. 1327 * 1328 * @param scheme name or {@code null} if this is a relative Uri 1329 */ scheme(String scheme)1330 public Builder scheme(String scheme) { 1331 this.scheme = scheme; 1332 return this; 1333 } 1334 opaquePart(Part opaquePart)1335 Builder opaquePart(Part opaquePart) { 1336 this.opaquePart = opaquePart; 1337 return this; 1338 } 1339 1340 /** 1341 * Encodes and sets the given opaque scheme-specific-part. 1342 * 1343 * @param opaquePart decoded opaque part 1344 */ opaquePart(String opaquePart)1345 public Builder opaquePart(String opaquePart) { 1346 return opaquePart(Part.fromDecoded(opaquePart)); 1347 } 1348 1349 /** 1350 * Sets the previously encoded opaque scheme-specific-part. 1351 * 1352 * @param opaquePart encoded opaque part 1353 */ encodedOpaquePart(String opaquePart)1354 public Builder encodedOpaquePart(String opaquePart) { 1355 return opaquePart(Part.fromEncoded(opaquePart)); 1356 } 1357 authority(Part authority)1358 Builder authority(Part authority) { 1359 // This URI will be hierarchical. 1360 this.opaquePart = null; 1361 1362 this.authority = authority; 1363 return this; 1364 } 1365 1366 /** 1367 * Encodes and sets the authority. 1368 */ authority(String authority)1369 public Builder authority(String authority) { 1370 return authority(Part.fromDecoded(authority)); 1371 } 1372 1373 /** 1374 * Sets the previously encoded authority. 1375 */ encodedAuthority(String authority)1376 public Builder encodedAuthority(String authority) { 1377 return authority(Part.fromEncoded(authority)); 1378 } 1379 path(PathPart path)1380 Builder path(PathPart path) { 1381 // This URI will be hierarchical. 1382 this.opaquePart = null; 1383 1384 this.path = path; 1385 return this; 1386 } 1387 1388 /** 1389 * Sets the path. Leaves '/' characters intact but encodes others as 1390 * necessary. 1391 * 1392 * <p>If the path is not null and doesn't start with a '/', and if 1393 * you specify a scheme and/or authority, the builder will prepend the 1394 * given path with a '/'. 1395 */ path(String path)1396 public Builder path(String path) { 1397 return path(PathPart.fromDecoded(path)); 1398 } 1399 1400 /** 1401 * Sets the previously encoded path. 1402 * 1403 * <p>If the path is not null and doesn't start with a '/', and if 1404 * you specify a scheme and/or authority, the builder will prepend the 1405 * given path with a '/'. 1406 */ encodedPath(String path)1407 public Builder encodedPath(String path) { 1408 return path(PathPart.fromEncoded(path)); 1409 } 1410 1411 /** 1412 * Encodes the given segment and appends it to the path. 1413 */ appendPath(String newSegment)1414 public Builder appendPath(String newSegment) { 1415 return path(PathPart.appendDecodedSegment(path, newSegment)); 1416 } 1417 1418 /** 1419 * Appends the given segment to the path. 1420 */ appendEncodedPath(String newSegment)1421 public Builder appendEncodedPath(String newSegment) { 1422 return path(PathPart.appendEncodedSegment(path, newSegment)); 1423 } 1424 query(Part query)1425 Builder query(Part query) { 1426 // This URI will be hierarchical. 1427 this.opaquePart = null; 1428 1429 this.query = query; 1430 return this; 1431 } 1432 1433 /** 1434 * Encodes and sets the query. 1435 */ query(String query)1436 public Builder query(String query) { 1437 return query(Part.fromDecoded(query)); 1438 } 1439 1440 /** 1441 * Sets the previously encoded query. 1442 */ encodedQuery(String query)1443 public Builder encodedQuery(String query) { 1444 return query(Part.fromEncoded(query)); 1445 } 1446 fragment(Part fragment)1447 Builder fragment(Part fragment) { 1448 this.fragment = fragment; 1449 return this; 1450 } 1451 1452 /** 1453 * Encodes and sets the fragment. 1454 */ fragment(String fragment)1455 public Builder fragment(String fragment) { 1456 return fragment(Part.fromDecoded(fragment)); 1457 } 1458 1459 /** 1460 * Sets the previously encoded fragment. 1461 */ encodedFragment(String fragment)1462 public Builder encodedFragment(String fragment) { 1463 return fragment(Part.fromEncoded(fragment)); 1464 } 1465 1466 /** 1467 * Encodes the key and value and then appends the parameter to the 1468 * query string. 1469 * 1470 * @param key which will be encoded 1471 * @param value which will be encoded 1472 */ appendQueryParameter(String key, String value)1473 public Builder appendQueryParameter(String key, String value) { 1474 // This URI will be hierarchical. 1475 this.opaquePart = null; 1476 1477 String encodedParameter = encode(key, null) + "=" 1478 + encode(value, null); 1479 1480 if (query == null) { 1481 query = Part.fromEncoded(encodedParameter); 1482 return this; 1483 } 1484 1485 String oldQuery = query.getEncoded(); 1486 if (oldQuery == null || oldQuery.length() == 0) { 1487 query = Part.fromEncoded(encodedParameter); 1488 } else { 1489 query = Part.fromEncoded(oldQuery + "&" + encodedParameter); 1490 } 1491 1492 return this; 1493 } 1494 1495 /** 1496 * Clears the the previously set query. 1497 */ clearQuery()1498 public Builder clearQuery() { 1499 return query((Part) null); 1500 } 1501 1502 /** 1503 * Constructs a Uri with the current attributes. 1504 * 1505 * @throws UnsupportedOperationException if the URI is opaque and the 1506 * scheme is null 1507 */ build()1508 public Uri build() { 1509 if (opaquePart != null) { 1510 if (this.scheme == null) { 1511 throw new UnsupportedOperationException( 1512 "An opaque URI must have a scheme."); 1513 } 1514 1515 return new OpaqueUri(scheme, opaquePart, fragment); 1516 } else { 1517 // Hierarchical URIs should not return null for getPath(). 1518 PathPart path = this.path; 1519 if (path == null || path == PathPart.NULL) { 1520 path = PathPart.EMPTY; 1521 } else { 1522 // If we have a scheme and/or authority, the path must 1523 // be absolute. Prepend it with a '/' if necessary. 1524 if (hasSchemeOrAuthority()) { 1525 path = PathPart.makeAbsolute(path); 1526 } 1527 } 1528 1529 return new HierarchicalUri( 1530 scheme, authority, path, query, fragment); 1531 } 1532 } 1533 hasSchemeOrAuthority()1534 private boolean hasSchemeOrAuthority() { 1535 return scheme != null 1536 || (authority != null && authority != Part.NULL); 1537 1538 } 1539 1540 @Override toString()1541 public String toString() { 1542 return build().toString(); 1543 } 1544 } 1545 1546 /** 1547 * Returns a set of the unique names of all query parameters. Iterating 1548 * over the set will return the names in order of their first occurrence. 1549 * 1550 * @throws UnsupportedOperationException if this isn't a hierarchical URI 1551 * 1552 * @return a set of decoded names 1553 */ getQueryParameterNames()1554 public Set<String> getQueryParameterNames() { 1555 if (isOpaque()) { 1556 throw new UnsupportedOperationException(NOT_HIERARCHICAL); 1557 } 1558 1559 String query = getEncodedQuery(); 1560 if (query == null) { 1561 return Collections.emptySet(); 1562 } 1563 1564 Set<String> names = new LinkedHashSet<String>(); 1565 int start = 0; 1566 do { 1567 int next = query.indexOf('&', start); 1568 int end = (next == -1) ? query.length() : next; 1569 1570 int separator = query.indexOf('=', start); 1571 if (separator > end || separator == -1) { 1572 separator = end; 1573 } 1574 1575 String name = query.substring(start, separator); 1576 names.add(decode(name)); 1577 1578 // Move start to end of name. 1579 start = end + 1; 1580 } while (start < query.length()); 1581 1582 return Collections.unmodifiableSet(names); 1583 } 1584 1585 /** 1586 * Searches the query string for parameter values with the given key. 1587 * 1588 * @param key which will be encoded 1589 * 1590 * @throws UnsupportedOperationException if this isn't a hierarchical URI 1591 * @throws NullPointerException if key is null 1592 * @return a list of decoded values 1593 */ getQueryParameters(String key)1594 public List<String> getQueryParameters(String key) { 1595 if (isOpaque()) { 1596 throw new UnsupportedOperationException(NOT_HIERARCHICAL); 1597 } 1598 if (key == null) { 1599 throw new NullPointerException("key"); 1600 } 1601 1602 String query = getEncodedQuery(); 1603 if (query == null) { 1604 return Collections.emptyList(); 1605 } 1606 1607 String encodedKey; 1608 try { 1609 encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING); 1610 } catch (UnsupportedEncodingException e) { 1611 throw new AssertionError(e); 1612 } 1613 1614 ArrayList<String> values = new ArrayList<String>(); 1615 1616 int start = 0; 1617 do { 1618 int nextAmpersand = query.indexOf('&', start); 1619 int end = nextAmpersand != -1 ? nextAmpersand : query.length(); 1620 1621 int separator = query.indexOf('=', start); 1622 if (separator > end || separator == -1) { 1623 separator = end; 1624 } 1625 1626 if (separator - start == encodedKey.length() 1627 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { 1628 if (separator == end) { 1629 values.add(""); 1630 } else { 1631 values.add(decode(query.substring(separator + 1, end))); 1632 } 1633 } 1634 1635 // Move start to end of name. 1636 if (nextAmpersand != -1) { 1637 start = nextAmpersand + 1; 1638 } else { 1639 break; 1640 } 1641 } while (true); 1642 1643 return Collections.unmodifiableList(values); 1644 } 1645 1646 /** 1647 * Searches the query string for the first value with the given key. 1648 * 1649 * <p><strong>Warning:</strong> Prior to Ice Cream Sandwich, this decoded 1650 * the '+' character as '+' rather than ' '. 1651 * 1652 * @param key which will be encoded 1653 * @throws UnsupportedOperationException if this isn't a hierarchical URI 1654 * @throws NullPointerException if key is null 1655 * @return the decoded value or null if no parameter is found 1656 */ getQueryParameter(String key)1657 public String getQueryParameter(String key) { 1658 if (isOpaque()) { 1659 throw new UnsupportedOperationException(NOT_HIERARCHICAL); 1660 } 1661 if (key == null) { 1662 throw new NullPointerException("key"); 1663 } 1664 1665 final String query = getEncodedQuery(); 1666 if (query == null) { 1667 return null; 1668 } 1669 1670 final String encodedKey = encode(key, null); 1671 final int length = query.length(); 1672 int start = 0; 1673 do { 1674 int nextAmpersand = query.indexOf('&', start); 1675 int end = nextAmpersand != -1 ? nextAmpersand : length; 1676 1677 int separator = query.indexOf('=', start); 1678 if (separator > end || separator == -1) { 1679 separator = end; 1680 } 1681 1682 if (separator - start == encodedKey.length() 1683 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) { 1684 if (separator == end) { 1685 return ""; 1686 } else { 1687 String encodedValue = query.substring(separator + 1, end); 1688 return UriCodec.decode(encodedValue, true, Charsets.UTF_8, false); 1689 } 1690 } 1691 1692 // Move start to end of name. 1693 if (nextAmpersand != -1) { 1694 start = nextAmpersand + 1; 1695 } else { 1696 break; 1697 } 1698 } while (true); 1699 return null; 1700 } 1701 1702 /** 1703 * Searches the query string for the first value with the given key and interprets it 1704 * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything 1705 * else is interpreted as <code>true</code>. 1706 * 1707 * @param key which will be decoded 1708 * @param defaultValue the default value to return if there is no query parameter for key 1709 * @return the boolean interpretation of the query parameter key 1710 */ getBooleanQueryParameter(String key, boolean defaultValue)1711 public boolean getBooleanQueryParameter(String key, boolean defaultValue) { 1712 String flag = getQueryParameter(key); 1713 if (flag == null) { 1714 return defaultValue; 1715 } 1716 flag = flag.toLowerCase(); 1717 return (!"false".equals(flag) && !"0".equals(flag)); 1718 } 1719 1720 /** 1721 * Return an equivalent URI with a lowercase scheme component. 1722 * This aligns the Uri with Android best practices for 1723 * intent filtering. 1724 * 1725 * <p>For example, "HTTP://www.android.com" becomes 1726 * "http://www.android.com" 1727 * 1728 * <p>All URIs received from outside Android (such as user input, 1729 * or external sources like Bluetooth, NFC, or the Internet) should 1730 * be normalized before they are used to create an Intent. 1731 * 1732 * <p class="note">This method does <em>not</em> validate bad URI's, 1733 * or 'fix' poorly formatted URI's - so do not use it for input validation. 1734 * A Uri will always be returned, even if the Uri is badly formatted to 1735 * begin with and a scheme component cannot be found. 1736 * 1737 * @return normalized Uri (never null) 1738 * @see {@link android.content.Intent#setData} 1739 * @see {@link #setNormalizedData} 1740 */ normalizeScheme()1741 public Uri normalizeScheme() { 1742 String scheme = getScheme(); 1743 if (scheme == null) return this; // give up 1744 String lowerScheme = scheme.toLowerCase(Locale.US); 1745 if (scheme.equals(lowerScheme)) return this; // no change 1746 1747 return buildUpon().scheme(lowerScheme).build(); 1748 } 1749 1750 /** Identifies a null parcelled Uri. */ 1751 private static final int NULL_TYPE_ID = 0; 1752 1753 /** 1754 * Reads Uris from Parcels. 1755 */ 1756 public static final Parcelable.Creator<Uri> CREATOR 1757 = new Parcelable.Creator<Uri>() { 1758 public Uri createFromParcel(Parcel in) { 1759 int type = in.readInt(); 1760 switch (type) { 1761 case NULL_TYPE_ID: return null; 1762 case StringUri.TYPE_ID: return StringUri.readFrom(in); 1763 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in); 1764 case HierarchicalUri.TYPE_ID: 1765 return HierarchicalUri.readFrom(in); 1766 } 1767 1768 throw new IllegalArgumentException("Unknown URI type: " + type); 1769 } 1770 1771 public Uri[] newArray(int size) { 1772 return new Uri[size]; 1773 } 1774 }; 1775 1776 /** 1777 * Writes a Uri to a Parcel. 1778 * 1779 * @param out parcel to write to 1780 * @param uri to write, can be null 1781 */ writeToParcel(Parcel out, Uri uri)1782 public static void writeToParcel(Parcel out, Uri uri) { 1783 if (uri == null) { 1784 out.writeInt(NULL_TYPE_ID); 1785 } else { 1786 uri.writeToParcel(out, 0); 1787 } 1788 } 1789 1790 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); 1791 1792 /** 1793 * Encodes characters in the given string as '%'-escaped octets 1794 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers 1795 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes 1796 * all other characters. 1797 * 1798 * @param s string to encode 1799 * @return an encoded version of s suitable for use as a URI component, 1800 * or null if s is null 1801 */ encode(String s)1802 public static String encode(String s) { 1803 return encode(s, null); 1804 } 1805 1806 /** 1807 * Encodes characters in the given string as '%'-escaped octets 1808 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers 1809 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes 1810 * all other characters with the exception of those specified in the 1811 * allow argument. 1812 * 1813 * @param s string to encode 1814 * @param allow set of additional characters to allow in the encoded form, 1815 * null if no characters should be skipped 1816 * @return an encoded version of s suitable for use as a URI component, 1817 * or null if s is null 1818 */ encode(String s, String allow)1819 public static String encode(String s, String allow) { 1820 if (s == null) { 1821 return null; 1822 } 1823 1824 // Lazily-initialized buffers. 1825 StringBuilder encoded = null; 1826 1827 int oldLength = s.length(); 1828 1829 // This loop alternates between copying over allowed characters and 1830 // encoding in chunks. This results in fewer method calls and 1831 // allocations than encoding one character at a time. 1832 int current = 0; 1833 while (current < oldLength) { 1834 // Start in "copying" mode where we copy over allowed chars. 1835 1836 // Find the next character which needs to be encoded. 1837 int nextToEncode = current; 1838 while (nextToEncode < oldLength 1839 && isAllowed(s.charAt(nextToEncode), allow)) { 1840 nextToEncode++; 1841 } 1842 1843 // If there's nothing more to encode... 1844 if (nextToEncode == oldLength) { 1845 if (current == 0) { 1846 // We didn't need to encode anything! 1847 return s; 1848 } else { 1849 // Presumably, we've already done some encoding. 1850 encoded.append(s, current, oldLength); 1851 return encoded.toString(); 1852 } 1853 } 1854 1855 if (encoded == null) { 1856 encoded = new StringBuilder(); 1857 } 1858 1859 if (nextToEncode > current) { 1860 // Append allowed characters leading up to this point. 1861 encoded.append(s, current, nextToEncode); 1862 } else { 1863 // assert nextToEncode == current 1864 } 1865 1866 // Switch to "encoding" mode. 1867 1868 // Find the next allowed character. 1869 current = nextToEncode; 1870 int nextAllowed = current + 1; 1871 while (nextAllowed < oldLength 1872 && !isAllowed(s.charAt(nextAllowed), allow)) { 1873 nextAllowed++; 1874 } 1875 1876 // Convert the substring to bytes and encode the bytes as 1877 // '%'-escaped octets. 1878 String toEncode = s.substring(current, nextAllowed); 1879 try { 1880 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING); 1881 int bytesLength = bytes.length; 1882 for (int i = 0; i < bytesLength; i++) { 1883 encoded.append('%'); 1884 encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]); 1885 encoded.append(HEX_DIGITS[bytes[i] & 0xf]); 1886 } 1887 } catch (UnsupportedEncodingException e) { 1888 throw new AssertionError(e); 1889 } 1890 1891 current = nextAllowed; 1892 } 1893 1894 // Encoded could still be null at this point if s is empty. 1895 return encoded == null ? s : encoded.toString(); 1896 } 1897 1898 /** 1899 * Returns true if the given character is allowed. 1900 * 1901 * @param c character to check 1902 * @param allow characters to allow 1903 * @return true if the character is allowed or false if it should be 1904 * encoded 1905 */ isAllowed(char c, String allow)1906 private static boolean isAllowed(char c, String allow) { 1907 return (c >= 'A' && c <= 'Z') 1908 || (c >= 'a' && c <= 'z') 1909 || (c >= '0' && c <= '9') 1910 || "_-!.~'()*".indexOf(c) != NOT_FOUND 1911 || (allow != null && allow.indexOf(c) != NOT_FOUND); 1912 } 1913 1914 /** 1915 * Decodes '%'-escaped octets in the given string using the UTF-8 scheme. 1916 * Replaces invalid octets with the unicode replacement character 1917 * ("\\uFFFD"). 1918 * 1919 * @param s encoded string to decode 1920 * @return the given string with escaped octets decoded, or null if 1921 * s is null 1922 */ decode(String s)1923 public static String decode(String s) { 1924 if (s == null) { 1925 return null; 1926 } 1927 return UriCodec.decode(s, false, Charsets.UTF_8, false); 1928 } 1929 1930 /** 1931 * Support for part implementations. 1932 */ 1933 static abstract class AbstractPart { 1934 1935 /** 1936 * Enum which indicates which representation of a given part we have. 1937 */ 1938 static class Representation { 1939 static final int BOTH = 0; 1940 static final int ENCODED = 1; 1941 static final int DECODED = 2; 1942 } 1943 1944 volatile String encoded; 1945 volatile String decoded; 1946 AbstractPart(String encoded, String decoded)1947 AbstractPart(String encoded, String decoded) { 1948 this.encoded = encoded; 1949 this.decoded = decoded; 1950 } 1951 getEncoded()1952 abstract String getEncoded(); 1953 getDecoded()1954 final String getDecoded() { 1955 @SuppressWarnings("StringEquality") 1956 boolean hasDecoded = decoded != NOT_CACHED; 1957 return hasDecoded ? decoded : (decoded = decode(encoded)); 1958 } 1959 writeTo(Parcel parcel)1960 final void writeTo(Parcel parcel) { 1961 @SuppressWarnings("StringEquality") 1962 boolean hasEncoded = encoded != NOT_CACHED; 1963 1964 @SuppressWarnings("StringEquality") 1965 boolean hasDecoded = decoded != NOT_CACHED; 1966 1967 if (hasEncoded && hasDecoded) { 1968 parcel.writeInt(Representation.BOTH); 1969 parcel.writeString(encoded); 1970 parcel.writeString(decoded); 1971 } else if (hasEncoded) { 1972 parcel.writeInt(Representation.ENCODED); 1973 parcel.writeString(encoded); 1974 } else if (hasDecoded) { 1975 parcel.writeInt(Representation.DECODED); 1976 parcel.writeString(decoded); 1977 } else { 1978 throw new IllegalArgumentException("Neither encoded nor decoded"); 1979 } 1980 } 1981 } 1982 1983 /** 1984 * Immutable wrapper of encoded and decoded versions of a URI part. Lazily 1985 * creates the encoded or decoded version from the other. 1986 */ 1987 static class Part extends AbstractPart { 1988 1989 /** A part with null values. */ 1990 static final Part NULL = new EmptyPart(null); 1991 1992 /** A part with empty strings for values. */ 1993 static final Part EMPTY = new EmptyPart(""); 1994 Part(String encoded, String decoded)1995 private Part(String encoded, String decoded) { 1996 super(encoded, decoded); 1997 } 1998 isEmpty()1999 boolean isEmpty() { 2000 return false; 2001 } 2002 getEncoded()2003 String getEncoded() { 2004 @SuppressWarnings("StringEquality") 2005 boolean hasEncoded = encoded != NOT_CACHED; 2006 return hasEncoded ? encoded : (encoded = encode(decoded)); 2007 } 2008 readFrom(Parcel parcel)2009 static Part readFrom(Parcel parcel) { 2010 int representation = parcel.readInt(); 2011 switch (representation) { 2012 case Representation.BOTH: 2013 return from(parcel.readString(), parcel.readString()); 2014 case Representation.ENCODED: 2015 return fromEncoded(parcel.readString()); 2016 case Representation.DECODED: 2017 return fromDecoded(parcel.readString()); 2018 default: 2019 throw new IllegalArgumentException("Unknown representation: " 2020 + representation); 2021 } 2022 } 2023 2024 /** 2025 * Returns given part or {@link #NULL} if the given part is null. 2026 */ nonNull(Part part)2027 static Part nonNull(Part part) { 2028 return part == null ? NULL : part; 2029 } 2030 2031 /** 2032 * Creates a part from the encoded string. 2033 * 2034 * @param encoded part string 2035 */ fromEncoded(String encoded)2036 static Part fromEncoded(String encoded) { 2037 return from(encoded, NOT_CACHED); 2038 } 2039 2040 /** 2041 * Creates a part from the decoded string. 2042 * 2043 * @param decoded part string 2044 */ fromDecoded(String decoded)2045 static Part fromDecoded(String decoded) { 2046 return from(NOT_CACHED, decoded); 2047 } 2048 2049 /** 2050 * Creates a part from the encoded and decoded strings. 2051 * 2052 * @param encoded part string 2053 * @param decoded part string 2054 */ from(String encoded, String decoded)2055 static Part from(String encoded, String decoded) { 2056 // We have to check both encoded and decoded in case one is 2057 // NOT_CACHED. 2058 2059 if (encoded == null) { 2060 return NULL; 2061 } 2062 if (encoded.length() == 0) { 2063 return EMPTY; 2064 } 2065 2066 if (decoded == null) { 2067 return NULL; 2068 } 2069 if (decoded .length() == 0) { 2070 return EMPTY; 2071 } 2072 2073 return new Part(encoded, decoded); 2074 } 2075 2076 private static class EmptyPart extends Part { EmptyPart(String value)2077 public EmptyPart(String value) { 2078 super(value, value); 2079 } 2080 2081 @Override isEmpty()2082 boolean isEmpty() { 2083 return true; 2084 } 2085 } 2086 } 2087 2088 /** 2089 * Immutable wrapper of encoded and decoded versions of a path part. Lazily 2090 * creates the encoded or decoded version from the other. 2091 */ 2092 static class PathPart extends AbstractPart { 2093 2094 /** A part with null values. */ 2095 static final PathPart NULL = new PathPart(null, null); 2096 2097 /** A part with empty strings for values. */ 2098 static final PathPart EMPTY = new PathPart("", ""); 2099 PathPart(String encoded, String decoded)2100 private PathPart(String encoded, String decoded) { 2101 super(encoded, decoded); 2102 } 2103 getEncoded()2104 String getEncoded() { 2105 @SuppressWarnings("StringEquality") 2106 boolean hasEncoded = encoded != NOT_CACHED; 2107 2108 // Don't encode '/'. 2109 return hasEncoded ? encoded : (encoded = encode(decoded, "/")); 2110 } 2111 2112 /** 2113 * Cached path segments. This doesn't need to be volatile--we don't 2114 * care if other threads see the result. 2115 */ 2116 private PathSegments pathSegments; 2117 2118 /** 2119 * Gets the individual path segments. Parses them if necessary. 2120 * 2121 * @return parsed path segments or null if this isn't a hierarchical 2122 * URI 2123 */ getPathSegments()2124 PathSegments getPathSegments() { 2125 if (pathSegments != null) { 2126 return pathSegments; 2127 } 2128 2129 String path = getEncoded(); 2130 if (path == null) { 2131 return pathSegments = PathSegments.EMPTY; 2132 } 2133 2134 PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder(); 2135 2136 int previous = 0; 2137 int current; 2138 while ((current = path.indexOf('/', previous)) > -1) { 2139 // This check keeps us from adding a segment if the path starts 2140 // '/' and an empty segment for "//". 2141 if (previous < current) { 2142 String decodedSegment 2143 = decode(path.substring(previous, current)); 2144 segmentBuilder.add(decodedSegment); 2145 } 2146 previous = current + 1; 2147 } 2148 2149 // Add in the final path segment. 2150 if (previous < path.length()) { 2151 segmentBuilder.add(decode(path.substring(previous))); 2152 } 2153 2154 return pathSegments = segmentBuilder.build(); 2155 } 2156 appendEncodedSegment(PathPart oldPart, String newSegment)2157 static PathPart appendEncodedSegment(PathPart oldPart, 2158 String newSegment) { 2159 // If there is no old path, should we make the new path relative 2160 // or absolute? I pick absolute. 2161 2162 if (oldPart == null) { 2163 // No old path. 2164 return fromEncoded("/" + newSegment); 2165 } 2166 2167 String oldPath = oldPart.getEncoded(); 2168 2169 if (oldPath == null) { 2170 oldPath = ""; 2171 } 2172 2173 int oldPathLength = oldPath.length(); 2174 String newPath; 2175 if (oldPathLength == 0) { 2176 // No old path. 2177 newPath = "/" + newSegment; 2178 } else if (oldPath.charAt(oldPathLength - 1) == '/') { 2179 newPath = oldPath + newSegment; 2180 } else { 2181 newPath = oldPath + "/" + newSegment; 2182 } 2183 2184 return fromEncoded(newPath); 2185 } 2186 appendDecodedSegment(PathPart oldPart, String decoded)2187 static PathPart appendDecodedSegment(PathPart oldPart, String decoded) { 2188 String encoded = encode(decoded); 2189 2190 // TODO: Should we reuse old PathSegments? Probably not. 2191 return appendEncodedSegment(oldPart, encoded); 2192 } 2193 readFrom(Parcel parcel)2194 static PathPart readFrom(Parcel parcel) { 2195 int representation = parcel.readInt(); 2196 switch (representation) { 2197 case Representation.BOTH: 2198 return from(parcel.readString(), parcel.readString()); 2199 case Representation.ENCODED: 2200 return fromEncoded(parcel.readString()); 2201 case Representation.DECODED: 2202 return fromDecoded(parcel.readString()); 2203 default: 2204 throw new IllegalArgumentException("Bad representation: " + representation); 2205 } 2206 } 2207 2208 /** 2209 * Creates a path from the encoded string. 2210 * 2211 * @param encoded part string 2212 */ fromEncoded(String encoded)2213 static PathPart fromEncoded(String encoded) { 2214 return from(encoded, NOT_CACHED); 2215 } 2216 2217 /** 2218 * Creates a path from the decoded string. 2219 * 2220 * @param decoded part string 2221 */ fromDecoded(String decoded)2222 static PathPart fromDecoded(String decoded) { 2223 return from(NOT_CACHED, decoded); 2224 } 2225 2226 /** 2227 * Creates a path from the encoded and decoded strings. 2228 * 2229 * @param encoded part string 2230 * @param decoded part string 2231 */ from(String encoded, String decoded)2232 static PathPart from(String encoded, String decoded) { 2233 if (encoded == null) { 2234 return NULL; 2235 } 2236 2237 if (encoded.length() == 0) { 2238 return EMPTY; 2239 } 2240 2241 return new PathPart(encoded, decoded); 2242 } 2243 2244 /** 2245 * Prepends path values with "/" if they're present, not empty, and 2246 * they don't already start with "/". 2247 */ makeAbsolute(PathPart oldPart)2248 static PathPart makeAbsolute(PathPart oldPart) { 2249 @SuppressWarnings("StringEquality") 2250 boolean encodedCached = oldPart.encoded != NOT_CACHED; 2251 2252 // We don't care which version we use, and we don't want to force 2253 // unneccessary encoding/decoding. 2254 String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded; 2255 2256 if (oldPath == null || oldPath.length() == 0 2257 || oldPath.startsWith("/")) { 2258 return oldPart; 2259 } 2260 2261 // Prepend encoded string if present. 2262 String newEncoded = encodedCached 2263 ? "/" + oldPart.encoded : NOT_CACHED; 2264 2265 // Prepend decoded string if present. 2266 @SuppressWarnings("StringEquality") 2267 boolean decodedCached = oldPart.decoded != NOT_CACHED; 2268 String newDecoded = decodedCached 2269 ? "/" + oldPart.decoded 2270 : NOT_CACHED; 2271 2272 return new PathPart(newEncoded, newDecoded); 2273 } 2274 } 2275 2276 /** 2277 * Creates a new Uri by appending an already-encoded path segment to a 2278 * base Uri. 2279 * 2280 * @param baseUri Uri to append path segment to 2281 * @param pathSegment encoded path segment to append 2282 * @return a new Uri based on baseUri with the given segment appended to 2283 * the path 2284 * @throws NullPointerException if baseUri is null 2285 */ withAppendedPath(Uri baseUri, String pathSegment)2286 public static Uri withAppendedPath(Uri baseUri, String pathSegment) { 2287 Builder builder = baseUri.buildUpon(); 2288 builder = builder.appendEncodedPath(pathSegment); 2289 return builder.build(); 2290 } 2291 } 2292