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