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