blob: 8fb59af3e022ab0020b062f6a29f7d5d67c45c51 [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
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -070019import android.os.Environment;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.os.Parcel;
21import android.os.Parcelable;
Jeff Sharkeya14acd22013-04-02 18:27:45 -070022import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.util.Log;
Jeff Sharkey846318a2014-04-04 12:12:41 -070024
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import java.io.File;
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -070026import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import java.io.UnsupportedEncodingException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import java.net.URLEncoder;
Elliott Hughesd396a442013-06-28 16:24:48 -070029import java.nio.charset.StandardCharsets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import java.util.AbstractList;
31import java.util.ArrayList;
32import java.util.Collections;
Ben Dodson58a34592010-08-18 18:23:18 -070033import java.util.LinkedHashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import java.util.List;
Nick Pellyccae4122012-01-09 14:12:58 -080035import java.util.Locale;
Jeff Sharkey846318a2014-04-04 12:12:41 -070036import java.util.Objects;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import java.util.RandomAccess;
Ben Dodson58a34592010-08-18 18:23:18 -070038import java.util.Set;
Jeff Sharkey846318a2014-04-04 12:12:41 -070039
Jesse Wilson0f28af22011-10-28 18:27:44 -040040import libcore.net.UriCodec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42/**
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 Allen8f8a11b2013-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 Allen8f8a11b2013-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 */
176 public abstract String getScheme();
177
178 /**
Newton Allen8f8a11b2013-11-26 10:25:38 -0800179 * Gets the scheme-specific part of this URI, i.e.&nbsp;everything between
180 * the scheme separator ':' and the fragment separator '#'. If this is a
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 * relative URI, this method returns the entire URI. Decodes escaped octets.
182 *
183 * <p>Example: "//www.google.com/search?q=android"
184 *
185 * @return the decoded scheme-specific-part
186 */
187 public abstract String getSchemeSpecificPart();
188
189 /**
Newton Allen8f8a11b2013-11-26 10:25:38 -0800190 * Gets the scheme-specific part of this URI, i.e.&nbsp;everything between
191 * the scheme separator ':' and the fragment separator '#'. If this is a
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 * relative URI, this method returns the entire URI. Leaves escaped octets
193 * intact.
194 *
195 * <p>Example: "//www.google.com/search?q=android"
196 *
197 * @return the decoded scheme-specific-part
198 */
199 public abstract String getEncodedSchemeSpecificPart();
200
201 /**
202 * Gets the decoded authority part of this URI. For
203 * server addresses, the authority is structured as follows:
204 * {@code [ userinfo '@' ] host [ ':' port ]}
205 *
206 * <p>Examples: "google.com", "bob@google.com:80"
207 *
208 * @return the authority for this URI or null if not present
209 */
210 public abstract String getAuthority();
211
212 /**
213 * Gets the encoded authority part of this URI. For
214 * server addresses, the authority is structured as follows:
215 * {@code [ userinfo '@' ] host [ ':' port ]}
216 *
217 * <p>Examples: "google.com", "bob@google.com:80"
218 *
219 * @return the authority for this URI or null if not present
220 */
221 public abstract String getEncodedAuthority();
222
223 /**
224 * Gets the decoded user information from the authority.
225 * For example, if the authority is "nobody@google.com", this method will
226 * return "nobody".
227 *
228 * @return the user info for this URI or null if not present
229 */
230 public abstract String getUserInfo();
231
232 /**
233 * Gets the encoded user information from the authority.
234 * For example, if the authority is "nobody@google.com", this method will
235 * return "nobody".
236 *
237 * @return the user info for this URI or null if not present
238 */
239 public abstract String getEncodedUserInfo();
240
241 /**
242 * Gets the encoded host from the authority for this URI. For example,
243 * if the authority is "bob@google.com", this method will return
244 * "google.com".
245 *
246 * @return the host for this URI or null if not present
247 */
248 public abstract String getHost();
249
250 /**
251 * Gets the port from the authority for this URI. For example,
252 * if the authority is "google.com:80", this method will return 80.
253 *
254 * @return the port for this URI or -1 if invalid or not present
255 */
256 public abstract int getPort();
257
258 /**
259 * Gets the decoded path.
260 *
261 * @return the decoded path, or null if this is not a hierarchical URI
262 * (like "mailto:nobody@google.com") or the URI is invalid
263 */
264 public abstract String getPath();
265
266 /**
267 * Gets the encoded path.
268 *
269 * @return the encoded path, or null if this is not a hierarchical URI
270 * (like "mailto:nobody@google.com") or the URI is invalid
271 */
272 public abstract String getEncodedPath();
273
274 /**
275 * Gets the decoded query component from this URI. The query comes after
276 * the query separator ('?') and before the fragment separator ('#'). This
277 * method would return "q=android" for
278 * "http://www.google.com/search?q=android".
279 *
280 * @return the decoded query or null if there isn't one
281 */
282 public abstract String getQuery();
283
284 /**
285 * Gets the encoded 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 encoded query or null if there isn't one
291 */
292 public abstract String getEncodedQuery();
293
294 /**
295 * Gets the decoded fragment part of this URI, everything after the '#'.
296 *
297 * @return the decoded fragment or null if there isn't one
298 */
299 public abstract String getFragment();
300
301 /**
302 * Gets the encoded fragment part of this URI, everything after the '#'.
303 *
304 * @return the encoded fragment or null if there isn't one
305 */
306 public abstract String getEncodedFragment();
307
308 /**
309 * Gets the decoded path segments.
310 *
311 * @return decoded path segments, each without a leading or trailing '/'
312 */
313 public abstract List<String> getPathSegments();
314
315 /**
316 * Gets the decoded last segment in the path.
317 *
318 * @return the decoded last segment or null if the path is empty
319 */
320 public abstract String getLastPathSegment();
321
322 /**
323 * Compares this Uri to another object for equality. Returns true if the
324 * encoded string representations of this Uri and the given Uri are
325 * equal. Case counts. Paths are not normalized. If one Uri specifies a
326 * default port explicitly and the other leaves it implicit, they will not
327 * be considered equal.
328 */
329 public boolean equals(Object o) {
330 if (!(o instanceof Uri)) {
331 return false;
332 }
333
334 Uri other = (Uri) o;
335
336 return toString().equals(other.toString());
337 }
338
339 /**
340 * Hashes the encoded string represention of this Uri consistently with
341 * {@link #equals(Object)}.
342 */
343 public int hashCode() {
344 return toString().hashCode();
345 }
346
347 /**
348 * Compares the string representation of this Uri with that of
349 * another.
350 */
351 public int compareTo(Uri other) {
352 return toString().compareTo(other.toString());
353 }
354
355 /**
356 * Returns the encoded string representation of this URI.
357 * Example: "http://google.com/"
358 */
359 public abstract String toString();
360
361 /**
Dianne Hackborn90c52de2011-09-23 12:57:44 -0700362 * Return a string representation of the URI that is safe to print
363 * to logs and other places where PII should be avoided.
364 * @hide
365 */
366 public String toSafeString() {
367 String scheme = getScheme();
368 String ssp = getSchemeSpecificPart();
369 if (scheme != null) {
370 if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
371 || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
372 || scheme.equalsIgnoreCase("mailto")) {
373 StringBuilder builder = new StringBuilder(64);
374 builder.append(scheme);
375 builder.append(':');
376 if (ssp != null) {
377 for (int i=0; i<ssp.length(); i++) {
378 char c = ssp.charAt(i);
379 if (c == '-' || c == '@' || c == '.') {
380 builder.append(c);
381 } else {
382 builder.append('x');
383 }
384 }
385 }
386 return builder.toString();
387 }
388 }
389 // Not a sensitive scheme, but let's still be conservative about
390 // the data we include -- only the ssp, not the query params or
391 // fragment, because those can often have sensitive info.
392 StringBuilder builder = new StringBuilder(64);
393 if (scheme != null) {
394 builder.append(scheme);
395 builder.append(':');
396 }
397 if (ssp != null) {
398 builder.append(ssp);
399 }
400 return builder.toString();
401 }
402
403 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 * Constructs a new builder, copying the attributes from this Uri.
405 */
406 public abstract Builder buildUpon();
407
408 /** Index of a component which was not found. */
409 private final static int NOT_FOUND = -1;
410
411 /** Placeholder value for an index which hasn't been calculated yet. */
412 private final static int NOT_CALCULATED = -2;
413
414 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 * Error message presented when a user tries to treat an opaque URI as
416 * hierarchical.
417 */
418 private static final String NOT_HIERARCHICAL
419 = "This isn't a hierarchical URI.";
420
421 /** Default encoding. */
422 private static final String DEFAULT_ENCODING = "UTF-8";
423
424 /**
425 * Creates a Uri which parses the given encoded URI string.
426 *
Simon Schoar8aa393b2009-06-10 01:10:58 +0200427 * @param uriString an RFC 2396-compliant, encoded URI
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 * @throws NullPointerException if uriString is null
429 * @return Uri for this given uri string
430 */
431 public static Uri parse(String uriString) {
432 return new StringUri(uriString);
433 }
434
435 /**
436 * Creates a Uri from a file. The URI has the form
437 * "file://<absolute path>". Encodes path characters with the exception of
438 * '/'.
439 *
440 * <p>Example: "file:///tmp/android.txt"
441 *
442 * @throws NullPointerException if file is null
443 * @return a Uri for the given file
444 */
445 public static Uri fromFile(File file) {
446 if (file == null) {
447 throw new NullPointerException("file");
448 }
449
450 PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
451 return new HierarchicalUri(
452 "file", Part.EMPTY, path, Part.NULL, Part.NULL);
453 }
454
455 /**
456 * An implementation which wraps a String URI. This URI can be opaque or
457 * hierarchical, but we extend AbstractHierarchicalUri in case we need
458 * the hierarchical functionality.
459 */
460 private static class StringUri extends AbstractHierarchicalUri {
461
462 /** Used in parcelling. */
463 static final int TYPE_ID = 1;
464
465 /** URI string representation. */
466 private final String uriString;
467
468 private StringUri(String uriString) {
469 if (uriString == null) {
470 throw new NullPointerException("uriString");
471 }
472
473 this.uriString = uriString;
474 }
475
476 static Uri readFrom(Parcel parcel) {
477 return new StringUri(parcel.readString());
478 }
479
480 public int describeContents() {
481 return 0;
482 }
483
484 public void writeToParcel(Parcel parcel, int flags) {
485 parcel.writeInt(TYPE_ID);
486 parcel.writeString(uriString);
487 }
488
489 /** Cached scheme separator index. */
490 private volatile int cachedSsi = NOT_CALCULATED;
491
492 /** Finds the first ':'. Returns -1 if none found. */
493 private int findSchemeSeparator() {
494 return cachedSsi == NOT_CALCULATED
495 ? cachedSsi = uriString.indexOf(':')
496 : cachedSsi;
497 }
498
499 /** Cached fragment separator index. */
500 private volatile int cachedFsi = NOT_CALCULATED;
501
502 /** Finds the first '#'. Returns -1 if none found. */
503 private int findFragmentSeparator() {
504 return cachedFsi == NOT_CALCULATED
505 ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
506 : cachedFsi;
507 }
508
509 public boolean isHierarchical() {
510 int ssi = findSchemeSeparator();
511
512 if (ssi == NOT_FOUND) {
513 // All relative URIs are hierarchical.
514 return true;
515 }
516
517 if (uriString.length() == ssi + 1) {
518 // No ssp.
519 return false;
520 }
521
522 // If the ssp starts with a '/', this is hierarchical.
523 return uriString.charAt(ssi + 1) == '/';
524 }
525
526 public boolean isRelative() {
527 // Note: We return true if the index is 0
528 return findSchemeSeparator() == NOT_FOUND;
529 }
530
531 private volatile String scheme = NOT_CACHED;
532
533 public String getScheme() {
534 @SuppressWarnings("StringEquality")
535 boolean cached = (scheme != NOT_CACHED);
536 return cached ? scheme : (scheme = parseScheme());
537 }
538
539 private String parseScheme() {
540 int ssi = findSchemeSeparator();
541 return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
542 }
543
544 private Part ssp;
545
546 private Part getSsp() {
547 return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
548 }
549
550 public String getEncodedSchemeSpecificPart() {
551 return getSsp().getEncoded();
552 }
553
554 public String getSchemeSpecificPart() {
555 return getSsp().getDecoded();
556 }
557
558 private String parseSsp() {
559 int ssi = findSchemeSeparator();
560 int fsi = findFragmentSeparator();
561
562 // Return everything between ssi and fsi.
563 return fsi == NOT_FOUND
564 ? uriString.substring(ssi + 1)
565 : uriString.substring(ssi + 1, fsi);
566 }
567
568 private Part authority;
569
570 private Part getAuthorityPart() {
571 if (authority == null) {
572 String encodedAuthority
573 = parseAuthority(this.uriString, findSchemeSeparator());
574 return authority = Part.fromEncoded(encodedAuthority);
575 }
576
577 return authority;
578 }
579
580 public String getEncodedAuthority() {
581 return getAuthorityPart().getEncoded();
582 }
583
584 public String getAuthority() {
585 return getAuthorityPart().getDecoded();
586 }
587
588 private PathPart path;
589
590 private PathPart getPathPart() {
591 return path == null
592 ? path = PathPart.fromEncoded(parsePath())
593 : path;
594 }
595
596 public String getPath() {
597 return getPathPart().getDecoded();
598 }
599
600 public String getEncodedPath() {
601 return getPathPart().getEncoded();
602 }
603
604 public List<String> getPathSegments() {
605 return getPathPart().getPathSegments();
606 }
607
608 private String parsePath() {
609 String uriString = this.uriString;
610 int ssi = findSchemeSeparator();
611
612 // If the URI is absolute.
613 if (ssi > -1) {
614 // Is there anything after the ':'?
615 boolean schemeOnly = ssi + 1 == uriString.length();
616 if (schemeOnly) {
617 // Opaque URI.
618 return null;
619 }
620
621 // A '/' after the ':' means this is hierarchical.
622 if (uriString.charAt(ssi + 1) != '/') {
623 // Opaque URI.
624 return null;
625 }
626 } else {
627 // All relative URIs are hierarchical.
628 }
629
630 return parsePath(uriString, ssi);
631 }
632
633 private Part query;
634
635 private Part getQueryPart() {
636 return query == null
637 ? query = Part.fromEncoded(parseQuery()) : query;
638 }
639
640 public String getEncodedQuery() {
641 return getQueryPart().getEncoded();
642 }
643
644 private String parseQuery() {
645 // It doesn't make sense to cache this index. We only ever
646 // calculate it once.
647 int qsi = uriString.indexOf('?', findSchemeSeparator());
648 if (qsi == NOT_FOUND) {
649 return null;
650 }
651
652 int fsi = findFragmentSeparator();
653
654 if (fsi == NOT_FOUND) {
655 return uriString.substring(qsi + 1);
656 }
657
658 if (fsi < qsi) {
659 // Invalid.
660 return null;
661 }
662
663 return uriString.substring(qsi + 1, fsi);
664 }
665
666 public String getQuery() {
667 return getQueryPart().getDecoded();
668 }
669
670 private Part fragment;
671
672 private Part getFragmentPart() {
673 return fragment == null
674 ? fragment = Part.fromEncoded(parseFragment()) : fragment;
675 }
676
677 public String getEncodedFragment() {
678 return getFragmentPart().getEncoded();
679 }
680
681 private String parseFragment() {
682 int fsi = findFragmentSeparator();
683 return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
684 }
685
686 public String getFragment() {
687 return getFragmentPart().getDecoded();
688 }
689
690 public String toString() {
691 return uriString;
692 }
693
694 /**
695 * Parses an authority out of the given URI string.
696 *
697 * @param uriString URI string
698 * @param ssi scheme separator index, -1 for a relative URI
699 *
700 * @return the authority or null if none is found
701 */
702 static String parseAuthority(String uriString, int ssi) {
703 int length = uriString.length();
704
705 // If "//" follows the scheme separator, we have an authority.
706 if (length > ssi + 2
707 && uriString.charAt(ssi + 1) == '/'
708 && uriString.charAt(ssi + 2) == '/') {
709 // We have an authority.
710
711 // Look for the start of the path, query, or fragment, or the
712 // end of the string.
713 int end = ssi + 3;
714 LOOP: while (end < length) {
715 switch (uriString.charAt(end)) {
716 case '/': // Start of path
Adam Vartanianfa3afbd2018-01-31 11:05:10 +0000717 case '\\':// Start of path
718 // Per http://url.spec.whatwg.org/#host-state, the \ character
719 // is treated as if it were a / character when encountered in a
720 // host
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721 case '?': // Start of query
722 case '#': // Start of fragment
723 break LOOP;
724 }
725 end++;
726 }
727
728 return uriString.substring(ssi + 3, end);
729 } else {
730 return null;
731 }
732
733 }
734
735 /**
736 * Parses a path out of this given URI string.
737 *
738 * @param uriString URI string
739 * @param ssi scheme separator index, -1 for a relative URI
740 *
741 * @return the path
742 */
743 static String parsePath(String uriString, int ssi) {
744 int length = uriString.length();
745
746 // Find start of path.
747 int pathStart;
748 if (length > ssi + 2
749 && uriString.charAt(ssi + 1) == '/'
750 && uriString.charAt(ssi + 2) == '/') {
751 // Skip over authority to path.
752 pathStart = ssi + 3;
753 LOOP: while (pathStart < length) {
754 switch (uriString.charAt(pathStart)) {
755 case '?': // Start of query
756 case '#': // Start of fragment
757 return ""; // Empty path.
758 case '/': // Start of path!
Adam Vartanianfa3afbd2018-01-31 11:05:10 +0000759 case '\\':// Start of path!
760 // Per http://url.spec.whatwg.org/#host-state, the \ character
761 // is treated as if it were a / character when encountered in a
762 // host
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763 break LOOP;
764 }
765 pathStart++;
766 }
767 } else {
768 // Path starts immediately after scheme separator.
769 pathStart = ssi + 1;
770 }
771
772 // Find end of path.
773 int pathEnd = pathStart;
774 LOOP: while (pathEnd < length) {
775 switch (uriString.charAt(pathEnd)) {
776 case '?': // Start of query
777 case '#': // Start of fragment
778 break LOOP;
779 }
780 pathEnd++;
781 }
782
783 return uriString.substring(pathStart, pathEnd);
784 }
785
786 public Builder buildUpon() {
787 if (isHierarchical()) {
788 return new Builder()
789 .scheme(getScheme())
790 .authority(getAuthorityPart())
791 .path(getPathPart())
792 .query(getQueryPart())
793 .fragment(getFragmentPart());
794 } else {
795 return new Builder()
796 .scheme(getScheme())
797 .opaquePart(getSsp())
798 .fragment(getFragmentPart());
799 }
800 }
801 }
802
803 /**
804 * Creates an opaque Uri from the given components. Encodes the ssp
805 * which means this method cannot be used to create hierarchical URIs.
806 *
807 * @param scheme of the URI
808 * @param ssp scheme-specific-part, everything between the
809 * scheme separator (':') and the fragment separator ('#'), which will
810 * get encoded
811 * @param fragment fragment, everything after the '#', null if undefined,
812 * will get encoded
813 *
814 * @throws NullPointerException if scheme or ssp is null
815 * @return Uri composed of the given scheme, ssp, and fragment
816 *
817 * @see Builder if you don't want the ssp and fragment to be encoded
818 */
819 public static Uri fromParts(String scheme, String ssp,
820 String fragment) {
821 if (scheme == null) {
822 throw new NullPointerException("scheme");
823 }
824 if (ssp == null) {
825 throw new NullPointerException("ssp");
826 }
827
828 return new OpaqueUri(scheme, Part.fromDecoded(ssp),
829 Part.fromDecoded(fragment));
830 }
831
832 /**
833 * Opaque URI.
834 */
835 private static class OpaqueUri extends Uri {
836
837 /** Used in parcelling. */
838 static final int TYPE_ID = 2;
839
840 private final String scheme;
841 private final Part ssp;
842 private final Part fragment;
843
844 private OpaqueUri(String scheme, Part ssp, Part fragment) {
845 this.scheme = scheme;
846 this.ssp = ssp;
847 this.fragment = fragment == null ? Part.NULL : fragment;
848 }
849
850 static Uri readFrom(Parcel parcel) {
851 return new OpaqueUri(
852 parcel.readString(),
853 Part.readFrom(parcel),
854 Part.readFrom(parcel)
855 );
856 }
857
858 public int describeContents() {
859 return 0;
860 }
861
862 public void writeToParcel(Parcel parcel, int flags) {
863 parcel.writeInt(TYPE_ID);
864 parcel.writeString(scheme);
865 ssp.writeTo(parcel);
866 fragment.writeTo(parcel);
867 }
868
869 public boolean isHierarchical() {
870 return false;
871 }
872
873 public boolean isRelative() {
874 return scheme == null;
875 }
876
877 public String getScheme() {
878 return this.scheme;
879 }
880
881 public String getEncodedSchemeSpecificPart() {
882 return ssp.getEncoded();
883 }
884
885 public String getSchemeSpecificPart() {
886 return ssp.getDecoded();
887 }
888
889 public String getAuthority() {
890 return null;
891 }
892
893 public String getEncodedAuthority() {
894 return null;
895 }
896
897 public String getPath() {
898 return null;
899 }
900
901 public String getEncodedPath() {
902 return null;
903 }
904
905 public String getQuery() {
906 return null;
907 }
908
909 public String getEncodedQuery() {
910 return null;
911 }
912
913 public String getFragment() {
914 return fragment.getDecoded();
915 }
916
917 public String getEncodedFragment() {
918 return fragment.getEncoded();
919 }
920
921 public List<String> getPathSegments() {
922 return Collections.emptyList();
923 }
924
925 public String getLastPathSegment() {
926 return null;
927 }
928
929 public String getUserInfo() {
930 return null;
931 }
932
933 public String getEncodedUserInfo() {
934 return null;
935 }
936
937 public String getHost() {
938 return null;
939 }
940
941 public int getPort() {
942 return -1;
943 }
944
945 private volatile String cachedString = NOT_CACHED;
946
947 public String toString() {
948 @SuppressWarnings("StringEquality")
949 boolean cached = cachedString != NOT_CACHED;
950 if (cached) {
951 return cachedString;
952 }
953
954 StringBuilder sb = new StringBuilder();
955
956 sb.append(scheme).append(':');
957 sb.append(getEncodedSchemeSpecificPart());
958
959 if (!fragment.isEmpty()) {
960 sb.append('#').append(fragment.getEncoded());
961 }
962
963 return cachedString = sb.toString();
964 }
965
966 public Builder buildUpon() {
967 return new Builder()
968 .scheme(this.scheme)
969 .opaquePart(this.ssp)
970 .fragment(this.fragment);
971 }
972 }
973
974 /**
975 * Wrapper for path segment array.
976 */
977 static class PathSegments extends AbstractList<String>
978 implements RandomAccess {
979
980 static final PathSegments EMPTY = new PathSegments(null, 0);
981
982 final String[] segments;
983 final int size;
984
985 PathSegments(String[] segments, int size) {
986 this.segments = segments;
987 this.size = size;
988 }
989
990 public String get(int index) {
991 if (index >= size) {
992 throw new IndexOutOfBoundsException();
993 }
994
995 return segments[index];
996 }
997
998 public int size() {
999 return this.size;
1000 }
1001 }
1002
1003 /**
1004 * Builds PathSegments.
1005 */
1006 static class PathSegmentsBuilder {
1007
1008 String[] segments;
1009 int size = 0;
1010
1011 void add(String segment) {
1012 if (segments == null) {
1013 segments = new String[4];
1014 } else if (size + 1 == segments.length) {
1015 String[] expanded = new String[segments.length * 2];
1016 System.arraycopy(segments, 0, expanded, 0, segments.length);
1017 segments = expanded;
1018 }
1019
1020 segments[size++] = segment;
1021 }
1022
1023 PathSegments build() {
1024 if (segments == null) {
1025 return PathSegments.EMPTY;
1026 }
1027
1028 try {
1029 return new PathSegments(segments, size);
1030 } finally {
1031 // Makes sure this doesn't get reused.
1032 segments = null;
1033 }
1034 }
1035 }
1036
1037 /**
1038 * Support for hierarchical URIs.
1039 */
1040 private abstract static class AbstractHierarchicalUri extends Uri {
1041
1042 public String getLastPathSegment() {
1043 // TODO: If we haven't parsed all of the segments already, just
1044 // grab the last one directly so we only allocate one string.
1045
1046 List<String> segments = getPathSegments();
1047 int size = segments.size();
1048 if (size == 0) {
1049 return null;
1050 }
1051 return segments.get(size - 1);
1052 }
1053
1054 private Part userInfo;
1055
1056 private Part getUserInfoPart() {
1057 return userInfo == null
1058 ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1059 }
1060
1061 public final String getEncodedUserInfo() {
1062 return getUserInfoPart().getEncoded();
1063 }
1064
1065 private String parseUserInfo() {
1066 String authority = getEncodedAuthority();
1067 if (authority == null) {
1068 return null;
1069 }
1070
Adam Vartaniancd6228d2017-11-07 12:22:23 +00001071 int end = authority.lastIndexOf('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 return end == NOT_FOUND ? null : authority.substring(0, end);
1073 }
1074
1075 public String getUserInfo() {
1076 return getUserInfoPart().getDecoded();
1077 }
1078
1079 private volatile String host = NOT_CACHED;
1080
1081 public String getHost() {
1082 @SuppressWarnings("StringEquality")
1083 boolean cached = (host != NOT_CACHED);
1084 return cached ? host
1085 : (host = parseHost());
1086 }
1087
1088 private String parseHost() {
Kenny Rootfc017942009-07-12 10:33:28 -05001089 String authority = getEncodedAuthority();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 if (authority == null) {
1091 return null;
1092 }
1093
1094 // Parse out user info and then port.
Adam Vartaniancd6228d2017-11-07 12:22:23 +00001095 int userInfoSeparator = authority.lastIndexOf('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 int portSeparator = authority.indexOf(':', userInfoSeparator);
1097
Kenny Rootfc017942009-07-12 10:33:28 -05001098 String encodedHost = portSeparator == NOT_FOUND
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 ? authority.substring(userInfoSeparator + 1)
1100 : authority.substring(userInfoSeparator + 1, portSeparator);
Kenny Rootfc017942009-07-12 10:33:28 -05001101
1102 return decode(encodedHost);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 }
1104
1105 private volatile int port = NOT_CALCULATED;
1106
1107 public int getPort() {
1108 return port == NOT_CALCULATED
1109 ? port = parsePort()
1110 : port;
1111 }
1112
1113 private int parsePort() {
Kenny Rootfc017942009-07-12 10:33:28 -05001114 String authority = getEncodedAuthority();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 if (authority == null) {
1116 return -1;
1117 }
1118
1119 // Make sure we look for the port separtor *after* the user info
1120 // separator. We have URLs with a ':' in the user info.
Adam Vartaniancd6228d2017-11-07 12:22:23 +00001121 int userInfoSeparator = authority.lastIndexOf('@');
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 int portSeparator = authority.indexOf(':', userInfoSeparator);
1123
1124 if (portSeparator == NOT_FOUND) {
1125 return -1;
1126 }
1127
Kenny Rootfc017942009-07-12 10:33:28 -05001128 String portString = decode(authority.substring(portSeparator + 1));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001129 try {
1130 return Integer.parseInt(portString);
1131 } catch (NumberFormatException e) {
1132 Log.w(LOG, "Error parsing port string.", e);
1133 return -1;
1134 }
1135 }
1136 }
1137
1138 /**
1139 * Hierarchical Uri.
1140 */
1141 private static class HierarchicalUri extends AbstractHierarchicalUri {
1142
1143 /** Used in parcelling. */
1144 static final int TYPE_ID = 3;
1145
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001146 private final String scheme; // can be null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 private final Part authority;
1148 private final PathPart path;
1149 private final Part query;
1150 private final Part fragment;
1151
1152 private HierarchicalUri(String scheme, Part authority, PathPart path,
1153 Part query, Part fragment) {
1154 this.scheme = scheme;
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001155 this.authority = Part.nonNull(authority);
1156 this.path = path == null ? PathPart.NULL : path;
1157 this.query = Part.nonNull(query);
1158 this.fragment = Part.nonNull(fragment);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 }
1160
1161 static Uri readFrom(Parcel parcel) {
1162 return new HierarchicalUri(
1163 parcel.readString(),
1164 Part.readFrom(parcel),
1165 PathPart.readFrom(parcel),
1166 Part.readFrom(parcel),
1167 Part.readFrom(parcel)
1168 );
1169 }
1170
1171 public int describeContents() {
1172 return 0;
1173 }
1174
1175 public void writeToParcel(Parcel parcel, int flags) {
1176 parcel.writeInt(TYPE_ID);
1177 parcel.writeString(scheme);
1178 authority.writeTo(parcel);
1179 path.writeTo(parcel);
1180 query.writeTo(parcel);
1181 fragment.writeTo(parcel);
1182 }
1183
1184 public boolean isHierarchical() {
1185 return true;
1186 }
1187
1188 public boolean isRelative() {
1189 return scheme == null;
1190 }
1191
1192 public String getScheme() {
1193 return scheme;
1194 }
1195
1196 private Part ssp;
1197
1198 private Part getSsp() {
1199 return ssp == null
1200 ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1201 }
1202
1203 public String getEncodedSchemeSpecificPart() {
1204 return getSsp().getEncoded();
1205 }
1206
1207 public String getSchemeSpecificPart() {
1208 return getSsp().getDecoded();
1209 }
1210
1211 /**
1212 * Creates the encoded scheme-specific part from its sub parts.
1213 */
1214 private String makeSchemeSpecificPart() {
1215 StringBuilder builder = new StringBuilder();
1216 appendSspTo(builder);
1217 return builder.toString();
1218 }
1219
1220 private void appendSspTo(StringBuilder builder) {
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001221 String encodedAuthority = authority.getEncoded();
1222 if (encodedAuthority != null) {
1223 // Even if the authority is "", we still want to append "//".
1224 builder.append("//").append(encodedAuthority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001225 }
1226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 String encodedPath = path.getEncoded();
1228 if (encodedPath != null) {
1229 builder.append(encodedPath);
1230 }
1231
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001232 if (!query.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 builder.append('?').append(query.getEncoded());
1234 }
1235 }
1236
1237 public String getAuthority() {
1238 return this.authority.getDecoded();
1239 }
1240
1241 public String getEncodedAuthority() {
1242 return this.authority.getEncoded();
1243 }
1244
1245 public String getEncodedPath() {
1246 return this.path.getEncoded();
1247 }
1248
1249 public String getPath() {
1250 return this.path.getDecoded();
1251 }
1252
1253 public String getQuery() {
1254 return this.query.getDecoded();
1255 }
1256
1257 public String getEncodedQuery() {
1258 return this.query.getEncoded();
1259 }
1260
1261 public String getFragment() {
1262 return this.fragment.getDecoded();
1263 }
1264
1265 public String getEncodedFragment() {
1266 return this.fragment.getEncoded();
1267 }
1268
1269 public List<String> getPathSegments() {
1270 return this.path.getPathSegments();
1271 }
1272
1273 private volatile String uriString = NOT_CACHED;
1274
1275 @Override
1276 public String toString() {
1277 @SuppressWarnings("StringEquality")
1278 boolean cached = (uriString != NOT_CACHED);
1279 return cached ? uriString
1280 : (uriString = makeUriString());
1281 }
1282
1283 private String makeUriString() {
1284 StringBuilder builder = new StringBuilder();
1285
1286 if (scheme != null) {
1287 builder.append(scheme).append(':');
1288 }
1289
1290 appendSspTo(builder);
1291
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001292 if (!fragment.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001293 builder.append('#').append(fragment.getEncoded());
1294 }
1295
1296 return builder.toString();
1297 }
1298
1299 public Builder buildUpon() {
1300 return new Builder()
1301 .scheme(scheme)
1302 .authority(authority)
1303 .path(path)
1304 .query(query)
1305 .fragment(fragment);
1306 }
1307 }
1308
1309 /**
1310 * Helper class for building or manipulating URI references. Not safe for
1311 * concurrent use.
1312 *
1313 * <p>An absolute hierarchical URI reference follows the pattern:
Ben Dodson4e8620f2010-08-25 10:55:47 -07001314 * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 *
1316 * <p>Relative URI references (which are always hierarchical) follow one
Ben Dodson4e8620f2010-08-25 10:55:47 -07001317 * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1318 * or {@code //<authority><absolute path>?<query>#<fragment>}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 *
1320 * <p>An opaque URI follows this pattern:
Ben Dodson4e8620f2010-08-25 10:55:47 -07001321 * {@code <scheme>:<opaque part>#<fragment>}
Jesse Wilson0f28af22011-10-28 18:27:44 -04001322 *
Ben Dodson58a34592010-08-18 18:23:18 -07001323 * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324 */
1325 public static final class Builder {
1326
1327 private String scheme;
1328 private Part opaquePart;
1329 private Part authority;
1330 private PathPart path;
1331 private Part query;
1332 private Part fragment;
1333
1334 /**
1335 * Constructs a new Builder.
1336 */
1337 public Builder() {}
1338
1339 /**
1340 * Sets the scheme.
1341 *
1342 * @param scheme name or {@code null} if this is a relative Uri
1343 */
1344 public Builder scheme(String scheme) {
1345 this.scheme = scheme;
1346 return this;
1347 }
1348
1349 Builder opaquePart(Part opaquePart) {
1350 this.opaquePart = opaquePart;
1351 return this;
1352 }
1353
1354 /**
1355 * Encodes and sets the given opaque scheme-specific-part.
1356 *
1357 * @param opaquePart decoded opaque part
1358 */
1359 public Builder opaquePart(String opaquePart) {
1360 return opaquePart(Part.fromDecoded(opaquePart));
1361 }
1362
1363 /**
1364 * Sets the previously encoded opaque scheme-specific-part.
1365 *
1366 * @param opaquePart encoded opaque part
1367 */
1368 public Builder encodedOpaquePart(String opaquePart) {
1369 return opaquePart(Part.fromEncoded(opaquePart));
1370 }
1371
1372 Builder authority(Part authority) {
1373 // This URI will be hierarchical.
1374 this.opaquePart = null;
1375
1376 this.authority = authority;
1377 return this;
1378 }
1379
1380 /**
1381 * Encodes and sets the authority.
1382 */
1383 public Builder authority(String authority) {
1384 return authority(Part.fromDecoded(authority));
1385 }
1386
1387 /**
1388 * Sets the previously encoded authority.
1389 */
1390 public Builder encodedAuthority(String authority) {
1391 return authority(Part.fromEncoded(authority));
1392 }
1393
1394 Builder path(PathPart path) {
1395 // This URI will be hierarchical.
1396 this.opaquePart = null;
1397
1398 this.path = path;
1399 return this;
1400 }
1401
1402 /**
1403 * Sets the path. Leaves '/' characters intact but encodes others as
1404 * necessary.
1405 *
1406 * <p>If the path is not null and doesn't start with a '/', and if
1407 * you specify a scheme and/or authority, the builder will prepend the
1408 * given path with a '/'.
1409 */
1410 public Builder path(String path) {
1411 return path(PathPart.fromDecoded(path));
1412 }
1413
1414 /**
1415 * Sets the previously encoded path.
1416 *
1417 * <p>If the path is not null and doesn't start with a '/', and if
1418 * you specify a scheme and/or authority, the builder will prepend the
1419 * given path with a '/'.
1420 */
1421 public Builder encodedPath(String path) {
1422 return path(PathPart.fromEncoded(path));
1423 }
1424
1425 /**
1426 * Encodes the given segment and appends it to the path.
1427 */
1428 public Builder appendPath(String newSegment) {
1429 return path(PathPart.appendDecodedSegment(path, newSegment));
1430 }
1431
1432 /**
1433 * Appends the given segment to the path.
1434 */
1435 public Builder appendEncodedPath(String newSegment) {
1436 return path(PathPart.appendEncodedSegment(path, newSegment));
1437 }
1438
1439 Builder query(Part query) {
1440 // This URI will be hierarchical.
1441 this.opaquePart = null;
1442
1443 this.query = query;
1444 return this;
1445 }
1446
1447 /**
1448 * Encodes and sets the query.
1449 */
1450 public Builder query(String query) {
1451 return query(Part.fromDecoded(query));
1452 }
1453
1454 /**
1455 * Sets the previously encoded query.
1456 */
1457 public Builder encodedQuery(String query) {
1458 return query(Part.fromEncoded(query));
1459 }
1460
1461 Builder fragment(Part fragment) {
1462 this.fragment = fragment;
1463 return this;
1464 }
1465
1466 /**
1467 * Encodes and sets the fragment.
1468 */
1469 public Builder fragment(String fragment) {
1470 return fragment(Part.fromDecoded(fragment));
1471 }
1472
1473 /**
1474 * Sets the previously encoded fragment.
1475 */
1476 public Builder encodedFragment(String fragment) {
1477 return fragment(Part.fromEncoded(fragment));
1478 }
1479
1480 /**
1481 * Encodes the key and value and then appends the parameter to the
1482 * query string.
1483 *
1484 * @param key which will be encoded
1485 * @param value which will be encoded
1486 */
1487 public Builder appendQueryParameter(String key, String value) {
1488 // This URI will be hierarchical.
1489 this.opaquePart = null;
1490
1491 String encodedParameter = encode(key, null) + "="
1492 + encode(value, null);
1493
1494 if (query == null) {
1495 query = Part.fromEncoded(encodedParameter);
1496 return this;
1497 }
1498
1499 String oldQuery = query.getEncoded();
1500 if (oldQuery == null || oldQuery.length() == 0) {
1501 query = Part.fromEncoded(encodedParameter);
1502 } else {
1503 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1504 }
1505
1506 return this;
1507 }
1508
1509 /**
Ben Dodson58a34592010-08-18 18:23:18 -07001510 * Clears the the previously set query.
1511 */
1512 public Builder clearQuery() {
1513 return query((Part) null);
1514 }
1515
1516 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 * Constructs a Uri with the current attributes.
1518 *
1519 * @throws UnsupportedOperationException if the URI is opaque and the
1520 * scheme is null
1521 */
1522 public Uri build() {
1523 if (opaquePart != null) {
1524 if (this.scheme == null) {
1525 throw new UnsupportedOperationException(
1526 "An opaque URI must have a scheme.");
1527 }
1528
1529 return new OpaqueUri(scheme, opaquePart, fragment);
1530 } else {
1531 // Hierarchical URIs should not return null for getPath().
1532 PathPart path = this.path;
1533 if (path == null || path == PathPart.NULL) {
1534 path = PathPart.EMPTY;
1535 } else {
1536 // If we have a scheme and/or authority, the path must
1537 // be absolute. Prepend it with a '/' if necessary.
1538 if (hasSchemeOrAuthority()) {
1539 path = PathPart.makeAbsolute(path);
1540 }
1541 }
1542
1543 return new HierarchicalUri(
1544 scheme, authority, path, query, fragment);
1545 }
1546 }
1547
1548 private boolean hasSchemeOrAuthority() {
1549 return scheme != null
1550 || (authority != null && authority != Part.NULL);
1551
1552 }
1553
1554 @Override
1555 public String toString() {
1556 return build().toString();
1557 }
1558 }
1559
1560 /**
Ben Dodson58a34592010-08-18 18:23:18 -07001561 * Returns a set of the unique names of all query parameters. Iterating
1562 * over the set will return the names in order of their first occurrence.
1563 *
1564 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1565 *
1566 * @return a set of decoded names
1567 */
1568 public Set<String> getQueryParameterNames() {
1569 if (isOpaque()) {
1570 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1571 }
1572
1573 String query = getEncodedQuery();
1574 if (query == null) {
1575 return Collections.emptySet();
1576 }
1577
1578 Set<String> names = new LinkedHashSet<String>();
1579 int start = 0;
1580 do {
1581 int next = query.indexOf('&', start);
1582 int end = (next == -1) ? query.length() : next;
1583
1584 int separator = query.indexOf('=', start);
1585 if (separator > end || separator == -1) {
1586 separator = end;
1587 }
1588
1589 String name = query.substring(start, separator);
1590 names.add(decode(name));
1591
1592 // Move start to end of name.
1593 start = end + 1;
1594 } while (start < query.length());
1595
1596 return Collections.unmodifiableSet(names);
1597 }
1598
1599 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001600 * Searches the query string for parameter values with the given key.
1601 *
1602 * @param key which will be encoded
1603 *
1604 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1605 * @throws NullPointerException if key is null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001606 * @return a list of decoded values
1607 */
1608 public List<String> getQueryParameters(String key) {
1609 if (isOpaque()) {
1610 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1611 }
Ben Dodson58a34592010-08-18 18:23:18 -07001612 if (key == null) {
1613 throw new NullPointerException("key");
1614 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001615
Jean-Baptiste Querua8675f62009-07-29 14:25:07 -07001616 String query = getEncodedQuery();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001617 if (query == null) {
1618 return Collections.emptyList();
1619 }
1620
1621 String encodedKey;
1622 try {
1623 encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1624 } catch (UnsupportedEncodingException e) {
1625 throw new AssertionError(e);
1626 }
1627
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001628 ArrayList<String> values = new ArrayList<String>();
1629
1630 int start = 0;
Ben Dodson58a34592010-08-18 18:23:18 -07001631 do {
1632 int nextAmpersand = query.indexOf('&', start);
1633 int end = nextAmpersand != -1 ? nextAmpersand : query.length();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001634
Ben Dodson58a34592010-08-18 18:23:18 -07001635 int separator = query.indexOf('=', start);
1636 if (separator > end || separator == -1) {
1637 separator = end;
1638 }
1639
1640 if (separator - start == encodedKey.length()
1641 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1642 if (separator == end) {
1643 values.add("");
1644 } else {
1645 values.add(decode(query.substring(separator + 1, end)));
1646 }
1647 }
1648
1649 // Move start to end of name.
1650 if (nextAmpersand != -1) {
1651 start = nextAmpersand + 1;
1652 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001653 break;
1654 }
Ben Dodson58a34592010-08-18 18:23:18 -07001655 } while (true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001656
1657 return Collections.unmodifiableList(values);
1658 }
1659
1660 /**
1661 * Searches the query string for the first value with the given key.
1662 *
Jesse Wilson41e08392011-11-11 11:16:17 -05001663 * <p><strong>Warning:</strong> Prior to Ice Cream Sandwich, this decoded
1664 * the '+' character as '+' rather than ' '.
1665 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001666 * @param key which will be encoded
1667 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1668 * @throws NullPointerException if key is null
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001669 * @return the decoded value or null if no parameter is found
1670 */
1671 public String getQueryParameter(String key) {
1672 if (isOpaque()) {
1673 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1674 }
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001675 if (key == null) {
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001676 throw new NullPointerException("key");
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001677 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001678
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001679 final String query = getEncodedQuery();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001680 if (query == null) {
1681 return null;
1682 }
1683
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001684 final String encodedKey = encode(key, null);
Ben Dodson58a34592010-08-18 18:23:18 -07001685 final int length = query.length();
1686 int start = 0;
1687 do {
1688 int nextAmpersand = query.indexOf('&', start);
1689 int end = nextAmpersand != -1 ? nextAmpersand : length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690
Ben Dodson58a34592010-08-18 18:23:18 -07001691 int separator = query.indexOf('=', start);
1692 if (separator > end || separator == -1) {
1693 separator = end;
1694 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001695
Ben Dodson58a34592010-08-18 18:23:18 -07001696 if (separator - start == encodedKey.length()
1697 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1698 if (separator == end) {
Jesse Wilson0f28af22011-10-28 18:27:44 -04001699 return "";
Ben Dodson58a34592010-08-18 18:23:18 -07001700 } else {
Jesse Wilson0f28af22011-10-28 18:27:44 -04001701 String encodedValue = query.substring(separator + 1, end);
Elliott Hughesd396a442013-06-28 16:24:48 -07001702 return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001703 }
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001704 }
Ben Dodson58a34592010-08-18 18:23:18 -07001705
1706 // Move start to end of name.
1707 if (nextAmpersand != -1) {
1708 start = nextAmpersand + 1;
1709 } else {
1710 break;
1711 }
1712 } while (true);
Brad Fitzpatrick70ece8d2009-10-26 14:03:54 -07001713 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001714 }
1715
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001716 /**
1717 * Searches the query string for the first value with the given key and interprets it
1718 * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1719 * else is interpreted as <code>true</code>.
1720 *
1721 * @param key which will be decoded
1722 * @param defaultValue the default value to return if there is no query parameter for key
1723 * @return the boolean interpretation of the query parameter key
1724 */
1725 public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1726 String flag = getQueryParameter(key);
1727 if (flag == null) {
1728 return defaultValue;
1729 }
Elliott Hughescb64d432013-08-02 10:00:44 -07001730 flag = flag.toLowerCase(Locale.ROOT);
Jeff Hamiltonf0cfe342010-08-09 16:54:05 -05001731 return (!"false".equals(flag) && !"0".equals(flag));
1732 }
1733
Nick Pellyccae4122012-01-09 14:12:58 -08001734 /**
Jesse Wilsonabc43dd2012-05-10 14:29:33 -04001735 * Return an equivalent URI with a lowercase scheme component.
Nick Pellyccae4122012-01-09 14:12:58 -08001736 * This aligns the Uri with Android best practices for
1737 * intent filtering.
1738 *
1739 * <p>For example, "HTTP://www.android.com" becomes
1740 * "http://www.android.com"
1741 *
1742 * <p>All URIs received from outside Android (such as user input,
1743 * or external sources like Bluetooth, NFC, or the Internet) should
1744 * be normalized before they are used to create an Intent.
1745 *
1746 * <p class="note">This method does <em>not</em> validate bad URI's,
1747 * or 'fix' poorly formatted URI's - so do not use it for input validation.
1748 * A Uri will always be returned, even if the Uri is badly formatted to
1749 * begin with and a scheme component cannot be found.
1750 *
1751 * @return normalized Uri (never null)
1752 * @see {@link android.content.Intent#setData}
1753 * @see {@link #setNormalizedData}
1754 */
Jesse Wilsonabc43dd2012-05-10 14:29:33 -04001755 public Uri normalizeScheme() {
Nick Pellyccae4122012-01-09 14:12:58 -08001756 String scheme = getScheme();
1757 if (scheme == null) return this; // give up
Elliott Hughescb64d432013-08-02 10:00:44 -07001758 String lowerScheme = scheme.toLowerCase(Locale.ROOT);
Nick Pellyccae4122012-01-09 14:12:58 -08001759 if (scheme.equals(lowerScheme)) return this; // no change
1760
1761 return buildUpon().scheme(lowerScheme).build();
1762 }
1763
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001764 /** Identifies a null parcelled Uri. */
1765 private static final int NULL_TYPE_ID = 0;
1766
1767 /**
1768 * Reads Uris from Parcels.
1769 */
1770 public static final Parcelable.Creator<Uri> CREATOR
1771 = new Parcelable.Creator<Uri>() {
1772 public Uri createFromParcel(Parcel in) {
1773 int type = in.readInt();
1774 switch (type) {
1775 case NULL_TYPE_ID: return null;
1776 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1777 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1778 case HierarchicalUri.TYPE_ID:
1779 return HierarchicalUri.readFrom(in);
1780 }
1781
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07001782 throw new IllegalArgumentException("Unknown URI type: " + type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001783 }
1784
1785 public Uri[] newArray(int size) {
1786 return new Uri[size];
1787 }
1788 };
1789
1790 /**
1791 * Writes a Uri to a Parcel.
1792 *
1793 * @param out parcel to write to
1794 * @param uri to write, can be null
1795 */
1796 public static void writeToParcel(Parcel out, Uri uri) {
1797 if (uri == null) {
1798 out.writeInt(NULL_TYPE_ID);
1799 } else {
1800 uri.writeToParcel(out, 0);
1801 }
1802 }
1803
1804 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1805
1806 /**
1807 * Encodes characters in the given string as '%'-escaped octets
1808 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1809 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1810 * all other characters.
1811 *
1812 * @param s string to encode
1813 * @return an encoded version of s suitable for use as a URI component,
1814 * or null if s is null
1815 */
1816 public static String encode(String s) {
1817 return encode(s, null);
1818 }
1819
1820 /**
1821 * Encodes characters in the given string as '%'-escaped octets
1822 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1823 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1824 * all other characters with the exception of those specified in the
1825 * allow argument.
1826 *
1827 * @param s string to encode
1828 * @param allow set of additional characters to allow in the encoded form,
1829 * null if no characters should be skipped
1830 * @return an encoded version of s suitable for use as a URI component,
1831 * or null if s is null
1832 */
1833 public static String encode(String s, String allow) {
1834 if (s == null) {
1835 return null;
1836 }
1837
1838 // Lazily-initialized buffers.
1839 StringBuilder encoded = null;
1840
1841 int oldLength = s.length();
1842
1843 // This loop alternates between copying over allowed characters and
1844 // encoding in chunks. This results in fewer method calls and
1845 // allocations than encoding one character at a time.
1846 int current = 0;
1847 while (current < oldLength) {
1848 // Start in "copying" mode where we copy over allowed chars.
1849
1850 // Find the next character which needs to be encoded.
1851 int nextToEncode = current;
1852 while (nextToEncode < oldLength
1853 && isAllowed(s.charAt(nextToEncode), allow)) {
1854 nextToEncode++;
1855 }
1856
1857 // If there's nothing more to encode...
1858 if (nextToEncode == oldLength) {
1859 if (current == 0) {
1860 // We didn't need to encode anything!
1861 return s;
1862 } else {
1863 // Presumably, we've already done some encoding.
1864 encoded.append(s, current, oldLength);
1865 return encoded.toString();
1866 }
1867 }
1868
1869 if (encoded == null) {
1870 encoded = new StringBuilder();
1871 }
1872
1873 if (nextToEncode > current) {
1874 // Append allowed characters leading up to this point.
1875 encoded.append(s, current, nextToEncode);
1876 } else {
1877 // assert nextToEncode == current
1878 }
1879
1880 // Switch to "encoding" mode.
1881
1882 // Find the next allowed character.
1883 current = nextToEncode;
1884 int nextAllowed = current + 1;
1885 while (nextAllowed < oldLength
1886 && !isAllowed(s.charAt(nextAllowed), allow)) {
1887 nextAllowed++;
1888 }
1889
1890 // Convert the substring to bytes and encode the bytes as
1891 // '%'-escaped octets.
1892 String toEncode = s.substring(current, nextAllowed);
1893 try {
1894 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1895 int bytesLength = bytes.length;
1896 for (int i = 0; i < bytesLength; i++) {
1897 encoded.append('%');
1898 encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1899 encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1900 }
1901 } catch (UnsupportedEncodingException e) {
1902 throw new AssertionError(e);
1903 }
1904
1905 current = nextAllowed;
1906 }
1907
1908 // Encoded could still be null at this point if s is empty.
1909 return encoded == null ? s : encoded.toString();
1910 }
1911
1912 /**
1913 * Returns true if the given character is allowed.
1914 *
1915 * @param c character to check
1916 * @param allow characters to allow
1917 * @return true if the character is allowed or false if it should be
1918 * encoded
1919 */
1920 private static boolean isAllowed(char c, String allow) {
1921 return (c >= 'A' && c <= 'Z')
1922 || (c >= 'a' && c <= 'z')
1923 || (c >= '0' && c <= '9')
1924 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1925 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1926 }
1927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001928 /**
1929 * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1930 * Replaces invalid octets with the unicode replacement character
1931 * ("\\uFFFD").
1932 *
1933 * @param s encoded string to decode
1934 * @return the given string with escaped octets decoded, or null if
1935 * s is null
1936 */
1937 public static String decode(String s) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 if (s == null) {
1939 return null;
1940 }
Elliott Hughesd396a442013-06-28 16:24:48 -07001941 return UriCodec.decode(s, false, StandardCharsets.UTF_8, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001942 }
1943
1944 /**
1945 * Support for part implementations.
1946 */
1947 static abstract class AbstractPart {
1948
1949 /**
1950 * Enum which indicates which representation of a given part we have.
1951 */
1952 static class Representation {
1953 static final int BOTH = 0;
1954 static final int ENCODED = 1;
1955 static final int DECODED = 2;
1956 }
1957
1958 volatile String encoded;
1959 volatile String decoded;
1960
1961 AbstractPart(String encoded, String decoded) {
1962 this.encoded = encoded;
1963 this.decoded = decoded;
1964 }
1965
1966 abstract String getEncoded();
1967
1968 final String getDecoded() {
1969 @SuppressWarnings("StringEquality")
1970 boolean hasDecoded = decoded != NOT_CACHED;
1971 return hasDecoded ? decoded : (decoded = decode(encoded));
1972 }
1973
1974 final void writeTo(Parcel parcel) {
1975 @SuppressWarnings("StringEquality")
1976 boolean hasEncoded = encoded != NOT_CACHED;
1977
1978 @SuppressWarnings("StringEquality")
1979 boolean hasDecoded = decoded != NOT_CACHED;
1980
1981 if (hasEncoded && hasDecoded) {
1982 parcel.writeInt(Representation.BOTH);
1983 parcel.writeString(encoded);
1984 parcel.writeString(decoded);
1985 } else if (hasEncoded) {
1986 parcel.writeInt(Representation.ENCODED);
1987 parcel.writeString(encoded);
1988 } else if (hasDecoded) {
1989 parcel.writeInt(Representation.DECODED);
1990 parcel.writeString(decoded);
1991 } else {
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07001992 throw new IllegalArgumentException("Neither encoded nor decoded");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001993 }
1994 }
1995 }
1996
1997 /**
1998 * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
1999 * creates the encoded or decoded version from the other.
2000 */
2001 static class Part extends AbstractPart {
2002
2003 /** A part with null values. */
2004 static final Part NULL = new EmptyPart(null);
2005
2006 /** A part with empty strings for values. */
2007 static final Part EMPTY = new EmptyPart("");
2008
2009 private Part(String encoded, String decoded) {
2010 super(encoded, decoded);
2011 }
2012
2013 boolean isEmpty() {
2014 return false;
2015 }
2016
2017 String getEncoded() {
2018 @SuppressWarnings("StringEquality")
2019 boolean hasEncoded = encoded != NOT_CACHED;
2020 return hasEncoded ? encoded : (encoded = encode(decoded));
2021 }
2022
2023 static Part readFrom(Parcel parcel) {
2024 int representation = parcel.readInt();
2025 switch (representation) {
2026 case Representation.BOTH:
2027 return from(parcel.readString(), parcel.readString());
2028 case Representation.ENCODED:
2029 return fromEncoded(parcel.readString());
2030 case Representation.DECODED:
2031 return fromDecoded(parcel.readString());
2032 default:
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07002033 throw new IllegalArgumentException("Unknown representation: "
2034 + representation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002035 }
2036 }
2037
2038 /**
2039 * Returns given part or {@link #NULL} if the given part is null.
2040 */
2041 static Part nonNull(Part part) {
2042 return part == null ? NULL : part;
2043 }
2044
2045 /**
2046 * Creates a part from the encoded string.
2047 *
2048 * @param encoded part string
2049 */
2050 static Part fromEncoded(String encoded) {
2051 return from(encoded, NOT_CACHED);
2052 }
2053
2054 /**
2055 * Creates a part from the decoded string.
2056 *
2057 * @param decoded part string
2058 */
2059 static Part fromDecoded(String decoded) {
2060 return from(NOT_CACHED, decoded);
2061 }
2062
2063 /**
2064 * Creates a part from the encoded and decoded strings.
2065 *
2066 * @param encoded part string
2067 * @param decoded part string
2068 */
2069 static Part from(String encoded, String decoded) {
2070 // We have to check both encoded and decoded in case one is
2071 // NOT_CACHED.
2072
2073 if (encoded == null) {
2074 return NULL;
2075 }
2076 if (encoded.length() == 0) {
2077 return EMPTY;
2078 }
2079
2080 if (decoded == null) {
2081 return NULL;
2082 }
2083 if (decoded .length() == 0) {
2084 return EMPTY;
2085 }
2086
2087 return new Part(encoded, decoded);
2088 }
2089
2090 private static class EmptyPart extends Part {
2091 public EmptyPart(String value) {
2092 super(value, value);
2093 }
2094
2095 @Override
2096 boolean isEmpty() {
2097 return true;
2098 }
2099 }
2100 }
2101
2102 /**
2103 * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2104 * creates the encoded or decoded version from the other.
2105 */
2106 static class PathPart extends AbstractPart {
2107
2108 /** A part with null values. */
2109 static final PathPart NULL = new PathPart(null, null);
2110
2111 /** A part with empty strings for values. */
2112 static final PathPart EMPTY = new PathPart("", "");
2113
2114 private PathPart(String encoded, String decoded) {
2115 super(encoded, decoded);
2116 }
2117
2118 String getEncoded() {
2119 @SuppressWarnings("StringEquality")
2120 boolean hasEncoded = encoded != NOT_CACHED;
2121
2122 // Don't encode '/'.
2123 return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2124 }
2125
2126 /**
2127 * Cached path segments. This doesn't need to be volatile--we don't
2128 * care if other threads see the result.
2129 */
2130 private PathSegments pathSegments;
2131
2132 /**
2133 * Gets the individual path segments. Parses them if necessary.
2134 *
2135 * @return parsed path segments or null if this isn't a hierarchical
2136 * URI
2137 */
2138 PathSegments getPathSegments() {
2139 if (pathSegments != null) {
2140 return pathSegments;
2141 }
2142
2143 String path = getEncoded();
2144 if (path == null) {
2145 return pathSegments = PathSegments.EMPTY;
2146 }
2147
2148 PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2149
2150 int previous = 0;
2151 int current;
2152 while ((current = path.indexOf('/', previous)) > -1) {
2153 // This check keeps us from adding a segment if the path starts
2154 // '/' and an empty segment for "//".
2155 if (previous < current) {
2156 String decodedSegment
2157 = decode(path.substring(previous, current));
2158 segmentBuilder.add(decodedSegment);
2159 }
2160 previous = current + 1;
2161 }
2162
2163 // Add in the final path segment.
2164 if (previous < path.length()) {
2165 segmentBuilder.add(decode(path.substring(previous)));
2166 }
2167
2168 return pathSegments = segmentBuilder.build();
2169 }
2170
2171 static PathPart appendEncodedSegment(PathPart oldPart,
2172 String newSegment) {
2173 // If there is no old path, should we make the new path relative
2174 // or absolute? I pick absolute.
2175
2176 if (oldPart == null) {
2177 // No old path.
2178 return fromEncoded("/" + newSegment);
2179 }
2180
2181 String oldPath = oldPart.getEncoded();
2182
2183 if (oldPath == null) {
2184 oldPath = "";
2185 }
2186
2187 int oldPathLength = oldPath.length();
2188 String newPath;
2189 if (oldPathLength == 0) {
2190 // No old path.
2191 newPath = "/" + newSegment;
2192 } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2193 newPath = oldPath + newSegment;
2194 } else {
2195 newPath = oldPath + "/" + newSegment;
2196 }
2197
2198 return fromEncoded(newPath);
2199 }
2200
2201 static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2202 String encoded = encode(decoded);
2203
2204 // TODO: Should we reuse old PathSegments? Probably not.
2205 return appendEncodedSegment(oldPart, encoded);
2206 }
2207
2208 static PathPart readFrom(Parcel parcel) {
2209 int representation = parcel.readInt();
2210 switch (representation) {
2211 case Representation.BOTH:
2212 return from(parcel.readString(), parcel.readString());
2213 case Representation.ENCODED:
2214 return fromEncoded(parcel.readString());
2215 case Representation.DECODED:
2216 return fromDecoded(parcel.readString());
2217 default:
Dianne Hackborn8e8d65f2011-08-11 19:36:18 -07002218 throw new IllegalArgumentException("Bad representation: " + representation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002219 }
2220 }
2221
2222 /**
2223 * Creates a path from the encoded string.
2224 *
2225 * @param encoded part string
2226 */
2227 static PathPart fromEncoded(String encoded) {
2228 return from(encoded, NOT_CACHED);
2229 }
2230
2231 /**
2232 * Creates a path from the decoded string.
2233 *
2234 * @param decoded part string
2235 */
2236 static PathPart fromDecoded(String decoded) {
2237 return from(NOT_CACHED, decoded);
2238 }
2239
2240 /**
2241 * Creates a path from the encoded and decoded strings.
2242 *
2243 * @param encoded part string
2244 * @param decoded part string
2245 */
2246 static PathPart from(String encoded, String decoded) {
2247 if (encoded == null) {
2248 return NULL;
2249 }
2250
2251 if (encoded.length() == 0) {
2252 return EMPTY;
2253 }
2254
2255 return new PathPart(encoded, decoded);
2256 }
2257
2258 /**
2259 * Prepends path values with "/" if they're present, not empty, and
2260 * they don't already start with "/".
2261 */
2262 static PathPart makeAbsolute(PathPart oldPart) {
2263 @SuppressWarnings("StringEquality")
2264 boolean encodedCached = oldPart.encoded != NOT_CACHED;
2265
2266 // We don't care which version we use, and we don't want to force
2267 // unneccessary encoding/decoding.
2268 String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2269
2270 if (oldPath == null || oldPath.length() == 0
2271 || oldPath.startsWith("/")) {
2272 return oldPart;
2273 }
2274
2275 // Prepend encoded string if present.
2276 String newEncoded = encodedCached
2277 ? "/" + oldPart.encoded : NOT_CACHED;
2278
2279 // Prepend decoded string if present.
2280 @SuppressWarnings("StringEquality")
2281 boolean decodedCached = oldPart.decoded != NOT_CACHED;
2282 String newDecoded = decodedCached
2283 ? "/" + oldPart.decoded
2284 : NOT_CACHED;
2285
2286 return new PathPart(newEncoded, newDecoded);
2287 }
2288 }
2289
2290 /**
2291 * Creates a new Uri by appending an already-encoded path segment to a
2292 * base Uri.
2293 *
2294 * @param baseUri Uri to append path segment to
2295 * @param pathSegment encoded path segment to append
Jesse Wilson0f28af22011-10-28 18:27:44 -04002296 * @return a new Uri based on baseUri with the given segment appended to
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002297 * the path
2298 * @throws NullPointerException if baseUri is null
2299 */
2300 public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2301 Builder builder = baseUri.buildUpon();
2302 builder = builder.appendEncodedPath(pathSegment);
2303 return builder.build();
2304 }
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -07002305
2306 /**
2307 * If this {@link Uri} is {@code file://}, then resolve and return its
2308 * canonical path. Also fixes legacy emulated storage paths so they are
2309 * usable across user boundaries. Should always be called from the app
2310 * process before sending elsewhere.
2311 *
2312 * @hide
2313 */
2314 public Uri getCanonicalUri() {
2315 if ("file".equals(getScheme())) {
2316 final String canonicalPath;
2317 try {
2318 canonicalPath = new File(getPath()).getCanonicalPath();
2319 } catch (IOException e) {
2320 return this;
2321 }
2322
2323 if (Environment.isExternalStorageEmulated()) {
2324 final String legacyPath = Environment.getLegacyExternalStorageDirectory()
2325 .toString();
2326
2327 // Splice in user-specific path when legacy path is found
2328 if (canonicalPath.startsWith(legacyPath)) {
2329 return Uri.fromFile(new File(
2330 Environment.getExternalStorageDirectory().toString(),
2331 canonicalPath.substring(legacyPath.length() + 1)));
2332 }
2333 }
2334
2335 return Uri.fromFile(new File(canonicalPath));
2336 } else {
2337 return this;
2338 }
2339 }
Jeff Sharkeya14acd22013-04-02 18:27:45 -07002340
2341 /**
2342 * If this is a {@code file://} Uri, it will be reported to
2343 * {@link StrictMode}.
2344 *
2345 * @hide
2346 */
2347 public void checkFileUriExposed(String location) {
2348 if ("file".equals(getScheme())) {
2349 StrictMode.onFileUriExposed(location);
2350 }
2351 }
Jeff Sharkey846318a2014-04-04 12:12:41 -07002352
2353 /**
2354 * Test if this is a path prefix match against the given Uri. Verifies that
2355 * scheme, authority, and atomic path segments match.
2356 *
2357 * @hide
2358 */
2359 public boolean isPathPrefixMatch(Uri prefix) {
2360 if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
2361 if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
2362
2363 List<String> seg = getPathSegments();
2364 List<String> prefixSeg = prefix.getPathSegments();
2365
2366 final int prefixSize = prefixSeg.size();
2367 if (seg.size() < prefixSize) return false;
2368
2369 for (int i = 0; i < prefixSize; i++) {
2370 if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
2371 return false;
2372 }
2373 }
2374
2375 return true;
2376 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002377}