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