package com.braintreegateway.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.KeyStore;
import java.security.Principal;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import com.braintreegateway.Configuration;
import com.braintreegateway.Request;
import com.braintreegateway.exceptions.AuthenticationException;
import com.braintreegateway.exceptions.AuthorizationException;
import com.braintreegateway.exceptions.DownForMaintenanceException;
import com.braintreegateway.exceptions.NotFoundException;
import com.braintreegateway.exceptions.ServerException;
import com.braintreegateway.exceptions.TimeoutException;
import com.braintreegateway.exceptions.TooManyRequestsException;
import com.braintreegateway.exceptions.UnexpectedException;
import com.braintreegateway.exceptions.UpgradeRequiredException;
import com.braintreegateway.org.apache.commons.codec.binary.Base64;
public class Http {
private volatile SSLSocketFactory sslSocketFactory;
enum RequestMethod {
DELETE, GET, POST, PUT;
}
private Configuration configuration;
public Http(Configuration configuration) {
this.configuration = configuration;
}
public void delete(String url) {
httpRequest(RequestMethod.DELETE, url);
}
public NodeWrapper get(String url) {
return httpRequest(RequestMethod.GET, url);
}
public NodeWrapper post(String url) {
return httpRequest(RequestMethod.POST, url, null);
}
public NodeWrapper post(String url, Request request) {
return httpRequest(RequestMethod.POST, url, request.toXML());
}
public NodeWrapper post(String url, String request) {
return httpRequest(RequestMethod.POST, url, request);
}
public NodeWrapper put(String url) {
return httpRequest(RequestMethod.PUT, url, null);
}
public NodeWrapper put(String url, Request request) {
return httpRequest(RequestMethod.PUT, url, request.toXML());
}
private NodeWrapper httpRequest(RequestMethod requestMethod, String url) {
return httpRequest(requestMethod, url, null);
}
private NodeWrapper httpRequest(RequestMethod requestMethod, String url, String postBody) {
HttpURLConnection connection = null;
NodeWrapper nodeWrapper = null;
try {
connection = buildConnection(requestMethod, url);
Logger logger = configuration.getLogger();
if (postBody != null) {
logger.log(Level.FINE, formatSanitizeBodyForLog(postBody));
}
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setSSLSocketFactory(getSSLSocketFactory());
}
if (postBody != null) {
OutputStream outputStream = null;
try {
outputStream = connection.getOutputStream();
outputStream.write(postBody.getBytes("UTF-8"));
} finally {
if (outputStream != null) {
outputStream.close();
}
}
}
throwExceptionIfErrorStatusCode(connection.getResponseCode(), null);
if (requestMethod.equals(RequestMethod.DELETE)) {
return null;
}
InputStream responseStream = null;
try {
responseStream = connection.getResponseCode() == 422 ? connection.getErrorStream() : connection.getInputStream();
if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) {
responseStream = new GZIPInputStream(responseStream);
}
String xml = StringUtils.inputStreamToString(responseStream);
logger.log(Level.INFO, "[Braintree] [{0}]] {1} {2}", new Object[] { getCurrentTime(), requestMethod.toString(), url });
logger.log(Level.FINE, "[Braintree] [{0}] {1} {2} {3}", new Object[] { getCurrentTime(), requestMethod.toString(), url, connection.getResponseCode() });
if (xml != null) {
logger.log(Level.FINE, formatSanitizeBodyForLog(xml));
}
nodeWrapper = NodeWrapperFactory.instance.create(xml);
} finally {
if (responseStream != null) {
responseStream.close();
}
}
} catch (SocketTimeoutException e) {
throw new TimeoutException(e.getMessage(), e);
} catch (IOException e) {
throw new UnexpectedException(e.getMessage(), e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return nodeWrapper;
}
private String formatSanitizeBodyForLog(String body) {
if (body == null) {
return body;
}
Pattern regex = Pattern.compile("(^)", Pattern.MULTILINE);
Matcher regexMatcher = regex.matcher(body);
if (regexMatcher.find()) {
body = regexMatcher.replaceAll("[Braintree] $1");
}
regex = Pattern.compile("<number>(.{6}).+?(.{4})</number>");
regexMatcher = regex.matcher(body);
if (regexMatcher.find()) {
body = regexMatcher.replaceAll("<number>$1******$2</number>");
}
body = body.replaceAll("<cvv>.+?</cvv>", "<cvv>***</cvv>");
return body;
}
private String getCurrentTime() {
return new SimpleDateFormat("d/MMM/yyyy HH:mm:ss Z").format(new Date());
}
private SSLSocketFactory getSSLSocketFactory() {
if (sslSocketFactory == null) {
synchronized (this) {
if (sslSocketFactory == null) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
for (String certificateFilename : configuration.getEnvironment().certificateFilenames) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream certStream = null;
try {
certStream = Http.class.getClassLoader().getResourceAsStream(certificateFilename);
Collection<? extends Certificate> coll = cf.generateCertificates(certStream);
for (Certificate cert : coll) {
if (cert instanceof X509Certificate) {
X509Certificate x509cert = (X509Certificate) cert;
Principal principal = x509cert.getSubjectDN();
String subject = principal.getName();
keyStore.setCertificateEntry(subject, cert);
}
}
} finally {
if (certStream != null) {
certStream.close();
}
}
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = null;
try {
// Use TLS v1.2 explicitly for Java 1.6 or Java 7 JVMs that support it but do not turn it on by
// default
sslContext = SSLContext.getInstance("TLSv1.2");
} catch (NoSuchAlgorithmException e) {
sslContext = SSLContext.getInstance("TLS");
}
sslContext.init((KeyManager[]) kmf.getKeyManagers(), tmf.getTrustManagers(), SecureRandom.getInstance("SHA1PRNG"));
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
Logger logger = configuration.getLogger();
logger.log(Level.SEVERE, "SSL Verification failed. Error message: {0}", new Object[] { e.getMessage() });
throw new UnexpectedException(e.getMessage(), e);
}
}
}
}
return sslSocketFactory;
}
private HttpURLConnection buildConnection(RequestMethod requestMethod, String urlString) throws java.io.IOException {
URL url = new URL(configuration.getBaseURL() + urlString);
HttpURLConnection connection;
if (configuration.usesProxy()) {
connection = (HttpURLConnection) url.openConnection(configuration.getProxy());
} else {
connection = (HttpURLConnection) url.openConnection();
}
connection.setRequestMethod(requestMethod.toString());
connection.addRequestProperty("Accept", "application/xml");
connection.addRequestProperty("User-Agent", "Braintree Java " + Configuration.VERSION);
connection.addRequestProperty("X-ApiVersion", Configuration.apiVersion());
connection.addRequestProperty("Authorization", authorizationHeader());
connection.addRequestProperty("Accept-Encoding", "gzip");
connection.addRequestProperty("Content-Type", "application/xml");
connection.setDoOutput(true);
connection.setReadTimeout(configuration.getTimeout());
return connection;
}
public static void throwExceptionIfErrorStatusCode(int statusCode, String message) {
String decodedMessage = null;
if (message != null) {
try {
decodedMessage = URLDecoder.decode(message, "UTF-8");
} catch (UnsupportedEncodingException e) {
Logger logger = Logger.getLogger("Braintree");
logger.log(Level.FINEST, e.getMessage(), e.getStackTrace());
}
}
if (isErrorCode(statusCode)) {
switch (statusCode) {
case 401:
throw new AuthenticationException();
case 403:
throw new AuthorizationException(decodedMessage);
case 404:
throw new NotFoundException();
case 426:
throw new UpgradeRequiredException();
case 429:
throw new TooManyRequestsException();
case 500:
throw new ServerException();
case 503:
throw new DownForMaintenanceException();
default:
throw new UnexpectedException("Unexpected HTTP_RESPONSE " + statusCode);
}
}
}
private static boolean isErrorCode(int responseCode) {
return responseCode != 200 && responseCode != 201 && responseCode != 422;
}
public String authorizationHeader() {
if (configuration.isAccessToken()) {
return "Bearer " + configuration.getAccessToken();
}
String credentials;
if (configuration.isClientCredentials()) {
credentials = configuration.getClientId() + ":" + configuration.getClientSecret();
} else {
credentials = configuration.getPublicKey() + ":" + configuration.getPrivateKey();
}
return "Basic " + Base64.encodeBase64String(credentials.getBytes()).trim();
}
}