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