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