185 lines
6.5 KiB
Java
185 lines
6.5 KiB
Java
package com.moparisthebest.dns.net;
|
|
|
|
import eu.geekplace.javapinning.java7.Java7Pinning;
|
|
|
|
import javax.net.ssl.SSLContext;
|
|
import javax.net.ssl.SSLSocketFactory;
|
|
import java.net.*;
|
|
import java.security.KeyManagementException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.*;
|
|
|
|
public class ParsedUrl {
|
|
|
|
private final SocketAddress addr;
|
|
private final URI uri; // minus #
|
|
private final Map<String, String> props; // after #, split by ;
|
|
private final Proxy proxy;
|
|
private final SSLSocketFactory sslSocketFactory;
|
|
|
|
public ParsedUrl(final SocketAddress addr, final URI uri, final Map<String, String> props, final Proxy proxy, final SSLSocketFactory sslSocketFactory) {
|
|
this.addr = addr;
|
|
this.uri = uri;
|
|
this.props = props;
|
|
this.proxy = proxy;
|
|
this.sslSocketFactory = sslSocketFactory;
|
|
}
|
|
|
|
public static ParsedUrl of(final String urlStr) {
|
|
return of(urlStr, null);
|
|
}
|
|
|
|
public static ParsedUrl of(final String urlStr, final Map<String, String> upperLevelProps) {
|
|
try {
|
|
final URI url = new URI(urlStr.trim());
|
|
final SocketAddress addr = new InetSocketAddress(url.getHost(), getPort(url));
|
|
final Map<String, String> props = upperLevelProps == null ? new HashMap<>() : new HashMap<>(upperLevelProps);
|
|
if(url.getFragment() != null)
|
|
Arrays.stream(url.getFragment().split(";"))
|
|
.map(p -> p.split("=", 2)).forEach(p -> props.put(p[0], p.length > 1 ? p[1] : null));
|
|
Proxy proxy = null;
|
|
final String proxyStr = props.get("proxy");
|
|
if (proxyStr != null) {
|
|
final URI proxyUrl = new URI(proxyStr);
|
|
if(proxyUrl.getPort() == -1)
|
|
throw new RuntimeException("proxy url must define port");
|
|
Proxy.Type type = null;
|
|
if (proxyUrl.getScheme().toLowerCase().startsWith("socks"))
|
|
type = Proxy.Type.SOCKS;
|
|
else if (proxyUrl.getScheme().toLowerCase().startsWith("http"))
|
|
type = Proxy.Type.HTTP;
|
|
else
|
|
throw new RuntimeException("proxy url must be socks or http");
|
|
proxy = new Proxy(type, new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort()));
|
|
}
|
|
SSLSocketFactory sslSocketFactory = null;
|
|
final String pubKeyPinsSha256 = props.get("pubKeyPinsSha256");
|
|
if (pubKeyPinsSha256 != null) {
|
|
final String[] pins = pubKeyPinsSha256.split(",");
|
|
// todo: ugh java-pinning only supports hex not base64 *and* hashes the cert one time per pin, fix this
|
|
for (int x = 0; x < pins.length; ++x) {
|
|
pins[x] = "SHA256:" + bytesToHex(Base64.getDecoder().decode(pins[x]));
|
|
}
|
|
final SSLContext sslContext;
|
|
try {
|
|
sslContext = Java7Pinning.forPins(pins);
|
|
} catch (KeyManagementException | NoSuchAlgorithmException e) {
|
|
throw new RuntimeException("invalid pins", e);
|
|
}
|
|
sslSocketFactory = sslContext.getSocketFactory();
|
|
}
|
|
if(sslSocketFactory == null && url.getScheme().equals("tls"))
|
|
sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
|
return new ParsedUrl(addr, url, props, proxy, sslSocketFactory);
|
|
} catch (Exception e) {
|
|
if (e instanceof RuntimeException)
|
|
throw (RuntimeException) e;
|
|
throw new RuntimeException("invalid URL format: '" + urlStr + "'", e);
|
|
}
|
|
}
|
|
|
|
private static int getPort(final URI uri) {
|
|
final int port = uri.getPort();
|
|
if(port != -1)
|
|
return port;
|
|
switch (uri.getScheme()) {
|
|
case "tcp":
|
|
case "udp":
|
|
return 53;
|
|
case "tls":
|
|
return 853;
|
|
case "http":
|
|
return 80;
|
|
case "https":
|
|
return 443;
|
|
}
|
|
return port;
|
|
}
|
|
|
|
public String getProtocol() {
|
|
return uri.getScheme().toLowerCase();
|
|
}
|
|
|
|
public URL getUrlWithoutFragment() {
|
|
return toUrlRemoveRef(uri);
|
|
}
|
|
|
|
public SocketAddress getAddr() {
|
|
return addr;
|
|
}
|
|
|
|
public URI getUri() {
|
|
return uri;
|
|
}
|
|
|
|
public Map<String, String> getProps() {
|
|
return props;
|
|
}
|
|
|
|
public Proxy getProxy() {
|
|
return proxy;
|
|
}
|
|
|
|
public SSLSocketFactory getSslSocketFactory() {
|
|
return sslSocketFactory;
|
|
}
|
|
|
|
private static URL toUrlRemoveRef(final URI uri) {
|
|
// mostly copied from java.net.URLStreamHandler.toExternalForm
|
|
try {
|
|
final URL u = uri.toURL();
|
|
|
|
// pre-compute length of StringBuffer
|
|
int len = u.getProtocol().length() + 1;
|
|
if (u.getAuthority() != null && u.getAuthority().length() > 0)
|
|
len += 2 + u.getAuthority().length();
|
|
if (u.getPath() != null) {
|
|
len += u.getPath().length();
|
|
}
|
|
if (u.getQuery() != null) {
|
|
len += 1 + u.getQuery().length();
|
|
}
|
|
/*
|
|
if (u.getRef() != null)
|
|
len += 1 + u.getRef().length();
|
|
*/
|
|
|
|
final StringBuilder result = new StringBuilder(len);
|
|
result.append(u.getProtocol());
|
|
result.append(":");
|
|
if (u.getAuthority() != null && u.getAuthority().length() > 0) {
|
|
result.append("//");
|
|
result.append(u.getAuthority());
|
|
}
|
|
if (u.getPath() != null) {
|
|
result.append(u.getPath());
|
|
}
|
|
if (u.getQuery() != null) {
|
|
result.append('?');
|
|
result.append(u.getQuery());
|
|
}
|
|
/*
|
|
if (u.getRef() != null) {
|
|
result.append("#");
|
|
result.append(u.getRef());
|
|
}
|
|
*/
|
|
return new URL(result.toString());
|
|
} catch (MalformedURLException e) {
|
|
throw new RuntimeException("invalid URL format: " + uri.toString());
|
|
}
|
|
}
|
|
|
|
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
|
|
|
private static String bytesToHex(byte[] bytes) {
|
|
char[] hexChars = new char[bytes.length * 2];
|
|
for (int j = 0; j < bytes.length; j++) {
|
|
int v = bytes[j] & 0xFF;
|
|
hexChars[j * 2] = hexArray[v >>> 4];
|
|
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
|
}
|
|
return new String(hexChars);
|
|
}
|
|
}
|