//
// 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 com.cloud.utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;
import javax.net.ssl.HttpsURLConnection;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.exception.CloudRuntimeException;
public class UriUtils {
public static final Logger s_logger = Logger.getLogger(UriUtils.class.getName());
public static String formNfsUri(String host, String path) {
try {
URI uri = new URI("nfs", host, path, null);
return uri.toString();
} catch (URISyntaxException e) {
throw new CloudRuntimeException("Unable to form nfs URI: " + host + " - " + path);
}
}
public static String formIscsiUri(String host, String iqn, Integer lun) {
try {
String path = iqn;
if (lun != null) {
path += "/" + lun.toString();
}
URI uri = new URI("iscsi", host, path, null);
return uri.toString();
} catch (URISyntaxException e) {
throw new CloudRuntimeException("Unable to form iscsi URI: " + host + " - " + iqn + " - " + lun);
}
}
public static String formFileUri(String path) {
File file = new File(path);
return file.toURI().toString();
}
// a simple URI component helper (Note: it does not deal with URI paramemeter area)
public static String encodeURIComponent(String url) {
int schemeTail = url.indexOf("://");
int pathStart = 0;
if (schemeTail > 0)
pathStart = url.indexOf('/', schemeTail + 3);
else
pathStart = url.indexOf('/');
if (pathStart > 0) {
String[] tokens = url.substring(pathStart + 1).split("/");
StringBuilder sb = new StringBuilder(url.substring(0, pathStart));
for (String token : tokens) {
sb.append("/").append(URLEncoder.encode(token));
}
return sb.toString();
}
// no need to do URL component encoding
return url;
}
public static String getCifsUriParametersProblems(URI uri) {
if (!UriUtils.hostAndPathPresent(uri)) {
String errMsg = "cifs URI missing host and/or path. Make sure it's of the format cifs://hostname/path";
s_logger.warn(errMsg);
return errMsg;
}
return null;
}
public static boolean hostAndPathPresent(URI uri) {
return !(uri.getHost() == null || uri.getHost().trim().isEmpty() || uri.getPath() == null || uri.getPath().trim().isEmpty());
}
public static boolean cifsCredentialsPresent(URI uri) {
List<NameValuePair> args = URLEncodedUtils.parse(uri, "UTF-8");
boolean foundUser = false;
boolean foundPswd = false;
for (NameValuePair nvp : args) {
String name = nvp.getName();
if (name.equals("user")) {
foundUser = true;
s_logger.debug("foundUser is" + foundUser);
} else if (name.equals("password")) {
foundPswd = true;
s_logger.debug("foundPswd is" + foundPswd);
}
}
return (foundUser && foundPswd);
}
public static String getUpdateUri(String url, boolean encrypt) {
String updatedPath = null;
try {
String query = URIUtil.getQuery(url);
URIBuilder builder = new URIBuilder(url);
builder.removeQuery();
StringBuilder updatedQuery = new StringBuilder();
List<NameValuePair> queryParams = getUserDetails(query);
ListIterator<NameValuePair> iterator = queryParams.listIterator();
while (iterator.hasNext()) {
NameValuePair param = iterator.next();
String value = null;
if ("password".equalsIgnoreCase(param.getName()) &&
param.getValue() != null) {
value = encrypt ? DBEncryptionUtil.encrypt(param.getValue()) : DBEncryptionUtil.decrypt(param.getValue());
} else {
value = param.getValue();
}
if (updatedQuery.length() == 0) {
updatedQuery.append(param.getName()).append('=')
.append(value);
} else {
updatedQuery.append('&').append(param.getName())
.append('=').append(value);
}
}
String schemeAndHost = "";
URI newUri = builder.build();
if (newUri.getScheme() != null) {
schemeAndHost = newUri.getScheme() + "://" + newUri.getHost();
}
updatedPath = schemeAndHost + newUri.getPath() + "?" + updatedQuery;
} catch (URISyntaxException e) {
throw new CloudRuntimeException("Couldn't generate an updated uri. " + e.getMessage());
}
return updatedPath;
}
private static List<NameValuePair> getUserDetails(String query) {
List<NameValuePair> details = new ArrayList<NameValuePair>();
if (query != null && !query.isEmpty()) {
StringTokenizer allParams = new StringTokenizer(query, "&");
while (allParams.hasMoreTokens()) {
String param = allParams.nextToken();
details.add(new BasicNameValuePair(param.substring(0, param.indexOf("=")),
param.substring(param.indexOf("=") + 1)));
}
}
return details;
}
// Get the size of a file from URL response header.
public static Long getRemoteSize(String url) {
Long remoteSize = (long)0;
HttpURLConnection httpConn = null;
HttpsURLConnection httpsConn = null;
try {
URI uri = new URI(url);
if (uri.getScheme().equalsIgnoreCase("http")) {
httpConn = (HttpURLConnection)uri.toURL().openConnection();
if (httpConn != null) {
httpConn.setConnectTimeout(2000);
httpConn.setReadTimeout(5000);
String contentLength = httpConn.getHeaderField("content-length");
if (contentLength != null) {
remoteSize = Long.parseLong(contentLength);
}
httpConn.disconnect();
}
} else if (uri.getScheme().equalsIgnoreCase("https")) {
httpsConn = (HttpsURLConnection)uri.toURL().openConnection();
if (httpsConn != null) {
String contentLength = httpsConn.getHeaderField("content-length");
if (contentLength != null) {
remoteSize = Long.parseLong(contentLength);
}
httpsConn.disconnect();
}
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid URL " + url);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to establish connection with URL " + url);
}
return remoteSize;
}
public static Pair<String, Integer> validateUrl(String url) throws IllegalArgumentException {
return validateUrl(null, url);
}
public static Pair<String, Integer> validateUrl(String format, String url) throws IllegalArgumentException {
try {
URI uri = new URI(url);
if ((uri.getScheme() == null) ||
(!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file"))) {
throw new IllegalArgumentException("Unsupported scheme for url: " + url);
}
int port = uri.getPort();
if (!(port == 80 || port == 8080 || port == 443 || port == -1)) {
throw new IllegalArgumentException("Only ports 80, 8080 and 443 are allowed");
}
if (port == -1 && uri.getScheme().equalsIgnoreCase("https")) {
port = 443;
} else if (port == -1 && uri.getScheme().equalsIgnoreCase("http")) {
port = 80;
}
String host = uri.getHost();
try {
InetAddress hostAddr = InetAddress.getByName(host);
if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) {
throw new IllegalArgumentException("Illegal host specified in url");
}
if (hostAddr instanceof Inet6Address) {
throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")");
}
} catch (UnknownHostException uhe) {
throw new IllegalArgumentException("Unable to resolve " + host);
}
// verify format
if (format != null) {
String uripath = uri.getPath();
checkFormat(format, uripath);
}
return new Pair<String, Integer>(host, port);
} catch (URISyntaxException use) {
throw new IllegalArgumentException("Invalid URL: " + url);
}
}
// use http HEAD method to validate url
public static void checkUrlExistence(String url) {
if (url.toLowerCase().startsWith("http") || url.toLowerCase().startsWith("https")) {
HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
HeadMethod httphead = new HeadMethod(url);
try {
if (httpClient.executeMethod(httphead) != HttpStatus.SC_OK) {
throw new IllegalArgumentException("Invalid URL: " + url);
}
} catch (HttpException hte) {
throw new IllegalArgumentException("Cannot reach URL: " + url);
} catch (IOException ioe) {
throw new IllegalArgumentException("Cannot reach URL: " + url);
}
}
}
// verify if a URI path is compliance with the file format given
private static void checkFormat(String format, String uripath) {
if ((!uripath.toLowerCase().endsWith("vhd")) && (!uripath.toLowerCase().endsWith("vhd.zip")) && (!uripath.toLowerCase().endsWith("vhd.bz2")) &&
(!uripath.toLowerCase().endsWith("vhdx")) && (!uripath.toLowerCase().endsWith("vhdx.gz")) &&
(!uripath.toLowerCase().endsWith("vhdx.bz2")) && (!uripath.toLowerCase().endsWith("vhdx.zip")) &&
(!uripath.toLowerCase().endsWith("vhd.gz")) && (!uripath.toLowerCase().endsWith("qcow2")) && (!uripath.toLowerCase().endsWith("qcow2.zip")) &&
(!uripath.toLowerCase().endsWith("qcow2.bz2")) && (!uripath.toLowerCase().endsWith("qcow2.gz")) && (!uripath.toLowerCase().endsWith("ova")) &&
(!uripath.toLowerCase().endsWith("ova.zip")) && (!uripath.toLowerCase().endsWith("ova.bz2")) && (!uripath.toLowerCase().endsWith("ova.gz")) &&
(!uripath.toLowerCase().endsWith("tar")) && (!uripath.toLowerCase().endsWith("tar.zip")) && (!uripath.toLowerCase().endsWith("tar.bz2")) &&
(!uripath.toLowerCase().endsWith("tar.gz")) && (!uripath.toLowerCase().endsWith("vmdk")) && (!uripath.toLowerCase().endsWith("vmdk.gz")) &&
(!uripath.toLowerCase().endsWith("vmdk.zip")) && (!uripath.toLowerCase().endsWith("vmdk.bz2")) && (!uripath.toLowerCase().endsWith("img")) &&
(!uripath.toLowerCase().endsWith("img.gz")) && (!uripath.toLowerCase().endsWith("img.zip")) && (!uripath.toLowerCase().endsWith("img.bz2")) &&
(!uripath.toLowerCase().endsWith("raw")) && (!uripath.toLowerCase().endsWith("raw.gz")) && (!uripath.toLowerCase().endsWith("raw.bz2")) &&
(!uripath.toLowerCase().endsWith("raw.zip")) && (!uripath.toLowerCase().endsWith("iso")) && (!uripath.toLowerCase().endsWith("iso.zip"))
&& (!uripath.toLowerCase().endsWith("iso.bz2")) && (!uripath.toLowerCase().endsWith("iso.gz"))) {
throw new IllegalArgumentException("Please specify a valid " + format.toLowerCase());
}
if ((format.equalsIgnoreCase("vhd")
&& (!uripath.toLowerCase().endsWith("vhd")
&& !uripath.toLowerCase().endsWith("vhd.zip")
&& !uripath.toLowerCase().endsWith("vhd.bz2")
&& !uripath.toLowerCase().endsWith("vhd.gz")))
|| (format.equalsIgnoreCase("vhdx")
&& (!uripath.toLowerCase().endsWith("vhdx")
&& !uripath.toLowerCase().endsWith("vhdx.zip")
&& !uripath.toLowerCase().endsWith("vhdx.bz2")
&& !uripath.toLowerCase().endsWith("vhdx.gz")))
|| (format.equalsIgnoreCase("qcow2")
&& (!uripath.toLowerCase().endsWith("qcow2")
&& !uripath.toLowerCase().endsWith("qcow2.zip")
&& !uripath.toLowerCase().endsWith("qcow2.bz2")
&& !uripath.toLowerCase().endsWith("qcow2.gz")))
|| (format.equalsIgnoreCase("ova")
&& (!uripath.toLowerCase().endsWith("ova")
&& !uripath.toLowerCase().endsWith("ova.zip")
&& !uripath.toLowerCase().endsWith("ova.bz2")
&& !uripath.toLowerCase().endsWith("ova.gz")))
|| (format.equalsIgnoreCase("tar")
&& (!uripath.toLowerCase().endsWith("tar")
&& !uripath.toLowerCase().endsWith("tar.zip")
&& !uripath.toLowerCase().endsWith("tar.bz2")
&& !uripath.toLowerCase().endsWith("tar.gz")))
|| (format.equalsIgnoreCase("raw")
&& (!uripath.toLowerCase().endsWith("img")
&& !uripath.toLowerCase().endsWith("img.zip")
&& !uripath.toLowerCase().endsWith("img.bz2")
&& !uripath.toLowerCase().endsWith("img.gz")
&& !uripath.toLowerCase().endsWith("raw")
&& !uripath.toLowerCase().endsWith("raw.bz2")
&& !uripath.toLowerCase().endsWith("raw.zip")
&& !uripath.toLowerCase().endsWith("raw.gz")))
|| (format.equalsIgnoreCase("vmdk")
&& (!uripath.toLowerCase().endsWith("vmdk")
&& !uripath.toLowerCase().endsWith("vmdk.zip")
&& !uripath.toLowerCase().endsWith("vmdk.bz2")
&& !uripath.toLowerCase().endsWith("vmdk.gz")))
|| (format.equalsIgnoreCase("iso")
&& (!uripath.toLowerCase().endsWith("iso")
&& !uripath.toLowerCase().endsWith("iso.zip")
&& !uripath.toLowerCase().endsWith("iso.bz2")
&& !uripath.toLowerCase().endsWith("iso.gz")))) {
throw new IllegalArgumentException("Please specify a valid URL. URL:" + uripath + " is an invalid for the format " + format.toLowerCase());
}
}
public static InputStream getInputStreamFromUrl(String url, String user, String password) {
try {
Pair<String, Integer> hostAndPort = validateUrl(url);
HttpClient httpclient = new HttpClient(new MultiThreadedHttpConnectionManager());
if ((user != null) && (password != null)) {
httpclient.getParams().setAuthenticationPreemptive(true);
Credentials defaultcreds = new UsernamePasswordCredentials(user, password);
httpclient.getState().setCredentials(new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds);
s_logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first() + ":" + hostAndPort.second());
}
// Execute the method.
GetMethod method = new GetMethod(url);
int statusCode = httpclient.executeMethod(method);
if (statusCode != HttpStatus.SC_OK) {
s_logger.error("Failed to read from URL: " + url);
return null;
}
return method.getResponseBodyAsStream();
} catch (Exception ex) {
s_logger.error("Failed to read from URL: " + url);
return null;
}
}
}