org.apache.brooklyn.util.net.Urls Maven / Gradle / Ivy
Show all versions of brooklyn-utils-common Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.util.net;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.io.BaseEncoding;
import com.google.common.net.MediaType;
public class Urls {
public static Function stringToUriFunction() {
return StringToUri.INSTANCE;
}
public static Function stringToUrlFunction() {
return StringToUrl.INSTANCE;
}
private static enum StringToUri implements Function {
INSTANCE;
@Override public URI apply(@Nullable String input) {
return toUri(input);
}
@Override
public String toString() {
return "StringToUri";
}
}
private static enum StringToUrl implements Function {
INSTANCE;
@Override public URL apply(@Nullable String input) {
return toUrl(input);
}
@Override
public String toString() {
return "StringToUrl";
}
}
/** creates a URL, preserving null and propagating exceptions *unchecked* */
public static final URL toUrl(@Nullable String url) {
if (url==null) return null;
try {
return new URL(url);
} catch (MalformedURLException e) {
// FOAD
throw Throwables.propagate(e);
}
}
/** creates a URL, preserving null and propagating exceptions *unchecked* */
public static final URL toUrl(@Nullable URI uri) {
if (uri==null) return null;
try {
return uri.toURL();
} catch (MalformedURLException e) {
// FOAD
throw Throwables.propagate(e);
}
}
/** creates a URI, preserving null and propagating exceptions *unchecked* */
public static final URI toUri(@Nullable String uri) {
if (uri==null) return null;
return URI.create(uri);
}
/** creates a URI, preserving null and propagating exceptions *unchecked* */
public static final URI toUri(@Nullable URL url) {
if (url==null) return null;
try {
return url.toURI();
} catch (URISyntaxException e) {
// FOAD
throw Throwables.propagate(e);
}
}
/** returns true if the string begins with a non-empty string of letters followed by a colon,
* i.e. "protocol:" returns true, but "/" returns false,
* followed by a non-space character. thereafter it is not strict, it can have spaces, but not newlines;
* see {@link #isUrlWithProtocol(String, boolean, boolean)} */
public static boolean isUrlWithProtocol(String x) {
return isUrlWithProtocol(x, true, false);
}
/** as {@link #isUrlWithProtocol(String)} but configurable to be strict (false, false) or allow newline chars (if e.g. in an unescaped argument) */
public static boolean isUrlWithProtocol(String x, boolean allowSpacesAfterCharAfterColon, boolean allowMultiline) {
if (x==null) return false;
for (int i=0; i=x.length()) return false;
char c2 = x.charAt(i+1);
// never allow a whitespace or quote mark right after the ':', that is too similar to json/yaml!
if (Character.isWhitespace(c2) || c2=='\'' || c2=='\"') return false;
if (!allowMultiline) {
if (x.indexOf('\n')>=0) return false;
if (x.indexOf('\r')>=0) return false;
}
if (!allowSpacesAfterCharAfterColon) {
if (x.indexOf(' ')>=0) return false;
if (x.indexOf('\t')>=0) return false;
}
return true;
}
// protocol schema as per https://en.wikipedia.org/wiki/Uniform_Resource_Locator
if (i==0) {
if (!Character.isLetter(c)) return false;
} else if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c!='+' && c!='.' && c!='-') {
return false;
}
}
// no colon found
return false;
}
/** returns the items with exactly one "/" between items (whether or not the individual items start or end with /),
* except where character before the / is a : (url syntax) in which case it will permit multiple (will not remove any).
* Throws a NullPointerException if any elements of 'items' is null.
* */
public static String mergePaths(String ...items) {
List parts = Arrays.asList(items);
if (parts.contains(null)) {
throw new NullPointerException(String.format("Unable to reliably merge path from parts: %s; input contains null values", parts));
}
StringBuilder result = new StringBuilder();
for (String part: parts) {
boolean trimThisMerge = result.length()>0 && !result.toString().endsWith("://") && !result.toString().endsWith(":///") && !result.toString().endsWith(":");
if (trimThisMerge) {
while (result.length()>0 && result.charAt(result.length()-1)=='/')
result.deleteCharAt(result.length()-1);
result.append('/');
}
int i = result.length();
result.append(part);
if (trimThisMerge) {
while (result.length()>i && result.charAt(i)=='/')
result.deleteCharAt(i);
}
}
return result.toString();
}
/**
* Encodes the string suitable for use in a URL, using default UTF-8
* (version of URLEncoder.encode that does not throw checked exception)
*/
public static String encode(String text) {
/*
* TODO URLEncoder.encode is a HTML encoder, to make strings safe to use in
* x-www-form-urlencoded. There is huge overlap between that and url-encoding, but
* they are not the same (e.g. space is converted to "+" rather than "%20", and
* "*" is not converted to "%2A").
*
* Correct URL-encoding depends on which part of the URL it is. For example, guava's
* UrlEscapers has separate escapers for path and fragment. Neither of these is
* appropriate for escaping the password in "http://myusername@mypassword:myhost".
*
* org.apache.commons.codec.net.URLCodec behaves the same as java.net.URLEncoder: it
* is for "www-form-urlencoded".
*/
try {
return URLEncoder.encode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Exceptions.propagate(e);
}
}
/** As {@link #encode(String)} */
public static String decode(String text) {
try {
return URLDecoder.decode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Exceptions.propagate(e);
}
}
/** returns the protocol (e.g. http) if one appears to be specified, or else null;
* 'protocol' here should consist of 2 or more _letters_ only followed by a colon
* (2 required to prevent {@code c:\xxx} being treated as a url)
*/
public static String getProtocol(String url) {
if (url==null) return null;
int i=0;
StringBuilder result = new StringBuilder();
while (true) {
if (url.length()<=i) return null;
char c = url.charAt(i);
if (Character.isLetter(c)) result.append(c);
else if (c==':') {
if (i>=2) return result.toString().toLowerCase();
return null;
} else return null;
i++;
}
}
/** return the last segment of the given url before any '?', e.g. the filename or last directory name in the case of directories
* (cf unix `basename`) */
public static String getBasename(String url) {
if (url==null) return null;
if (getProtocol(url)!=null) {
int firstQ = url.indexOf('?');
if (firstQ>=0)
url = url.substring(0, firstQ);
}
url = Strings.removeAllFromEnd(url, "/");
return url.substring(url.lastIndexOf('/')+1);
}
public static boolean isDirectory(String fileUrl) {
File file;
if (isUrlWithProtocol(fileUrl)) {
if (getProtocol(fileUrl).equals("file")) {
file = new File(URI.create(fileUrl));
} else {
return false;
}
} else {
file = new File(fileUrl);
}
return file.isDirectory();
}
public static File toFile(String fileUrl) {
if (isUrlWithProtocol(fileUrl)) {
if (getProtocol(fileUrl).equals("file")) {
return new File(URI.create(fileUrl));
} else {
throw new IllegalArgumentException("Not a file protocol URL: " + fileUrl);
}
} else {
return new File(fileUrl);
}
}
/** as {@link #asDataUrlBase64(String)} with plain text */
public static String asDataUrlBase64(String data) {
return asDataUrlBase64(MediaType.PLAIN_TEXT_UTF_8, data.getBytes());
}
/**
* Creates a "data:..." scheme URL for use with supported parsers, using Base64 encoding.
* (But note, by default Java's URL is not one of them, although Brooklyn's ResourceUtils does support it.)
*
* It is not necessary (at least for Brookyn's routines) to base64 encode it, but recommended as that is likely more
* portable and easier to work with if odd characters are included.
*
* It is worth noting that Base64 uses '+' which can be replaced by ' ' in some URL parsing.
* But in practice it does not seem to cause issues.
* An alternative is to use {@link BaseEncoding#base64Url()} but it is not clear how widely that is supported
* (nor what parameter should be given to indicate that type of encoding, as the spec calls for 'base64'!)
*
* null type means no type info will be included in the URL. */
public static String asDataUrlBase64(MediaType type, byte[] bytes) {
return "data:"+(type!=null ? type.withoutParameters().toString() : "")+";base64,"+new String(BaseEncoding.base64().encode(bytes));
}
}