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