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