package ctbrec.io; import static java.nio.charset.StandardCharsets.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.nio.file.Files; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import ctbrec.Config; import ctbrec.Settings.ProxyType; import okhttp3.ConnectionPool; import okhttp3.Cookie; import okhttp3.Credentials; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; import okhttp3.Request; import okhttp3.Response; import okhttp3.Route; import okhttp3.WebSocket; import okhttp3.WebSocketListener; public abstract class HttpClient { private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class); private static final ConnectionPool GLOBAL_HTTP_CONN_POOL = new ConnectionPool(10, 2, TimeUnit.MINUTES); protected OkHttpClient client; protected CookieJarImpl cookieJar = new CookieJarImpl(); protected boolean loggedIn = false; protected int loginTries = 0; private String name; protected HttpClient(String name) { this.name = name; cookieJar = createCookieJar(); reconfigure(); } protected CookieJarImpl createCookieJar() { return new CookieJarImpl(); } private void loadProxySettings() { ProxyType proxyType = Config.getInstance().getSettings().proxyType; switch (proxyType) { case HTTP: System.setProperty("http.proxyHost", Config.getInstance().getSettings().proxyHost); System.setProperty("http.proxyPort", Config.getInstance().getSettings().proxyPort); System.setProperty("https.proxyHost", Config.getInstance().getSettings().proxyHost); System.setProperty("https.proxyPort", Config.getInstance().getSettings().proxyPort); if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) { String username = Config.getInstance().getSettings().proxyUser; String password = Config.getInstance().getSettings().proxyPassword; System.setProperty("http.proxyUser", username); System.setProperty("http.proxyPassword", password); } break; case SOCKS4: System.setProperty("socksProxyVersion", "4"); System.setProperty("socksProxyHost", Config.getInstance().getSettings().proxyHost); System.setProperty("socksProxyPort", Config.getInstance().getSettings().proxyPort); break; case SOCKS5: System.setProperty("socksProxyVersion", "5"); System.setProperty("socksProxyHost", Config.getInstance().getSettings().proxyHost); System.setProperty("socksProxyPort", Config.getInstance().getSettings().proxyPort); if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) { String username = Config.getInstance().getSettings().proxyUser; String password = Config.getInstance().getSettings().proxyPassword; Authenticator.setDefault(new SocksProxyAuth(username, password)); } break; case DIRECT: default: System.clearProperty("http.proxyHost"); System.clearProperty("http.proxyPort"); System.clearProperty("https.proxyHost"); System.clearProperty("https.proxyPort"); System.clearProperty("socksProxyVersion"); System.clearProperty("socksProxyHost"); System.clearProperty("socksProxyPort"); System.clearProperty("java.net.socks.username"); System.clearProperty("java.net.socks.password"); System.clearProperty("http.proxyUser"); System.clearProperty("http.proxyPassword"); break; } } public Response execute(Request req) throws IOException { Response resp = client.newCall(req).execute(); return resp; } public Response execute(Request request, int timeoutInMillis) throws IOException { return client.newBuilder() // .connectTimeout(timeoutInMillis, TimeUnit.MILLISECONDS) // .readTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).build() // .newCall(request).execute(); } public abstract boolean login() throws IOException; public void reconfigure() { loadProxySettings(); loadCookies(); Builder builder = new OkHttpClient.Builder() .cookieJar(cookieJar) .connectionPool(GLOBAL_HTTP_CONN_POOL) .connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS) .readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS); //.addInterceptor(new LoggingInterceptor()); ProxyType proxyType = Config.getInstance().getSettings().proxyType; if (proxyType == ProxyType.HTTP) { String username = Config.getInstance().getSettings().proxyUser; String password = Config.getInstance().getSettings().proxyPassword; if (username != null && !username.isEmpty()) { builder.proxyAuthenticator(createHttpProxyAuthenticator(username, password)); } } // if transport layer security (TLS) is switched on, accept the self signed cert from the server if (Config.getInstance().getSettings().transportLayerSecurity) { acceptAllTlsCerts(builder); } client = builder.build(); } /** * This is a very simple and insecure solution to accept the self-signed cert from * the server. The side effect is, that certificates from other servers are neither checked! * TODO Delegate to the default trustmanager, if it is not the self-signed cert */ private void acceptAllTlsCerts(Builder builder) { X509TrustManager x509TrustManager = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { X509Certificate[] x509Certificates = new X509Certificate[0]; return x509Certificates; } @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} }; try { final TrustManager[] trustManagers = new TrustManager[] { x509TrustManager }; final String PROTOCOL = "TLSv1.2"; SSLContext sslContext = SSLContext.getInstance(PROTOCOL); KeyManager[] keyManagers = null; SecureRandom secureRandom = new SecureRandom(); sslContext.init(keyManagers, trustManagers, secureRandom); SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); builder.sslSocketFactory(sslSocketFactory, x509TrustManager); builder.hostnameVerifier((name, sslSession) -> true); } catch (KeyManagementException | NoSuchAlgorithmException e) { LOG.error("Couldn't install trust managers for TLS connections"); } } public void shutdown() { persistCookies(); client.connectionPool().evictAll(); client.dispatcher().executorService().shutdown(); } private void persistCookies() { try { CookieContainer cookies = new CookieContainer(); cookies.putAll(cookieJar.getCookies()); Moshi moshi = new Moshi.Builder() .add(CookieContainer.class, new CookieContainerJsonAdapter()) .build(); JsonAdapter adapter = moshi.adapter(CookieContainer.class).indent(" "); String json = adapter.toJson(cookies); File cookieFile = new File(Config.getInstance().getConfigDir(), "cookies-" + name + ".json"); try(FileOutputStream fout = new FileOutputStream(cookieFile)) { fout.write(json.getBytes(UTF_8)); } } catch (Exception e) { LOG.error("Couldn't persist cookies for {}", name, e); } } @SuppressWarnings({ "unchecked", "rawtypes" }) private void loadCookies() { try { File cookieFile = new File(Config.getInstance().getConfigDir(), "cookies-" + name + ".json"); if(!cookieFile.exists()) { return; } byte[] jsonBytes = Files.readAllBytes(cookieFile.toPath()); String json = new String(jsonBytes, UTF_8); Map> cookies = cookieJar.getCookies(); Moshi moshi = new Moshi.Builder() .add(CookieContainer.class, new CookieContainerJsonAdapter()) .build(); JsonAdapter adapter = moshi.adapter(CookieContainer.class).indent(" "); CookieContainer fromJson = adapter.fromJson(json); Set entries = fromJson.entrySet(); for (Object _entry : entries) { Entry entry = (Entry) _entry; cookies.put((String)entry.getKey(), (List)entry.getValue()); } } catch (Exception e) { LOG.error("Couldn't load cookies for {}", name, e); } } public static class CookieContainer extends HashMap> { } private okhttp3.Authenticator createHttpProxyAuthenticator(String username, String password) { return new okhttp3.Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(username, password); return response.request().newBuilder().header("Proxy-Authorization", credential).build(); } }; } public static class SocksProxyAuth extends Authenticator { private PasswordAuthentication auth; private SocksProxyAuth(String user, String password) { auth = new PasswordAuthentication(user, password == null ? new char[]{} : password.toCharArray()); } @Override protected PasswordAuthentication getPasswordAuthentication() { return auth; } } public CookieJarImpl getCookieJar() { return cookieJar; } public void logout() { getCookieJar().clear(); loggedIn = false; } public WebSocket newWebSocket(Request request, WebSocketListener l) { return client.newWebSocket(request, l); } public List getCookiesByName(String... names) { List result = new ArrayList<>(); Map> cookies = getCookieJar().getCookies(); for (List cookieList : cookies.values()) { for (Cookie cookie : cookieList) { for (String cookieName : names) { if (Objects.equals(cookieName, cookie.name())) { result.add(cookie); } } } } return result; } }