package org.emergent.android.weave.client; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpMessage; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.StatusLine; import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthState; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.ClientContext; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.params.ConnManagerPNames; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.conn.ssl.AbstractVerifier; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.util.InetAddressUtils; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.SingleClientConnManager; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; import javax.net.ssl.SSLException; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; /** * @author Patrick Woodworth */ class WeaveTransport { private static final int HTTP_PORT_DEFAULT = 80; private static final int HTTPS_PORT_DEFAULT = 443; private static final HttpRequestInterceptor sm_preemptiveAuth = new MyInterceptor(); private static final MyResponseHandler sm_responseHandler = new MyResponseHandler(); private static final HttpParams sm_httpParams; static { HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, "UTF-8"); HttpProtocolParams.setUserAgent(params, WeaveConstants.USER_AGENT); HttpProtocolParams.setUseExpectContinue(params, false); // params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false); params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30); params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30)); sm_httpParams = params; } private final SocketFactory m_sslSocketFactory; private final ClientConnectionManager m_clientConMgr; public WeaveTransport() { this(WeaveConstants.CONNECTION_POOL_ENABLED_DEFAULT); } public WeaveTransport(boolean useConnectionPool) { this(useConnectionPool, WeaveConstants.ALLOW_INVALID_CERTS_DEFAULT); } public WeaveTransport(boolean useConnectionPool, boolean allowInvalidCerts) { m_sslSocketFactory = createSocketFactory(allowInvalidCerts); m_clientConMgr = useConnectionPool ? createClientConnectionManager(true) : null; } public void shutdown() { // if (m_clientConMgr != null) // m_clientConMgr.shutdown(); } public WeaveResponse execDeleteMethod(String username, String password, URI uri) throws IOException, WeaveException { HttpDelete method = new HttpDelete(uri); return execGenericMethod(username, password, uri, method); } public WeaveResponse execGetMethod(String username, String password, URI uri) throws IOException, WeaveException { HttpGet method = new HttpGet(uri); return execGenericMethod(username, password, uri, method); } public WeaveResponse execPostMethod(String username, String password, URI uri, HttpEntity entity) throws IOException, WeaveException { HttpPost method = new HttpPost(uri); method.setEntity(entity); return execGenericMethod(username, password, uri, method); } public WeaveResponse execPutMethod(String username, String password, URI uri, HttpEntity entity) throws IOException, WeaveException { HttpPut method = new HttpPut(uri); method.setEntity(entity); return execGenericMethod(username, password, uri, method); } private void setMethodHeaders(HttpMessage method) { method.addHeader("Pragma","no-cache"); method.addHeader("Cache-Control","no-cache"); } private WeaveResponse execGenericMethod(String username, String password, URI uri, HttpRequestBase method) throws IOException, WeaveException { HttpClient client = null; try { client = createHttpClient(username, password); return execGenericMethod(client, uri, method); // } catch (IOException e) { // throw new WeaveException("Unable to communicate with Weave server.", e); } finally { if (m_clientConMgr == null && client != null) { client.getConnectionManager().shutdown(); } } } private WeaveResponse execGenericMethod(HttpClient client, URI uri, HttpRequestBase method) throws IOException, WeaveException { setMethodHeaders(method); MyResponseHandler responseHandler = sm_responseHandler; String scheme = uri.getScheme(); String hostname = uri.getHost(); int port = uri.getPort(); HttpHost httpHost = new HttpHost(hostname, port, scheme); WeaveResponseHeaders responseHeaders = null; try { WeaveResponse response = client.execute(httpHost, method, responseHandler); response.setUri(uri); responseHeaders = response.getResponseHeaders(); return response; } catch (WeaveResponseException e) { responseHeaders = e.getResponseHeaders(); throw e; } finally { if (responseHeaders != null) { // long backoff = responseHeaders.getBackoffSeconds(); // if (backoff > 0) { // long newbackoff = System.currentTimeMillis() + backoff; // m_backoff.set(newbackoff); // } } } } private DefaultHttpClient createDefaultHttpClient() { ClientConnectionManager connectionManager; if (m_clientConMgr != null) { connectionManager = m_clientConMgr; } else { connectionManager = createClientConnectionManager(false); } return new DefaultHttpClient(connectionManager, sm_httpParams); } private HttpClient createHttpClient(String userId, String password) { DefaultHttpClient retval = createDefaultHttpClient(); Credentials defaultcreds = new UsernamePasswordCredentials(userId, password); retval.getCredentialsProvider().setCredentials(AuthScope.ANY, defaultcreds); retval.addRequestInterceptor(sm_preemptiveAuth, 0); return retval; } private ClientConnectionManager createClientConnectionManager(boolean threadSafe) { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), HTTP_PORT_DEFAULT)); schemeRegistry.register(new Scheme("https", m_sslSocketFactory, HTTPS_PORT_DEFAULT)); if (threadSafe) { return new ThreadSafeClientConnManager(sm_httpParams, schemeRegistry); } else { return new SingleClientConnManager(sm_httpParams, schemeRegistry); } } private static SocketFactory createSocketFactory(boolean allowInvalidCerts) { SocketFactory sslSocketFactory; if (allowInvalidCerts) { sslSocketFactory = new WeaveSSLSocketFactory(); } else { sslSocketFactory = SSLSocketFactory.getSocketFactory(); ((SSLSocketFactory)sslSocketFactory).setHostnameVerifier(new WeaveHostnameVerifier()); } return sslSocketFactory; } public static class WeaveResponseHeaders { private final Header[] m_headers; public WeaveResponseHeaders(HttpResponse response) { m_headers = response.getAllHeaders(); } public Header[] getHeaders() { return m_headers; } public Date getServerTimestamp() { Date retval = null; String ststamp = getHeaderValue(WeaveHeader.X_WEAVE_TIMESTAMP); if (ststamp != null) retval = WeaveUtil.toModifiedTimeDate(ststamp); return retval; } public long getBackoffSeconds() { long retval = 0; try { String valStr = getHeaderValue(WeaveHeader.X_WEAVE_BACKOFF); if (valStr != null) retval = Long.parseLong(valStr); } catch (Exception ignored) { } return retval; } private String getHeaderValue(WeaveHeader header) { return getHeaderValue(header.getName()); } private String getHeaderValue(String headerName) { for (Header header : m_headers) { if (headerName.equals(header.getName())) return header.getValue(); } return null; } } @SuppressWarnings("serial") public static class WeaveResponseException extends HttpResponseException { private final WeaveResponseHeaders m_responseHeaders; public WeaveResponseException(int statusCode, String reasonPhrase, HttpResponse response) { // super(statusCode, String.format("statusCode = %s ; reason = %s", statusCode, reasonPhrase)); super(statusCode, reasonPhrase); m_responseHeaders = new WeaveResponseHeaders(response); } public WeaveResponseHeaders getResponseHeaders() { return m_responseHeaders; } @Override public String toString() { String s = getClass().getName(); s += ": (statusCode=" + getStatusCode() + ")"; String message = getLocalizedMessage(); return (message != null) ? (s + " : " + message) : s; } } /** * Based on BasicResponseHandler */ private static class MyResponseHandler implements ResponseHandler<WeaveResponse> { /** * Returns the response body as a String if the response was successful (a 2xx status code). If no response body * exists, this returns null. If the response was unsuccessful (>= 300 status code), throws an * {@link org.apache.http.client.HttpResponseException}. */ public WeaveResponse handleResponse(final HttpResponse response) throws HttpResponseException, IOException { StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() >= 300) { throw new WeaveResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase(), response); } return new WeaveResponse(response); } } private static class MyInterceptor implements HttpRequestInterceptor { public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { AuthState authState = (AuthState)context.getAttribute(ClientContext.TARGET_AUTH_STATE); CredentialsProvider credsProvider = (CredentialsProvider)context.getAttribute(ClientContext.CREDS_PROVIDER); HttpHost targetHost = (HttpHost)context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); if (authState.getAuthScheme() == null) { AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort()); Credentials creds = credsProvider.getCredentials(authScope); if (creds != null) { authState.setAuthScheme(new BasicScheme()); authState.setCredentials(creds); } } } } /** * @author Patrick Woodworth */ static class WeaveHostnameVerifier extends AbstractVerifier { public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { if (isIPAddress(host) && cns != null && cns.length > 0 && cns[0] != null) { HashSet<String> expandedAlts = new HashSet<String>(); resolveHostAddresses(cns[0], expandedAlts); if (subjectAlts != null) expandedAlts.addAll(Arrays.asList(subjectAlts)); subjectAlts = expandedAlts.toArray(new String[expandedAlts.size()]); } verify(host, cns, subjectAlts, false); } private static void resolveHostAddresses(String cn, Collection<String> retval) { try { InetAddress[] addresses = InetAddress.getAllByName(cn); for (InetAddress address : addresses) { retval.add(address.getHostAddress()); } } catch (UnknownHostException e) { Dbg.d(e); } } private static boolean isIPAddress(final String hostname) { return hostname != null && (InetAddressUtils.isIPv4Address(hostname) || InetAddressUtils.isIPv6Address(hostname)); } } }