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