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