aQute.libg.uri.URIUtil Maven / Gradle / Ivy
package aQute.libg.uri;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Pattern;
public final class URIUtil {
private static final Pattern WINDOWS_FILE_PATTERN = Pattern.compile("(?:\\p{Alpha}:[\\\\/]|\\\\\\\\|//)");
/**
* Resolves a URI reference against a base URI. Work-around for bugs in
* java.net.URI
* (e.g.http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535)
*
* @param baseURI
* @param reference
* @return the resolved {@code URI}
* @throws URISyntaxException
*/
public static URI resolve(URI baseURI, String reference) throws URISyntaxException {
if (reference.isEmpty()) {
return new URI(baseURI.getScheme(), baseURI.getSchemeSpecificPart(), null);
}
// A Windows path such as "C:\Users" is interpreted as a URI with
// a scheme of "C". Use a regex that matches the colon-backslash
// combination to handle this specifically as an absolute file URI.
if (WINDOWS_FILE_PATTERN.matcher(reference)
.lookingAt()) {
return new File(reference).toURI();
}
reference = reference.replace('\\', '/');
URI refURI;
try {
refURI = new URI(reference);
} catch (URISyntaxException e) {
refURI = new URI(null, reference, null);
}
return baseURI.resolve(refURI);
}
private static final URI EMPTYURI = URI.create("");
/**
* Attempts to fetch a path on the file system for the given URI string.
* Tries a few more tricks than the standard method of
* Paths.get(new URI(uriString)) - it can handle plain paths, and
* also nested URI pseudo-schemes like reference: and
* jar:. Examples:
*
* - /path/to/file => /path/to/file
* - reference:file:/path/to/file => /path/to/file
* - jar:file:/path/to/file.jar!/some/contained/element =>
* /path/to/file.jar
* - http://server/path/to/file => null
*
*
* @param uriString The URI string for which we are attempting to construct
* a path.
* @return The method's best guess as to which file on the local filesystem
* this URI refers, or an empty Optional if it's invalid or not a
* local filesystem URI.
*/
public static Optional pathFromURI(String uriString) {
if (uriString == null) {
return Optional.empty();
}
try {
URI uri = resolve(EMPTYURI, uriString);
return pathFromURI(uriString, uri);
} catch (URISyntaxException e1) {
return Optional.empty();
}
}
/**
* Attempts to fetch a path on the file system for the given URI. Tries a
* few more tricks than the standard method of Paths.get(uri) - it
* can handle plain paths, and also nested URI pseudo-schemes like
* reference: and jar:. Examples:
*
* - /path/to/file => /path/to/file
* - reference:file:/path/to/file => /path/to/file
* - jar:file:/path/to/file.jar!/some/contained/element =>
* /path/to/file.jar
* - http://server/path/to/file => null
*
*
* @param uri The URI for which we are attempting to construct a path.
* @return The method's best guess as to which file on the local filesystem
* this URI refers, or an empty Optional if it's invalid or not a
* local filesystem URI.
*/
public static Optional pathFromURI(URI uri) {
if (uri == null) {
return Optional.empty();
}
return pathFromURI(uri.getSchemeSpecificPart(), uri);
}
private static Optional pathFromURI(String uriString, URI uri) {
try {
while (!EMPTYURI.equals(uri)) {
final String scheme = uri.getScheme();
if (scheme == null) {
try {
return Optional.of(Paths.get(uriString));
} catch (InvalidPathException e) {
return Optional.empty();
}
}
switch (scheme.toLowerCase(Locale.ROOT)) {
case "file" :
return Optional.of(Paths.get(uri));
case "jar" :
case "bundle" :
case "zip" :
uriString = uri.getSchemeSpecificPart();
int index = uriString.indexOf("!/");
if (index >= 0) {
uriString = uriString.substring(0, index);
}
uri = resolve(EMPTYURI, uriString);
break;
case "reference" :
uriString = uri.getSchemeSpecificPart();
uri = resolve(EMPTYURI, uriString);
break;
default :
uriString = uri.getSchemeSpecificPart();
uri = new URI(uriString);
if (uri.getScheme() == null) {
return Optional.empty();
}
break;
}
}
} catch (URISyntaxException e1) {}
return Optional.empty();
}
/**
* Answer if the given URL is on the local system or remote
*/
public static boolean isRemote(URI uri) {
String scheme = uri.getScheme();
if (scheme == null)
return false;
switch (scheme.toLowerCase(Locale.ROOT)) {
case "file" :
case "jar" :
case "data" :
return false;
default :
return true;
}
}
public static int getDefaultPort(String scheme) {
switch (scheme) {
case "http" :
return 80;
case "https" :
return 443;
case "ftp" :
return 20;
}
return -1;
}
public static String encodePath(String path) {
if ((path == null) || path.isEmpty()) {
return path;
}
byte[] bytes = path.getBytes(UTF_8);
final int length = bytes.length;
byte[] encoded = new byte[length * 3];
int i = 0;
for (byte b : bytes) {
if (isPathAllowed(b)) {
encoded[i++] = b;
} else {
encoded[i++] = '%';
encoded[i++] = (byte) Character.forDigit((b >> 4) & 0xF, 16);
encoded[i++] = (byte) Character.forDigit(b & 0xF, 16);
}
}
if (i > length) {
return new String(encoded, 0, i, UTF_8);
}
return path;
}
static boolean isPathAllowed(int b) {
switch (b) {
case '/' : // path_segments
case ';' : // segment
case ':' : // pchar
case '@' :
case '&' :
case '=' :
case '+' :
case '$' :
case ',' :
case '-' : // mark
case '_' :
case '.' :
case '!' :
case '~' :
case '*' :
case '\'' :
case '(' :
case ')' :
return true;
default :
return isAlphanum(b);
}
}
static boolean isAlphanum(int b) {
return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z');
}
public static String decode(String source) {
if ((source == null) || source.isEmpty()) {
return source;
}
final int length = source.length();
byte[] decoded = new byte[length];
int i = 0;
for (int j = 0; j < length; j++) {
int c = source.charAt(j);
if (c == '%') {
decoded[i++] = (byte) ((Character.digit(source.charAt(++j), 16) << 4)
| Character.digit(source.charAt(++j), 16));
} else {
decoded[i++] = (byte) c;
}
}
if (i < length) {
return new String(decoded, 0, i, UTF_8);
}
return source;
}
}