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