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