package de.thedevstack.conversationsplus.http; import android.support.annotation.NonNull; import org.apache.http.conn.ssl.StrictHostnameVerifier; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Locale; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.SSLSocketHelper; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; import okio.Buffer; import okio.BufferedSource; import okio.ForwardingSource; import okio.Okio; import okio.Source; /** * */ public final class HttpClient implements Http { private static HttpClient INSTANCE; private static final String LOGTAG = "http-client"; private final OkHttpClient client; public static synchronized void init() { INSTANCE = new HttpClient(); } public static synchronized HttpClient getClient() { if (null == INSTANCE) { init(); } return INSTANCE; } private static OkHttpClient.Builder getBuilder(boolean interactive) { OkHttpClient.Builder builder = INSTANCE.client.newBuilder(); INSTANCE.initTrustManager(builder, interactive); return builder; } public static synchronized OkHttpClient getOkHttpClient(boolean interactive) { return getBuilder(interactive).build(); } public static synchronized Call openCancelableAndProgressListenedCall(String url, final ProgressListener progressListener, boolean interactive) { OkHttpClient.Builder builder = getBuilder(interactive); OkHttpClient client = builder.addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); return originalResponse.newBuilder() .body(new ProgressResponseBody(originalResponse.body(), progressListener)) .build(); } }) .build(); return client.newCall(new Request.Builder().url(url).build()); } public static void retrieveHead(String url, @NonNull Callback callback) throws IOException { OkHttpClient client = HttpClient.getOkHttpClient(true); Request request = new Request.Builder() .url(url) //.addHeader(HEADER_NAME_ACCEPT_ENCODING, "") .head() .build(); client.newCall(request).enqueue(callback); } private HttpClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.addInterceptor(new UserAgentInterceptor()); builder.addInterceptor(new LoggingInterceptor()); this.client = builder.build(); } private static void initTrustManager(final OkHttpClient.Builder builder, final boolean interactive) { final X509TrustManager trustManager; final HostnameVerifier hostnameVerifier; if (interactive) { trustManager = ConversationsPlusApplication.getMemorizingTrustManager(); hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager().wrapHostnameVerifier( new StrictHostnameVerifier()); } else { trustManager = ConversationsPlusApplication.getMemorizingTrustManager() .getNonInteractive(); hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager() .wrapHostnameVerifierNonInteractive( new StrictHostnameVerifier()); } try { final SSLContext sc = SSLSocketHelper.getSSLContext(); sc.init(null, new X509TrustManager[]{trustManager}, ConversationsPlusApplication.getSecureRandom()); final SSLSocketFactory sf = sc.getSocketFactory(); final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( sf.getSupportedCipherSuites()); if (cipherSuites.length > 0) { sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); } builder.sslSocketFactory(sf, trustManager); builder.hostnameVerifier(hostnameVerifier); } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } } private static class UserAgentInterceptor implements Interceptor, Http { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest.newBuilder() .header(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion()) .build(); return chain.proceed(requestWithUserAgent); } } private static class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); Logging.d(LOGTAG, String.format(Locale.getDefault(), "Sending %s request %s on %s%n%s", request.method(), request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); Logging.d(LOGTAG, String.format(Locale.getDefault(), "Received response for %s request %s in %.1fms%n%s", response.request().method(), response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } private static class ProgressResponseBody extends ResponseBody { private final ResponseBody responseBody; private final ProgressListener progressListener; private BufferedSource bufferedSource; public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) { this.responseBody = responseBody; this.progressListener = progressListener; } @Override public MediaType contentType() { return responseBody.contentType(); } @Override public long contentLength() { return responseBody.contentLength(); } @Override public BufferedSource source() { if (bufferedSource == null) { bufferedSource = Okio.buffer(source(responseBody.source())); } return bufferedSource; } private Source source(Source source) { return new ForwardingSource(source) { @Override public long read(Buffer sink, long byteCount) throws IOException { long bytesRead = super.read(sink, byteCount); // read() returns the number of bytes read, or -1 if this source is exhausted. boolean done = bytesRead == -1; long currentBytesRead = !done ? bytesRead : 0; progressListener.update(currentBytesRead, responseBody.contentLength(), done); return bytesRead; } }; } } }