package com.koushikdutta.async.http;
import android.net.Uri;
import android.text.TextUtils;
import com.koushikdutta.async.AsyncSSLSocket;
import com.koushikdutta.async.AsyncSSLSocketWrapper;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.LineEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
public class AsyncSSLSocketMiddleware extends AsyncSocketMiddleware {
public AsyncSSLSocketMiddleware(AsyncHttpClient client) {
super(client, "https", 443);
}
protected SSLContext sslContext;
public void setSSLContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
public SSLContext getSSLContext() {
return sslContext != null ? sslContext : AsyncSSLSocketWrapper.getDefaultSSLContext();
}
protected TrustManager[] trustManagers;
public void setTrustManagers(TrustManager[] trustManagers) {
this.trustManagers = trustManagers;
}
protected HostnameVerifier hostnameVerifier;
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
protected List<AsyncSSLEngineConfigurator> engineConfigurators = new ArrayList<AsyncSSLEngineConfigurator>();
public void addEngineConfigurator(AsyncSSLEngineConfigurator engineConfigurator) {
engineConfigurators.add(engineConfigurator);
}
public void clearEngineConfigurators() {
engineConfigurators.clear();
}
protected SSLEngine createConfiguredSSLEngine(GetSocketData data, String host, int port) {
SSLContext sslContext = getSSLContext();
SSLEngine sslEngine = sslContext.createSSLEngine();
for (AsyncSSLEngineConfigurator configurator : engineConfigurators) {
configurator.configureEngine(sslEngine, data, host, port);
}
return sslEngine;
}
protected AsyncSSLSocketWrapper.HandshakeCallback createHandshakeCallback(final GetSocketData data, final ConnectCallback callback) {
return new AsyncSSLSocketWrapper.HandshakeCallback() {
@Override
public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket) {
callback.onConnectCompleted(e, socket);
}
};
}
protected void tryHandshake(AsyncSocket socket, GetSocketData data, final Uri uri, final int port, final ConnectCallback callback) {
AsyncSSLSocketWrapper.handshake(socket, uri.getHost(), port,
createConfiguredSSLEngine(data, uri.getHost(), port),
trustManagers, hostnameVerifier, true,
createHandshakeCallback(data, callback));
}
@Override
protected ConnectCallback wrapCallback(final GetSocketData data, final Uri uri, final int port, final boolean proxied, final ConnectCallback callback) {
return new ConnectCallback() {
@Override
public void onConnectCompleted(Exception ex, final AsyncSocket socket) {
if (ex != null) {
callback.onConnectCompleted(ex, socket);
return;
}
if (!proxied) {
tryHandshake(socket, data, uri, port, callback);
return;
}
// this SSL connection is proxied, must issue a CONNECT request to the proxy server
// http://stackoverflow.com/a/6594880/704837
// some proxies also require 'Host' header, it should be safe to provide it every time
String connect = String.format(Locale.ENGLISH, "CONNECT %s:%s HTTP/1.1\r\nHost: %s\r\n\r\n", uri.getHost(), port, uri.getHost());
data.request.logv("Proxying: " + connect);
Util.writeAll(socket, connect.getBytes(), new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (ex != null) {
callback.onConnectCompleted(ex, socket);
return;
}
LineEmitter liner = new LineEmitter();
liner.setLineCallback(new LineEmitter.StringCallback() {
String statusLine;
@Override
public void onStringAvailable(String s) {
data.request.logv(s);
if (statusLine == null) {
statusLine = s.trim();
if (!statusLine.matches("HTTP/1.\\d 2\\d\\d .*")) { // connect response is allowed to have any 2xx status code
socket.setDataCallback(null);
socket.setEndCallback(null);
callback.onConnectCompleted(new IOException("non 2xx status line: " + statusLine), socket);
}
}
else if (TextUtils.isEmpty(s.trim())) { // skip all headers, complete handshake once empty line is received
socket.setDataCallback(null);
socket.setEndCallback(null);
tryHandshake(socket, data, uri, port, callback);
}
}
});
socket.setDataCallback(liner);
socket.setEndCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (!socket.isOpen() && ex == null)
ex = new IOException("socket closed before proxy connect response");
callback.onConnectCompleted(ex, socket);
}
});
}
});
}
};
}
}