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