forked from Qortal/qortal
Add support for fetching updates using a combination of hostname and IP address.
IP address used to create socket, hostname used for SNI, HTTPS, etc. Added hostname+IP auto-update locations to Settings.
This commit is contained in:
parent
8dd4745c5c
commit
02e8bdb034
@ -4,13 +4,16 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SNIHostName;
|
||||
@ -29,23 +32,32 @@ import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||
|
||||
public class ApiRequest {
|
||||
|
||||
private static final Pattern proxyUrlPattern = Pattern.compile("(https://)([^@:/]+)@([0-9.]{7,15})(/.*)");
|
||||
|
||||
public static class FixedIpSocket extends Socket {
|
||||
private final String ipAddress;
|
||||
|
||||
public FixedIpSocket(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint, int timeout) throws IOException {
|
||||
InetSocketAddress inetEndpoint = (InetSocketAddress) endpoint;
|
||||
InetSocketAddress newEndpoint = new InetSocketAddress(ipAddress, inetEndpoint.getPort());
|
||||
super.connect(newEndpoint, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public static String perform(String uri, Map<String, String> params) {
|
||||
if (params != null && !params.isEmpty())
|
||||
uri += "?" + getParamsString(params);
|
||||
|
||||
InputStream in = fetchStream(uri);
|
||||
if (in == null)
|
||||
return null;
|
||||
|
||||
try (Scanner scanner = new Scanner(in, "UTF8")) {
|
||||
try (InputStream in = fetchStream(uri); Scanner scanner = new Scanner(in, "UTF8")) {
|
||||
scanner.useDelimiter("\\A");
|
||||
return scanner.hasNext() ? scanner.next() : "";
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
// We tried...
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,11 +67,7 @@ public class ApiRequest {
|
||||
if (params != null && !params.isEmpty())
|
||||
uri += "?" + getParamsString(params);
|
||||
|
||||
InputStream in = fetchStream(uri);
|
||||
if (in == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
try (InputStream in = fetchStream(uri)) {
|
||||
StreamSource json = new StreamSource(in);
|
||||
|
||||
// Attempt to unmarshal JSON stream to Settings
|
||||
@ -74,6 +82,8 @@ public class ApiRequest {
|
||||
throw new RuntimeException("Unable to unmarshall API response", e);
|
||||
} catch (JAXBException e) {
|
||||
throw new RuntimeException("Unable to unmarshall API response", e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to unmarshall API response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,39 +125,43 @@ public class ApiRequest {
|
||||
return resultString.length() > 0 ? resultString.substring(0, resultString.length() - 1) : resultString;
|
||||
}
|
||||
|
||||
public static InputStream fetchStream(String uri) {
|
||||
try {
|
||||
URL url = new URL(uri);
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
/**
|
||||
* Returns InputStream for given URI.
|
||||
* <p>
|
||||
* Also accepts special URI form:<br>
|
||||
* <tt>https://<hostname>@<ip-address>/...</tt>
|
||||
|
||||
try {
|
||||
con.setRequestMethod("GET");
|
||||
con.setConnectTimeout(5000);
|
||||
con.setReadTimeout(5000);
|
||||
ApiRequest.setConnectionSSL(con);
|
||||
* @param uri
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static InputStream fetchStream(String uri) throws IOException {
|
||||
String ipAddress = null;
|
||||
|
||||
try {
|
||||
int status = con.getResponseCode();
|
||||
|
||||
if (status != 200)
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return con.getInputStream();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Malformed API request", e);
|
||||
} catch (IOException e) {
|
||||
// Temporary fail
|
||||
return null;
|
||||
// Check for special proxy form
|
||||
Matcher uriMatcher = proxyUrlPattern.matcher(uri);
|
||||
if (uriMatcher.matches()) {
|
||||
ipAddress = uriMatcher.group(3);
|
||||
uri = uriMatcher.replaceFirst(uriMatcher.group(1) + uriMatcher.group(2) + uriMatcher.group(4));
|
||||
}
|
||||
|
||||
URL url = new URL(uri);
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
|
||||
con.setRequestMethod("GET");
|
||||
con.setConnectTimeout(5000);
|
||||
con.setReadTimeout(5000);
|
||||
ApiRequest.setConnectionSSL(con, ipAddress);
|
||||
|
||||
int status = con.getResponseCode();
|
||||
|
||||
if (status != 200)
|
||||
throw new IOException("Non-OK HTTP response");
|
||||
|
||||
return con.getInputStream();
|
||||
}
|
||||
|
||||
public static void setConnectionSSL(HttpURLConnection con) {
|
||||
public static void setConnectionSSL(HttpURLConnection con, String ipAddress) {
|
||||
if (!(con instanceof HttpsURLConnection))
|
||||
return;
|
||||
|
||||
@ -155,6 +169,14 @@ public class ApiRequest {
|
||||
URL url = con.getURL();
|
||||
|
||||
httpsCon.setSSLSocketFactory(new org.bouncycastle.jsse.util.CustomSSLSocketFactory(httpsCon.getSSLSocketFactory()) {
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
if (ipAddress == null)
|
||||
return super.createSocket();
|
||||
|
||||
return new FixedIpSocket(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Socket configureSocket(Socket s) {
|
||||
if (s instanceof SSLSocket) {
|
||||
|
@ -148,56 +148,60 @@ public class AutoUpdate extends Thread {
|
||||
}
|
||||
|
||||
private static boolean attemptUpdate(byte[] commitHash, byte[] downloadHash, String repoBaseUri) {
|
||||
LOGGER.info(String.format("Fetching update from %s", repoBaseUri));
|
||||
InputStream in = ApiRequest.fetchStream(String.format(repoBaseUri, HashCode.fromBytes(commitHash).toString()));
|
||||
if (in == null) {
|
||||
LOGGER.warn(String.format("Failed to fetch update from %s", repoBaseUri));
|
||||
return false; // failed - try another repo
|
||||
}
|
||||
|
||||
String repoUri = String.format(repoBaseUri, HashCode.fromBytes(commitHash).toString());
|
||||
LOGGER.info(String.format("Fetching update from %s", repoUri));
|
||||
Path newJar = Paths.get(NEW_JAR_FILENAME);
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (InputStream in = ApiRequest.fetchStream(repoUri)) {
|
||||
MessageDigest sha256;
|
||||
try {
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return true; // not repo's fault
|
||||
}
|
||||
|
||||
// Save input stream into new JAR
|
||||
LOGGER.debug(String.format("Saving update from %s into %s", repoBaseUri, newJar.toString()));
|
||||
LOGGER.debug(String.format("Saving update from %s into %s", repoUri, newJar.toString()));
|
||||
|
||||
OutputStream out = Files.newOutputStream(newJar);
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
do {
|
||||
int nread = in.read(buffer);
|
||||
if (nread == -1)
|
||||
break;
|
||||
try (OutputStream out = Files.newOutputStream(newJar)) {
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
do {
|
||||
int nread = in.read(buffer);
|
||||
if (nread == -1)
|
||||
break;
|
||||
|
||||
sha256.update(buffer, 0, nread);
|
||||
out.write(buffer, 0, nread);
|
||||
} while (true);
|
||||
sha256.update(buffer, 0, nread);
|
||||
out.write(buffer, 0, nread);
|
||||
} while (true);
|
||||
out.flush();
|
||||
|
||||
// Check hash
|
||||
byte[] hash = sha256.digest();
|
||||
if (!Arrays.equals(downloadHash, hash)) {
|
||||
LOGGER.warn(String.format("Downloaded JAR's hash %s doesn't match %s", HashCode.fromBytes(hash).toString(), HashCode.fromBytes(downloadHash).toString()));
|
||||
// Check hash
|
||||
byte[] hash = sha256.digest();
|
||||
if (!Arrays.equals(downloadHash, hash)) {
|
||||
LOGGER.warn(String.format("Downloaded JAR's hash %s doesn't match %s", HashCode.fromBytes(hash).toString(), HashCode.fromBytes(downloadHash).toString()));
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(newJar);
|
||||
} catch (IOException de) {
|
||||
LOGGER.warn(String.format("Failed to delete download: %s", de.getMessage()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(String.format("Failed to save update from %s into %s", repoUri, newJar.toString()));
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(newJar);
|
||||
} catch (IOException de) {
|
||||
LOGGER.warn(String.format("Failed to delete download: %s", de.getMessage()));
|
||||
LOGGER.warn(String.format("Failed to delete partial download: %s", de.getMessage()));
|
||||
}
|
||||
|
||||
return false;
|
||||
return false; // failed - try another repo
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(String.format("Failed to save update from %s into %s", repoBaseUri, newJar.toString()));
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(newJar);
|
||||
} catch (IOException de) {
|
||||
LOGGER.warn(String.format("Failed to delete partial download: %s", de.getMessage()));
|
||||
}
|
||||
|
||||
LOGGER.warn(String.format("Failed to fetch update from %s", repoUri));
|
||||
return false; // failed - try another repo
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return true; // not repo's fault
|
||||
}
|
||||
|
||||
// Call ApplyUpdate to end this process (unlocking current JAR so it can be replaced)
|
||||
|
@ -83,7 +83,8 @@ public class Settings {
|
||||
|
||||
// Auto-update sources
|
||||
private String[] autoUpdateRepos = new String[] {
|
||||
"https://github.com/catbref/qora-core/raw/%s/qora-core.jar"
|
||||
"https://github.com/catbref/qora-core/raw/%s/qora-core.jar",
|
||||
"https://raw.githubusercontent.com@151.101.16.133/catbref/qora-core/%s/qora-core.jar"
|
||||
};
|
||||
|
||||
// Constructors
|
||||
|
120
src/test/java/org/qora/test/ProxyTest.java
Normal file
120
src/test/java/org/qora/test/ProxyTest.java
Normal file
@ -0,0 +1,120 @@
|
||||
package org.qora.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SNIHostName;
|
||||
import javax.net.ssl.SNIServerName;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
public class ProxyTest {
|
||||
private static final Pattern proxyUrlPattern = Pattern.compile("(https://)([^@:/]+)@([0-9.]{7,15})(/.*)");
|
||||
|
||||
public static void main(String args[]) {
|
||||
String uri = "https://raw.githubusercontent.com@151.101.16.133/catbref/qora-core/894f0e54a6c22e68d4f4162b2ebcdf9b4e39162a/qora-core.jar";
|
||||
// String uri = "https://raw.githubusercontent.com/catbref/qora-core/894f0e54a6c22e68d4f4162b2ebcdf9b4e39162a/qora-core.jar";
|
||||
|
||||
try (InputStream in = fetchStream(uri)) {
|
||||
int byteCount = 0;
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
do {
|
||||
int nread = in.read(buffer);
|
||||
if (nread == -1)
|
||||
break;
|
||||
|
||||
byteCount += nread;
|
||||
} while (true);
|
||||
|
||||
System.out.println(String.format("Fetched %d bytes", byteCount));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream fetchStream(String uri) throws IOException {
|
||||
String ipAddress = null;
|
||||
|
||||
// Check for special proxy form
|
||||
Matcher uriMatcher = proxyUrlPattern.matcher(uri);
|
||||
if (uriMatcher.matches()) {
|
||||
ipAddress = uriMatcher.group(3);
|
||||
uri = uriMatcher.replaceFirst(uriMatcher.group(1) + uriMatcher.group(2) + uriMatcher.group(4));
|
||||
}
|
||||
|
||||
URL url = new URL(uri);
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
|
||||
con.setRequestMethod("GET");
|
||||
con.setConnectTimeout(5000);
|
||||
con.setReadTimeout(5000);
|
||||
setConnectionSSL(con, ipAddress);
|
||||
|
||||
int status = con.getResponseCode();
|
||||
|
||||
if (status != 200)
|
||||
throw new IOException("Bad response");
|
||||
|
||||
return con.getInputStream();
|
||||
}
|
||||
|
||||
public static class FixedIpSocket extends Socket {
|
||||
private final String ipAddress;
|
||||
|
||||
public FixedIpSocket(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint, int timeout) throws IOException {
|
||||
InetSocketAddress inetEndpoint = (InetSocketAddress) endpoint;
|
||||
InetSocketAddress newEndpoint = new InetSocketAddress(ipAddress, inetEndpoint.getPort());
|
||||
super.connect(newEndpoint, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setConnectionSSL(HttpURLConnection con, String ipAddress) {
|
||||
if (!(con instanceof HttpsURLConnection))
|
||||
return;
|
||||
|
||||
HttpsURLConnection httpsCon = (HttpsURLConnection) con;
|
||||
URL url = con.getURL();
|
||||
|
||||
httpsCon.setSSLSocketFactory(new org.bouncycastle.jsse.util.CustomSSLSocketFactory(httpsCon.getSSLSocketFactory()) {
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
if (ipAddress == null)
|
||||
return super.createSocket();
|
||||
|
||||
return new FixedIpSocket(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Socket configureSocket(Socket s) {
|
||||
if (s instanceof SSLSocket) {
|
||||
SSLSocket ssl = (SSLSocket) s;
|
||||
|
||||
SNIHostName sniHostName = new SNIHostName(url.getHost());
|
||||
if (null != sniHostName) {
|
||||
SSLParameters sslParameters = new SSLParameters();
|
||||
|
||||
sslParameters.setServerNames(Collections.<SNIServerName>singletonList(sniHostName));
|
||||
ssl.setSSLParameters(sslParameters);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user