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