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