ctbrec-5.3.2-experimental/common/src/main/java/ctbrec/io/HttpClient.java

297 lines
12 KiB
Java

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<CookieContainer> 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<String, List<Cookie>> cookies = cookieJar.getCookies();
Moshi moshi = new Moshi.Builder()
.add(CookieContainer.class, new CookieContainerJsonAdapter())
.build();
JsonAdapter<CookieContainer> 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<Cookie>)entry.getValue());
}
} catch (Exception e) {
LOG.error("Couldn't load cookies for {}", name, e);
}
}
public static class CookieContainer extends HashMap<String, List<Cookie>> {
}
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<Cookie> getCookiesByName(String... names) {
List<Cookie> result = new ArrayList<>();
Map<String, List<Cookie>> cookies = getCookieJar().getCookies();
for (List<Cookie> cookieList : cookies.values()) {
for (Cookie cookie : cookieList) {
for (String cookieName : names) {
if (Objects.equals(cookieName, cookie.name())) {
result.add(cookie);
}
}
}
}
return result;
}
}