2022-10-02 23:06:56 +02:00
|
|
|
package android.net;
|
|
|
|
|
|
2023-09-01 16:03:31 +02:00
|
|
|
import java.net.URI;
|
2024-03-20 22:21:09 +01:00
|
|
|
import java.net.URISyntaxException;
|
|
|
|
|
|
2023-09-14 09:45:24 +02:00
|
|
|
import libcore.net.UriCodec;
|
2024-02-17 15:15:05 +01:00
|
|
|
|
|
|
|
|
import java.io.File;
|
2023-09-14 09:45:24 +02:00
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
2023-09-01 16:03:31 +02:00
|
|
|
|
2024-03-16 12:49:28 +01:00
|
|
|
import android.os.Parcelable;
|
|
|
|
|
|
|
|
|
|
public class Uri implements Parcelable {
|
2023-09-19 23:22:21 +02:00
|
|
|
|
|
|
|
|
public static final Uri EMPTY = new Uri();
|
|
|
|
|
|
2024-03-16 18:14:46 +01:00
|
|
|
public URI uri;
|
2023-09-01 16:03:31 +02:00
|
|
|
|
2023-01-09 12:07:57 +01:00
|
|
|
public static Uri parse(String s) {
|
2023-09-01 16:03:31 +02:00
|
|
|
Uri ret = new Uri();
|
|
|
|
|
try {
|
2024-03-29 23:56:28 +01:00
|
|
|
ret.uri = URI.create(s.trim());
|
2023-09-01 16:03:31 +02:00
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
2023-01-09 12:07:57 +01:00
|
|
|
}
|
2023-08-12 13:09:33 +02:00
|
|
|
|
2023-09-14 09:45:24 +02:00
|
|
|
public static String decode(String s) {
|
|
|
|
|
if (s == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return UriCodec.decode(s, false, StandardCharsets.UTF_8, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String encode(String s) {
|
|
|
|
|
return encode(s, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* -- FROM ANDROID, Licensed under the Apache License, Version 2.0 --
|
|
|
|
|
* Returns true if the given character is allowed.
|
|
|
|
|
*
|
|
|
|
|
* @param c character to check
|
|
|
|
|
* @param allow characters to allow
|
|
|
|
|
* @return true if the character is allowed or false if it should be
|
|
|
|
|
* encoded
|
|
|
|
|
*/
|
|
|
|
|
private final static int NOT_FOUND = -1;
|
|
|
|
|
private static boolean isAllowed(char c, String allow) {
|
|
|
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || "_-!.~'()*".indexOf(c) != NOT_FOUND || (allow != null && allow.indexOf(c) != NOT_FOUND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* -- FROM ANDROID, Licensed under the Apache License, Version 2.0 --
|
|
|
|
|
* Encodes characters in the given string as '%'-escaped octets
|
|
|
|
|
* using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
|
|
|
|
|
* ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
|
|
|
|
|
* all other characters with the exception of those specified in the
|
|
|
|
|
* allow argument.
|
|
|
|
|
*
|
|
|
|
|
* @param s string to encode
|
|
|
|
|
* @param allow set of additional characters to allow in the encoded form,
|
|
|
|
|
* null if no characters should be skipped
|
|
|
|
|
* @return an encoded version of s suitable for use as a URI component,
|
|
|
|
|
* or null if s is null
|
|
|
|
|
*/
|
|
|
|
|
private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
|
|
|
|
|
private static final String DEFAULT_ENCODING = "UTF-8";
|
|
|
|
|
public static String encode(String s, String allow) {
|
|
|
|
|
if (s == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lazily-initialized buffers.
|
|
|
|
|
StringBuilder encoded = null;
|
|
|
|
|
|
|
|
|
|
int oldLength = s.length();
|
|
|
|
|
|
|
|
|
|
// This loop alternates between copying over allowed characters and
|
|
|
|
|
// encoding in chunks. This results in fewer method calls and
|
|
|
|
|
// allocations than encoding one character at a time.
|
|
|
|
|
int current = 0;
|
|
|
|
|
while (current < oldLength) {
|
|
|
|
|
// Start in "copying" mode where we copy over allowed chars.
|
|
|
|
|
|
|
|
|
|
// Find the next character which needs to be encoded.
|
|
|
|
|
int nextToEncode = current;
|
|
|
|
|
while (nextToEncode < oldLength && isAllowed(s.charAt(nextToEncode), allow)) {
|
|
|
|
|
nextToEncode++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there's nothing more to encode...
|
|
|
|
|
if (nextToEncode == oldLength) {
|
|
|
|
|
if (current == 0) {
|
|
|
|
|
// We didn't need to encode anything!
|
|
|
|
|
return s;
|
|
|
|
|
} else {
|
|
|
|
|
// Presumably, we've already done some encoding.
|
|
|
|
|
encoded.append(s, current, oldLength);
|
|
|
|
|
return encoded.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (encoded == null) {
|
|
|
|
|
encoded = new StringBuilder();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nextToEncode > current) {
|
|
|
|
|
// Append allowed characters leading up to this point.
|
|
|
|
|
encoded.append(s, current, nextToEncode);
|
|
|
|
|
} else {
|
|
|
|
|
// assert nextToEncode == current
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Switch to "encoding" mode.
|
|
|
|
|
|
|
|
|
|
// Find the next allowed character.
|
|
|
|
|
current = nextToEncode;
|
|
|
|
|
int nextAllowed = current + 1;
|
|
|
|
|
while (nextAllowed < oldLength && !isAllowed(s.charAt(nextAllowed), allow)) {
|
|
|
|
|
nextAllowed++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert the substring to bytes and encode the bytes as
|
|
|
|
|
// '%'-escaped octets.
|
|
|
|
|
String toEncode = s.substring(current, nextAllowed);
|
|
|
|
|
try {
|
|
|
|
|
byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
|
|
|
|
|
int bytesLength = bytes.length;
|
|
|
|
|
for (int i = 0; i < bytesLength; i++) {
|
|
|
|
|
encoded.append('%');
|
|
|
|
|
encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
|
|
|
|
|
encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
|
|
|
|
|
}
|
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
|
throw new AssertionError(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current = nextAllowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encoded could still be null at this point if s is empty.
|
|
|
|
|
return encoded == null ? s : encoded.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-12 13:09:33 +02:00
|
|
|
public Builder buildUpon() {
|
2024-03-29 23:56:28 +01:00
|
|
|
Builder builder = new Builder();
|
|
|
|
|
builder.scheme = getScheme();
|
|
|
|
|
builder.authority = getAuthority();
|
|
|
|
|
builder.path = getPath();
|
|
|
|
|
builder.query = uri.getQuery();
|
|
|
|
|
return builder;
|
2023-08-12 13:09:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static final class Builder {
|
2024-03-20 22:21:09 +01:00
|
|
|
private String scheme;
|
|
|
|
|
private String authority;
|
|
|
|
|
private String path;
|
2024-03-29 23:56:28 +01:00
|
|
|
private String query;
|
2024-03-20 22:21:09 +01:00
|
|
|
|
2023-08-12 13:09:33 +02:00
|
|
|
public Builder appendQueryParameter(String key, String value) {
|
2024-03-29 23:56:28 +01:00
|
|
|
this.query = (this.query != null ? this.query + "&" : "") + key + "=" + value;
|
2023-08-12 13:09:33 +02:00
|
|
|
return this;
|
|
|
|
|
}
|
2024-03-20 22:21:09 +01:00
|
|
|
|
|
|
|
|
public Builder scheme(String scheme) {
|
|
|
|
|
this.scheme = scheme;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Builder authority(String authority) {
|
|
|
|
|
this.authority = authority;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Builder encodedPath(String encodedPath) {
|
|
|
|
|
this.path = decode(encodedPath);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 23:56:28 +01:00
|
|
|
public Builder appendPath(String path) {
|
|
|
|
|
this.path = (this.path != null ? this.path : "") + "/" + path;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 22:21:09 +01:00
|
|
|
public Uri build() throws URISyntaxException {
|
|
|
|
|
if ("content".equals(scheme)) { // hack: content providers not yet supported
|
|
|
|
|
scheme = "file";
|
|
|
|
|
authority = null;
|
|
|
|
|
path = path.substring(path.indexOf("/"));
|
|
|
|
|
}
|
|
|
|
|
Uri ret = new Uri();
|
2024-03-29 23:56:28 +01:00
|
|
|
ret.uri = new URI(scheme, authority, path, query, null);
|
2024-03-20 22:21:09 +01:00
|
|
|
return ret;
|
|
|
|
|
}
|
2024-03-29 23:56:28 +01:00
|
|
|
|
|
|
|
|
public String toString() {
|
|
|
|
|
try {
|
|
|
|
|
return build().toString();
|
|
|
|
|
} catch (URISyntaxException e) {
|
|
|
|
|
return super.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-12 13:09:33 +02:00
|
|
|
}
|
2023-09-01 16:03:31 +02:00
|
|
|
|
2023-09-12 23:18:47 +02:00
|
|
|
public String getScheme() {
|
|
|
|
|
return uri.getScheme();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 22:50:28 +02:00
|
|
|
public String getPath() {
|
|
|
|
|
return uri.getPath();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-16 18:14:46 +01:00
|
|
|
public String getAuthority() {
|
|
|
|
|
return uri.getAuthority();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 16:03:31 +02:00
|
|
|
@Override
|
|
|
|
|
public String toString() {
|
|
|
|
|
return String.valueOf(uri);
|
|
|
|
|
}
|
2024-02-17 15:15:05 +01:00
|
|
|
|
|
|
|
|
public static Uri fromFile(File file) {
|
|
|
|
|
Uri ret = new Uri();
|
|
|
|
|
ret.uri = file.toURI();
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getLastPathSegment() {
|
|
|
|
|
String[] segments = uri.getPath().split("/");
|
|
|
|
|
return segments[segments.length - 1];
|
|
|
|
|
}
|
2024-03-29 23:56:28 +01:00
|
|
|
|
|
|
|
|
public String getQueryParameter(String key) {
|
|
|
|
|
for (String pair : uri.getQuery().split("&")) {
|
|
|
|
|
if (pair.startsWith(key + "=")) {
|
|
|
|
|
return pair.substring(key.length() + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2022-10-02 23:06:56 +02:00
|
|
|
}
|