Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.amazonservices.mws.client.MwsUtl Maven / Gradle / Ivy
package com.amazonservices.mws.client;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Node;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.XMLConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.Closeable;
import java.io.StringWriter;
import java.net.URI;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Static utility functions used by MWS client classes.
*
* @author mayerj
*/
public class MwsUtl {
/** Commons logging. */
@SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(MwsUtl.class);
/** Match an asterisk character. */
private static final Pattern asteriskPtn = Pattern.compile("*", Pattern.LITERAL);
/** Match one back-slash. */
private static final Pattern BackSlashPtn = Pattern.compile("\\", Pattern.LITERAL);
/** A cached simple date format for generating time-stamps. */
private static final AtomicReference dateFormatPool = new AtomicReference();
/** Match one equal character. */
private static final Pattern EqualPtn = Pattern.compile("=", Pattern.LITERAL);
/** Escaped back slash character. */
private static final String EscBackSlash = "\\\\";
/** Escaped equal character. */
private static final String EscEqual = "\\=";
/** Escaped forward slash character. */
private static final String EscForwardSlash = "\\/";
/** Escaped left parenthesis character. */
private static final String EscLParen = "\\(";
/** Escaped right parenthesis character. */
private static final String EscRParen = "\\)";
/** Escaped semicolon character. */
private static final String EscSemicolon = "\\;";
/** Match one forward-slash. */
private static final Pattern ForwardSlashPtn = Pattern.compile("/", Pattern.LITERAL);
/** Match one right parenthesis character. */
private static final Pattern LParenPtn = Pattern.compile("(", Pattern.LITERAL);
/** Match leading and/or trailing white spaces. */
private static final Pattern OuterWhiteSpacesPtn = Pattern.compile("\\A\\s+|\\s+\\z");
/** Match "%2F". */
private static final Pattern pct2FPtn = Pattern.compile("%2F", Pattern.LITERAL);
/** Match "%7E". */
private static final Pattern pct7EPtn = Pattern.compile("%7E", Pattern.LITERAL);
/** Match a + character. */
private static final Pattern plusPtn = Pattern.compile("+", Pattern.LITERAL);
/** Match one right parenthesis character. */
private static final Pattern RParenPtn = Pattern.compile(")", Pattern.LITERAL);
/** Match one semicolon character. */
private static final Pattern SemicolonPtn = Pattern.compile(";", Pattern.LITERAL);
/** Match one or more white spaces. */
private static final Pattern WhiteSpacesPtn = Pattern.compile("\\s+");
/** Default character encoding. */
static final String DEFAULT_ENCODING = "UTF-8";
/** For creating xml dates. */
private static final ThreadLocal threadDTF = new ThreadLocal();
/** For creating xml documents. */
private static final ThreadLocal threadDBF = new ThreadLocal();
/** Thread local transformer factory. */
private static final ThreadLocal threadTF = new ThreadLocal();
/** Map from enum classes to hash maps of names. */
private static final ConcurrentHashMap> enumMaps =
new ConcurrentHashMap>();
/**
* Calculate String to Sign for SignatureVersion 0
*
* @param parameters
* request parameters
* @return String to Sign
*/
private static String calculateStringToSignV0(Map parameters) {
StringBuilder data = new StringBuilder();
data.append(parameters.get("Action")).append(parameters.get("Timestamp"));
return data.toString();
}
/**
* Calculate String to Sign for SignatureVersion 1
*
* @param parameters
* request parameters
* @return String to Sign
*/
private static String calculateStringToSignV1(Map parameters) {
StringBuilder data = new StringBuilder();
Map sorted = new TreeMap(String.CASE_INSENSITIVE_ORDER);
sorted.putAll(parameters);
Iterator> pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Entry pair = pairs.next();
data.append(pair.getKey());
data.append(pair.getValue());
}
return data.toString();
}
/**
* Calculate String to Sign for SignatureVersion 2
*
* @param serviceUri
*
* @param parameters
* request parameters
* @return String to Sign
*/
static String calculateStringToSignV2(URI serviceUri, Map parameters) {
StringBuilder data = new StringBuilder();
data.append("POST");
data.append("\n");
data.append(serviceUri.getHost().toLowerCase());
if (!usesStandardPort(serviceUri)) {
data.append(":");
data.append(serviceUri.getPort());
}
data.append("\n");
String uri = serviceUri.getPath();
data.append(MwsUtl.urlEncode(uri, true));
data.append("\n");
Map sorted = new TreeMap();
sorted.putAll(parameters);
Iterator> pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Entry pair = pairs.next();
String key = pair.getKey();
data.append(MwsUtl.urlEncode(key, false));
data.append("=");
String value = pair.getValue();
data.append(MwsUtl.urlEncode(value, false));
if (pairs.hasNext()) {
data.append("&");
}
}
return data.toString();
}
/**
* Clean white space. Remove leading and trailing, and replace internal runs
* with a single space character.
*
* @param s
*
* @return The clean string.
*/
private static String cleanWS(String s) {
s = replaceAll(s, OuterWhiteSpacesPtn, "");
s = replaceAll(s, WhiteSpacesPtn, " ");
return s;
}
/**
* Replace a pattern in a string.
*
* Do not do recursive replacement. Return the original string if no changes
* are required.
*
* @param s
* The string to search.
*
* @param p
* The pattern to search for.
*
* @param r
* The string to replace occurrences with.
*
* @return The new string.
*/
static String replaceAll(String s, Pattern p, String r) {
int n = s == null ? 0 : s.length();
if (n == 0) {
return s;
}
Matcher m = p.matcher(s);
if (!m.find()) {
return s;
}
StringBuilder buf = new StringBuilder(n + 12);
int k = 0;
do {
buf.append(s, k, m.start());
buf.append(r);
k = m.end();
} while (m.find());
if (k < n) {
buf.append(s, k, n);
}
return buf.toString();
}
/**
* Computes RFC 2104-compliant HMAC signature.
*
* @param data
* The data to sign.
* @param key
* The key to use for signing.
*
* @param algorithm
* The signing algorithm.
*
* @return The signature.
*/
static String sign(String data, String key, String algorithm) {
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key.getBytes(DEFAULT_ENCODING), algorithm));
byte[] signature = Base64.encodeBase64(mac.doFinal(data.getBytes(DEFAULT_ENCODING)));
String encoded = new String(signature, DEFAULT_ENCODING);
//log.debug("\nSign:"+data+"\nKey:"+key+"\nAlgorithm:"+algorithm+"\nSignature:"+encoded);
return encoded;
} catch (Exception e) {
throw MwsUtl.wrap(e);
}
}
/**
* URL encode a value.
*
* @param value
*
* @param path
* true if is a path and '/' should not be encoded.
*
* @return The encoded string.
*/
protected static String urlEncode(String value, boolean path) {
try {
value = URLEncoder.encode(value, DEFAULT_ENCODING);
} catch (Exception e) {
throw wrap(e);
}
value = replaceAll(value, plusPtn, "%20");
value = replaceAll(value, asteriskPtn, "%2A");
value = replaceAll(value, pct7EPtn, "~");
if (path) {
value = replaceAll(value, pct2FPtn, "/");
}
return value;
}
/**
* Get a thread local DocumentBuilderFactory.
*
* DocumentBuilderFactory is NOT thread safe. This method uses a thread
* local to create one per calling thread.
*
* @return The instance for this thread.
*/
static DocumentBuilderFactory getDBF() throws ParserConfigurationException {
DocumentBuilderFactory dbf = threadDBF.get();
if (dbf == null) {
dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setValidating(true);
threadDBF.set(dbf);
}
return dbf;
}
/**
* Gets an enum instance from the enum class and name.
*
* Caches hashmaps of enum names. If enum has an Other entry it will be
* returned instead of null when no match is found.
*
* @param cls
* @param name
*
* @return The found instance.
*/
@SuppressWarnings("unchecked")
static T getEnumValue(Class cls, String name) {
HashMap enumMap = enumMaps.get(cls);
if (enumMap == null) {
T[] consts = cls.getEnumConstants();
enumMap = new HashMap(consts.length);
for (T e : consts) {
enumMap.put(((Enum>) e).toString(), e);
}
enumMaps.put(cls, enumMap);
}
T v = (T) enumMap.get(name);
if (v == null) {
v = (T) enumMap.get("Other");
}
return v;
}
/**
* Get a ISO 8601 formatted timestamp of now.
*
* @return The time stamp.
*/
static String getFormattedTimestamp() {
DateFormat df = dateFormatPool.getAndSet(null);
if (df == null) {
df = createISODateFormat();
}
String timestamp = df.format(new Date());
dateFormatPool.set(df);
return timestamp;
}
/**
* Parse an ISO 8601 formatted timestamp
*
* @return the parsed date
*/
static Date parseTimestamp(String timestamp) throws ParseException {
DateFormat df = dateFormatPool.getAndSet(null);
if (df == null) {
df = createISODateFormat();
}
Date date = df.parse(timestamp);
dateFormatPool.set(df);
return date;
}
/**
* @return a new ISO 8601 date formatter
*/
static DateFormat createISODateFormat() {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
return df;
}
/**
* Create a new instance of a class, wrap exceptions.
*
* @param cls
*
* @return The new instance.
*/
static T newInstance(Class cls) {
try {
return cls.newInstance();
} catch (Exception e) {
throw wrap(e);
}
}
/**
* Computes RFC 2104-compliant HMAC signature for request parameters
* Implements AWS Signature, as per following spec:
*
* If Signature Version is 0, it signs concatenated Action and Timestamp
*
* If Signature Version is 1, it performs the following:
*
* Sorts all parameters (including SignatureVersion and excluding Signature,
* the value of which is being created), ignoring case.
*
* Iterate over the sorted list and append the parameter name (in original
* case) and then its value. It will not URL-encode the parameter values
* before constructing this string. There are no separators.
*
* If Signature Version is 2, string to sign is based on following:
*
* 1. The HTTP Request Method followed by an ASCII newline (%0A) 2. The HTTP
* Host header in the form of lowercase host, followed by an ASCII newline.
* 3. The URL encoded HTTP absolute path component of the URI (up to but not
* including the query string parameters); if this is empty use a forward
* '/'. This parameter is followed by an ASCII newline. 4. The concatenation
* of all query string components (names and values) as UTF-8 characters
* which are URL encoded as per RFC 3986 (hex characters MUST be uppercase),
* sorted using lexicographic byte ordering. Parameter names are separated
* from their values by the '=' character (ASCII character 61), even if the
* value is empty. Pairs of parameter and values are separated by the '&'
* character (ASCII code 38).
*
* @param serviceUri
* Including host, port, api name, and api version
* @param parameters
* @param signatureVersion
* @param signatureMethod
* @param awsSecretKey
*
* @return The base64 encoding of the signature.
*/
static String signParameters(URI serviceUri, String signatureVersion, String signatureMethod,
Map parameters, String aswSecretKey) {
parameters.put("SignatureVersion", signatureVersion);
String algorithm = "HmacSHA1";
String stringToSign = null;
if ("0".equals(signatureVersion)) {
stringToSign = calculateStringToSignV0(parameters);
} else if ("1".equals(signatureVersion)) {
stringToSign = calculateStringToSignV1(parameters);
} else if ("2".equals(signatureVersion)) {
algorithm = signatureMethod;
parameters.put("SignatureMethod", algorithm);
stringToSign = calculateStringToSignV2(serviceUri, parameters);
} else {
throw new IllegalArgumentException("Invalid Signature Version specified");
}
return sign(stringToSign, aswSecretKey, algorithm);
}
/**
* Get xml string for contents of node.
*
* @param node
*
* @return The node as xml.
*/
static String toXmlString(Node node) {
try {
Transformer transformer = getTF().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
DOMSource source = new DOMSource(node);
transformer.transform(source, result);
return sw.toString();
} catch (Exception e) {
throw wrap(e);
}
}
/**
* Determine if a uri uses https.
*
* @param uri
*
* @return true if uses https.
*/
static boolean usesHttps(URI uri) {
return uri.getScheme().equals("https");
}
/**
* Determine if a url uses the standard port.
*
* Port 80 for http, 443 for https.
*
* @param uri
*
* @return true if standard port is used.
*/
static boolean usesStandardPort(URI uri) {
int portNumber = uri.getPort();
if (portNumber == -1) {
return true;
}
String schema = uri.getScheme();
int standardPort = schema.equals("https") ? 443 : 80;
return portNumber == standardPort;
}
/**
* Close a Closeable if it is not null.
*
* @param a
* The Closeable or null.
*/
public static void close(Closeable a) {
try {
if (a != null) {
a.close();
}
} catch (Exception e) {
throw wrap(e);
}
}
/**
* Escape application name before using to form user agent string.
*
* Clean up white space and then escape back slash and forward slash
* characters.
*
* @param s
*
* @return The escaped app name.
*/
public static String escapeAppName(String s) {
s = cleanWS(s);
s = replaceAll(s, BackSlashPtn, EscBackSlash);
s = replaceAll(s, ForwardSlashPtn, EscForwardSlash);
return s;
}
/**
* Escape an application version string.
*
* @param s
*
* @return The escaped app version.
*/
public static String escapeAppVersion(String s) {
s = cleanWS(s);
s = replaceAll(s, BackSlashPtn, EscBackSlash);
s = replaceAll(s, LParenPtn, EscLParen);
return s;
}
/**
* Standardize and escape an attribute name.
*
* Clean white space. Escape back-slashes and equals-sign with a back-slash.
*
* @param s
* The attribute name to standardize and escape.
*
* @return The standardized and escaped attribute name.
*/
public static String escapeAttributeName(String s) {
s = cleanWS(s);
s = replaceAll(s, BackSlashPtn, EscBackSlash);
s = replaceAll(s, EqualPtn, EscEqual);
return s;
}
/**
* Standardize and escape an attribute value.
*
* Clean white space. Escape back-slashes, semi-colons, and right
* parenthesis with a back-slash.
*
* @param s
* The attribute value to standardize and escape.
*
* @return The standardized and escaped attribute value.
*/
public static String escapeAttributeValue(String s) {
s = cleanWS(s);
s = replaceAll(s, BackSlashPtn, EscBackSlash);
s = replaceAll(s, SemicolonPtn, EscSemicolon);
s = replaceAll(s, RParenPtn, EscRParen);
return s;
}
/**
* Get a thread local DatatypeFactory.
*
* DatatypeFactory is NOT required to be thread safe. This method uses a
* thread local to create one per calling thread.
*
* @return A thread local DatatypeFactory.
*/
public static DatatypeFactory getDTF() {
DatatypeFactory dtf = threadDTF.get();
if (dtf == null) {
try {
dtf = DatatypeFactory.newInstance();
} catch (Exception e) {
throw wrap(e);
}
threadDTF.set(dtf);
}
return dtf;
}
/**
* Get a thread local transformer factory.
*
* @return The factory.
*/
public static TransformerFactory getTF() throws TransformerConfigurationException {
TransformerFactory tf = threadTF.get();
if (tf == null) {
tf = TransformerFactory.newInstance();
// Disable external doctype declarations (DTDs) and schemas
// to ensure XML transformation is secure against XML External Entity Injection attacks.
// @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md
// @see https://skb.corp.amazon.com/implementations/189
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
threadTF.set(tf);
}
return tf;
}
/**
* Wrap Checked exceptions in runtime exception.
*
* @param e
*
* @return e, wrapped in a runtime exception if necessary.
*/
public static RuntimeException wrap(Throwable e) {
if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException(e);
}
/**
* Hide utility class constructor.
*/
private MwsUtl() {
// Hidden constructor
}
}