package de.danoeh.antennapod.core.service.download; import android.os.Build; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import com.squareup.okhttp.Credentials; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.http.StatusLine; import java.io.IOException; import java.net.CookieManager; import java.net.CookiePolicy; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.net.SocketAddress; import java.net.URL; import java.security.GeneralSecurityException; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBWriter; /** * Provides access to a HttpClient singleton. */ public class AntennapodHttpClient { private AntennapodHttpClient(){} private static final String TAG = "AntennapodHttpClient"; public static final int CONNECTION_TIMEOUT = 30000; public static final int READ_TIMEOUT = 30000; public static final int MAX_CONNECTIONS = 8; private static volatile OkHttpClient httpClient = null; /** * Returns the HttpClient singleton. */ public static synchronized OkHttpClient getHttpClient() { if (httpClient == null) { httpClient = newHttpClient(); } return httpClient; } public static synchronized void reinit() { httpClient = newHttpClient(); } /** * Creates a new HTTP client. Most users should just use * getHttpClient() to get the standard AntennaPod client, * but sometimes it's necessary for others to have their own * copy so that the clients don't share state. * @return http client */ @NonNull public static OkHttpClient newHttpClient() { Log.d(TAG, "Creating new instance of HTTP client"); System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS)); OkHttpClient client = new OkHttpClient(); // detect 301 Moved permanently and 308 Permanent Redirect client.networkInterceptors().add(chain -> { Request request = chain.request(); Response response = chain.proceed(request); if (response.code() == HttpURLConnection.HTTP_MOVED_PERM || response.code() == StatusLine.HTTP_PERM_REDIRECT) { String location = response.header("Location"); if (location.startsWith("/")) { // URL is not absolute, but relative URL url = request.url(); location = url.getProtocol() + "://" + url.getHost() + location; } else if (!location.toLowerCase().startsWith("http://") && !location.toLowerCase().startsWith("https://")) { // Reference is relative to current path URL url = request.url(); String path = url.getPath(); String newPath = path.substring(0, path.lastIndexOf("/") + 1) + location; location = url.getProtocol() + "://" + url.getHost() + newPath; } try { DBWriter.updateFeedDownloadURL(request.urlString(), location).get(); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } return response; }); // set cookie handler CookieManager cm = new CookieManager(); cm.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); client.setCookieHandler(cm); // set timeouts client.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); client.setReadTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS); client.setWriteTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS); // configure redirects client.setFollowRedirects(true); client.setFollowSslRedirects(true); ProxyConfig config = UserPreferences.getProxyConfig(); if (config.type != Proxy.Type.DIRECT) { int port = config.port > 0 ? config.port : ProxyConfig.DEFAULT_PORT; SocketAddress address = InetSocketAddress.createUnresolved(config.host, port); Proxy proxy = new Proxy(config.type, address); client.setProxy(proxy); if (!TextUtils.isEmpty(config.username)) { String credentials = Credentials.basic(config.username, config.password); client.interceptors().add(chain -> { Request request = chain.request().newBuilder() .header("Proxy-Authorization", credentials).build(); return chain.proceed(request); }); } } if(16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) { client.setSslSocketFactory(new CustomSslSocketFactory()); } return client; } /** * Closes expired connections. This method should be called by the using class once has finished its work with * the HTTP client. */ public static synchronized void cleanup() { if (httpClient != null) { // does nothing at the moment } } private static class CustomSslSocketFactory extends SSLSocketFactory { private SSLSocketFactory factory; public CustomSslSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, null, null); factory= sslContext.getSocketFactory(); } catch(GeneralSecurityException e) { e.printStackTrace(); } } @Override public String[] getDefaultCipherSuites() { return factory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return factory.getSupportedCipherSuites(); } public Socket createSocket() throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(); configureSocket(result); return result; } public Socket createSocket(String var1, int var2) throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); configureSocket(result); return result; } public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); configureSocket(result); return result; } public Socket createSocket(InetAddress var1, int var2) throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); configureSocket(result); return result; } public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); configureSocket(result); return result; } public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException { SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); configureSocket(result); return result; } private void configureSocket(SSLSocket s) { s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" } ); } } }