297 lines
12 KiB
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;
|
|
}
|
|
}
|