blob: 40465ceafcb8050ccdd67746604ad743a6927627 [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 /**
Dianne Hackborn90c52de2011-09-23 12:57:44 -0700377 * Return a string representation of the URI that is safe to print
378 * to logs and other places where PII should be avoided.
379 * @hide
380 */
Mathew Inwoodfa3a7462018-08-08 14:52:47 +0100381 @UnsupportedAppUsage
Dianne Hackborn90c52de2011-09-23 12:57:44 -0700382 public String toSafeString() {
383 String scheme = getScheme();
384 String ssp = getSchemeSpecificPart();
385 if (scheme != null) {
386 if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
387 || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
Akinobu Nakashima37e70282018-06-01 16:35:10 +0900388 || scheme.equalsIgnoreCase("mailto") || scheme.equalsIgnoreCase("nfc")) {
Dianne Hackborn90c52de2011-09-23 12:57:44 -0700389 StringBuilder builder = new StringBuilder(64);
390 builder.append(scheme);
391 builder.append(':');
392 if (ssp != null) {
393 for (int i=0; i<ssp.length(); i++) {
394 char c = ssp.charAt(i);
395 if (c == '-' || c == '@' || c == '.') {
396 builder.append(c);
397 } else {
398 builder.append('x');
399 }
400 }
401 }
402 return builder.toString();
Alex Klyubin3f24a1d2015-04-01 10:59:29 -0700403 } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
404 || scheme.equalsIgnoreCase("ftp")) {
405 ssp = "//" + ((getHost() != null) ? getHost() : "")
406 + ((getPort() != -1) ? (":" + getPort()) : "")
407 + "/...";
Dianne Hackborn90c52de2011-09-23 12:57:44 -0700408 }
409 }
410 // Not a sensitive scheme, but let's still be conservative about
411 // the data we include -- only the ssp, not the query params or
412 // fragment, because those can often have sensitive info.
413 StringBuilder builder = new StringBuilder(64);
414 if (scheme != null) {
415 builder.append(scheme);
416 builder.append(':');
417 }
418 if (ssp != null) {
419 builder.append(ssp);
420 }
421 return builder.toString();
422 }
423
424 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 * Constructs a new builder, copying the attributes from this Uri.
426 */
427 public abstract Builder buildUpon();
428
429 /** Index of a component which was not found. */
430 private final static int NOT_FOUND = -1;
431
432 /** Placeholder value for an index which hasn't been calculated yet. */
433 private final static int NOT_CALCULATED = -2;
434
435 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 * Error message presented when a user tries to treat an opaque URI as
437 * hierarchical.
438 */
439 private static final String NOT_HIERARCHICAL
440 = "This isn't a hierarchical URI.";
441
442 /** Default encoding. */
443 private static final String DEFAULT_ENCODING = "UTF-8";
444
445 /**
446 * Creates a Uri which parses the given encoded URI string.
447 *
Simon Schoar8aa393b2009-06-10 01:10:58 +0200448 * @param uriString an RFC 2396-compliant, encoded URI
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 * @throws NullPointerException if uriString is null
450 * @return Uri for this given uri string
451 */
452 public static Uri parse(String uriString) {
453 return new StringUri(uriString);
454 }
455
456 /**
457 * Creates a Uri from a file. The URI has the form
458 * "file://<absolute path>". Encodes path characters with the exception of
459 * '/'.
460 *
461 * <p>Example: "file:///tmp/android.txt"
462 *
463 * @throws NullPointerException if file is null
464 * @return a Uri for the given file
465 */
466 public static Uri fromFile(File file) {
467 if (file == null) {
468 throw new NullPointerException("file");
469 }
470
471 PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
472 return new HierarchicalUri(
473 "file", Part.EMPTY, path, Part.NULL, Part.NULL);
474 }
475
476 /**
477 * An implementation which wraps a String URI. This URI can be opaque or
478 * hierarchical, but we extend AbstractHierarchicalUri in case we need
479 * the hierarchical functionality.
480 */
481 private static class StringUri extends AbstractHierarchicalUri {
482
483 /** Used in parcelling. */
484 static final int TYPE_ID = 1;
485
486 /** URI string representation. */
487 private final String uriString;
488
489 private StringUri(String uriString) {
490 if (uriString == null) {
491 throw new NullPointerException("uriString");
492 }
493
494 this.uriString = uriString;
495 }
496
497 static Uri readFrom(Parcel parcel) {
498 return new StringUri(parcel.readString());
499 }
500
501 public int describeContents() {
502 return 0;
503 }
504
505 public void writeToParcel(Parcel parcel, int flags) {
506 parcel.writeInt(TYPE_ID);
507 parcel.writeString(uriString);
508 }
509
510 /** Cached scheme separator index. */
511 private volatile int cachedSsi = NOT_CALCULATED;
512
513 /** Finds the first ':'. Returns -1 if none found. */
514 private int findSchemeSeparator() {
515 return cachedSsi == NOT_CALCULATED
516 ? cachedSsi = uriString.indexOf(':')
517 : cachedSsi;
518 }
519
520 /** Cached fragment separator index. */
521 private volatile int cachedFsi = NOT_CALCULATED;
522
523 /** Finds the first '#'. Returns -1 if none found. */
524 private int findFragmentSeparator() {
525 return cachedFsi == NOT_CALCULATED
526 ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
527 : cachedFsi;
528 }
529
530 public boolean isHierarchical() {
531 int ssi = findSchemeSeparator();
532
533 if (ssi == NOT_FOUND) {
534 // All relative URIs are hierarchical.
535 return true;
536 }
537
538 if (uriString.length() == ssi + 1) {
539 // No ssp.
540 return false;
541 }
542
543 // If the ssp starts with a '/', this is hierarchical.
544 return uriString.charAt(ssi + 1) == '/';
545 }
546
547 public boolean isRelative() {
548 // Note: We return true if the index is 0
549 return findSchemeSeparator() == NOT_FOUND;
550 }
551
552 private volatile String scheme = NOT_CACHED;
553
554 public String getScheme() {
555 @SuppressWarnings("StringEquality")
556 boolean cached = (scheme != NOT_CACHED);
557 return cached ? scheme : (scheme = parseScheme());
558 }
559
560 private String parseScheme() {
561 int ssi = findSchemeSeparator();
562 return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
563 }
564
565 private Part ssp;
566
567 private Part getSsp() {
568 return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
569 }
570
571 public String getEncodedSchemeSpecificPart() {
572 return getSsp().getEncoded();
573 }
574
575 public String getSchemeSpecificPart() {
576 return getSsp().getDecoded();
577 }
578
579 private String parseSsp() {
580 int ssi = findSchemeSeparator();
581 int fsi = findFragmentSeparator();
582
583 // Return everything between ssi and fsi.
584 return fsi == NOT_FOUND
585 ? uriString.substring(ssi + 1)
586 : uriString.substring(ssi + 1, fsi);
587 }
588
589 private Part authority;
590
591 private Part getAuthorityPart() {
592 if (authority == null) {
593 String encodedAuthority
594 = parseAuthority(this.uriString, findSchemeSeparator());
595 return authority = Part.fromEncoded(encodedAuthority);
596 }
597
598 return authority;
599 }
600
601 public String getEncodedAuthority() {
602 return getAuthorityPart().getEncoded();
603 }
604
605 public String getAuthority() {
606 return getAuthorityPart().getDecoded();
607 }
608
609 private PathPart path;
610
611 private PathPart getPathPart() {
612 return path == null
613 ? path = PathPart.fromEncoded(parsePath())
614 : path;
615 }
616
617 public String getPath() {
618 return getPathPart().getDecoded();
619 }
620
621 public String getEncodedPath() {
622 return getPathPart().getEncoded();
623 }
624
625 public List<String> getPathSegments() {
626 return getPathPart().getPathSegments();
627 }
628
629 private String parsePath() {
630 String uriString = this.uriString;
631 int ssi = findSchemeSeparator();
632
633 // If the URI is absolute.
634 if (ssi > -1) {
635 // Is there anything after the ':'?
636 boolean schemeOnly = ssi + 1 == uriString.length();
637 if (schemeOnly) {
638 // Opaque URI.
639 return null;
640 }
641
642 // A '/' after the ':' means this is hierarchical.
643 if (uriString.charAt(ssi + 1) != '/') {
644 // Opaque URI.
645 return null;
646 }
647 } else {
648 // All relative URIs are hierarchical.
649 }
650
651 return parsePath(uriString, ssi);
652 }
653
654 private Part query;
655
656 private Part getQueryPart() {
657 return query == null
658 ? query = Part.fromEncoded(parseQuery()) : query;
659 }
660
661 public String getEncodedQuery() {
662 return getQueryPart().getEncoded();
663 }
664
665 private String parseQuery() {
666 // It doesn't make sense to cache this index. We only ever
667 // calculate it once.
668 int qsi = uriString.indexOf('?', findSchemeSeparator());
669 if (qsi == NOT_FOUND) {
670 return null;
671 }
672
673 int fsi = findFragmentSeparator();
674
675 if (fsi == NOT_FOUND) {
676 return uriString.substring(qsi + 1);
677 }
678
679 if (fsi < qsi) {
680 // Invalid.
681 return null;
682 }
683
684 return uriString.substring(qsi + 1, fsi);
685 }
686
687 public String getQuery() {
688 return getQueryPart().getDecoded();
689 }
690
691 private Part fragment;
692
693 private Part getFragmentPart() {
694 return fragment == null
695 ? fragment = Part.fromEncoded(parseFragment()) : fragment;
696 }
697
698 public String getEncodedFragment() {
699 return getFragmentPart().getEncoded();
700 }
701
702 private String parseFragment() {
703 int fsi = findFragmentSeparator();
704 return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
705 }
706
707 public String getFragment() {
708 return getFragmentPart().getDecoded();
709 }
710
711 public String toString() {
712 return uriString;
713 }
714
715 /**
716 * Parses an authority out of the given URI string.
717 *
718 * @param uriString URI string
719 * @param ssi scheme separator index, -1 for a relative URI
720 *
721 * @return the authority or null if none is found
722 */
723 static String parseAuthority(String uriString, int ssi) {
724 int length = uriString.length();
725
726 // If "//" follows the scheme separator, we have an authority.
727 if (length > ssi + 2
728 && uriString.charAt(ssi + 1) == '/'
729 && uriString.charAt(ssi + 2) == '/') {
730 // We have an authority.
731
732 // Look for the start of the path, query, or fragment, or the
733 // end of the string.
734 int end = ssi + 3;
735 LOOP: while (end < length) {
736 switch (uriString.charAt(end)) {
737 case '/': // Start of path
Adam Vartanianfa3afbd2018-01-31 11:05:10 +0000738 case '\\':// Start of path
739 // Per http://url.spec.whatwg.org/#host-state, the \ character
740 // is treated as if it were a / character when encountered in a
741 // host
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 case '?': // Start of query
743 case '#': // Start of fragment
744 break LOOP;
745 }
746 end++;
747 }
748
749 return uriString.substring(ssi + 3, end);
750 } else {
751 return null;
752 }
753
754 }
755
756 /**
757 * Parses a path out of this given URI string.
758 *
759 * @param uriString URI string
760 * @param ssi scheme separator index, -1 for a relative URI
761 *
762 * @return the path
763 */
764 static String parsePath(String uriString, int ssi) {
765 int length = uriString.length();
766
767 // Find start of path.
768 int pathStart;
769 if (length > ssi + 2
770 && uriString.charAt(ssi + 1) == '/'
771 && uriString.charAt(ssi + 2) == '/') {
772 // Skip over authority to path.
773 pathStart = ssi + 3;
774 LOOP: while (pathStart < length) {
775 switch (uriString.charAt(pathStart)) {
776 case '?': // Start of query
777 case '#': // Start of fragment
778 return ""; // Empty path.
779 case '/': // Start of path!
Adam Vartanianfa3afbd2018-01-31 11:05:10 +0000780 case '\\':// Start of path!
781 // Per http://url.spec.whatwg.org/#host-state, the \ character
782 // is treated as if it were a / character when encountered in a
783 // host
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 break LOOP;
785 }
786 pathStart++;
787 }
788 } else {
789 // Path starts immediately after scheme separator.
790 pathStart = ssi + 1;
791 }
792
793 // Find end of path.
794 int pathEnd = pathStart;
795 LOOP: while (pathEnd < length) {
796 switch (uriString.charAt(pathEnd)) {
797 case '?': // Start of query
798 case '#': // Start of fragment
799 break LOOP;
800 }
801 pathEnd++;
802 }
803
804 return uriString.substring(pathStart, pathEnd);
805 }
806
807 public Builder buildUpon() {
808 if (isHierarchical()) {
809 return new Builder()
810 .scheme(getScheme())
811 .authority(getAuthorityPart())
812 .path(getPathPart())
813 .query(getQueryPart())
814 .fragment(getFragmentPart());
815 } else {
816 return new Builder()
817 .scheme(getScheme())
818 .opaquePart(getSsp())
819 .fragment(getFragmentPart());
820 }
821 }
822 }
823
824 /**
825 * Creates an opaque Uri from the given components. Encodes the ssp
826 * which means this method cannot be used to create hierarchical URIs.
827 *
828 * @param scheme of the URI
829 * @param ssp scheme-specific-part, everything between the
830 * scheme separator (':') and the fragment separator ('#'), which will
831 * get encoded
832 * @param fragment fragment, everything after the '#', null if undefined,
833 * will get encoded
834 *
835 * @throws NullPointerException if scheme or ssp is null
836 * @return Uri composed of the given scheme, ssp, and fragment
837 *
838 * @see Builder if you don't want the ssp and fragment to be encoded
839 */
840 public static Uri fromParts(String scheme, String ssp,
841 String fragment) {
842 if (scheme == null) {
843 throw new NullPointerException("scheme");
844 }
845 if (ssp == null) {
846 throw new NullPointerException("ssp");
847 }
848
849 return new OpaqueUri(scheme, Part.fromDecoded(ssp),
850 Part.fromDecoded(fragment));
851 }
852
853 /**
854 * Opaque URI.
855 */
856 private static class OpaqueUri extends Uri {
857
858 /** Used in parcelling. */
859 static final int TYPE_ID = 2;
860
861 private final String scheme;
862 private final Part ssp;
863 private final Part fragment;
864
865 private OpaqueUri(String scheme, Part ssp, Part fragment) {
866 this.scheme = scheme;
867 this.ssp = ssp;
868 this.fragment = fragment == null ? Part.NULL : fragment;
869 }
870
871 static Uri readFrom(Parcel parcel) {
872 return new OpaqueUri(
873 parcel.readString(),
874 Part.readFrom(parcel),
875 Part.readFrom(parcel)
876 );
877 }
878
879 public int describeContents() {
880 return 0;
881 }
882
883 public void writeToParcel(Parcel parcel, int flags) {
884 parcel.writeInt(TYPE_ID);
885 parcel.writeString(scheme);
886 ssp.writeTo(parcel);
887 fragment.writeTo(parcel);
888 }
889
890 public boolean isHierarchical() {
891 return false;
892 }
893
894 public boolean isRelative() {
895 return scheme == null;
896 }
897
898 public String getScheme() {
899 return this.scheme;
900 }
901
902 public String getEncodedSchemeSpecificPart() {
903 return ssp.getEncoded();
904 }
905
906 public String getSchemeSpecificPart() {
907 return ssp.getDecoded();
908 }
909
910 public String getAuthority() {
911 return null;
912 }
913
914 public String getEncodedAuthority() {
915 return null;
916 }
917
918 public String getPath() {
919 return null;
920 }
921
922 public String getEncodedPath() {
923 return null;
924 }
925
926 public String getQuery() {
927 return null;
928 }
929
930 public String getEncodedQuery() {
931 return null;
932 }
933
934 public String getFragment() {
935 return fragment.getDecoded();
936 }
937
938 public String getEncodedFragment() {
939 return fragment.getEncoded();
940 }
941
942 public List<String> getPathSegments() {
943 return Collections.emptyList();
944 }
945
946 public String getLastPathSegment() {
947 return null;
948 }
949
950 public String getUserInfo() {
951 return null;
952 }
953
954 public String getEncodedUserInfo() {
955 return null;
956 }
957
958 public String getHost() {
959 return null;
960 }
961
962 public int getPort() {
963 return -1;
964 }
965
966 private volatile String cachedString = NOT_CACHED;
967
968 public String toString() {
969 @SuppressWarnings("StringEquality")
970 boolean cached = cachedString != NOT_CACHED;
971 if (cached) {
972 return cachedString;
973 }
974
975 StringBuilder sb = new StringBuilder();
976
977 sb.append(scheme).append(':');
978 sb.append(getEncodedSchemeSpecificPart());
979
980 if (!fragment.isEmpty()) {
981 sb.append('#').append(fragment.getEncoded());
982 }
983
984 return cachedString = sb.toString();
985 }
986
987 public Builder buildUpon() {
988 return new Builder()
989 .scheme(this.scheme)
990 .opaquePart(this.ssp)
991 .fragment(this.fragment);
992 }
993 }
994
995 /**
996 * Wrapper for path segment array.
997 */
998 static class PathSegments extends AbstractList<String>
999 implements RandomAccess {
1000
1001 static final PathSegments EMPTY = new PathSegments(null, 0);
1002
1003 final String[] segments;
1004 final int size;
1005
1006 PathSegments(String[] segments, int size) {
1007 this.segments = segments;
1008 this.size = size;
1009 }
1010
1011 public String get(int index) {
1012 if (index >= size) {
1013 throw new IndexOutOfBoundsException();
1014 }
1015
1016 return segments[index];
1017 }
1018
1019 public int size() {
1020 return this.size;
1021 }
1022 }
1023
1024 /**
1025 * Builds PathSegments.
1026 */
1027 static class PathSegmentsBuilder {
1028
1029 String[] segments;
1030 int size = 0;
1031
1032 void add(String segment) {
1033 if (segments == null) {
1034 segments = new String[4];
1035 } else if (size + 1 == segments.length) {
1036 String[] expanded = new String[segments.length * 2];
1037 System.arraycopy(segments, 0, expanded, 0, segments.length);
1038 segments = expanded;
1039 }
1040
1041 segments[size++] = segment;
1042 }
1043
1044 PathSegments build() {
1045 if (segments == null) {
1046 return PathSegments.EMPTY;
1047 }
1048
1049 try {
1050 return new PathSegments(segments, size);
1051 } finally {
1052 // Makes sure this doesn't get reused.
1053 segments = null;
1054 }
1055 }
1056 }
1057
1058 /**
1059 * Support for hierarchical URIs.
1060 */
1061 private abstract static class AbstractHierarchicalUri extends Uri {
1062
1063 public String getLastPathSegment() {
1064 // TODO: If we haven't parsed all of the segments already, just
1065 // grab the last one directly so we only allocate one string.
1066
1067 List<String> segments = getPathSegments();
1068 int size = segments.size();
1069 if (size == 0) {
1070 return null;
1071 }
1072 return segments.get(size - 1);
1073 }
1074
1075 private Part userInfo;
1076
1077 private Part getUserInfoPart() {
1078 return userInfo == null
1079 ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1080 }
1081
1082 public final String getEncodedUserInfo() {
1083 return getUserInfoPart().getEncoded();
1084 }
1085
1086 private String parseUserInfo() {
1087 String authority = getEncodedAuthority();
1088 if (authority == null) {
1089 return null;
1090 }
1091
Adam Vartaniancd6228d2017-11-07 12:22:23 +00001092 int end = authority.lastIndexOf('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001093 return end == NOT_FOUND ? null : authority.substring(0, end);
1094 }
1095
1096 public String getUserInfo() {
1097 return getUserInfoPart().getDecoded();
1098 }
1099
1100 private volatile String host = NOT_CACHED;
1101
1102 public String getHost() {
1103 @SuppressWarnings("StringEquality")
1104 boolean cached = (host != NOT_CACHED);
1105 return cached ? host
1106 : (host = parseHost());
1107 }
1108
1109 private String parseHost() {
Kenny Rootfc017942009-07-12 10:33:28 -05001110 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('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001117 int portSeparator = authority.indexOf(':', userInfoSeparator);
1118
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() {
Kenny Rootfc017942009-07-12 10:33:28 -05001135 String authority = getEncodedAuthority();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 if (authority == null) {
1137 return -1;
1138 }
1139
1140 // Make sure we look for the port separtor *after* the user info
1141 // separator. We have URLs with a ':' in the user info.
Adam Vartaniancd6228d2017-11-07 12:22:23 +00001142 int userInfoSeparator = authority.lastIndexOf('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 int portSeparator = authority.indexOf(':', userInfoSeparator);
1144
1145 if (portSeparator == NOT_FOUND) {
1146 return -1;
1147 }
1148
Kenny Rootfc017942009-07-12 10:33:28 -05001149 String portString = decode(authority.substring(portSeparator + 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 try {
1151 return Integer.parseInt(portString);
1152 } catch (NumberFormatException e) {
1153 Log.w(LOG, "Error parsing port string.", e);
1154 return -1;
1155 }
1156 }
1157 }
1158
1159 /**
1160 * Hierarchical Uri.
1161 */
1162 private static class HierarchicalUri extends AbstractHierarchicalUri {
1163
1164 /** Used in parcelling. */
1165 static final int TYPE_ID = 3;
1166
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001167 private final String scheme; // can be null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 private final Part authority;
1169 private final PathPart path;
1170 private final Part query;
1171 private final Part fragment;
1172
1173 private HierarchicalUri(String scheme, Part authority, PathPart path,
1174 Part query, Part fragment) {
1175 this.scheme = scheme;
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001176 this.authority = Part.nonNull(authority);
1177 this.path = path == null ? PathPart.NULL : path;
1178 this.query = Part.nonNull(query);
1179 this.fragment = Part.nonNull(fragment);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 }
1181
1182 static Uri readFrom(Parcel parcel) {
1183 return new HierarchicalUri(
1184 parcel.readString(),
1185 Part.readFrom(parcel),
1186 PathPart.readFrom(parcel),
1187 Part.readFrom(parcel),
1188 Part.readFrom(parcel)
1189 );
1190 }
1191
1192 public int describeContents() {
1193 return 0;
1194 }
1195
1196 public void writeToParcel(Parcel parcel, int flags) {
1197 parcel.writeInt(TYPE_ID);
1198 parcel.writeString(scheme);
1199 authority.writeTo(parcel);
1200 path.writeTo(parcel);
1201 query.writeTo(parcel);
1202 fragment.writeTo(parcel);
1203 }
1204
1205 public boolean isHierarchical() {
1206 return true;
1207 }
1208
1209 public boolean isRelative() {
1210 return scheme == null;
1211 }
1212
1213 public String getScheme() {
1214 return scheme;
1215 }
1216
1217 private Part ssp;
1218
1219 private Part getSsp() {
1220 return ssp == null
1221 ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1222 }
1223
1224 public String getEncodedSchemeSpecificPart() {
1225 return getSsp().getEncoded();
1226 }
1227
1228 public String getSchemeSpecificPart() {
1229 return getSsp().getDecoded();
1230 }
1231
1232 /**
1233 * Creates the encoded scheme-specific part from its sub parts.
1234 */
1235 private String makeSchemeSpecificPart() {
1236 StringBuilder builder = new StringBuilder();
1237 appendSspTo(builder);
1238 return builder.toString();
1239 }
1240
1241 private void appendSspTo(StringBuilder builder) {
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001242 String encodedAuthority = authority.getEncoded();
1243 if (encodedAuthority != null) {
1244 // Even if the authority is "", we still want to append "//".
1245 builder.append("//").append(encodedAuthority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 }
1247
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 String encodedPath = path.getEncoded();
1249 if (encodedPath != null) {
1250 builder.append(encodedPath);
1251 }
1252
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001253 if (!query.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 builder.append('?').append(query.getEncoded());
1255 }
1256 }
1257
1258 public String getAuthority() {
1259 return this.authority.getDecoded();
1260 }
1261
1262 public String getEncodedAuthority() {
1263 return this.authority.getEncoded();
1264 }
1265
1266 public String getEncodedPath() {
1267 return this.path.getEncoded();
1268 }
1269
1270 public String getPath() {
1271 return this.path.getDecoded();
1272 }
1273
1274 public String getQuery() {
1275 return this.query.getDecoded();
1276 }
1277
1278 public String getEncodedQuery() {
1279 return this.query.getEncoded();
1280 }
1281
1282 public String getFragment() {
1283 return this.fragment.getDecoded();
1284 }
1285
1286 public String getEncodedFragment() {
1287 return this.fragment.getEncoded();
1288 }
1289
1290 public List<String> getPathSegments() {
1291 return this.path.getPathSegments();
1292 }
1293
1294 private volatile String uriString = NOT_CACHED;
1295
1296 @Override
1297 public String toString() {
1298 @SuppressWarnings("StringEquality")
1299 boolean cached = (uriString != NOT_CACHED);
1300 return cached ? uriString
1301 : (uriString = makeUriString());
1302 }
1303
1304 private String makeUriString() {
1305 StringBuilder builder = new StringBuilder();
1306
1307 if (scheme != null) {
1308 builder.append(scheme).append(':');
1309 }
1310
1311 appendSspTo(builder);
1312
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001313 if (!fragment.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 builder.append('#').append(fragment.getEncoded());
1315 }
1316
1317 return builder.toString();
1318 }
1319
1320 public Builder buildUpon() {
1321 return new Builder()
1322 .scheme(scheme)
1323 .authority(authority)
1324 .path(path)
1325 .query(query)
1326 .fragment(fragment);
1327 }
1328 }
1329
1330 /**
1331 * Helper class for building or manipulating URI references. Not safe for
1332 * concurrent use.
1333 *
1334 * <p>An absolute hierarchical URI reference follows the pattern:
Ben Dodson4e8620f2010-08-25 10:55:47 -07001335 * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 *
1337 * <p>Relative URI references (which are always hierarchical) follow one
Ben Dodson4e8620f2010-08-25 10:55:47 -07001338 * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1339 * or {@code //<authority><absolute path>?<query>#<fragment>}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001340 *
1341 * <p>An opaque URI follows this pattern:
Ben Dodson4e8620f2010-08-25 10:55:47 -07001342 * {@code <scheme>:<opaque part>#<fragment>}
Jesse Wilson0f28af22011-10-28 18:27:44 -04001343 *
Ben Dodson58a34592010-08-18 18:23:18 -07001344 * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001345 */
1346 public static final class Builder {
1347
1348 private String scheme;
1349 private Part opaquePart;
1350 private Part authority;
1351 private PathPart path;
1352 private Part query;
1353 private Part fragment;
1354
1355 /**
1356 * Constructs a new Builder.
1357 */
1358 public Builder() {}
1359
1360 /**
1361 * Sets the scheme.
1362 *
1363 * @param scheme name or {@code null} if this is a relative Uri
1364 */
1365 public Builder scheme(String scheme) {
1366 this.scheme = scheme;
1367 return this;
1368 }
1369
1370 Builder opaquePart(Part opaquePart) {
1371 this.opaquePart = opaquePart;
1372 return this;
1373 }
1374
1375 /**
1376 * Encodes and sets the given opaque scheme-specific-part.
1377 *
1378 * @param opaquePart decoded opaque part
1379 */
1380 public Builder opaquePart(String opaquePart) {
1381 return opaquePart(Part.fromDecoded(opaquePart));
1382 }
1383
1384 /**
1385 * Sets the previously encoded opaque scheme-specific-part.
1386 *
1387 * @param opaquePart encoded opaque part
1388 */
1389 public Builder encodedOpaquePart(String opaquePart) {
1390 return opaquePart(Part.fromEncoded(opaquePart));
1391 }
1392
1393 Builder authority(Part authority) {
1394 // This URI will be hierarchical.
1395 this.opaquePart = null;
1396
1397 this.authority = authority;
1398 return this;
1399 }
1400
1401 /**
1402 * Encodes and sets the authority.
1403 */
1404 public Builder authority(String authority) {
1405 return authority(Part.fromDecoded(authority));
1406 }
1407
1408 /**
1409 * Sets the previously encoded authority.
1410 */
1411 public Builder encodedAuthority(String authority) {
1412 return authority(Part.fromEncoded(authority));
1413 }
1414
1415 Builder path(PathPart path) {
1416 // This URI will be hierarchical.
1417 this.opaquePart = null;
1418
1419 this.path = path;
1420 return this;
1421 }
1422
1423 /**
1424 * Sets the path. Leaves '/' characters intact but encodes others as
1425 * necessary.
1426 *
1427 * <p>If the path is not null and doesn't start with a '/', and if
1428 * you specify a scheme and/or authority, the builder will prepend the
1429 * given path with a '/'.
1430 */
1431 public Builder path(String path) {
1432 return path(PathPart.fromDecoded(path));
1433 }
1434
1435 /**
1436 * Sets the previously encoded path.
1437 *
1438 * <p>If the path is not null and doesn't start with a '/', and if
1439 * you specify a scheme and/or authority, the builder will prepend the
1440 * given path with a '/'.
1441 */
1442 public Builder encodedPath(String path) {
1443 return path(PathPart.fromEncoded(path));
1444 }
1445
1446 /**
1447 * Encodes the given segment and appends it to the path.
1448 */
1449 public Builder appendPath(String newSegment) {
1450 return path(PathPart.appendDecodedSegment(path, newSegment));
1451 }
1452
1453 /**
1454 * Appends the given segment to the path.
1455 */
1456 public Builder appendEncodedPath(String newSegment) {
1457 return path(PathPart.appendEncodedSegment(path, newSegment));
1458 }
1459
1460 Builder query(Part query) {
1461 // This URI will be hierarchical.
1462 this.opaquePart = null;
1463
1464 this.query = query;
1465 return this;
1466 }
1467
1468 /**
1469 * Encodes and sets the query.
1470 */
1471 public Builder query(String query) {
1472 return query(Part.fromDecoded(query));
1473 }
1474
1475 /**
1476 * Sets the previously encoded query.
1477 */
1478 public Builder encodedQuery(String query) {
1479 return query(Part.fromEncoded(query));
1480 }
1481
1482 Builder fragment(Part fragment) {
1483 this.fragment = fragment;
1484 return this;
1485 }
1486
1487 /**
1488 * Encodes and sets the fragment.
1489 */
1490 public Builder fragment(String fragment) {
1491 return fragment(Part.fromDecoded(fragment));
1492 }
1493
1494 /**
1495 * Sets the previously encoded fragment.
1496 */
1497 public Builder encodedFragment(String fragment) {
1498 return fragment(Part.fromEncoded(fragment));
1499 }
1500
1501 /**
1502 * Encodes the key and value and then appends the parameter to the
1503 * query string.
1504 *
1505 * @param key which will be encoded
1506 * @param value which will be encoded
1507 */
1508 public Builder appendQueryParameter(String key, String value) {
1509 // This URI will be hierarchical.
1510 this.opaquePart = null;
1511
1512 String encodedParameter = encode(key, null) + "="
1513 + encode(value, null);
1514
1515 if (query == null) {
1516 query = Part.fromEncoded(encodedParameter);
1517 return this;
1518 }
1519
1520 String oldQuery = query.getEncoded();
1521 if (oldQuery == null || oldQuery.length() == 0) {
1522 query = Part.fromEncoded(encodedParameter);
1523 } else {
1524 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1525 }
1526
1527 return this;
1528 }
1529
1530 /**
Ben Dodson58a34592010-08-18 18:23:18 -07001531 * Clears the the previously set query.
1532 */
1533 public Builder clearQuery() {
1534 return query((Part) null);
1535 }
1536
1537 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001538 * Constructs a Uri with the current attributes.
1539 *
1540 * @throws UnsupportedOperationException if the URI is opaque and the
1541 * scheme is null
1542 */
1543 public Uri build() {
1544 if (opaquePart != null) {
1545 if (this.scheme == null) {
1546 throw new UnsupportedOperationException(
1547 "An opaque URI must have a scheme.");
1548 }
1549
1550 return new OpaqueUri(scheme, opaquePart, fragment);
1551 } else {
1552 // Hierarchical URIs should not return null for getPath().
1553 PathPart path = this.path;
1554 if (path == null || path == PathPart.NULL) {
1555 path = PathPart.EMPTY;
1556 } else {
1557 // If we have a scheme and/or authority, the path must
1558 // be absolute. Prepend it with a '/' if necessary.
1559 if (hasSchemeOrAuthority()) {
1560 path = PathPart.makeAbsolute(path);
1561 }
1562 }
1563
1564 return new HierarchicalUri(
1565 scheme, authority, path, query, fragment);
1566 }
1567 }
1568
1569 private boolean hasSchemeOrAuthority() {
1570 return scheme != null
1571 || (authority != null && authority != Part.NULL);
1572
1573 }
1574
1575 @Override
1576 public String toString() {
1577 return build().toString();
1578 }
1579 }
1580
1581 /**
Ben Dodson58a34592010-08-18 18:23:18 -07001582 * Returns a set of the unique names of all query parameters. Iterating
1583 * over the set will return the names in order of their first occurrence.
1584 *
1585 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1586 *
1587 * @return a set of decoded names
1588 */
1589 public Set<String> getQueryParameterNames() {
1590 if (isOpaque()) {
1591 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1592 }
1593
1594 String query = getEncodedQuery();
1595 if (query == null) {
1596 return Collections.emptySet();
1597 }
1598
1599 Set<String> names = new LinkedHashSet<String>();
1600 int start = 0;
1601 do {
1602 int next = query.indexOf('&', start);
1603 int end = (next == -1) ? query.length() : next;
1604
1605 int separator = query.indexOf('=', start);
1606 if (separator > end || separator == -1) {
1607 separator = end;
1608 }
1609
1610 String name = query.substring(start, separator);
1611 names.add(decode(name));
1612
1613 // Move start to end of name.
1614 start = end + 1;
1615 } while (start < query.length());
1616
1617 return Collections.unmodifiableSet(names);
1618 }
1619
1620 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001621 * Searches the query string for parameter values with the given key.
1622 *
1623 * @param key which will be encoded
1624 *
1625 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1626 * @throws NullPointerException if key is null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001627 * @return a list of decoded values
1628 */
1629 public List<String> getQueryParameters(String key) {
1630 if (isOpaque()) {
1631 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1632 }
Ben Dodson58a34592010-08-18 18:23:18 -07001633 if (key == null) {
1634 throw new NullPointerException("key");
1635 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001636
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001637 String query = getEncodedQuery();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001638 if (query == null) {
1639 return Collections.emptyList();
1640 }
1641
1642 String encodedKey;
1643 try {
1644 encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1645 } catch (UnsupportedEncodingException e) {
1646 throw new AssertionError(e);
1647 }
1648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001649 ArrayList<String> values = new ArrayList<String>();
1650
1651 int start = 0;
Ben Dodson58a34592010-08-18 18:23:18 -07001652 do {
1653 int nextAmpersand = query.indexOf('&', start);
1654 int end = nextAmpersand != -1 ? nextAmpersand : query.length();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655
Ben Dodson58a34592010-08-18 18:23:18 -07001656 int separator = query.indexOf('=', start);
1657 if (separator > end || separator == -1) {
1658 separator = end;
1659 }
1660
1661 if (separator - start == encodedKey.length()
1662 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1663 if (separator == end) {
1664 values.add("");
1665 } else {
1666 values.add(decode(query.substring(separator + 1, end)));
1667 }
1668 }
1669
1670 // Move start to end of name.
1671 if (nextAmpersand != -1) {
1672 start = nextAmpersand + 1;
1673 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001674 break;
1675 }
Ben Dodson58a34592010-08-18 18:23:18 -07001676 } while (true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001677
1678 return Collections.unmodifiableList(values);
1679 }
1680
1681 /**
1682 * Searches the query string for the first value with the given key.
1683 *
Narayan Kamath4356c952015-04-27 11:00:54 +01001684 * <p><strong>Warning:</strong> Prior to Jelly Bean, this decoded
Jesse Wilson41e08392011-11-11 11:16:17 -05001685 * the '+' character as '+' rather than ' '.
1686 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001687 * @param key which will be encoded
1688 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1689 * @throws NullPointerException if key is null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690 * @return the decoded value or null if no parameter is found
1691 */
Scott Kennedye26450b2018-02-15 15:36:40 -08001692 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 public String getQueryParameter(String key) {
1694 if (isOpaque()) {
1695 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1696 }
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001697 if (key == null) {
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001698 throw new NullPointerException("key");
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001699 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001700
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001701 final String query = getEncodedQuery();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001702 if (query == null) {
1703 return null;
1704 }
1705
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001706 final String encodedKey = encode(key, null);
Ben Dodson58a34592010-08-18 18:23:18 -07001707 final int length = query.length();
1708 int start = 0;
1709 do {
1710 int nextAmpersand = query.indexOf('&', start);
1711 int end = nextAmpersand != -1 ? nextAmpersand : length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001712
Ben Dodson58a34592010-08-18 18:23:18 -07001713 int separator = query.indexOf('=', start);
1714 if (separator > end || separator == -1) {
1715 separator = end;
1716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001717
Ben Dodson58a34592010-08-18 18:23:18 -07001718 if (separator - start == encodedKey.length()
1719 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1720 if (separator == end) {
Jesse Wilson0f28af22011-10-28 18:27:44 -04001721 return "";
Ben Dodson58a34592010-08-18 18:23:18 -07001722 } else {
Jesse Wilson0f28af22011-10-28 18:27:44 -04001723 String encodedValue = query.substring(separator + 1, end);
Elliott Hughesd396a442013-06-28 16:24:48 -07001724 return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001725 }
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001726 }
Ben Dodson58a34592010-08-18 18:23:18 -07001727
1728 // Move start to end of name.
1729 if (nextAmpersand != -1) {
1730 start = nextAmpersand + 1;
1731 } else {
1732 break;
1733 }
1734 } while (true);
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001735 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001736 }
1737
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001738 /**
1739 * Searches the query string for the first value with the given key and interprets it
1740 * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1741 * else is interpreted as <code>true</code>.
1742 *
1743 * @param key which will be decoded
1744 * @param defaultValue the default value to return if there is no query parameter for key
1745 * @return the boolean interpretation of the query parameter key
1746 */
1747 public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1748 String flag = getQueryParameter(key);
1749 if (flag == null) {
1750 return defaultValue;
1751 }
Elliott Hughescb64d432013-08-02 10:00:44 -07001752 flag = flag.toLowerCase(Locale.ROOT);
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001753 return (!"false".equals(flag) && !"0".equals(flag));
1754 }
1755
Nick Pellyccae4122012-01-09 14:12:58 -08001756 /**
Jesse Wilsonabc43dd2012-05-10 14:29:33 -04001757 * Return an equivalent URI with a lowercase scheme component.
Nick Pellyccae4122012-01-09 14:12:58 -08001758 * This aligns the Uri with Android best practices for
1759 * intent filtering.
1760 *
1761 * <p>For example, "HTTP://www.android.com" becomes
1762 * "http://www.android.com"
1763 *
1764 * <p>All URIs received from outside Android (such as user input,
1765 * or external sources like Bluetooth, NFC, or the Internet) should
1766 * be normalized before they are used to create an Intent.
1767 *
1768 * <p class="note">This method does <em>not</em> validate bad URI's,
1769 * or 'fix' poorly formatted URI's - so do not use it for input validation.
1770 * A Uri will always be returned, even if the Uri is badly formatted to
1771 * begin with and a scheme component cannot be found.
1772 *
1773 * @return normalized Uri (never null)
Elliot Waite54de7742017-01-11 15:30:35 -08001774 * @see android.content.Intent#setData
1775 * @see android.content.Intent#setDataAndNormalize
Nick Pellyccae4122012-01-09 14:12:58 -08001776 */
Jesse Wilsonabc43dd2012-05-10 14:29:33 -04001777 public Uri normalizeScheme() {
Nick Pellyccae4122012-01-09 14:12:58 -08001778 String scheme = getScheme();
1779 if (scheme == null) return this; // give up
Elliott Hughescb64d432013-08-02 10:00:44 -07001780 String lowerScheme = scheme.toLowerCase(Locale.ROOT);
Nick Pellyccae4122012-01-09 14:12:58 -08001781 if (scheme.equals(lowerScheme)) return this; // no change
1782
1783 return buildUpon().scheme(lowerScheme).build();
1784 }
1785
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001786 /** Identifies a null parcelled Uri. */
1787 private static final int NULL_TYPE_ID = 0;
1788
1789 /**
1790 * Reads Uris from Parcels.
1791 */
1792 public static final Parcelable.Creator<Uri> CREATOR
1793 = new Parcelable.Creator<Uri>() {
1794 public Uri createFromParcel(Parcel in) {
1795 int type = in.readInt();
1796 switch (type) {
1797 case NULL_TYPE_ID: return null;
1798 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1799 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1800 case HierarchicalUri.TYPE_ID:
1801 return HierarchicalUri.readFrom(in);
1802 }
1803
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07001804 throw new IllegalArgumentException("Unknown URI type: " + type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001805 }
1806
1807 public Uri[] newArray(int size) {
1808 return new Uri[size];
1809 }
1810 };
1811
1812 /**
1813 * Writes a Uri to a Parcel.
1814 *
1815 * @param out parcel to write to
1816 * @param uri to write, can be null
1817 */
1818 public static void writeToParcel(Parcel out, Uri uri) {
1819 if (uri == null) {
1820 out.writeInt(NULL_TYPE_ID);
1821 } else {
1822 uri.writeToParcel(out, 0);
1823 }
1824 }
1825
1826 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1827
1828 /**
1829 * Encodes characters in the given string as '%'-escaped octets
1830 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1831 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1832 * all other characters.
1833 *
1834 * @param s string to encode
1835 * @return an encoded version of s suitable for use as a URI component,
1836 * or null if s is null
1837 */
1838 public static String encode(String s) {
1839 return encode(s, null);
1840 }
1841
1842 /**
1843 * Encodes characters in the given string as '%'-escaped octets
1844 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1845 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1846 * all other characters with the exception of those specified in the
1847 * allow argument.
1848 *
1849 * @param s string to encode
1850 * @param allow set of additional characters to allow in the encoded form,
1851 * null if no characters should be skipped
1852 * @return an encoded version of s suitable for use as a URI component,
1853 * or null if s is null
1854 */
1855 public static String encode(String s, String allow) {
1856 if (s == null) {
1857 return null;
1858 }
1859
1860 // Lazily-initialized buffers.
1861 StringBuilder encoded = null;
1862
1863 int oldLength = s.length();
1864
1865 // This loop alternates between copying over allowed characters and
1866 // encoding in chunks. This results in fewer method calls and
1867 // allocations than encoding one character at a time.
1868 int current = 0;
1869 while (current < oldLength) {
1870 // Start in "copying" mode where we copy over allowed chars.
1871
1872 // Find the next character which needs to be encoded.
1873 int nextToEncode = current;
1874 while (nextToEncode < oldLength
1875 && isAllowed(s.charAt(nextToEncode), allow)) {
1876 nextToEncode++;
1877 }
1878
1879 // If there's nothing more to encode...
1880 if (nextToEncode == oldLength) {
1881 if (current == 0) {
1882 // We didn't need to encode anything!
1883 return s;
1884 } else {
1885 // Presumably, we've already done some encoding.
1886 encoded.append(s, current, oldLength);
1887 return encoded.toString();
1888 }
1889 }
1890
1891 if (encoded == null) {
1892 encoded = new StringBuilder();
1893 }
1894
1895 if (nextToEncode > current) {
1896 // Append allowed characters leading up to this point.
1897 encoded.append(s, current, nextToEncode);
1898 } else {
1899 // assert nextToEncode == current
1900 }
1901
1902 // Switch to "encoding" mode.
1903
1904 // Find the next allowed character.
1905 current = nextToEncode;
1906 int nextAllowed = current + 1;
1907 while (nextAllowed < oldLength
1908 && !isAllowed(s.charAt(nextAllowed), allow)) {
1909 nextAllowed++;
1910 }
1911
1912 // Convert the substring to bytes and encode the bytes as
1913 // '%'-escaped octets.
1914 String toEncode = s.substring(current, nextAllowed);
1915 try {
1916 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1917 int bytesLength = bytes.length;
1918 for (int i = 0; i < bytesLength; i++) {
1919 encoded.append('%');
1920 encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1921 encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1922 }
1923 } catch (UnsupportedEncodingException e) {
1924 throw new AssertionError(e);
1925 }
1926
1927 current = nextAllowed;
1928 }
1929
1930 // Encoded could still be null at this point if s is empty.
1931 return encoded == null ? s : encoded.toString();
1932 }
1933
1934 /**
1935 * Returns true if the given character is allowed.
1936 *
1937 * @param c character to check
1938 * @param allow characters to allow
1939 * @return true if the character is allowed or false if it should be
1940 * encoded
1941 */
1942 private static boolean isAllowed(char c, String allow) {
1943 return (c >= 'A' && c <= 'Z')
1944 || (c >= 'a' && c <= 'z')
1945 || (c >= '0' && c <= '9')
1946 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1947 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1948 }
1949
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001950 /**
1951 * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1952 * Replaces invalid octets with the unicode replacement character
1953 * ("\\uFFFD").
1954 *
1955 * @param s encoded string to decode
1956 * @return the given string with escaped octets decoded, or null if
1957 * s is null
1958 */
1959 public static String decode(String s) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001960 if (s == null) {
1961 return null;
1962 }
Neil Fuller0f6f3bd2018-07-13 20:02:58 +01001963 return UriCodec.decode(
1964 s, false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001965 }
1966
1967 /**
1968 * Support for part implementations.
1969 */
1970 static abstract class AbstractPart {
1971
1972 /**
1973 * Enum which indicates which representation of a given part we have.
1974 */
1975 static class Representation {
1976 static final int BOTH = 0;
1977 static final int ENCODED = 1;
1978 static final int DECODED = 2;
1979 }
1980
1981 volatile String encoded;
1982 volatile String decoded;
1983
1984 AbstractPart(String encoded, String decoded) {
1985 this.encoded = encoded;
1986 this.decoded = decoded;
1987 }
1988
1989 abstract String getEncoded();
1990
1991 final String getDecoded() {
1992 @SuppressWarnings("StringEquality")
1993 boolean hasDecoded = decoded != NOT_CACHED;
1994 return hasDecoded ? decoded : (decoded = decode(encoded));
1995 }
1996
1997 final void writeTo(Parcel parcel) {
1998 @SuppressWarnings("StringEquality")
1999 boolean hasEncoded = encoded != NOT_CACHED;
2000
2001 @SuppressWarnings("StringEquality")
2002 boolean hasDecoded = decoded != NOT_CACHED;
2003
2004 if (hasEncoded && hasDecoded) {
2005 parcel.writeInt(Representation.BOTH);
2006 parcel.writeString(encoded);
2007 parcel.writeString(decoded);
2008 } else if (hasEncoded) {
2009 parcel.writeInt(Representation.ENCODED);
2010 parcel.writeString(encoded);
2011 } else if (hasDecoded) {
2012 parcel.writeInt(Representation.DECODED);
2013 parcel.writeString(decoded);
2014 } else {
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07002015 throw new IllegalArgumentException("Neither encoded nor decoded");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002016 }
2017 }
2018 }
2019
2020 /**
2021 * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
2022 * creates the encoded or decoded version from the other.
2023 */
2024 static class Part extends AbstractPart {
2025
2026 /** A part with null values. */
2027 static final Part NULL = new EmptyPart(null);
2028
2029 /** A part with empty strings for values. */
2030 static final Part EMPTY = new EmptyPart("");
2031
2032 private Part(String encoded, String decoded) {
2033 super(encoded, decoded);
2034 }
2035
2036 boolean isEmpty() {
2037 return false;
2038 }
2039
2040 String getEncoded() {
2041 @SuppressWarnings("StringEquality")
2042 boolean hasEncoded = encoded != NOT_CACHED;
2043 return hasEncoded ? encoded : (encoded = encode(decoded));
2044 }
2045
2046 static Part readFrom(Parcel parcel) {
2047 int representation = parcel.readInt();
2048 switch (representation) {
2049 case Representation.BOTH:
2050 return from(parcel.readString(), parcel.readString());
2051 case Representation.ENCODED:
2052 return fromEncoded(parcel.readString());
2053 case Representation.DECODED:
2054 return fromDecoded(parcel.readString());
2055 default:
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07002056 throw new IllegalArgumentException("Unknown representation: "
2057 + representation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002058 }
2059 }
2060
2061 /**
2062 * Returns given part or {@link #NULL} if the given part is null.
2063 */
2064 static Part nonNull(Part part) {
2065 return part == null ? NULL : part;
2066 }
2067
2068 /**
2069 * Creates a part from the encoded string.
2070 *
2071 * @param encoded part string
2072 */
2073 static Part fromEncoded(String encoded) {
2074 return from(encoded, NOT_CACHED);
2075 }
2076
2077 /**
2078 * Creates a part from the decoded string.
2079 *
2080 * @param decoded part string
2081 */
2082 static Part fromDecoded(String decoded) {
2083 return from(NOT_CACHED, decoded);
2084 }
2085
2086 /**
2087 * Creates a part from the encoded and decoded strings.
2088 *
2089 * @param encoded part string
2090 * @param decoded part string
2091 */
2092 static Part from(String encoded, String decoded) {
2093 // We have to check both encoded and decoded in case one is
2094 // NOT_CACHED.
2095
2096 if (encoded == null) {
2097 return NULL;
2098 }
2099 if (encoded.length() == 0) {
2100 return EMPTY;
2101 }
2102
2103 if (decoded == null) {
2104 return NULL;
2105 }
2106 if (decoded .length() == 0) {
2107 return EMPTY;
2108 }
2109
2110 return new Part(encoded, decoded);
2111 }
2112
2113 private static class EmptyPart extends Part {
2114 public EmptyPart(String value) {
2115 super(value, value);
2116 }
2117
2118 @Override
2119 boolean isEmpty() {
2120 return true;
2121 }
2122 }
2123 }
2124
2125 /**
2126 * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2127 * creates the encoded or decoded version from the other.
2128 */
2129 static class PathPart extends AbstractPart {
2130
2131 /** A part with null values. */
2132 static final PathPart NULL = new PathPart(null, null);
2133
2134 /** A part with empty strings for values. */
2135 static final PathPart EMPTY = new PathPart("", "");
2136
2137 private PathPart(String encoded, String decoded) {
2138 super(encoded, decoded);
2139 }
2140
2141 String getEncoded() {
2142 @SuppressWarnings("StringEquality")
2143 boolean hasEncoded = encoded != NOT_CACHED;
2144
2145 // Don't encode '/'.
2146 return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2147 }
2148
2149 /**
2150 * Cached path segments. This doesn't need to be volatile--we don't
2151 * care if other threads see the result.
2152 */
2153 private PathSegments pathSegments;
2154
2155 /**
2156 * Gets the individual path segments. Parses them if necessary.
2157 *
2158 * @return parsed path segments or null if this isn't a hierarchical
2159 * URI
2160 */
2161 PathSegments getPathSegments() {
2162 if (pathSegments != null) {
2163 return pathSegments;
2164 }
2165
2166 String path = getEncoded();
2167 if (path == null) {
2168 return pathSegments = PathSegments.EMPTY;
2169 }
2170
2171 PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2172
2173 int previous = 0;
2174 int current;
2175 while ((current = path.indexOf('/', previous)) > -1) {
2176 // This check keeps us from adding a segment if the path starts
2177 // '/' and an empty segment for "//".
2178 if (previous < current) {
2179 String decodedSegment
2180 = decode(path.substring(previous, current));
2181 segmentBuilder.add(decodedSegment);
2182 }
2183 previous = current + 1;
2184 }
2185
2186 // Add in the final path segment.
2187 if (previous < path.length()) {
2188 segmentBuilder.add(decode(path.substring(previous)));
2189 }
2190
2191 return pathSegments = segmentBuilder.build();
2192 }
2193
2194 static PathPart appendEncodedSegment(PathPart oldPart,
2195 String newSegment) {
2196 // If there is no old path, should we make the new path relative
2197 // or absolute? I pick absolute.
2198
2199 if (oldPart == null) {
2200 // No old path.
2201 return fromEncoded("/" + newSegment);
2202 }
2203
2204 String oldPath = oldPart.getEncoded();
2205
2206 if (oldPath == null) {
2207 oldPath = "";
2208 }
2209
2210 int oldPathLength = oldPath.length();
2211 String newPath;
2212 if (oldPathLength == 0) {
2213 // No old path.
2214 newPath = "/" + newSegment;
2215 } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2216 newPath = oldPath + newSegment;
2217 } else {
2218 newPath = oldPath + "/" + newSegment;
2219 }
2220
2221 return fromEncoded(newPath);
2222 }
2223
2224 static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2225 String encoded = encode(decoded);
2226
2227 // TODO: Should we reuse old PathSegments? Probably not.
2228 return appendEncodedSegment(oldPart, encoded);
2229 }
2230
2231 static PathPart readFrom(Parcel parcel) {
2232 int representation = parcel.readInt();
2233 switch (representation) {
2234 case Representation.BOTH:
2235 return from(parcel.readString(), parcel.readString());
2236 case Representation.ENCODED:
2237 return fromEncoded(parcel.readString());
2238 case Representation.DECODED:
2239 return fromDecoded(parcel.readString());
2240 default:
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07002241 throw new IllegalArgumentException("Bad representation: " + representation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002242 }
2243 }
2244
2245 /**
2246 * Creates a path from the encoded string.
2247 *
2248 * @param encoded part string
2249 */
2250 static PathPart fromEncoded(String encoded) {
2251 return from(encoded, NOT_CACHED);
2252 }
2253
2254 /**
2255 * Creates a path from the decoded string.
2256 *
2257 * @param decoded part string
2258 */
2259 static PathPart fromDecoded(String decoded) {
2260 return from(NOT_CACHED, decoded);
2261 }
2262
2263 /**
2264 * Creates a path from the encoded and decoded strings.
2265 *
2266 * @param encoded part string
2267 * @param decoded part string
2268 */
2269 static PathPart from(String encoded, String decoded) {
2270 if (encoded == null) {
2271 return NULL;
2272 }
2273
2274 if (encoded.length() == 0) {
2275 return EMPTY;
2276 }
2277
2278 return new PathPart(encoded, decoded);
2279 }
2280
2281 /**
2282 * Prepends path values with "/" if they're present, not empty, and
2283 * they don't already start with "/".
2284 */
2285 static PathPart makeAbsolute(PathPart oldPart) {
2286 @SuppressWarnings("StringEquality")
2287 boolean encodedCached = oldPart.encoded != NOT_CACHED;
2288
2289 // We don't care which version we use, and we don't want to force
2290 // unneccessary encoding/decoding.
2291 String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2292
2293 if (oldPath == null || oldPath.length() == 0
2294 || oldPath.startsWith("/")) {
2295 return oldPart;
2296 }
2297
2298 // Prepend encoded string if present.
2299 String newEncoded = encodedCached
2300 ? "/" + oldPart.encoded : NOT_CACHED;
2301
2302 // Prepend decoded string if present.
2303 @SuppressWarnings("StringEquality")
2304 boolean decodedCached = oldPart.decoded != NOT_CACHED;
2305 String newDecoded = decodedCached
2306 ? "/" + oldPart.decoded
2307 : NOT_CACHED;
2308
2309 return new PathPart(newEncoded, newDecoded);
2310 }
2311 }
2312
2313 /**
2314 * Creates a new Uri by appending an already-encoded path segment to a
2315 * base Uri.
2316 *
2317 * @param baseUri Uri to append path segment to
2318 * @param pathSegment encoded path segment to append
Jesse Wilson0f28af22011-10-28 18:27:44 -04002319 * @return a new Uri based on baseUri with the given segment appended to
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002320 * the path
2321 * @throws NullPointerException if baseUri is null
2322 */
2323 public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2324 Builder builder = baseUri.buildUpon();
2325 builder = builder.appendEncodedPath(pathSegment);
2326 return builder.build();
2327 }
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -07002328
2329 /**
2330 * If this {@link Uri} is {@code file://}, then resolve and return its
2331 * canonical path. Also fixes legacy emulated storage paths so they are
2332 * usable across user boundaries. Should always be called from the app
2333 * process before sending elsewhere.
2334 *
2335 * @hide
2336 */
Mathew Inwoodfa3a7462018-08-08 14:52:47 +01002337 @UnsupportedAppUsage
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -07002338 public Uri getCanonicalUri() {
2339 if ("file".equals(getScheme())) {
2340 final String canonicalPath;
2341 try {
2342 canonicalPath = new File(getPath()).getCanonicalPath();
2343 } catch (IOException e) {
2344 return this;
2345 }
2346
2347 if (Environment.isExternalStorageEmulated()) {
2348 final String legacyPath = Environment.getLegacyExternalStorageDirectory()
2349 .toString();
2350
2351 // Splice in user-specific path when legacy path is found
2352 if (canonicalPath.startsWith(legacyPath)) {
2353 return Uri.fromFile(new File(
2354 Environment.getExternalStorageDirectory().toString(),
2355 canonicalPath.substring(legacyPath.length() + 1)));
2356 }
2357 }
2358
2359 return Uri.fromFile(new File(canonicalPath));
2360 } else {
2361 return this;
2362 }
2363 }
Jeff Sharkeya14acd22013-04-02 18:27:45 -07002364
2365 /**
2366 * If this is a {@code file://} Uri, it will be reported to
2367 * {@link StrictMode}.
2368 *
2369 * @hide
2370 */
2371 public void checkFileUriExposed(String location) {
Jeff Sharkeyfb833f32016-12-01 14:59:59 -07002372 if ("file".equals(getScheme())
2373 && (getPath() != null) && !getPath().startsWith("/system/")) {
Jeff Sharkey344744b2016-01-28 19:03:30 -07002374 StrictMode.onFileUriExposed(this, location);
Jeff Sharkeya14acd22013-04-02 18:27:45 -07002375 }
2376 }
Jeff Sharkey846318a2014-04-04 12:12:41 -07002377
2378 /**
Jeff Sharkeyfb833f32016-12-01 14:59:59 -07002379 * If this is a {@code content://} Uri without access flags, it will be
2380 * reported to {@link StrictMode}.
2381 *
2382 * @hide
2383 */
2384 public void checkContentUriWithoutPermission(String location, int flags) {
2385 if ("content".equals(getScheme()) && !Intent.isAccessUriMode(flags)) {
2386 StrictMode.onContentUriWithoutPermission(this, location);
2387 }
2388 }
2389
2390 /**
Jeff Sharkey846318a2014-04-04 12:12:41 -07002391 * Test if this is a path prefix match against the given Uri. Verifies that
2392 * scheme, authority, and atomic path segments match.
2393 *
2394 * @hide
2395 */
2396 public boolean isPathPrefixMatch(Uri prefix) {
2397 if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
2398 if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
2399
2400 List<String> seg = getPathSegments();
2401 List<String> prefixSeg = prefix.getPathSegments();
2402
2403 final int prefixSize = prefixSeg.size();
2404 if (seg.size() < prefixSize) return false;
2405
2406 for (int i = 0; i < prefixSize; i++) {
2407 if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
2408 return false;
2409 }
2410 }
2411
2412 return true;
2413 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002414}