package se.bjurr.prnfb.http; import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Throwables.propagate; import static com.google.common.collect.Lists.newArrayList; import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION; import static javax.xml.bind.DatatypeConverter.printBase64Binary; import static org.slf4j.LoggerFactory.getLogger; import static se.bjurr.prnfb.http.UrlInvoker.HTTP_METHOD.GET; import static se.bjurr.prnfb.http.UrlInvoker.HTTP_METHOD.POST; import static se.bjurr.prnfb.http.UrlInvoker.HTTP_METHOD.PUT; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.cert.X509Certificate; import java.util.List; import javax.net.ssl.SSLContext; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.ProxyAuthenticationStrategy; import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import se.bjurr.prnfb.settings.PrnfbHeader; import se.bjurr.prnfb.settings.PrnfbNotification; /** * If told to accept all certificates, an unsafe X509 trust manager is used.<br> * <br> * If setup of the "trust-all" HttpClient fails, a non-configured HttpClient is returned.<br> * <br> * Inspired by:<br> * Philip Dodds (pdodds) https://github.com/pdodds<br> * Michael Irwin (mikesir87) https://github.com/Nerdwin15<br> */ public class UrlInvoker { public enum HTTP_METHOD { DELETE, GET, POST, PUT } private static final Logger LOG = getLogger(UrlInvoker.class); @VisibleForTesting public static String getHeaderValue(PrnfbHeader header) { return header.getValue(); } public static UrlInvoker urlInvoker() { return new UrlInvoker(); } private ClientKeyStore clientKeyStore; private final List<PrnfbHeader> headers = newArrayList(); private HTTP_METHOD method = GET; private Optional<String> postContent = absent(); private Optional<String> proxyHost = absent(); private Optional<String> proxyPassword = absent(); private Optional<Integer> proxyPort = absent(); private Optional<String> proxySchema = absent(); private Optional<String> proxyUser = absent(); private HttpResponse response; private boolean shouldAcceptAnyCertificate; private String urlParam; UrlInvoker() {} public UrlInvoker appendBasicAuth(PrnfbNotification notification) { if (notification.getUser().isPresent() && notification.getPassword().isPresent()) { final String userpass = notification.getUser().get() + ":" + notification.getPassword().get(); final String basicAuth = "Basic " + new String(printBase64Binary(userpass.getBytes(UTF_8))); withHeader(AUTHORIZATION, basicAuth); } return this; } public ClientKeyStore getClientKeyStore() { return this.clientKeyStore; } public List<PrnfbHeader> getHeaders() { return this.headers; } public HTTP_METHOD getMethod() { return this.method; } public Optional<String> getPostContent() { return this.postContent; } public Optional<String> getProxyHost() { return this.proxyHost; } public Optional<String> getProxyPassword() { return this.proxyPassword; } public Optional<Integer> getProxyPort() { return this.proxyPort; } public Optional<String> getProxySchema() { return proxySchema; } public Optional<String> getProxyUser() { return this.proxyUser; } public HttpResponse getResponse() { return this.response; } public InputStream getResponseStringStream() { return new ByteArrayInputStream(getResponse().getContent().getBytes(UTF_8)); } public String getUrlParam() { return this.urlParam; } public HttpResponse invoke() { LOG.info("Url: \"" + this.urlParam + "\""); HttpRequestBase httpRequestBase = newHttpRequestBase(); configureUrl(httpRequestBase); addHeaders(httpRequestBase); HttpClientBuilder builder = HttpClientBuilder.create(); configureSsl(builder); configureProxy(builder); this.response = doInvoke(httpRequestBase, builder); if (LOG.isDebugEnabled()) { if (this.response != null) { LOG.debug(this.response.getContent()); } } return this.response; } public void setResponse(HttpResponse response) { this.response = response; } @VisibleForTesting public boolean shouldAcceptAnyCertificate() { return this.shouldAcceptAnyCertificate; } public UrlInvoker shouldAcceptAnyCertificate(boolean shouldAcceptAnyCertificate) { this.shouldAcceptAnyCertificate = shouldAcceptAnyCertificate; return this; } @VisibleForTesting public boolean shouldAuthenticateProxy() { return getProxyUser().isPresent() && getProxyPassword().isPresent(); } @VisibleForTesting public boolean shouldPostContent() { return (this.method == POST || this.method == PUT) && this.postContent.isPresent(); } @VisibleForTesting public boolean shouldUseProxy() { return getProxyHost().isPresent() && getProxyPort().isPresent() && getProxyPort().get() > 0; } public UrlInvoker withClientKeyStore(ClientKeyStore clientKeyStore) { this.clientKeyStore = clientKeyStore; return this; } public UrlInvoker withHeader(String name, String value) { this.headers.add(new PrnfbHeader(name, value)); return this; } public UrlInvoker withMethod(HTTP_METHOD method) { this.method = method; return this; } public UrlInvoker withPostContent(Optional<String> postContent) { this.postContent = postContent; return this; } public UrlInvoker withProxyPassword(Optional<String> proxyPassword) { this.proxyPassword = proxyPassword; return this; } public UrlInvoker withProxyPort(Integer proxyPort) { this.proxyPort = fromNullable(proxyPort); return this; } public UrlInvoker withProxySchema(Optional<String> proxySchema) { this.proxySchema = proxySchema; return this; } public UrlInvoker withProxyServer(Optional<String> proxyHost) { this.proxyHost = proxyHost; return this; } public UrlInvoker withProxyUser(Optional<String> proxyUser) { this.proxyUser = proxyUser; return this; } public UrlInvoker withUrlParam(String urlParam) { this.urlParam = urlParam.replaceAll("\\s", "%20"); return this; } private void addHeaders(HttpRequestBase httpRequestBase) { for (PrnfbHeader header : this.headers) { if (header.getName().equals(AUTHORIZATION)) { LOG.debug("header: \"" + header.getName() + "\" value: \"**********\""); } else { LOG.debug("header: \"" + header.getName() + "\" value: \"" + header.getValue() + "\""); } httpRequestBase.addHeader(header.getName(), getHeaderValue(header)); } } private void configureUrl(HttpRequestBase httpRequestBase) { try { httpRequestBase.setURI(new URI(this.urlParam)); } catch (URISyntaxException e) { propagate(e); } } private SSLContextBuilder doAcceptAnyCertificate(SSLContextBuilder customContext) throws Exception { TrustStrategy easyStrategy = new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] chain, String authType) { return true; } }; customContext = customContext.loadTrustMaterial(null, easyStrategy); return customContext; } private SSLContext newSslContext() throws Exception { SSLContextBuilder sslContextBuilder = SSLContexts.custom(); if (this.shouldAcceptAnyCertificate) { doAcceptAnyCertificate(sslContextBuilder); if (this.clientKeyStore.getKeyStore().isPresent()) { sslContextBuilder.loadKeyMaterial( this.clientKeyStore.getKeyStore().get(), this.clientKeyStore.getPassword()); } } return sslContextBuilder.build(); } private boolean shouldUseSsl() { return this.urlParam.startsWith("https"); } @VisibleForTesting HttpClientBuilder configureProxy(HttpClientBuilder builder) { if (!shouldUseProxy()) { return builder; } if (this.proxyUser.isPresent() && this.proxyPassword.isPresent()) { String username = this.proxyUser.get(); String password = this.proxyPassword.get(); UsernamePasswordCredentials creds = new UsernamePasswordCredentials(username, password); CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( new AuthScope(this.proxyHost.get(), this.proxyPort.get()), creds); builder.setDefaultCredentialsProvider(credsProvider); } builder.useSystemProperties(); builder.setProxy( new HttpHost(this.proxyHost.get(), this.proxyPort.get(), this.proxySchema.orNull())); builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); return builder; } @VisibleForTesting HttpClientBuilder configureSsl(HttpClientBuilder builder) { if (shouldUseSsl()) { try { SSLContext s = newSslContext(); SSLConnectionSocketFactory sslConnSocketFactory = new SSLConnectionSocketFactory(s); builder.setSSLSocketFactory(sslConnSocketFactory); Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("https", sslConnSocketFactory) .build(); HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry); builder.setConnectionManager(ccm); } catch (Exception e) { propagate(e); } } return builder; } @VisibleForTesting HttpResponse doInvoke(HttpRequestBase httpRequestBase, HttpClientBuilder builder) { CloseableHttpResponse httpResponse = null; try { httpResponse = builder // .build() // .execute(httpRequestBase); HttpEntity entity = httpResponse.getEntity(); String entityString = ""; if (entity != null) { entityString = EntityUtils.toString(entity, UTF_8); } URI uri = httpRequestBase.getURI(); int statusCode = httpResponse.getStatusLine().getStatusCode(); return new HttpResponse(uri, statusCode, entityString); } catch (final Exception e) { LOG.error("", e); } finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { propagate(e); } } return null; } @VisibleForTesting HttpEntityEnclosingRequestBase newHttpEntityEnclosingRequestBase( HTTP_METHOD method, String entity) { HttpEntityEnclosingRequestBase entityEnclosing = new HttpEntityEnclosingRequestBase() { @Override public String getMethod() { return method.name(); } }; if (entity != null) { entityEnclosing.setEntity(new ByteArrayEntity(entity.getBytes())); } return entityEnclosing; } @VisibleForTesting HttpRequestBase newHttpRequestBase() { if (shouldPostContent()) { return newHttpEntityEnclosingRequestBase(this.method, this.postContent.get()); } return newHttpRequestBase(this.method); } @VisibleForTesting HttpRequestBase newHttpRequestBase(HTTP_METHOD method) { return new HttpRequestBase() { @Override public String getMethod() { return method.name(); } }; } }