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