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