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/...}. For all other uri schemes, only the scheme, 394 * host and port are returned. 395 * @return the common forms PII redacted string of this URI 396 * @hide 397 */ 398 @SystemApi toSafeString()399 public @NonNull String toSafeString() { 400 String scheme = getScheme(); 401 String ssp = getSchemeSpecificPart(); 402 StringBuilder builder = new StringBuilder(64); 403 404 if (scheme != null) { 405 builder.append(scheme); 406 builder.append(":"); 407 if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") 408 || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") 409 || scheme.equalsIgnoreCase("mailto") || scheme.equalsIgnoreCase("nfc")) { 410 if (ssp != null) { 411 for (int i=0; i<ssp.length(); i++) { 412 char c = ssp.charAt(i); 413 if (c == '-' || c == '@' || c == '.') { 414 builder.append(c); 415 } else { 416 builder.append('x'); 417 } 418 } 419 } 420 } else { 421 // For other schemes, let's be conservative about 422 // the data we include -- only the host and port, not the query params, path or 423 // fragment, because those can often have sensitive info. 424 final String host = getHost(); 425 final int port = getPort(); 426 final String path = getPath(); 427 final String authority = getAuthority(); 428 if (authority != null) builder.append("//"); 429 if (host != null) builder.append(host); 430 if (port != -1) builder.append(":").append(port); 431 if (authority != null || path != null) builder.append("/..."); 432 } 433 } 434 return builder.toString(); 435 } 436 437 /** 438 * Constructs a new builder, copying the attributes from this Uri. 439 */ buildUpon()440 public abstract Builder buildUpon(); 441 442 /** Index of a component which was not found. */ 443 private final static int NOT_FOUND = -1; 444 445 /** Placeholder value for an index which hasn't been calculated yet. */ 446 private final static int NOT_CALCULATED = -2; 447 448 /** 449 * Error message presented when a user tries to treat an opaque URI as 450 * hierarchical. 451 */ 452 private static final String NOT_HIERARCHICAL 453 = "This isn't a hierarchical URI."; 454 455 /** Default encoding. */ 456 private static final String DEFAULT_ENCODING = "UTF-8"; 457 458 /** 459 * Creates a Uri which parses the given encoded URI string. 460 * 461 * @param uriString an RFC 2396-compliant, encoded URI 462 * @throws NullPointerException if uriString is null 463 * @return Uri for this given uri string 464 */ parse(String uriString)465 public static Uri parse(String uriString) { 466 return new StringUri(uriString); 467 } 468 469 /** 470 * Creates a Uri from a file. The URI has the form 471 * "file://<absolute path>". Encodes path characters with the exception of 472 * '/'. 473 * 474 * <p>Example: "file:///tmp/android.txt" 475 * 476 * @throws NullPointerException if file is null 477 * @return a Uri for the given file 478 */ fromFile(File file)479 public static Uri fromFile(File file) { 480 if (file == null) { 481 throw new NullPointerException("file"); 482 } 483 484 PathPart path = PathPart.fromDecoded(file.getAbsolutePath()); 485 return new HierarchicalUri( 486 "file", Part.EMPTY, path, Part.NULL, Part.NULL); 487 } 488 489 /** 490 * An implementation which wraps a String URI. This URI can be opaque or 491 * hierarchical, but we extend AbstractHierarchicalUri in case we need 492 * the hierarchical functionality. 493 */ 494 private static class StringUri extends AbstractHierarchicalUri { 495 496 /** Used in parcelling. */ 497 static final int TYPE_ID = 1; 498 499 /** URI string representation. */ 500 private final String uriString; 501 StringUri(String uriString)502 private StringUri(String uriString) { 503 if (uriString == null) { 504 throw new NullPointerException("uriString"); 505 } 506 507 this.uriString = uriString; 508 } 509 readFrom(Parcel parcel)510 static Uri readFrom(Parcel parcel) { 511 return new StringUri(parcel.readString8()); 512 } 513 describeContents()514 public int describeContents() { 515 return 0; 516 } 517 writeToParcel(Parcel parcel, int flags)518 public void writeToParcel(Parcel parcel, int flags) { 519 parcel.writeInt(TYPE_ID); 520 parcel.writeString8(uriString); 521 } 522 523 /** Cached scheme separator index. */ 524 private volatile int cachedSsi = NOT_CALCULATED; 525 526 /** Finds the first ':'. Returns -1 if none found. */ findSchemeSeparator()527 private int findSchemeSeparator() { 528 return cachedSsi == NOT_CALCULATED 529 ? cachedSsi = uriString.indexOf(':') 530 : cachedSsi; 531 } 532 533 /** Cached fragment separator index. */ 534 private volatile int cachedFsi = NOT_CALCULATED; 535 536 /** Finds the first '#'. Returns -1 if none found. */ findFragmentSeparator()537 private int findFragmentSeparator() { 538 return cachedFsi == NOT_CALCULATED 539 ? cachedFsi = uriString.indexOf('#', findSchemeSeparator()) 540 : cachedFsi; 541 } 542 isHierarchical()543 public boolean isHierarchical() { 544 int ssi = findSchemeSeparator(); 545 546 if (ssi == NOT_FOUND) { 547 // All relative URIs are hierarchical. 548 return true; 549 } 550 551 if (uriString.length() == ssi + 1) { 552 // No ssp. 553 return false; 554 } 555 556 // If the ssp starts with a '/', this is hierarchical. 557 return uriString.charAt(ssi + 1) == '/'; 558 } 559 isRelative()560 public boolean isRelative() { 561 // Note: We return true if the index is 0 562 return findSchemeSeparator() == NOT_FOUND; 563 } 564 565 private volatile String scheme = NotCachedHolder.NOT_CACHED; 566 getScheme()567 public String getScheme() { 568 @SuppressWarnings("StringEquality") 569 boolean cached = (scheme != NotCachedHolder.NOT_CACHED); 570 return cached ? scheme : (scheme = parseScheme()); 571 } 572 parseScheme()573 private String parseScheme() { 574 int ssi = findSchemeSeparator(); 575 return ssi == NOT_FOUND ? null : uriString.substring(0, ssi); 576 } 577 578 private Part ssp; 579 getSsp()580 private Part getSsp() { 581 return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp; 582 } 583 getEncodedSchemeSpecificPart()584 public String getEncodedSchemeSpecificPart() { 585 return getSsp().getEncoded(); 586 } 587 getSchemeSpecificPart()588 public String getSchemeSpecificPart() { 589 return getSsp().getDecoded(); 590 } 591 parseSsp()592 private String parseSsp() { 593 int ssi = findSchemeSeparator(); 594 int fsi = findFragmentSeparator(); 595 596 // Return everything between ssi and fsi. 597 return fsi == NOT_FOUND 598 ? uriString.substring(ssi + 1) 599 : uriString.substring(ssi + 1, fsi); 600 } 601 602 private Part authority; 603 getAuthorityPart()604 private Part getAuthorityPart() { 605 if (authority == null) { 606 String encodedAuthority 607 = parseAuthority(this.uriString, findSchemeSeparator()); 608 return authority = Part.fromEncoded(encodedAuthority); 609 } 610 611 return authority; 612 } 613 getEncodedAuthority()614 public String getEncodedAuthority() { 615 return getAuthorityPart().getEncoded(); 616 } 617 getAuthority()618 public String getAuthority() { 619 return getAuthorityPart().getDecoded(); 620 } 621 622 private PathPart path; 623 getPathPart()624 private PathPart getPathPart() { 625 return path == null 626 ? path = PathPart.fromEncoded(parsePath()) 627 : path; 628 } 629 getPath()630 public String getPath() { 631 return getPathPart().getDecoded(); 632 } 633 getEncodedPath()634 public String getEncodedPath() { 635 return getPathPart().getEncoded(); 636 } 637 getPathSegments()638 public List<String> getPathSegments() { 639 return getPathPart().getPathSegments(); 640 } 641 parsePath()642 private String parsePath() { 643 String uriString = this.uriString; 644 int ssi = findSchemeSeparator(); 645 646 // If the URI is absolute. 647 if (ssi > -1) { 648 // Is there anything after the ':'? 649 boolean schemeOnly = ssi + 1 == uriString.length(); 650 if (schemeOnly) { 651 // Opaque URI. 652 return null; 653 } 654 655 // A '/' after the ':' means this is hierarchical. 656 if (uriString.charAt(ssi + 1) != '/') { 657 // Opaque URI. 658 return null; 659 } 660 } else { 661 // All relative URIs are hierarchical. 662 } 663 664 return parsePath(uriString, ssi); 665 } 666 667 private Part query; 668 getQueryPart()669 private Part getQueryPart() { 670 return query == null 671 ? query = Part.fromEncoded(parseQuery()) : query; 672 } 673 getEncodedQuery()674 public String getEncodedQuery() { 675 return getQueryPart().getEncoded(); 676 } 677 parseQuery()678 private String parseQuery() { 679 // It doesn't make sense to cache this index. We only ever 680 // calculate it once. 681 int qsi = uriString.indexOf('?', findSchemeSeparator()); 682 if (qsi == NOT_FOUND) { 683 return null; 684 } 685 686 int fsi = findFragmentSeparator(); 687 688 if (fsi == NOT_FOUND) { 689 return uriString.substring(qsi + 1); 690 } 691 692 if (fsi < qsi) { 693 // Invalid. 694 return null; 695 } 696 697 return uriString.substring(qsi + 1, fsi); 698 } 699 getQuery()700 public String getQuery() { 701 return getQueryPart().getDecoded(); 702 } 703 704 private Part fragment; 705 getFragmentPart()706 private Part getFragmentPart() { 707 return fragment == null 708 ? fragment = Part.fromEncoded(parseFragment()) : fragment; 709 } 710 getEncodedFragment()711 public String getEncodedFragment() { 712 return getFragmentPart().getEncoded(); 713 } 714 parseFragment()715 private String parseFragment() { 716 int fsi = findFragmentSeparator(); 717 return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1); 718 } 719 getFragment()720 public String getFragment() { 721 return getFragmentPart().getDecoded(); 722 } 723 toString()724 public String toString() { 725 return uriString; 726 } 727 728 /** 729 * Parses an authority out of the given URI string. 730 * 731 * @param uriString URI string 732 * @param ssi scheme separator index, -1 for a relative URI 733 * 734 * @return the authority or null if none is found 735 */ parseAuthority(String uriString, int ssi)736 static String parseAuthority(String uriString, int ssi) { 737 int length = uriString.length(); 738 739 // If "//" follows the scheme separator, we have an authority. 740 if (length > ssi + 2 741 && uriString.charAt(ssi + 1) == '/' 742 && uriString.charAt(ssi + 2) == '/') { 743 // We have an authority. 744 745 // Look for the start of the path, query, or fragment, or the 746 // end of the string. 747 int end = ssi + 3; 748 LOOP: while (end < length) { 749 switch (uriString.charAt(end)) { 750 case '/': // Start of path 751 case '\\':// Start of path 752 // Per http://url.spec.whatwg.org/#host-state, the \ character 753 // is treated as if it were a / character when encountered in a 754 // host 755 case '?': // Start of query 756 case '#': // Start of fragment 757 break LOOP; 758 } 759 end++; 760 } 761 762 return uriString.substring(ssi + 3, end); 763 } else { 764 return null; 765 } 766 767 } 768 769 /** 770 * Parses a path out of this given URI string. 771 * 772 * @param uriString URI string 773 * @param ssi scheme separator index, -1 for a relative URI 774 * 775 * @return the path 776 */ parsePath(String uriString, int ssi)777 static String parsePath(String uriString, int ssi) { 778 int length = uriString.length(); 779 780 // Find start of path. 781 int pathStart; 782 if (length > ssi + 2 783 && uriString.charAt(ssi + 1) == '/' 784 && uriString.charAt(ssi + 2) == '/') { 785 // Skip over authority to path. 786 pathStart = ssi + 3; 787 LOOP: while (pathStart < length) { 788 switch (uriString.charAt(pathStart)) { 789 case '?': // Start of query 790 case '#': // Start of fragment 791 return ""; // Empty path. 792 case '/': // Start of path! 793 case '\\':// Start of path! 794 // Per http://url.spec.whatwg.org/#host-state, the \ character 795 // is treated as if it were a / character when encountered in a 796 // host 797 break LOOP; 798 } 799 pathStart++; 800 } 801 } else { 802 // Path starts immediately after scheme separator. 803 pathStart = ssi + 1; 804 } 805 806 // Find end of path. 807 int pathEnd = pathStart; 808 LOOP: while (pathEnd < length) { 809 switch (uriString.charAt(pathEnd)) { 810 case '?': // Start of query 811 case '#': // Start of fragment 812 break LOOP; 813 } 814 pathEnd++; 815 } 816 817 return uriString.substring(pathStart, pathEnd); 818 } 819 buildUpon()820 public Builder buildUpon() { 821 if (isHierarchical()) { 822 return new Builder() 823 .scheme(getScheme()) 824 .authority(getAuthorityPart()) 825 .path(getPathPart()) 826 .query(getQueryPart()) 827 .fragment(getFragmentPart()); 828 } else { 829 return new Builder() 830 .scheme(getScheme()) 831 .opaquePart(getSsp()) 832 .fragment(getFragmentPart()); 833 } 834 } 835 } 836 837 /** 838 * Creates an opaque Uri from the given components. Encodes the ssp 839 * which means this method cannot be used to create hierarchical URIs. 840 * 841 * @param scheme of the URI 842 * @param ssp scheme-specific-part, everything between the 843 * scheme separator (':') and the fragment separator ('#'), which will 844 * get encoded 845 * @param fragment fragment, everything after the '#', null if undefined, 846 * will get encoded 847 * 848 * @throws NullPointerException if scheme or ssp is null 849 * @return Uri composed of the given scheme, ssp, and fragment 850 * 851 * @see Builder if you don't want the ssp and fragment to be encoded 852 */ fromParts(String scheme, String ssp, String fragment)853 public static Uri fromParts(String scheme, String ssp, 854 String fragment) { 855 if (scheme == null) { 856 throw new NullPointerException("scheme"); 857 } 858 if (ssp == null) { 859 throw new NullPointerException("ssp"); 860 } 861 862 return new OpaqueUri(scheme, Part.fromDecoded(ssp), 863 Part.fromDecoded(fragment)); 864 } 865 866 /** 867 * Opaque URI. 868 */ 869 private static class OpaqueUri extends Uri { 870 871 /** Used in parcelling. */ 872 static final int TYPE_ID = 2; 873 874 private final String scheme; 875 private final Part ssp; 876 private final Part fragment; 877 OpaqueUri(String scheme, Part ssp, Part fragment)878 private OpaqueUri(String scheme, Part ssp, Part fragment) { 879 this.scheme = scheme; 880 this.ssp = ssp; 881 this.fragment = fragment == null ? Part.NULL : fragment; 882 } 883 readFrom(Parcel parcel)884 static Uri readFrom(Parcel parcel) { 885 return new OpaqueUri( 886 parcel.readString8(), 887 Part.readFrom(parcel), 888 Part.readFrom(parcel) 889 ); 890 } 891 describeContents()892 public int describeContents() { 893 return 0; 894 } 895 writeToParcel(Parcel parcel, int flags)896 public void writeToParcel(Parcel parcel, int flags) { 897 parcel.writeInt(TYPE_ID); 898 parcel.writeString8(scheme); 899 ssp.writeTo(parcel); 900 fragment.writeTo(parcel); 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 = path == null ? PathPart.NULL : path; 1200 this.query = Part.nonNull(query); 1201 this.fragment = Part.nonNull(fragment); 1202 } 1203 readFrom(Parcel parcel)1204 static Uri readFrom(Parcel parcel) { 1205 final String scheme = parcel.readString8(); 1206 final Part authority = Part.readFrom(parcel); 1207 // In RFC3986 the path should be determined based on whether there is a scheme or 1208 // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). 1209 final boolean hasSchemeOrAuthority = 1210 (scheme != null && scheme.length() > 0) || !authority.isEmpty(); 1211 final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); 1212 final Part query = Part.readFrom(parcel); 1213 final Part fragment = Part.readFrom(parcel); 1214 return new HierarchicalUri(scheme, authority, path, query, fragment); 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 readFrom(boolean hasSchemeOrAuthority, Parcel parcel)2273 static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) { 2274 final PathPart path = readFrom(parcel); 2275 return hasSchemeOrAuthority ? makeAbsolute(path) : path; 2276 } 2277 2278 /** 2279 * Creates a path from the encoded string. 2280 * 2281 * @param encoded part string 2282 */ fromEncoded(String encoded)2283 static PathPart fromEncoded(String encoded) { 2284 return from(encoded, NotCachedHolder.NOT_CACHED); 2285 } 2286 2287 /** 2288 * Creates a path from the decoded string. 2289 * 2290 * @param decoded part string 2291 */ fromDecoded(String decoded)2292 static PathPart fromDecoded(String decoded) { 2293 return from(NotCachedHolder.NOT_CACHED, decoded); 2294 } 2295 2296 /** 2297 * Creates a path from the encoded and decoded strings. 2298 * 2299 * @param encoded part string 2300 * @param decoded part string 2301 */ from(String encoded, String decoded)2302 static PathPart from(String encoded, String decoded) { 2303 if (encoded == null) { 2304 return NULL; 2305 } 2306 2307 if (encoded.length() == 0) { 2308 return EMPTY; 2309 } 2310 2311 return new PathPart(encoded, decoded); 2312 } 2313 2314 /** 2315 * Prepends path values with "/" if they're present, not empty, and 2316 * they don't already start with "/". 2317 */ makeAbsolute(PathPart oldPart)2318 static PathPart makeAbsolute(PathPart oldPart) { 2319 @SuppressWarnings("StringEquality") 2320 boolean encodedCached = oldPart.encoded != NotCachedHolder.NOT_CACHED; 2321 2322 // We don't care which version we use, and we don't want to force 2323 // unneccessary encoding/decoding. 2324 String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded; 2325 2326 if (oldPath == null || oldPath.length() == 0 2327 || oldPath.startsWith("/")) { 2328 return oldPart; 2329 } 2330 2331 // Prepend encoded string if present. 2332 String newEncoded = encodedCached 2333 ? "/" + oldPart.encoded : NotCachedHolder.NOT_CACHED; 2334 2335 // Prepend decoded string if present. 2336 @SuppressWarnings("StringEquality") 2337 boolean decodedCached = oldPart.decoded != NotCachedHolder.NOT_CACHED; 2338 String newDecoded = decodedCached 2339 ? "/" + oldPart.decoded 2340 : NotCachedHolder.NOT_CACHED; 2341 2342 return new PathPart(newEncoded, newDecoded); 2343 } 2344 } 2345 2346 /** 2347 * Creates a new Uri by appending an already-encoded path segment to a 2348 * base Uri. 2349 * 2350 * @param baseUri Uri to append path segment to 2351 * @param pathSegment encoded path segment to append 2352 * @return a new Uri based on baseUri with the given segment appended to 2353 * the path 2354 * @throws NullPointerException if baseUri is null 2355 */ withAppendedPath(Uri baseUri, String pathSegment)2356 public static Uri withAppendedPath(Uri baseUri, String pathSegment) { 2357 Builder builder = baseUri.buildUpon(); 2358 builder = builder.appendEncodedPath(pathSegment); 2359 return builder.build(); 2360 } 2361 2362 /** 2363 * If this {@link Uri} is {@code file://}, then resolve and return its 2364 * canonical path. Also fixes legacy emulated storage paths so they are 2365 * usable across user boundaries. Should always be called from the app 2366 * process before sending elsewhere. 2367 * 2368 * @hide 2369 */ 2370 @UnsupportedAppUsage getCanonicalUri()2371 public Uri getCanonicalUri() { 2372 if ("file".equals(getScheme())) { 2373 final String canonicalPath; 2374 try { 2375 canonicalPath = new File(getPath()).getCanonicalPath(); 2376 } catch (IOException e) { 2377 return this; 2378 } 2379 2380 if (Environment.isExternalStorageEmulated()) { 2381 final String legacyPath = Environment.getLegacyExternalStorageDirectory() 2382 .toString(); 2383 2384 // Splice in user-specific path when legacy path is found 2385 if (canonicalPath.startsWith(legacyPath)) { 2386 return Uri.fromFile(new File( 2387 Environment.getExternalStorageDirectory().toString(), 2388 canonicalPath.substring(legacyPath.length() + 1))); 2389 } 2390 } 2391 2392 return Uri.fromFile(new File(canonicalPath)); 2393 } else { 2394 return this; 2395 } 2396 } 2397 2398 /** 2399 * If this is a {@code file://} Uri, it will be reported to 2400 * {@link StrictMode}. 2401 * 2402 * @hide 2403 */ checkFileUriExposed(String location)2404 public void checkFileUriExposed(String location) { 2405 if ("file".equals(getScheme()) 2406 && (getPath() != null) && !getPath().startsWith("/system/")) { 2407 StrictMode.onFileUriExposed(this, location); 2408 } 2409 } 2410 2411 /** 2412 * If this is a {@code content://} Uri without access flags, it will be 2413 * reported to {@link StrictMode}. 2414 * 2415 * @hide 2416 */ checkContentUriWithoutPermission(String location, int flags)2417 public void checkContentUriWithoutPermission(String location, int flags) { 2418 if ("content".equals(getScheme()) && !Intent.isAccessUriMode(flags)) { 2419 StrictMode.onContentUriWithoutPermission(this, location); 2420 } 2421 } 2422 2423 /** 2424 * Test if this is a path prefix match against the given Uri. Verifies that 2425 * scheme, authority, and atomic path segments match. 2426 * 2427 * @hide 2428 */ isPathPrefixMatch(Uri prefix)2429 public boolean isPathPrefixMatch(Uri prefix) { 2430 if (!Objects.equals(getScheme(), prefix.getScheme())) return false; 2431 if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false; 2432 2433 List<String> seg = getPathSegments(); 2434 List<String> prefixSeg = prefix.getPathSegments(); 2435 2436 final int prefixSize = prefixSeg.size(); 2437 if (seg.size() < prefixSize) return false; 2438 2439 for (int i = 0; i < prefixSize; i++) { 2440 if (!Objects.equals(seg.get(i), prefixSeg.get(i))) { 2441 return false; 2442 } 2443 } 2444 2445 return true; 2446 } 2447 } 2448