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