Support PAC proxies.
What a horrible wart lingering from the early days of the Internet. Note to future designers: please do not express configuration using a Turing complete language. Thanks jenriq for getting the ball rolling on this. Closes #196.
This commit is contained in:
@@ -38,6 +38,12 @@
|
||||
<version>1.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -189,6 +195,7 @@
|
||||
<lib>${java.home}/jmods/java.base.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.desktop.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.logging.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.scripting.jmod</lib>
|
||||
<lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
|
||||
</libs>
|
||||
</configuration>
|
||||
|
||||
@@ -8,17 +8,27 @@ package com.threerings.getdown.launcher;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.net.Authenticator;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import ca.beq.util.win32.registry.RegistryKey;
|
||||
import ca.beq.util.win32.registry.RegistryValue;
|
||||
import ca.beq.util.win32.registry.RootKey;
|
||||
@@ -61,21 +71,35 @@ public class ProxyUtil {
|
||||
enabled = value.getStringValue().equals("1");
|
||||
}
|
||||
if (value.getName().equals("ProxyServer")) {
|
||||
String strval = value.getStringValue();
|
||||
int cidx = strval.indexOf(":");
|
||||
if (cidx != -1) {
|
||||
rport = strval.substring(cidx+1);
|
||||
strval = strval.substring(0, cidx);
|
||||
String[] hostPort = splitHostPort(value.getStringValue());
|
||||
rhost = hostPort[0];
|
||||
rport = hostPort[1];
|
||||
}
|
||||
if (value.getName().equals("AutoConfigURL")) {
|
||||
String acurl = value.getStringValue();
|
||||
Reader acjs = new InputStreamReader(new URL(acurl).openStream());
|
||||
// technically we should be returning all this info and trying each proxy
|
||||
// in succession, but that's complexity we'll leave for another day
|
||||
for (String proxy : findPACProxiesForURL(acjs, app.getRemoteURL(""))) {
|
||||
if (proxy.startsWith("PROXY ")) {
|
||||
String[] hostPort = splitHostPort(proxy.substring(6));
|
||||
rhost = hostPort[0];
|
||||
rport = hostPort[1];
|
||||
// TODO: is this valid? Does AutoConfigURL imply proxy enabled?
|
||||
enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rhost = strval;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
host = rhost;
|
||||
port = rport;
|
||||
} else {
|
||||
log.info("Detected no proxy settings in the registry.");
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.info("Failed to find proxy settings in Windows registry", "error", t);
|
||||
}
|
||||
@@ -205,6 +229,54 @@ public class ProxyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Resolver {
|
||||
public String dnsResolve (String host) {
|
||||
try {
|
||||
return InetAddress.getByName(host).toString();
|
||||
} catch (UnknownHostException uhe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] findPACProxiesForURL (Reader pac, URL url) {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
ScriptEngine engine = manager.getEngineByName("javascript");
|
||||
Bindings globals = engine.createBindings();
|
||||
globals.put("resolver", new Resolver());
|
||||
engine.setBindings(globals, ScriptContext.GLOBAL_SCOPE);
|
||||
try {
|
||||
URL utils = ProxyUtil.class.getResource("PacUtils.js");
|
||||
if (utils == null) {
|
||||
log.error("Unable to load PacUtils.js");
|
||||
return new String[0];
|
||||
}
|
||||
engine.eval(new InputStreamReader(utils.openStream()));
|
||||
Object res = engine.eval(pac);
|
||||
if (engine instanceof Invocable) {
|
||||
Object[] args = new Object[] { url.toString(), url.getHost() };
|
||||
res = ((Invocable) engine).invokeFunction("FindProxyForURL", args);
|
||||
}
|
||||
String[] proxies = res.toString().split(";");
|
||||
for (int ii = 0; ii < proxies.length; ii += 1) {
|
||||
proxies[ii] = proxies[ii].trim();
|
||||
}
|
||||
return proxies;
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to resolve PAC proxy", e);
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
private static String[] splitHostPort (String hostPort) {
|
||||
int cidx = hostPort.indexOf(":");
|
||||
if (cidx == -1) {
|
||||
return new String[] { hostPort, null};
|
||||
} else {
|
||||
return new String[] { hostPort.substring(0, cidx), hostPort.substring(cidx+1) };
|
||||
}
|
||||
}
|
||||
|
||||
protected static final String PROXY_REGISTRY =
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Extracted from Mozilla Firefox 3.0.4, \mozilla\netwerk\base\src\nsProxyAutoConfig.js
|
||||
*/
|
||||
function dnsDomainIs (host, domain) {
|
||||
return (host.length >= domain.length &&
|
||||
host.substring(host.length - domain.length) == domain);
|
||||
}
|
||||
|
||||
function dnsDomainLevels (host) {
|
||||
return host.split('.').length-1;
|
||||
}
|
||||
|
||||
function convert_addr (ipchars) {
|
||||
var bytes = ipchars.split('.');
|
||||
var result = (((bytes[0] & 0xff) << 24) |
|
||||
((bytes[1] & 0xff) << 16) |
|
||||
((bytes[2] & 0xff) << 8) |
|
||||
( bytes[3] & 0xff ));
|
||||
return result;
|
||||
}
|
||||
|
||||
function dnsResolve (host) {
|
||||
return resolver.dnsResolve(host)
|
||||
}
|
||||
|
||||
function isInNet (addrOrHost, pattern, maskstr) {
|
||||
var testRE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
||||
var test = testRE.exec(addrOrHost);
|
||||
if (test == null) {
|
||||
addrOrHost = dnsResolve(addrOrHost);
|
||||
if (addrOrHost == null) {
|
||||
return false;
|
||||
}
|
||||
} else if (test[1] > 255 || test[2] > 255 || test[3] > 255 || test[4] > 255) {
|
||||
return false; // not an IP address
|
||||
}
|
||||
var host = convert_addr(addrOrHost);
|
||||
var pat = convert_addr(pattern);
|
||||
var mask = convert_addr(maskstr);
|
||||
return ((host & mask) == (pat & mask));
|
||||
}
|
||||
|
||||
function isPlainHostName (host) {
|
||||
return (host.search('\\.') == -1);
|
||||
}
|
||||
|
||||
function isResolvable (host) {
|
||||
var ip = dnsResolve(host);
|
||||
return (ip != null);
|
||||
}
|
||||
|
||||
function localHostOrDomainIs (host, hostdom) {
|
||||
return (host == hostdom) || (hostdom.lastIndexOf(host + '.', 0) == 0);
|
||||
}
|
||||
|
||||
function shExpMatch (url, pattern) {
|
||||
pattern = pattern.replace(/\./g, '\\.');
|
||||
pattern = pattern.replace(/\*/g, '.*');
|
||||
pattern = pattern.replace(/\?/g, '.');
|
||||
var newRe = new RegExp('^'+pattern+'$');
|
||||
return newRe.test(url);
|
||||
}
|
||||
|
||||
var wdays = {
|
||||
SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6
|
||||
};
|
||||
|
||||
var months = {
|
||||
JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11
|
||||
};
|
||||
|
||||
function weekdayRange () {
|
||||
function getDay (weekday) {
|
||||
if (weekday in wdays) {
|
||||
return wdays[weekday];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
var date = new Date();
|
||||
var argc = arguments.length;
|
||||
var wday;
|
||||
if (argc < 1) {
|
||||
return false;
|
||||
}
|
||||
if (arguments[argc - 1] == 'GMT') {
|
||||
argc--;
|
||||
wday = date.getUTCDay();
|
||||
} else {
|
||||
wday = date.getDay();
|
||||
}
|
||||
var wd1 = getDay(arguments[0]);
|
||||
var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;
|
||||
return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2);
|
||||
}
|
||||
|
||||
function dateRange () {
|
||||
function getMonth (name) {
|
||||
if (name in months) {
|
||||
return months[name];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
var date = new Date();
|
||||
var argc = arguments.length;
|
||||
if (argc < 1) {
|
||||
return false;
|
||||
}
|
||||
var isGMT = (arguments[argc - 1] == 'GMT');
|
||||
if (isGMT) {
|
||||
argc--;
|
||||
}
|
||||
|
||||
// function will work even without explict handling of this case
|
||||
if (argc == 1) {
|
||||
var tmp = parseInt(arguments[0]);
|
||||
if (isNaN(tmp)) {
|
||||
return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]));
|
||||
} else if (tmp < 32) {
|
||||
return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);
|
||||
} else {
|
||||
return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp);
|
||||
}
|
||||
}
|
||||
|
||||
var year = date.getFullYear();
|
||||
var date1, date2;
|
||||
date1 = new Date(year, 0, 1, 0, 0, 0);
|
||||
date2 = new Date(year, 11, 31, 23, 59, 59);
|
||||
var adjustMonth = false;
|
||||
for (var i = 0; i < (argc >> 1); i++) {
|
||||
var tmp = parseInt(arguments[i]);
|
||||
if (isNaN(tmp)) {
|
||||
var mon = getMonth(arguments[i]);
|
||||
date1.setMonth(mon);
|
||||
} else if (tmp < 32) {
|
||||
adjustMonth = (argc <= 2);
|
||||
date1.setDate(tmp);
|
||||
} else {
|
||||
date1.setFullYear(tmp);
|
||||
}
|
||||
}
|
||||
for (var i = (argc >> 1); i < argc; i++) {
|
||||
var tmp = parseInt(arguments[i]);
|
||||
if (isNaN(tmp)) {
|
||||
var mon = getMonth(arguments[i]);
|
||||
date2.setMonth(mon);
|
||||
} else if(tmp < 32) {
|
||||
date2.setDate(tmp);
|
||||
} else {
|
||||
date2.setFullYear(tmp);
|
||||
}
|
||||
}
|
||||
if (adjustMonth) {
|
||||
date1.setMonth(date.getMonth());
|
||||
date2.setMonth(date.getMonth());
|
||||
}
|
||||
if (isGMT) {
|
||||
var tmp = date;
|
||||
tmp.setFullYear(date.getUTCFullYear());
|
||||
tmp.setMonth(date.getUTCMonth());
|
||||
tmp.setDate(date.getUTCDate());
|
||||
tmp.setHours(date.getUTCHours());
|
||||
tmp.setMinutes(date.getUTCMinutes());
|
||||
tmp.setSeconds(date.getUTCSeconds());
|
||||
date = tmp;
|
||||
}
|
||||
return ((date1 <= date) && (date <= date2));
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2018 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.launcher;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ProxyUtilTest {
|
||||
|
||||
static String[] strs (String... strs) { return strs; }
|
||||
|
||||
static String[] findPAC (String code, String url) throws Exception {
|
||||
return ProxyUtil.findPACProxiesForURL(new StringReader(code), new URL(url));
|
||||
}
|
||||
|
||||
static void testPAC (String code, String url, String... expectedProxies) throws Exception {
|
||||
assertArrayEquals(expectedProxies, findPAC(code, url));
|
||||
}
|
||||
|
||||
@Test public void testPACProxy () throws Exception {
|
||||
String EXAMPLE0 =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (shExpMatch(host, '*.example.com')) { return 'DIRECT'; }\n" +
|
||||
" if (isInNet(host, '10.0.0.0', '255.255.248.0')) {\n" +
|
||||
" return 'PROXY fastproxy.example.com:8080';\n" +
|
||||
" }\n" +
|
||||
" return 'PROXY proxy.example.com:8080; DIRECT';\n" +
|
||||
"}\n";
|
||||
|
||||
testPAC(EXAMPLE0, "http://test.example.com/", "DIRECT");
|
||||
testPAC(EXAMPLE0, "http://10.0.1.1/", "PROXY fastproxy.example.com:8080");
|
||||
testPAC(EXAMPLE0, "http://chicken.gov/", "PROXY proxy.example.com:8080", "DIRECT");
|
||||
|
||||
String EXAMPLE1 =
|
||||
"function FindProxyForURL(url, host) {" +
|
||||
" if (isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) {" +
|
||||
" return 'DIRECT';" +
|
||||
" } else {" +
|
||||
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';" +
|
||||
" }" +
|
||||
"}";
|
||||
testPAC(EXAMPLE1, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://foo.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://localhost/", "DIRECT");
|
||||
|
||||
String EXAMPLE2 =
|
||||
" function FindProxyForURL(url, host) {\n" +
|
||||
" if ((isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) &&\n" +
|
||||
" !localHostOrDomainIs(host, 'www.mozilla.org') &&\n" +
|
||||
" !localHostOrDomainIs(host, 'merchant.mozilla.org')) {\n" +
|
||||
" return 'DIRECT';\n" +
|
||||
" } else {\n" +
|
||||
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';\n" +
|
||||
" }\n" +
|
||||
" }";
|
||||
testPAC(EXAMPLE2, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://www.mozilla.org/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://www/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://foo.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://localhost/", "DIRECT");
|
||||
|
||||
String EXAMPLE3A =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (isResolvable(host)) return 'DIRECT';\n" +
|
||||
" else return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||
"}";
|
||||
testPAC(EXAMPLE3A, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE3A, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||
|
||||
String EXAMPLE3B =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (isPlainHostName(host) ||\n" +
|
||||
" dnsDomainIs(host, '.mydomain.com') ||\n" +
|
||||
" isResolvable(host)) {\n" +
|
||||
" return 'DIRECT';\n" +
|
||||
" } else {\n" +
|
||||
" return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
testPAC(EXAMPLE3B, "http://plain/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://foo.mydomain.com/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||
|
||||
// example 4
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isInNet(host, '198.95.0.0', '255.255.0.0')) return 'DIRECT';
|
||||
// else return 'PROXY proxy.mydomain.com:8080';
|
||||
// }
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isPlainHostName(host) ||
|
||||
// dnsDomainIs(host, '.mydomain.com') ||
|
||||
// isInNet(host, '198.95.0.0', '255.255.0.0')) {
|
||||
// return 'DIRECT';
|
||||
// } else {
|
||||
// return 'PROXY proxy.mydomain.com:8080';
|
||||
// }
|
||||
// }
|
||||
|
||||
// example 5
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isPlainHostName(host) || dnsDomainIs(host, '.mydomain.com')) return 'DIRECT';
|
||||
// else if (shExpMatch(host, '*.com')) return 'PROXY proxy1.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// else if (shExpMatch(host, '*.edu')) return 'PROXY proxy2.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// else return 'PROXY proxy3.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// }
|
||||
|
||||
// example 6
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (url.substring(0, 5) == 'http:') {
|
||||
// return 'PROXY http-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 4) == 'ftp:') {
|
||||
// return 'PROXY ftp-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 7) == 'gopher:') {
|
||||
// return 'PROXY gopher-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 6) == 'https:' ||
|
||||
// url.substring(0, 6) == 'snews:') {
|
||||
// return 'PROXY security-proxy.mydomain.com:8080';
|
||||
// } else {
|
||||
// return 'DIRECT';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user