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