diff options
Diffstat (limited to 'src/main/java/com/btr/proxy/selector')
23 files changed, 3010 insertions, 0 deletions
diff --git a/src/main/java/com/btr/proxy/selector/direct/NoProxySelector.java b/src/main/java/com/btr/proxy/selector/direct/NoProxySelector.java new file mode 100644 index 0000000..cf9e150 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/direct/NoProxySelector.java @@ -0,0 +1,70 @@ +package com.btr.proxy.selector.direct; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +import com.btr.proxy.util.ProxyUtil; + +/***************************************************************************** + * This proxy selector will always return a "DIRECT" proxy. + * Implemented as singleton. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class NoProxySelector extends ProxySelector { + + private static NoProxySelector instance; + + /************************************************************************* + * Constructor + ************************************************************************/ + + private NoProxySelector() { + super(); + } + + /************************************************************************* + * Gets the one and only instance of this selector. + * @return a DirectSelector. + ************************************************************************/ + + public static synchronized NoProxySelector getInstance() { + if (NoProxySelector.instance == null) { + NoProxySelector.instance = new NoProxySelector(); + } + return instance; + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // Not used. + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + return ProxyUtil.noProxyList(); + } + +} + + + + +/* + * $Log: $ + */
\ No newline at end of file diff --git a/src/main/java/com/btr/proxy/selector/fixed/FixedProxySelector.java b/src/main/java/com/btr/proxy/selector/fixed/FixedProxySelector.java new file mode 100644 index 0000000..498ac52 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/fixed/FixedProxySelector.java @@ -0,0 +1,69 @@ +package com.btr.proxy.selector.fixed; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/***************************************************************************** + * This proxy selector is configured with a fixed proxy. This proxy will be + * returned for all URIs passed to the select method. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class FixedProxySelector extends ProxySelector { + + private final List<Proxy> proxyList; + + + /************************************************************************* + * Constructor + * @param proxy the proxy to use. + ************************************************************************/ + + public FixedProxySelector(Proxy proxy) { + super(); + + List<Proxy> list = new ArrayList<Proxy>(1); + list.add(proxy); + this.proxyList = Collections.unmodifiableList(list); + } + + /************************************************************************* + * Constructor + * @param proxyHost the host name or IP address of the proxy to use. + * @param proxyPort the port of the proxy. + ************************************************************************/ + + public FixedProxySelector(String proxyHost, int proxyPort) { + this(new Proxy(Proxy.Type.HTTP, + InetSocketAddress.createUnresolved(proxyHost, proxyPort))); + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // Not used + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + return this.proxyList; + } + +} diff --git a/src/main/java/com/btr/proxy/selector/fixed/FixedSocksSelector.java b/src/main/java/com/btr/proxy/selector/fixed/FixedSocksSelector.java new file mode 100644 index 0000000..d1f7ccc --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/fixed/FixedSocksSelector.java @@ -0,0 +1,27 @@ +package com.btr.proxy.selector.fixed; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +/***************************************************************************** + * This proxy selector is configured with a fixed proxy. This proxy will be + * returned for all URIs passed to the select method. This implementation + * can be used for SOCKS 4 and 5 proxy support. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class FixedSocksSelector extends FixedProxySelector { + + /************************************************************************* + * Constructor + * @param proxyHost the host name or IP address of the proxy to use. + * @param proxyPort the port of the proxy. + ************************************************************************/ + + public FixedSocksSelector(String proxyHost, int proxyPort) { + super(new Proxy(Proxy.Type.SOCKS, + InetSocketAddress.createUnresolved(proxyHost, proxyPort))); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/misc/BufferedProxySelector.java b/src/main/java/com/btr/proxy/selector/misc/BufferedProxySelector.java new file mode 100644 index 0000000..f006314 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/misc/BufferedProxySelector.java @@ -0,0 +1,126 @@ +package com.btr.proxy.selector.misc; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/***************************************************************************** + * Implements a cache that can be used to warp it around an existing ProxySelector. + * You can specify a maximum cache size and a "time to live" for positive resolves. + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class BufferedProxySelector extends ProxySelector { + + private ProxySelector delegate; + + private ConcurrentHashMap<String, CacheEntry> cache; + private int maxSize; + private long ttl; + + private static class CacheEntry { + List<Proxy> result; + long expireAt; + + public CacheEntry(List<Proxy> r, long expireAt) { + super(); + this.result = new ArrayList<Proxy>(r.size()); + this.result.addAll(r); + this.result = Collections.unmodifiableList(this.result); + this.expireAt = expireAt; + } + + public boolean isExpired() { + return System.nanoTime() >= this.expireAt; + } + } + + /************************************************************************* + * Constructor + * @param maxSize the max size for the cache. + * @param ttl the "time to live" for cache entries as amount in milliseconds. + * @param delegate the delegate to use. + ************************************************************************/ + + public BufferedProxySelector(int maxSize, long ttl, ProxySelector delegate) { + super(); + this.cache = new ConcurrentHashMap<String, CacheEntry>(); + this.maxSize = maxSize; + this.delegate = delegate; + this.ttl = ttl; + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + this.delegate.connectFailed(uri, sa, ioe); + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + //String cacheKey = uri.getHost(); // Caching per host may produce wrong results + String cacheKey = uri.toString(); + + CacheEntry entry = this.cache.get(cacheKey); + if (entry == null || entry.isExpired()) { + List<Proxy> result = this.delegate.select(uri); + entry = new CacheEntry(result, System.nanoTime()+this.ttl*1000*1000); + + synchronized (this.cache) { + if (this.cache.size() >= this.maxSize) { + purgeCache(); + } + this.cache.put(cacheKey, entry); + } + } + + return entry.result; + } + + /************************************************************************* + * Purge cache to get some free space for a new entry. + ************************************************************************/ + + private void purgeCache() { + + // Remove all expired entries and find the oldest. + boolean removedOne = false; + Entry<String, CacheEntry> oldest = null; + + Set<Entry<String, CacheEntry>> entries = this.cache.entrySet(); + for (Iterator<Entry<String, CacheEntry>> it = entries.iterator(); it.hasNext();) { + Entry<String, CacheEntry> entry = it.next(); + if (entry.getValue().isExpired()) { + it.remove(); + removedOne = true; + } else + if (oldest == null || entry.getValue().expireAt < oldest.getValue().expireAt) { + oldest = entry; + } + } + + // Remove oldest if no expired entries were found. + if (!removedOne && oldest != null) { + this.cache.remove(oldest.getKey()); + } + } + +} diff --git a/src/main/java/com/btr/proxy/selector/misc/ProtocolDispatchSelector.java b/src/main/java/com/btr/proxy/selector/misc/ProtocolDispatchSelector.java new file mode 100644 index 0000000..5d7f563 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/misc/ProtocolDispatchSelector.java @@ -0,0 +1,115 @@ +package com.btr.proxy.selector.misc; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.btr.proxy.selector.direct.NoProxySelector; + +/***************************************************************************** + * This is a facade for a list of ProxySelecor objects. You can register + * different ProxySelectors per Protocol. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProtocolDispatchSelector extends ProxySelector { + + private Map<String, ProxySelector> selectors; + private ProxySelector fallbackSelector; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public ProtocolDispatchSelector() { + super(); + this.selectors = new ConcurrentHashMap<String, ProxySelector>(); + this.fallbackSelector = NoProxySelector.getInstance(); + } + + /************************************************************************* + * Sets a selector responsible for the given protocol. + * @param protocol the name of the protocol. + * @param selector the selector to use. + ************************************************************************/ + + public void setSelector(String protocol, ProxySelector selector) { + if (protocol == null) { + throw new NullPointerException("Protocol must not be null."); + } + if (selector == null) { + throw new NullPointerException("Selector must not be null."); + } + this.selectors.put(protocol, selector); + } + + /************************************************************************* + * Removes the selector installed for the given protocol. + * @param protocol the protocol name. + * @return the old selector that is removed. + ************************************************************************/ + + public ProxySelector removeSelector(String protocol) { + return this.selectors.remove(protocol); + } + + /************************************************************************* + * Gets the selector installed for the given protocol. + * @param protocol the protocol name. + * @return the selector for that protocol, null if none is currently set. + ************************************************************************/ + + public ProxySelector getSelector(String protocol) { + return this.selectors.get(protocol); + } + + /************************************************************************* + * Sets the fallback selector that is always called when no matching + * protocol selector was found.. + * @param selector the selector to use. + ************************************************************************/ + + public void setFallbackSelector(ProxySelector selector) { + if (selector == null) { + throw new NullPointerException("Selector must not be null."); + } + this.fallbackSelector = selector; + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + ProxySelector selector = this.fallbackSelector; + String protocol = uri.getScheme(); + if (protocol != null && this.selectors.get(protocol) != null) { + selector = this.selectors.get(protocol); + } + selector.connectFailed(uri, sa, ioe); + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + ProxySelector selector = this.fallbackSelector; + String protocol = uri.getScheme(); + if (protocol != null && this.selectors.get(protocol) != null) { + selector = this.selectors.get(protocol); + } + return selector.select(uri); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/misc/ProxyListFallbackSelector.java b/src/main/java/com/btr/proxy/selector/misc/ProxyListFallbackSelector.java new file mode 100644 index 0000000..41859ec --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/misc/ProxyListFallbackSelector.java @@ -0,0 +1,150 @@ +package com.btr.proxy.selector.misc; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/***************************************************************************** + * Implements a fallback selector to warp it around an existing ProxySelector. + * This will remove proxies from a list of proxies and implement an automatic + * retry mechanism. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2011 + ****************************************************************************/ + +public class ProxyListFallbackSelector extends ProxySelector { + + // Retry a unresponsive proxy after 10 minutes per default. + private static final int DEFAULT_RETRY_DELAY = 1000*60*10; + + private ProxySelector delegate; + private ConcurrentHashMap<SocketAddress, Long> failedDelayCache; + private long retryAfterMs; + + /************************************************************************* + * Constructor + * @param delegate the delegate to use. + ************************************************************************/ + + public ProxyListFallbackSelector(ProxySelector delegate) { + this(DEFAULT_RETRY_DELAY, delegate); + } + + /************************************************************************* + * Constructor + * @param retryAfterMs the "retry delay" as amount of milliseconds. + * @param delegate the delegate to use. + ************************************************************************/ + + public ProxyListFallbackSelector(long retryAfterMs, ProxySelector delegate) { + super(); + this.failedDelayCache = new ConcurrentHashMap<SocketAddress, Long>(); + this.delegate = delegate; + this.retryAfterMs = retryAfterMs; + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + this.failedDelayCache.put(sa, System.currentTimeMillis()); + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + cleanupCache(); + List<Proxy> proxyList = this.delegate.select(uri); + List<Proxy> result = filterUnresponsiveProxiesFromList(proxyList); + return result; + } + + /************************************************************************* + * Cleanup the entries from the cache that are no longer unresponsive. + ************************************************************************/ + + private void cleanupCache() { + Iterator<Entry<SocketAddress, Long>> it + = this.failedDelayCache.entrySet().iterator(); + while (it.hasNext()) { + Entry<SocketAddress, Long> e = it.next(); + Long lastFailTime = e.getValue(); + if (retryDelayHasPassedBy(lastFailTime)) { + it.remove(); + } + } + } + + /************************************************************************* + * @param proxyList + * @return + ************************************************************************/ + + private List<Proxy> filterUnresponsiveProxiesFromList(List<Proxy> proxyList) { + if (this.failedDelayCache.isEmpty()) { + return proxyList; + } + List<Proxy> result = new ArrayList<Proxy>(proxyList.size()); + for (Proxy proxy : proxyList) { + if (isDirect(proxy) || isNotUnresponsive(proxy)) { + result.add(proxy); + } + } + return result; + } + + /************************************************************************* + * @param proxy + * @return + ************************************************************************/ + + private boolean isDirect(Proxy proxy) { + return Proxy.NO_PROXY.equals(proxy); + } + + /************************************************************************* + * @param proxy + * @return + ************************************************************************/ + + private boolean isNotUnresponsive(Proxy proxy) { + Long lastFailTime = this.failedDelayCache.get(proxy.address()); + return retryDelayHasPassedBy(lastFailTime); + } + + /************************************************************************* + * @param lastFailTime + * @return + ************************************************************************/ + + private boolean retryDelayHasPassedBy(Long lastFailTime) { + return lastFailTime == null + || lastFailTime + this.retryAfterMs < System.currentTimeMillis(); + } + + /************************************************************************* + * Only used for unit testing not part of the public API. + * @param retryAfterMs The retryAfterMs to set. + ************************************************************************/ + + final void setRetryAfterMs(long retryAfterMs) { + this.retryAfterMs = retryAfterMs; + } + + + +} diff --git a/src/main/java/com/btr/proxy/selector/pac/JavaxPacScriptParser.java b/src/main/java/com/btr/proxy/selector/pac/JavaxPacScriptParser.java new file mode 100644 index 0000000..5d293fa --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/JavaxPacScriptParser.java @@ -0,0 +1,147 @@ +package com.btr.proxy.selector.pac;
+
+import java.lang.reflect.Method;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import com.btr.proxy.util.Logger;
+import com.btr.proxy.util.Logger.LogLevel;
+
+/*****************************************************************************
+ * PAC parser using the Rhino JavaScript engine bundled with Java 1.6<br/>
+ * If you need PAC support with Java 1.5 then you should have a look at
+ * RhinoPacScriptParser.
+ *
+ * More information about PAC can be found there:<br/>
+ * <a href="http://en.wikipedia.org/wiki/Proxy_auto-config">Proxy_auto-config</a><br/>
+ * <a href="http://homepages.tesco.net/~J.deBoynePollard/FGA/web-browser-auto-proxy-configuration.html">web-browser-auto-proxy-configuration</a>
+ * </p>
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ****************************************************************************/
+public class JavaxPacScriptParser implements PacScriptParser {
+ static final String SCRIPT_METHODS_OBJECT = "__pacutil";
+
+ private final PacScriptSource source;
+ private final ScriptEngine engine;
+
+ /*************************************************************************
+ * Constructor
+ *
+ * @param source
+ * the source for the PAC script.
+ * @throws ProxyEvaluationException
+ * on error.
+ ************************************************************************/
+ public JavaxPacScriptParser(PacScriptSource source)
+ throws ProxyEvaluationException {
+ this.source = source;
+ this.engine = setupEngine();
+ }
+
+ /*************************************************************************
+ * Initializes the JavaScript engine and adds aliases for the functions
+ * defined in ScriptMethods.
+ *
+ * @throws ProxyEvaluationException
+ * on error.
+ ************************************************************************/
+ private ScriptEngine setupEngine() throws ProxyEvaluationException {
+ ScriptEngineManager mng = new ScriptEngineManager();
+ ScriptEngine engine = mng.getEngineByMimeType("text/javascript");
+ engine.put(SCRIPT_METHODS_OBJECT, new PacScriptMethods());
+
+ Class<?> scriptMethodsClazz = ScriptMethods.class;
+ Method[] scriptMethods = scriptMethodsClazz.getMethods();
+
+ for (Method method : scriptMethods) {
+ String name = method.getName();
+ int args = method.getParameterTypes().length;
+ StringBuilder toEval = new StringBuilder(name).append(" = function(");
+ for (int i = 0; i < args; i++) {
+ if (i > 0) {
+ toEval.append(",");
+ }
+ toEval.append("arg").append(i);
+ }
+ toEval.append(") {return ");
+
+ String functionCall = buildFunctionCallCode(name, args);
+
+ // If return type is java.lang.String convert it to a JS string
+ if (String.class.isAssignableFrom(method.getReturnType())) {
+ functionCall = "String("+functionCall+")";
+ }
+ toEval.append(functionCall).append("; }");
+ try {
+ engine.eval(toEval.toString());
+ } catch (ScriptException e) {
+ Logger.log(getClass(), LogLevel.ERROR,
+ "JS evaluation error when creating alias for " + name + ".", e);
+ throw new ProxyEvaluationException(
+ "Error setting up script engine", e);
+ }
+ }
+
+ return engine;
+ }
+
+ /*************************************************************************
+ * Builds a JavaScript code snippet to call a function that we bind.
+ * @param functionName of the bound function
+ * @param args of the bound function
+ * @return the JS code to invoke the method.
+ ************************************************************************/
+
+ private String buildFunctionCallCode(String functionName, int args) {
+ StringBuilder functionCall = new StringBuilder();
+ functionCall.append(SCRIPT_METHODS_OBJECT)
+ .append(".").append(functionName).append("(");
+ for (int i = 0; i < args; i++) {
+ if (i > 0) {
+ functionCall.append(",");
+ }
+ functionCall.append("arg").append(i);
+ }
+ functionCall.append(")");
+ return functionCall.toString();
+ }
+
+ /***************************************************************************
+ * Gets the source of the PAC script used by this parser.
+ *
+ * @return a PacScriptSource.
+ **************************************************************************/
+ public PacScriptSource getScriptSource() {
+ return this.source;
+ }
+
+ /*************************************************************************
+ * Evaluates the given URL and host against the PAC script.
+ *
+ * @param url
+ * the URL to evaluate.
+ * @param host
+ * the host name part of the URL.
+ * @return the script result.
+ * @throws ProxyEvaluationException
+ * on execution error.
+ ************************************************************************/
+ public String evaluate(String url, String host)
+ throws ProxyEvaluationException {
+ try {
+ StringBuilder script = new StringBuilder(
+ this.source.getScriptContent());
+ String evalMethod = " ;FindProxyForURL (\"" + url + "\",\"" + host + "\")";
+ script.append(evalMethod);
+ Object result = this.engine.eval(script.toString());
+ return (String) result;
+ } catch (Exception e) {
+ Logger.log(getClass(), LogLevel.ERROR, "JS evaluation error.", e);
+ throw new ProxyEvaluationException(
+ "Error while executing PAC script: " + e.getMessage(), e);
+ }
+
+ }
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/PacProxySelector.java b/src/main/java/com/btr/proxy/selector/pac/PacProxySelector.java new file mode 100644 index 0000000..8e81b03 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/PacProxySelector.java @@ -0,0 +1,184 @@ +package com.btr.proxy.selector.pac;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import com.btr.proxy.util.Logger;
+import com.btr.proxy.util.Logger.LogLevel;
+import com.btr.proxy.util.ProxyUtil;
+
+
+/*****************************************************************************
+ * ProxySelector that will use a PAC script to find an proxy for a given URI.
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ****************************************************************************/
+public class PacProxySelector extends ProxySelector {
+
+ private final boolean JAVAX_PARSER = ScriptAvailability.isJavaxScriptingAvailable();
+
+ // private static final String PAC_PROXY = "PROXY";
+ private static final String PAC_SOCKS = "SOCKS";
+ private static final String PAC_DIRECT = "DIRECT";
+
+ private PacScriptParser pacScriptParser;
+
+ private static volatile boolean enabled = true;
+
+ /*************************************************************************
+ * Constructor
+ * @param pacSource the source for the PAC file. + ************************************************************************/
+
+ public PacProxySelector(PacScriptSource pacSource) {
+ super(); + selectEngine(pacSource);
+ }
+
+ /*************************************************************************
+ * Can be used to enable / disable the proxy selector.
+ * If disabled it will return DIRECT for all urls.
+ * @param enable the new status to set.
+ ************************************************************************/
+
+ public static void setEnabled(boolean enable) {
+ enabled = enable;
+ }
+
+ /*************************************************************************
+ * Checks if the selector is currently enabled.
+ * @return true if enabled else false.
+ ************************************************************************/
+
+ public static boolean isEnabled() {
+ return enabled;
+ }
+
+ /*************************************************************************
+ * Selects one of the available PAC parser engines.
+ * @param pacSource to use as input.
+ ************************************************************************/
+
+ private void selectEngine(PacScriptSource pacSource) {
+ try {
+ if (this.JAVAX_PARSER) {
+ Logger.log(getClass(), LogLevel.INFO,
+ "Using javax.script JavaScript engine.");
+ this.pacScriptParser = new JavaxPacScriptParser(pacSource);
+ } else {
+ Logger.log(getClass(), LogLevel.INFO,
+ "Using Rhino JavaScript engine.");
+ this.pacScriptParser = new RhinoPacScriptParser(pacSource);
+ }
+ } catch (Exception e) {
+ Logger.log(getClass(), LogLevel.ERROR, "PAC parser error.", e);
+ }
+ }
+
+ /*************************************************************************
+ * connectFailed
+ * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ // Not used.
+ }
+
+ /*************************************************************************
+ * select
+ * @see java.net.ProxySelector#select(java.net.URI)
+ ************************************************************************/
+ @Override
+ public List<Proxy> select(URI uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI must not be null.");
+ }
+
+ // Fix for Java 1.6.16+ where we get a infinite loop because
+ // URL.connect(Proxy.NO_PROXY) does not work as expected.
+ if (!enabled) {
+ return ProxyUtil.noProxyList();
+ }
+
+ return findProxy(uri);
+ }
+
+ /*************************************************************************
+ * Evaluation of the given URL with the PAC-file.
+ *
+ * Two cases can be handled here: + * DIRECT Fetch the object directly from the content HTTP server denoted by its URL + * PROXY name:port Fetch the object via the proxy HTTP server at the given location (name and port) + *
+ * @param uri <code>URI</code> to be evaluated. + * @return <code>Proxy</code>-object list as result of the evaluation.
+ ************************************************************************/
+
+ private List<Proxy> findProxy(URI uri) {
+ try {
+ List<Proxy> proxies = new ArrayList<Proxy>();
+ String parseResult = this.pacScriptParser.evaluate(uri.toString(),
+ uri.getHost());
+ String[] proxyDefinitions = parseResult.split("[;]");
+ for (String proxyDef : proxyDefinitions) {
+ if (proxyDef.trim().length() > 0) {
+ Proxy proxy = buildProxyFromPacResult(proxyDef);
+ if (proxy.type() == Proxy.Type.SOCKS) {
+ if (!proxies.isEmpty() && proxies.get(0).type() == Proxy.Type.DIRECT) {
+ proxies.add(1, proxy);
+ } else
+ proxies.add(0, proxy);
+ } else {
+ proxies.add(proxy);
+ }
+ }
+ }
+ return proxies;
+ } catch (ProxyEvaluationException e) {
+ Logger.log(getClass(), LogLevel.ERROR, "PAC resolving error.", e);
+ return ProxyUtil.noProxyList();
+ }
+ }
+
+ /*************************************************************************
+ * The proxy evaluator will return a proxy string. This method will + * take this string and build a matching <code>Proxy</code> for it. + * @param pacResult the result from the PAC parser. + * @return a Proxy
+ ************************************************************************/
+
+ private Proxy buildProxyFromPacResult(String pacResult) {
+ if (pacResult == null || pacResult.trim().length() < 6) {
+ return Proxy.NO_PROXY;
+ }
+ String proxyDef = pacResult.trim();
+ if (proxyDef.toUpperCase().startsWith(PAC_DIRECT)) {
+ return Proxy.NO_PROXY;
+ }
+
+ // Check proxy type.
+ Proxy.Type type = Proxy.Type.HTTP;
+ if (proxyDef.toUpperCase().startsWith(PAC_SOCKS)) {
+ type = Proxy.Type.SOCKS;
+ }
+
+ String host = proxyDef.substring(6);
+ Integer port = ProxyUtil.DEFAULT_PROXY_PORT;
+
+ // Split port from host
+ int indexOfPort = host.indexOf(':');
+ if (indexOfPort != -1) {
+ port = Integer.parseInt(host.substring(indexOfPort+1).trim());
+ host = host.substring(0, indexOfPort).trim();
+ }
+
+ SocketAddress adr = InetSocketAddress.createUnresolved(host, port);
+ return new Proxy(type, adr);
+ }
+
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/PacScriptMethods.java b/src/main/java/com/btr/proxy/selector/pac/PacScriptMethods.java new file mode 100644 index 0000000..101c267 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/PacScriptMethods.java @@ -0,0 +1,656 @@ +package com.btr.proxy.selector.pac;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+import com.btr.proxy.util.Logger;
+import com.btr.proxy.util.Logger.LogLevel;
+
+/***************************************************************************
+ * Implementation of PAC JavaScript functions.
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ***************************************************************************
+ */
+public class PacScriptMethods implements ScriptMethods {
+
+ public static final String OVERRIDE_LOCAL_IP = "com.btr.proxy.pac.overrideLocalIP";
+
+ private final static String GMT = "GMT";
+
+ private final static List<String> DAYS = Collections.unmodifiableList(
+ Arrays.asList("SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"));
+
+ private final static List<String> MONTH = Collections.unmodifiableList(
+ Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"));
+
+ private Calendar currentTime;
+
+ /*************************************************************************
+ * Constructor
+ ************************************************************************/
+
+ public PacScriptMethods() {
+ super();
+ }
+
+ /*************************************************************************
+ * isPlainHostName
+ * @see com.btr.proxy.selector.pac.ScriptMethods#isPlainHostName(java.lang.String)
+ ************************************************************************/
+
+ public boolean isPlainHostName(String host) {
+ return host.indexOf(".") < 0;
+ }
+
+ /*************************************************************************
+ * Tests if an URL is in a given domain.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @param domain
+ * is the domain name to test the host name against.
+ * @return true if the domain of host name matches.
+ ************************************************************************/
+
+ public boolean dnsDomainIs(String host, String domain) {
+ return host.endsWith(domain);
+ }
+
+ /*************************************************************************
+ * Is true if the host name matches exactly the specified host name, or if
+ * there is no domain name part in the host name, but the unqualified host
+ * name matches.
+ *
+ * @param host
+ * the host name from the URL.
+ * @param domain
+ * fully qualified host name with domain to match against.
+ * @return true if matches else false.
+ ************************************************************************/
+
+ public boolean localHostOrDomainIs(String host, String domain) {
+ return domain.startsWith(host);
+ }
+
+ /*************************************************************************
+ * Tries to resolve the host name. Returns true if succeeds.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @return true if resolvable else false.
+ ************************************************************************/
+
+ public boolean isResolvable(String host) {
+ try {
+ InetAddress.getByName(host).getHostAddress();
+ return true;
+ } catch (UnknownHostException ex) {
+ Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG,
+ "Hostname not resolveable {0}.", host);
+ }
+ return false;
+ }
+
+ /*************************************************************************
+ * Returns true if the IP address of the host matches the specified IP
+ * address pattern. Pattern and mask specification is done the same way as
+ * for SOCKS configuration.
+ *
+ * Example: isInNet(host, "198.95.0.0", "255.255.0.0") is true if the IP
+ * address of the host matches 198.95.*.*.
+ *
+ * @param host
+ * a DNS host name, or IP address. If a host name is passed, it
+ * will be resolved into an IP address by this function.
+ * @param pattern
+ * an IP address pattern in the dot-separated format.
+ * @param mask
+ * mask for the IP address pattern informing which parts of the
+ * IP address should be matched against. 0 means ignore, 255
+ * means match.
+ * @return true if it matches else false.
+ ************************************************************************/
+
+ public boolean isInNet(String host, String pattern, String mask) {
+ host = dnsResolve(host);
+ if (host == null || host.length() == 0) {
+ return false;
+ }
+ long lhost = parseIpAddressToLong(host);
+ long lpattern = parseIpAddressToLong(pattern);
+ long lmask = parseIpAddressToLong(mask);
+ return (lhost & lmask) == lpattern;
+ }
+
+ /*************************************************************************
+ * Convert a string representation of a IP to a long.
+ * @param address to convert.
+ * @return the address as long.
+ ************************************************************************/
+
+ private long parseIpAddressToLong(String address) {
+ long result = 0;
+ String[] parts = address.split("\\.");
+ long shift = 24;
+ for (String part : parts) {
+ long lpart = Long.parseLong(part);
+
+ result |= (lpart << shift);
+ shift -= 8;
+ }
+ return result;
+ }
+
+ /*************************************************************************
+ * Resolves the given DNS host name into an IP address, and returns it in
+ * the dot separated format as a string.
+ *
+ * @param host
+ * the host to resolve.
+ * @return the resolved IP, empty string if not resolvable.
+ ************************************************************************/
+
+ public String dnsResolve(String host) {
+ try {
+ return InetAddress.getByName(host).getHostAddress();
+ } catch (UnknownHostException e) {
+ Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG,
+ "DNS name not resolvable {0}.", host);
+ }
+ return "";
+ }
+
+ /*************************************************************************
+ * Returns the IP address of the host that the process is running on, as a
+ * string in the dot-separated integer format.
+ *
+ * @return an IP as string.
+ ************************************************************************/
+
+ public String myIpAddress() {
+ return getLocalAddressOfType(Inet4Address.class);
+ }
+
+ /*************************************************************************
+ * Get the current IP address of the computer.
+ * This will return the first address of the first network interface that is
+ * a "real" IP address of the given type.
+ * @param cl the type of address we are searching for.
+ * @return the address as string or "" if not found.
+ ************************************************************************/
+
+ private String getLocalAddressOfType(Class<? extends InetAddress> cl) {
+ try {
+ String overrideIP = System.getProperty(OVERRIDE_LOCAL_IP);
+ if (overrideIP != null && overrideIP.trim().length() > 0) {
+ return overrideIP.trim();
+ }
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ while (interfaces.hasMoreElements()){
+ NetworkInterface current = interfaces.nextElement();
+ if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
+ continue;
+ }
+ Enumeration<InetAddress> addresses = current.getInetAddresses();
+ while (addresses.hasMoreElements()){
+ InetAddress adr = addresses.nextElement();
+ if (cl.isInstance(adr)) {
+ Logger.log(JavaxPacScriptParser.class, LogLevel.TRACE,
+ "Local address resolved to {0}", adr);
+ return adr.getHostAddress();
+ }
+ }
+ }
+ return "";
+ } catch (IOException e) {
+ Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG,
+ "Local address not resolvable.");
+ return "";
+ }
+ }
+
+ /*************************************************************************
+ * Returns the number of DNS domain levels (number of dots) in the host
+ * name.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @return number of DNS domain levels.
+ ************************************************************************/
+
+ public int dnsDomainLevels(String host) {
+ int count = 0;
+ int startPos = 0;
+ while ((startPos = host.indexOf(".", startPos + 1)) > -1) {
+ count++;
+ }
+ return count;
+ }
+
+ /*************************************************************************
+ * Returns true if the string matches the specified shell expression.
+ * Actually, currently the patterns are shell expressions, not regular
+ * expressions.
+ *
+ * @param str
+ * is any string to compare (e.g. the URL, or the host name).
+ * @param shexp
+ * is a shell expression to compare against.
+ * @return true if the string matches, else false.
+ ************************************************************************/
+
+ public boolean shExpMatch(String str, String shexp) {
+ StringTokenizer tokenizer = new StringTokenizer(shexp, "*");
+ int startPos = 0;
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ // 07.05.2009 Incorrect? first token can be startsWith and last one
+ // can be endsWith
+ int temp = str.indexOf(token, startPos);
+ if (temp == -1) {
+ return false;
+ } else {
+ startPos = temp + token.length();
+ }
+ }
+ return true;
+ }
+
+ /*************************************************************************
+ * Only the first parameter is mandatory. Either the second, the third, or
+ * both may be left out. If only one parameter is present, the function
+ * yields a true value on the weekday that the parameter represents. If the
+ * string "GMT" is specified as a second parameter, times are taken to be in
+ * GMT, otherwise in local time zone. If both wd1 and wd2 are defined, the
+ * condition is true if the current weekday is in between those two
+ * weekdays. Bounds are inclusive. If the "GMT" parameter is specified,
+ * times are taken to be in GMT, otherwise the local time zone is used.
+ *
+ * @param wd1
+ * weekday 1 is one of SUN MON TUE WED THU FRI SAT
+ * @param wd2
+ * weekday 2 is one of SUN MON TUE WED THU FRI SAT
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if current day matches the criteria.
+ ************************************************************************/
+
+ public boolean weekdayRange(String wd1, String wd2, String gmt) {
+ boolean useGmt = GMT.equalsIgnoreCase(wd2) || GMT.equalsIgnoreCase(gmt);
+ Calendar cal = getCurrentTime(useGmt);
+
+ int currentDay = cal.get(Calendar.DAY_OF_WEEK) - 1;
+ int from = DAYS.indexOf(wd1 == null ? null : wd1.toUpperCase());
+ int to = DAYS.indexOf(wd2 == null ? null : wd2.toUpperCase());
+ if (to == -1) {
+ to = from;
+ }
+
+ if (to < from) {
+ return currentDay >= from || currentDay <= to;
+ } else {
+ return currentDay >= from && currentDay <= to;
+ }
+ }
+
+ /*************************************************************************
+ * Sets a calendar with the current time. If this is set all date and time
+ * based methods will use this calendar to determine the current time
+ * instead of the real time. This is only be used by unit tests and is not
+ * part of the public API.
+ *
+ * @param cal
+ * a Calendar to set.
+ ************************************************************************/
+
+ public void setCurrentTime(Calendar cal) {
+ this.currentTime = cal;
+ }
+
+ /*************************************************************************
+ * Gets a calendar set to the current time. This is used by the date and
+ * time based methods.
+ *
+ * @param useGmt
+ * flag to indicate if the calendar is to be created in GMT time
+ * or local time.
+ * @return a Calendar set to the current time.
+ ************************************************************************/
+
+ private Calendar getCurrentTime(boolean useGmt) {
+ if (this.currentTime != null) { // Only used for unit tests
+ return (Calendar) this.currentTime.clone();
+ }
+ return Calendar.getInstance(useGmt ? TimeZone.getTimeZone(GMT)
+ : TimeZone.getDefault());
+ }
+
+ /*************************************************************************
+ * Only the first parameter is mandatory. All other parameters can be left
+ * out therefore the meaning of the parameters changes. The method
+ * definition shows the version with the most possible parameters filled.
+ * The real meaning of the parameters is guessed from it's value. If "from"
+ * and "to" are specified then the bounds are inclusive. If the "GMT"
+ * parameter is specified, times are taken to be in GMT, otherwise the local
+ * time zone is used.
+ *
+ * @param day1
+ * is the day of month between 1 and 31 (as an integer).
+ * @param month1
+ * one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year1
+ * is the full year number, for example 1995 (but not 95).
+ * Integer.
+ * @param day2
+ * is the day of month between 1 and 31 (as an integer).
+ * @param month2
+ * one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year2
+ * is the full year number, for example 1995 (but not 95).
+ * Integer.
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if the current date matches the given range.
+ ************************************************************************/
+
+ public boolean dateRange(Object day1, Object month1, Object year1,
+ Object day2, Object month2, Object year2, Object gmt) {
+
+ // Guess the parameter meanings.
+ Map<String, Integer> params = new HashMap<String, Integer>();
+ parseDateParam(params, day1);
+ parseDateParam(params, month1);
+ parseDateParam(params, year1);
+ parseDateParam(params, day2);
+ parseDateParam(params, month2);
+ parseDateParam(params, year2);
+ parseDateParam(params, gmt);
+
+ // Get current date
+ boolean useGmt = params.get("gmt") != null;
+ Calendar cal = getCurrentTime(useGmt);
+ Date current = cal.getTime();
+
+ // Build the "from" date
+ if (params.get("day1") != null) {
+ cal.set(Calendar.DAY_OF_MONTH, params.get("day1"));
+ }
+ if (params.get("month1") != null) {
+ cal.set(Calendar.MONTH, params.get("month1"));
+ }
+ if (params.get("year1") != null) {
+ cal.set(Calendar.YEAR, params.get("year1"));
+ }
+ Date from = cal.getTime();
+
+ // Build the "to" date
+ Date to;
+ if (params.get("day2") != null) {
+ cal.set(Calendar.DAY_OF_MONTH, params.get("day2"));
+ }
+ if (params.get("month2") != null) {
+ cal.set(Calendar.MONTH, params.get("month2"));
+ }
+ if (params.get("year2") != null) {
+ cal.set(Calendar.YEAR, params.get("year2"));
+ }
+ to = cal.getTime();
+
+ // Need to increment to the next month?
+ if (to.before(from)) {
+ cal.add(Calendar.MONTH, +1);
+ to = cal.getTime();
+ }
+ // Need to increment to the next year?
+ if (to.before(from)) {
+ cal.add(Calendar.YEAR, +1);
+ cal.add(Calendar.MONTH, -1);
+ to = cal.getTime();
+ }
+
+ return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
+ }
+
+ /*************************************************************************
+ * Try to guess the type of the given parameter and put it into the params
+ * map.
+ *
+ * @param params
+ * a map to put the parsed parameters into.
+ * @param value
+ * to parse and specify the type for.
+ ************************************************************************/
+
+ private void parseDateParam(Map<String, Integer> params, Object value) {
+ if (value instanceof Number) {
+ int n = ((Number) value).intValue();
+ if (n <= 31) {
+ // Its a day
+ if (params.get("day1") == null) {
+ params.put("day1", n);
+ } else {
+ params.put("day2", n);
+ }
+ } else {
+ // Its a year
+ if (params.get("year1") == null) {
+ params.put("year1", n);
+ } else {
+ params.put("year2", n);
+ }
+ }
+ }
+
+ if (value instanceof String) {
+ int n = MONTH.indexOf(((String) value).toUpperCase());
+ if (n > -1) {
+ // Its a month
+ if (params.get("month1") == null) {
+ params.put("month1", n);
+ } else {
+ params.put("month2", n);
+ }
+ }
+ }
+
+ if (GMT.equalsIgnoreCase(String.valueOf(value))) {
+ params.put("gmt", 1);
+ }
+ }
+
+ /*************************************************************************
+ * Some parameters can be left out therefore the meaning of the parameters
+ * changes. The method definition shows the version with the most possible
+ * parameters filled. The real meaning of the parameters is guessed from
+ * it's value. If "from" and "to" are specified then the bounds are
+ * inclusive. If the "GMT" parameter is specified, times are taken to be in
+ * GMT, otherwise the local time zone is used.<br/>
+ *
+ * <pre>
+ * timeRange(hour)
+ * timeRange(hour1, hour2)
+ * timeRange(hour1, min1, hour2, min2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)
+ * </pre>
+ *
+ * @param hour1
+ * is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min1
+ * minutes from 0 to 59.
+ * @param sec1
+ * seconds from 0 to 59.
+ * @param hour2
+ * is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min2
+ * minutes from 0 to 59.
+ * @param sec2
+ * seconds from 0 to 59.
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if the current time matches the given range.
+ ************************************************************************/
+
+ public boolean timeRange(Object hour1, Object min1, Object sec1,
+ Object hour2, Object min2, Object sec2, Object gmt) {
+ boolean useGmt = GMT.equalsIgnoreCase(String.valueOf(min1))
+ || GMT.equalsIgnoreCase(String.valueOf(sec1))
+ || GMT.equalsIgnoreCase(String.valueOf(min2))
+ || GMT.equalsIgnoreCase(String.valueOf(gmt));
+
+ Calendar cal = getCurrentTime(useGmt);
+ cal.set(Calendar.MILLISECOND, 0);
+ Date current = cal.getTime();
+ Date from;
+ Date to;
+ if (sec2 instanceof Number) {
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
+ cal.set(Calendar.MINUTE, ((Number) min1).intValue());
+ cal.set(Calendar.SECOND, ((Number) sec1).intValue());
+ from = cal.getTime();
+
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour2).intValue());
+ cal.set(Calendar.MINUTE, ((Number) min2).intValue());
+ cal.set(Calendar.SECOND, ((Number) sec2).intValue());
+ to = cal.getTime();
+ } else if (hour2 instanceof Number) {
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
+ cal.set(Calendar.MINUTE, ((Number) min1).intValue());
+ cal.set(Calendar.SECOND, 0);
+ from = cal.getTime();
+
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) sec1).intValue());
+ cal.set(Calendar.MINUTE, ((Number) hour2).intValue());
+ cal.set(Calendar.SECOND, 59);
+ to = cal.getTime();
+ } else if (min1 instanceof Number) {
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ from = cal.getTime();
+
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) min1).intValue());
+ cal.set(Calendar.MINUTE, 59);
+ cal.set(Calendar.SECOND, 59);
+ to = cal.getTime();
+ } else {
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ from = cal.getTime();
+
+ cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
+ cal.set(Calendar.MINUTE, 59);
+ cal.set(Calendar.SECOND, 59);
+ to = cal.getTime();
+ }
+
+ if (to.before(from)) {
+ cal.setTime(to);
+ cal.add(Calendar.DATE, +1);
+ to = cal.getTime();
+ }
+
+ return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
+ }
+
+ // Microsoft PAC extensions for IPv6 support.
+
+ /*************************************************************************
+ * isResolvableEx
+ * @see com.btr.proxy.selector.pac.ScriptMethods#isResolvableEx(java.lang.String)
+ ************************************************************************/
+
+ public boolean isResolvableEx(String host) {
+ return isResolvable(host);
+ }
+
+ /*************************************************************************
+ * isInNetEx
+ * @see com.btr.proxy.selector.pac.ScriptMethods#isInNetEx(java.lang.String, java.lang.String)
+ ************************************************************************/
+
+ public boolean isInNetEx(String ipAddress, String ipPrefix) {
+ // TODO rossi 27.06.2011 Auto-generated method stub
+ return false;
+ }
+
+ /*************************************************************************
+ * dnsResolveEx
+ * @see com.btr.proxy.selector.pac.ScriptMethods#dnsResolveEx(java.lang.String)
+ ************************************************************************/
+
+ public String dnsResolveEx(String host) {
+ StringBuilder result = new StringBuilder();
+ try {
+ InetAddress[] list = InetAddress.getAllByName(host);
+ for (InetAddress inetAddress : list) {
+ result.append(inetAddress.getHostAddress());
+ result.append("; ");
+ }
+ } catch (UnknownHostException e) {
+ Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG,
+ "DNS name not resolvable {0}.", host);
+ }
+ return result.toString();
+ }
+
+ /*************************************************************************
+ * myIpAddressEx
+ * @see com.btr.proxy.selector.pac.ScriptMethods#myIpAddressEx()
+ ************************************************************************/
+
+ public String myIpAddressEx() {
+ return getLocalAddressOfType(Inet6Address.class);
+ }
+
+ /*************************************************************************
+ * sortIpAddressList
+ * @see com.btr.proxy.selector.pac.ScriptMethods#sortIpAddressList(java.lang.String)
+ ************************************************************************/
+
+ public String sortIpAddressList(String ipAddressList) {
+ if (ipAddressList == null || ipAddressList.trim().length() == 0) {
+ return "";
+ }
+ String[] ipAddressToken = ipAddressList.split(";");
+ List<InetAddress> parsedAddresses = new ArrayList<InetAddress>();
+ for (String ip : ipAddressToken) {
+ try {
+ parsedAddresses.add(InetAddress.getByName(ip));
+ } catch (UnknownHostException e) {
+ // TODO rossi 01.11.2011 Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ Collections.sort(parsedAddresses, null);
+ // TODO rossi 27.06.2011 Implement me.
+ return ipAddressList;
+ }
+
+ /*************************************************************************
+ * getClientVersion
+ * @see com.btr.proxy.selector.pac.ScriptMethods#getClientVersion()
+ ************************************************************************/
+
+ public String getClientVersion() {
+ return "1.0";
+ }
+
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/PacScriptParser.java b/src/main/java/com/btr/proxy/selector/pac/PacScriptParser.java new file mode 100644 index 0000000..5e7fd73 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/PacScriptParser.java @@ -0,0 +1,29 @@ +package com.btr.proxy.selector.pac;
+
+/***************************************************************************
+ * Common interface for PAC script parsers.
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ***************************************************************************/
+public interface PacScriptParser {
+
+ /***************************************************************************
+ * Gets the source of the PAC script used by this parser.
+ *
+ * @return a PacScriptSource.
+ **************************************************************************/
+ public PacScriptSource getScriptSource();
+
+ /*************************************************************************
+ * Evaluates the given URL and host against the PAC script.
+ *
+ * @param url
+ * the URL to evaluate.
+ * @param host
+ * the host name part of the URL.
+ * @return the script result.
+ * @throws ProxyEvaluationException
+ * on execution error.
+ ************************************************************************/
+ public String evaluate(String url, String host) throws ProxyEvaluationException;
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/PacScriptSource.java b/src/main/java/com/btr/proxy/selector/pac/PacScriptSource.java new file mode 100644 index 0000000..05e00b6 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/PacScriptSource.java @@ -0,0 +1,31 @@ +package com.btr.proxy.selector.pac; + +import java.io.IOException; + +/***************************************************************************** + * An source to fetch the PAC script from. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public interface PacScriptSource { + + /************************************************************************* + * Gets the PAC script content as String. + * @return a script. + * @throws IOException on read error. + ************************************************************************/ + + public String getScriptContent() throws IOException; + + /************************************************************************* + * Checks if the content of the script is valid and if it is possible + * to use this script source for a PAC selector. + * Note that this might trigger a download of the script content from + * a remote location. + * @return true if everything is fine, else false. + ************************************************************************/ + + public boolean isScriptValid(); + +}
\ No newline at end of file diff --git a/src/main/java/com/btr/proxy/selector/pac/ProxyEvaluationException.java b/src/main/java/com/btr/proxy/selector/pac/ProxyEvaluationException.java new file mode 100644 index 0000000..25fd977 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/ProxyEvaluationException.java @@ -0,0 +1,51 @@ +package com.btr.proxy.selector.pac; + +import com.btr.proxy.util.ProxyException; + +/***************************************************************************** + * Exception for PAC script errors. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxyEvaluationException extends ProxyException { + + private static final long serialVersionUID = 1L; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public ProxyEvaluationException() { + super(); + } + + /************************************************************************* + * Constructor + * @param message the error message. + * @param cause the causing exception for exception chaining. + ************************************************************************/ + + public ProxyEvaluationException(String message, Throwable cause) { + super(message, cause); + } + + /************************************************************************* + * Constructor + * @param message the error message. + ************************************************************************/ + + public ProxyEvaluationException(String message) { + super(message); + } + + /************************************************************************* + * Constructor + * @param cause the causing exception for exception chaining. + ************************************************************************/ + + public ProxyEvaluationException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/pac/RhinoPacScriptParser.java b/src/main/java/com/btr/proxy/selector/pac/RhinoPacScriptParser.java new file mode 100644 index 0000000..f6ff6e2 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/RhinoPacScriptParser.java @@ -0,0 +1,318 @@ +package com.btr.proxy.selector.pac;
+
+import java.util.Calendar;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+import com.btr.proxy.util.Logger;
+import com.btr.proxy.util.Logger.LogLevel;
+
+/*****************************************************************************
+ * PAC parser using the Rhino JavaScript engine.<br/>
+ * Depends on js.jar of the <a href="http://www.mozilla.org/rhino/">Apache Rhino </a> project.
+ * <p>
+ * More information about PAC can be found there:<br/>
+ * <a href="http://en.wikipedia.org/wiki/Proxy_auto-config">Proxy_auto-config</a><br/>
+ * <a href="http://homepages.tesco.net/~J.deBoynePollard/FGA/web-browser-auto-proxy-configuration.html">web-browser-auto-proxy-configuration</a>
+ * </p>
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ****************************************************************************/
+
+public class RhinoPacScriptParser extends ScriptableObject implements PacScriptParser {
+
+ private static final long serialVersionUID = 1L;
+
+ // Define some PAC script functions. These functions are not part of ECMA.
+ private static final String[] JS_FUNCTION_NAMES = {
+ "shExpMatch", "dnsResolve", "isResolvable",
+ "isInNet", "dnsDomainIs", "isPlainHostName", "myIpAddress",
+ "dnsDomainLevels", "localHostOrDomainIs", "weekdayRange",
+ "dateRange", "timeRange"
+ };
+
+ private Scriptable scope;
+ private PacScriptSource source;
+ private static final PacScriptMethods SCRIPT_METHODS = new PacScriptMethods();
+
+ /*************************************************************************
+ * Constructor
+ * @param source the source for the PAC script.
+ * @throws ProxyEvaluationException on error.
+ ************************************************************************/
+
+ public RhinoPacScriptParser(PacScriptSource source) throws ProxyEvaluationException {
+ super();
+ this.source = source;
+
+ setupEngine();
+ }
+
+ /*************************************************************************
+ * Initializes the JavaScript engine.
+ * @throws ProxyEvaluationException on error.
+ ************************************************************************/
+
+ public void setupEngine() throws ProxyEvaluationException {
+
+ Context context = new ContextFactory().enterContext();
+ try {
+ defineFunctionProperties(JS_FUNCTION_NAMES, RhinoPacScriptParser.class, ScriptableObject.DONTENUM);
+ } catch (Exception e) {
+ Logger.log(getClass(), LogLevel.ERROR, "JS Engine setup error.", e);
+ throw new ProxyEvaluationException(e.getMessage(), e);
+ }
+
+ this.scope = context.initStandardObjects(this);
+ }
+
+ /***************************************************************************
+ * Gets the source of the PAC script used by this parser.
+ * @return a PacScriptSource.
+ **************************************************************************/
+
+ public PacScriptSource getScriptSource() {
+ return this.source;
+ }
+
+ /*************************************************************************
+ * Evaluates the given URL and host against the PAC script.
+ * @param url the URL to evaluate.
+ * @param host the host name part of the URL.
+ * @return the script result.
+ * @throws ProxyEvaluationException on execution error.
+ ************************************************************************/
+
+ public String evaluate(String url, String host) throws ProxyEvaluationException {
+ try {
+ // FindProxyForURL function signature
+ StringBuilder script = new StringBuilder(this.source.getScriptContent());
+ String evalMethod = " ;FindProxyForURL (\"" + url + "\",\"" + host + "\")";
+ script.append(evalMethod);
+
+ Context context = Context.enter();
+ try {
+ Object result = context.evaluateString(this.scope,
+ script.toString(), "userPacFile", 1, null);
+
+ return Context.toString(result);
+ } finally {
+ Context.exit();
+ }
+ } catch (Exception e) {
+ Logger.log(getClass(), LogLevel.ERROR, "JS evaluation error.", e);
+ throw new ProxyEvaluationException(
+ "Error while executing PAC script: " + e.getMessage(), e);
+ }
+ }
+
+ /*************************************************************************
+ * getClassName
+ * See also org.mozilla.javascript.ScriptableObject#getClassName()
+ ************************************************************************/
+ @Override
+ public String getClassName() {
+ return getClass().getSimpleName();
+ }
+
+
+
+// ***************************************************************************
+// Defining PAC script methods needed in JS
+// ***************************************************************************
+
+
+ /*************************************************************************
+ * Tests if the given name is a plain host name without a domain name.
+ * @param host the host name from the URL (excluding port number)
+ * @return true if there is no domain name in the host name (no dots).
+ ************************************************************************/
+
+ public static boolean isPlainHostName(String host) {
+ return SCRIPT_METHODS.isPlainHostName(host);
+ }
+
+ /*************************************************************************
+ * Tests if an URL is in a given domain.
+ * @param host is the host name from the URL.
+ * @param domain is the domain name to test the host name against.
+ * @return true if the domain of host name matches.
+ ************************************************************************/
+
+ public static boolean dnsDomainIs(String host, String domain) {
+ return SCRIPT_METHODS.dnsDomainIs(host, domain);
+ }
+
+ /*************************************************************************
+ * Is true if the host name matches exactly the specified host name,
+ * or if there is no domain name part in the host name, but the unqualified
+ * host name matches.
+ * @param host the host name from the URL.
+ * @param domain fully qualified host name with domain to match against.
+ * @return true if matches else false.
+ ************************************************************************/
+
+ public static boolean localHostOrDomainIs(String host, String domain) {
+ return SCRIPT_METHODS.localHostOrDomainIs(host, domain);
+ }
+
+ /*************************************************************************
+ * Tries to resolve the host name. Returns true if succeeds.
+ * @param host is the host name from the URL.
+ * @return true if resolvable else false.
+ ************************************************************************/
+
+ public static boolean isResolvable(String host) {
+ return SCRIPT_METHODS.isResolvable(host);
+ }
+
+ /*************************************************************************
+ * Returns true if the IP address of the host matches the specified IP
+ * address pattern. Pattern and mask specification is done the same way
+ * as for SOCKS configuration.
+ *
+ * Example:
+ * isInNet(host, "198.95.0.0", "255.255.0.0")
+ * is true if the IP address of the host matches 198.95.*.*.
+ *
+ * @param host a DNS host name, or IP address.
+ * If a host name is passed, it will be resolved into an IP address by this function.
+ * @param pattern an IP address pattern in the dot-separated format.
+ * @param mask mask for the IP address pattern informing which parts of
+ * the IP address should be matched against. 0 means ignore, 255 means match.
+ * @return true if it matches else false.
+ ************************************************************************/
+
+ public static boolean isInNet(String host, String pattern, String mask) {
+ return SCRIPT_METHODS.isInNet(host, pattern, mask);
+ }
+
+ /*************************************************************************
+ * Resolves the given DNS host name into an IP address, and returns it in
+ * the dot separated format as a string.
+ * @param host the host to resolve.
+ * @return the resolved IP, empty string if not resolvable.
+ ************************************************************************/
+
+ public static String dnsResolve(String host) {
+ return SCRIPT_METHODS.dnsResolve(host);
+ }
+
+ /*************************************************************************
+ * Returns the IP address of the host that the process is running on,
+ * as a string in the dot-separated integer format.
+ * @return an IP as string.
+ ************************************************************************/
+
+ public static String myIpAddress() {
+ return SCRIPT_METHODS.myIpAddress();
+ }
+
+ /*************************************************************************
+ * Returns the number of DNS domain levels (number of dots) in the host name.
+ * @param host is the host name from the URL.
+ * @return number of DNS domain levels.
+ ************************************************************************/
+
+ public static int dnsDomainLevels(String host) {
+ return SCRIPT_METHODS.dnsDomainLevels(host);
+ }
+
+ /*************************************************************************
+ * Returns true if the string matches the specified shell expression.
+ * Actually, currently the patterns are shell expressions, not regular expressions.
+ * @param str is any string to compare (e.g. the URL, or the host name).
+ * @param shexp is a shell expression to compare against.
+ * @return true if the string matches, else false.
+ ************************************************************************/
+
+ public static boolean shExpMatch(String str, String shexp) {
+ return SCRIPT_METHODS.shExpMatch(str, shexp);
+ }
+
+ /*************************************************************************
+ * Only the first parameter is mandatory.
+ * Either the second, the third, or both may be left out.
+ * If only one parameter is present, the function yields a true value on
+ * the weekday that the parameter represents. If the string "GMT" is
+ * specified as a second parameter, times are taken to be in GMT,
+ * otherwise in local time zone. If both wd1 and wd2 are defined, the
+ * condition is true if the current weekday is in between those two weekdays.
+ * Bounds are inclusive. If the "GMT" parameter is specified, times are
+ * taken to be in GMT, otherwise the local time zone is used.
+ * @param wd1 weekday 1 is one of SUN MON TUE WED THU FRI SAT
+ * @param wd2 weekday 2 is one of SUN MON TUE WED THU FRI SAT
+ * @param gmt "GMT" for gmt time format else "undefined"
+ * @return true if current day matches the criteria.
+ ************************************************************************/
+
+ public static boolean weekdayRange(String wd1, String wd2, String gmt) {
+ return SCRIPT_METHODS.weekdayRange(wd1, wd2, gmt);
+ }
+
+ /*************************************************************************
+ * Sets a calendar with the current time. If this is set all date and time
+ * based methods will use this calendar to determine the current time
+ * instead of the real time. This is only be used by unit tests and is not
+ * part of the public API.
+ * @param cal a Calendar to set.
+ ************************************************************************/
+
+ static void setCurrentTime(Calendar cal) {
+ SCRIPT_METHODS.setCurrentTime(cal);
+ }
+
+ /*************************************************************************
+ * Only the first parameter is mandatory.
+ * All other parameters can be left out therefore the meaning of the parameters
+ * changes. The method definition shows the version with the most possible
+ * parameters filled. The real meaning of the parameters is guessed from it's
+ * value. If "from" and "to" are specified then the bounds are inclusive.
+ * If the "GMT" parameter is specified, times are taken to be in GMT,
+ * otherwise the local time zone is used.
+ * @param day1 is the day of month between 1 and 31 (as an integer).
+ * @param month1 one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year1 is the full year number, for example 1995 (but not 95). Integer.
+ * @param day2 is the day of month between 1 and 31 (as an integer).
+ * @param month2 one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year2 is the full year number, for example 1995 (but not 95). Integer.
+ * @param gmt "GMT" for gmt time format else "undefined"
+ * @return true if the current date matches the given range.
+ ************************************************************************/
+
+ public static boolean dateRange(Object day1, Object month1, Object year1, Object day2, Object month2, Object year2, Object gmt) {
+ return SCRIPT_METHODS.dateRange(day1, month1, year1, day2, month2, year2, gmt);
+ }
+
+ /*************************************************************************
+ * Some parameters can be left out therefore the meaning of the parameters
+ * changes. The method definition shows the version with the most possible
+ * parameters filled. The real meaning of the parameters is guessed from it's
+ * value. If "from" and "to" are specified then the bounds are inclusive.
+ * If the "GMT" parameter is specified, times are taken to be in GMT,
+ * otherwise the local time zone is used.<br/>
+ *
+ * <pre>
+ * timeRange(hour)
+ * timeRange(hour1, hour2)
+ * timeRange(hour1, min1, hour2, min2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)
+ * </pre>
+ *
+ * @param hour1 is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min1 minutes from 0 to 59.
+ * @param sec1 seconds from 0 to 59.
+ * @param hour2 is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min2 minutes from 0 to 59.
+ * @param sec2 seconds from 0 to 59.
+ * @param gmt "GMT" for gmt time format else "undefined"
+ * @return true if the current time matches the given range.
+ ************************************************************************/
+
+ public static boolean timeRange(Object hour1, Object min1, Object sec1, Object hour2, Object min2, Object sec2, Object gmt) {
+ return SCRIPT_METHODS.timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt);
+ }
+
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/ScriptAvailability.java b/src/main/java/com/btr/proxy/selector/pac/ScriptAvailability.java new file mode 100644 index 0000000..d7f6e04 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/ScriptAvailability.java @@ -0,0 +1,46 @@ +package com.btr.proxy.selector.pac;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/****************************************************************************
+ * Utility to check availablility of javax.script
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ***************************************************************************/
+abstract class ScriptAvailability {
+
+ /*************************************************************************
+ * Checks whether javax.script is available or not.
+ * Completely done per Reflection to allow compilation under Java 1.5
+ * @return true if javax.script is available; false otherwise
+ ************************************************************************/
+ public static boolean isJavaxScriptingAvailable() {
+ Object engine = null;
+ try {
+ Class<?> managerClass = Class.forName("javax.script.ScriptEngineManager");
+ Method m = managerClass.getMethod("getEngineByMimeType", String.class);
+ engine = m.invoke(managerClass.newInstance(), "text/javascript");
+ } catch (ClassNotFoundException e) {
+ // javax.script not available
+ } catch (NoSuchMethodException e) {
+ // javax.script not available
+ } catch (IllegalAccessException e) {
+ // javax.script not available
+ } catch (InvocationTargetException e) {
+ // javax.script not available
+ } catch (InstantiationException e) {
+ // javax.script not available
+ }
+
+ return engine != null;
+ }
+
+ /*************************************************************************
+ * Constructor
+ ************************************************************************/
+
+ ScriptAvailability() {
+ super();
+ }
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/ScriptMethods.java b/src/main/java/com/btr/proxy/selector/pac/ScriptMethods.java new file mode 100644 index 0000000..9f559c5 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/ScriptMethods.java @@ -0,0 +1,256 @@ +package com.btr.proxy.selector.pac;
+
+/***************************************************************************
+ * Defines the public interface for PAC scripts.
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ***************************************************************************/
+public interface ScriptMethods {
+
+ public boolean isPlainHostName(String host);
+
+ /*************************************************************************
+ * Tests if an URL is in a given domain.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @param domain
+ * is the domain name to test the host name against.
+ * @return true if the domain of host name matches.
+ ************************************************************************/
+
+ public boolean dnsDomainIs(String host, String domain);
+
+ /*************************************************************************
+ * Is true if the host name matches exactly the specified host name, or if
+ * there is no domain name part in the host name, but the unqualified host
+ * name matches.
+ *
+ * @param host
+ * the host name from the URL.
+ * @param domain
+ * fully qualified host name with domain to match against.
+ * @return true if matches else false.
+ ************************************************************************/
+
+ public boolean localHostOrDomainIs(String host, String domain);
+
+ /*************************************************************************
+ * Tries to resolve the host name. Returns true if succeeds.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @return true if resolvable else false.
+ ************************************************************************/
+
+ public boolean isResolvable(String host);
+
+ /*************************************************************************
+ * Tries to resolve the host name. Returns true if succeeds to resolve
+ * the host to an IPv4 or IPv6 address.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @return true if resolvable else false.
+ ************************************************************************/
+
+ public boolean isResolvableEx(String host);
+
+ /*************************************************************************
+ * Returns true if the IP address of the host matches the specified IP
+ * address pattern. Pattern and mask specification is done the same way as
+ * for SOCKS configuration.
+ *
+ * Example: isInNet(host, "198.95.0.0", "255.255.0.0") is true if the IP
+ * address of the host matches 198.95.*.*.
+ *
+ * @param host
+ * a DNS host name, or IP address. If a host name is passed, it
+ * will be resolved into an IP address by this function.
+ * @param pattern
+ * an IP address pattern in the dot-separated format.
+ * @param mask
+ * mask for the IP address pattern informing which parts of the
+ * IP address should be matched against. 0 means ignore, 255
+ * means match.
+ * @return true if it matches else false.
+ ************************************************************************/
+
+ public boolean isInNet(String host, String pattern, String mask);
+
+ /*************************************************************************
+ * Extension of the isInNet method to support IPv6.
+ * @param ipAddress an IP4 or IP6 address
+ * @param ipPrefix A string containing colon delimited IP prefix
+ * with top n bits specified in the bit field
+ * (i.e. 3ffe:8311:ffff::/48 or 123.112.0.0/16).
+ * @return true if the host is in the given subnet, else false.
+ ************************************************************************/
+
+ public boolean isInNetEx(String ipAddress, String ipPrefix);
+
+ /*************************************************************************
+ * Resolves the given DNS host name into an IP address, and returns it in
+ * the dot separated format as a string.
+ *
+ * @param host
+ * the host to resolve.
+ * @return the resolved IP, empty string if not resolvable.
+ ************************************************************************/
+
+ public String dnsResolve(String host);
+
+ /*************************************************************************
+ * @param host the host to resolve
+ * @return a semicolon separated list of IP6 and IP4 addresses the host
+ * name resolves to, empty string if not resolvable.
+ ************************************************************************/
+
+ public String dnsResolveEx(String host);
+
+ /*************************************************************************
+ * Returns the IP address of the host that the process is running on, as a
+ * string in the dot-separated integer format.
+ *
+ * @return an IP as string.
+ ************************************************************************/
+
+ public String myIpAddress();
+
+ /*************************************************************************
+ * Returns a list of IP4 and IP6 addresses of the host that the process
+ * is running on. The list is separated with semicolons.
+ * @return the list, empty string if not available.
+ ************************************************************************/
+
+ public String myIpAddressEx();
+
+ /*************************************************************************
+ * Returns the number of DNS domain levels (number of dots) in the host
+ * name.
+ *
+ * @param host
+ * is the host name from the URL.
+ * @return number of DNS domain levels.
+ ************************************************************************/
+
+ public int dnsDomainLevels(String host);
+
+ /*************************************************************************
+ * Returns true if the string matches the specified shell expression.
+ * Actually, currently the patterns are shell expressions, not regular
+ * expressions.
+ *
+ * @param str
+ * is any string to compare (e.g. the URL, or the host name).
+ * @param shexp
+ * is a shell expression to compare against.
+ * @return true if the string matches, else false.
+ ************************************************************************/
+
+ public boolean shExpMatch(String str, String shexp);
+
+ /*************************************************************************
+ * Only the first parameter is mandatory. Either the second, the third, or
+ * both may be left out. If only one parameter is present, the function
+ * yields a true value on the weekday that the parameter represents. If the
+ * string "GMT" is specified as a second parameter, times are taken to be in
+ * GMT, otherwise in local time zone. If both wd1 and wd2 are defined, the
+ * condition is true if the current weekday is in between those two
+ * weekdays. Bounds are inclusive. If the "GMT" parameter is specified,
+ * times are taken to be in GMT, otherwise the local time zone is used.
+ *
+ * @param wd1
+ * weekday 1 is one of SUN MON TUE WED THU FRI SAT
+ * @param wd2
+ * weekday 2 is one of SUN MON TUE WED THU FRI SAT
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if current day matches the criteria.
+ ************************************************************************/
+
+ public boolean weekdayRange(String wd1, String wd2, String gmt);
+
+ /*************************************************************************
+ * Only the first parameter is mandatory. All other parameters can be left
+ * out therefore the meaning of the parameters changes. The method
+ * definition shows the version with the most possible parameters filled.
+ * The real meaning of the parameters is guessed from it's value. If "from"
+ * and "to" are specified then the bounds are inclusive. If the "GMT"
+ * parameter is specified, times are taken to be in GMT, otherwise the local
+ * time zone is used.
+ *
+ * @param day1
+ * is the day of month between 1 and 31 (as an integer).
+ * @param month1
+ * one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year1
+ * is the full year number, for example 1995 (but not 95).
+ * Integer.
+ * @param day2
+ * is the day of month between 1 and 31 (as an integer).
+ * @param month2
+ * one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ * @param year2
+ * is the full year number, for example 1995 (but not 95).
+ * Integer.
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if the current date matches the given range.
+ ************************************************************************/
+
+ public boolean dateRange(Object day1, Object month1, Object year1, Object day2, Object month2, Object year2,
+ Object gmt);
+
+ /*************************************************************************
+ * Some parameters can be left out therefore the meaning of the parameters
+ * changes. The method definition shows the version with the most possible
+ * parameters filled. The real meaning of the parameters is guessed from
+ * it's value. If "from" and "to" are specified then the bounds are
+ * inclusive. If the "GMT" parameter is specified, times are taken to be in
+ * GMT, otherwise the local time zone is used.<br/>
+ *
+ * <pre>
+ * timeRange(hour)
+ * timeRange(hour1, hour2)
+ * timeRange(hour1, min1, hour2, min2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2)
+ * timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)
+ * </pre>
+ *
+ * @param hour1
+ * is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min1
+ * minutes from 0 to 59.
+ * @param sec1
+ * seconds from 0 to 59.
+ * @param hour2
+ * is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
+ * @param min2
+ * minutes from 0 to 59.
+ * @param sec2
+ * seconds from 0 to 59.
+ * @param gmt
+ * "GMT" for gmt time format else "undefined"
+ * @return true if the current time matches the given range.
+ ************************************************************************/
+
+ public boolean timeRange(Object hour1, Object min1, Object sec1, Object hour2, Object min2, Object sec2, Object gmt);
+
+ /*************************************************************************
+ * Sorts a list of IP4 and IP6 addresses. Separated by semicolon.
+ * Dual addresses first, then IPv6 and last IPv4.
+ * @param ipAddressList the address list.
+ * @return the sorted list, empty string if sort is not possible
+ ************************************************************************/
+
+ public String sortIpAddressList(String ipAddressList);
+
+ /*************************************************************************
+ * Gets the version of the PAC extension that is available.
+ * @return the extension version, currently 1.0
+ ************************************************************************/
+
+ public String getClientVersion();
+
+}
diff --git a/src/main/java/com/btr/proxy/selector/pac/UrlPacScriptSource.java b/src/main/java/com/btr/proxy/selector/pac/UrlPacScriptSource.java new file mode 100644 index 0000000..6ac8faa --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/pac/UrlPacScriptSource.java @@ -0,0 +1,270 @@ +package com.btr.proxy.selector.pac; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URISyntaxException; +import java.net.URL; + +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Script source that will load the content of a PAC file from an webserver. + * The script content is cached once it was downloaded. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class UrlPacScriptSource implements PacScriptSource { + + private static final int DEFAULT_CONNECT_TIMEOUT = 15 * 1000; // seconds + private static final int DEFAULT_READ_TIMEOUT = 20 * 1000; // seconds + public static final String OVERRIDE_CONNECT_TIMEOUT = "com.btr.proxy.url.connectTimeout"; + public static final String OVERRIDE_READ_TIMEOUT = "com.btr.proxy.url.readTimeout"; + + private final String scriptUrl; + private String scriptContent; + private long expireAtMillis; + + /************************************************************************* + * Constructor + * @param url the URL to download the script from. + ************************************************************************/ + + public UrlPacScriptSource(String url) { + super(); + this.expireAtMillis = 0; + this.scriptUrl = url; + } + + /************************************************************************* + * getScriptContent + * @see com.btr.proxy.selector.pac.PacScriptSource#getScriptContent() + ************************************************************************/ + + public synchronized String getScriptContent() throws IOException { + if (this.scriptContent == null || + (this.expireAtMillis > 0 + && this.expireAtMillis > System.currentTimeMillis())) { + try { + if (this.scriptUrl.startsWith("file:/") || this.scriptUrl.indexOf(":/") == -1) { + this.scriptContent = readPacFileContent(this.scriptUrl); + } else { + this.scriptContent = downloadPacContent(this.scriptUrl); + } + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Loading script failed from: {0} with error {1}", this.scriptUrl, e); + this.scriptContent = ""; + throw e; + } + } + return this.scriptContent; + } + + /************************************************************************* + * Reads a PAC script from a local file. + * @param scriptUrl + * @return the content of the script file. + * @throws IOException + * @throws URISyntaxException + ************************************************************************/ + + private String readPacFileContent(String scriptUrl) throws IOException { + try { + File file = null; + if (scriptUrl.indexOf(":/") == -1) { + file = new File(scriptUrl); + } else { + file = new File(new URL(scriptUrl).toURI()); + } + BufferedReader r = new BufferedReader(new FileReader(file)); + StringBuilder result = new StringBuilder(); + try { + String line; + while ((line = r.readLine()) != null) { + result.append(line).append("\n"); + } + } finally { + r.close(); + } + return result.toString(); + } catch (Exception e) { + Logger.log(getClass(), LogLevel.ERROR, "File reading error.", e); + throw new IOException(e.getMessage()); + } + } + + /************************************************************************* + * Downloads the script from a webserver. + * @param url the URL to the script file. + * @return the script content. + * @throws IOException on read error. + ************************************************************************/ + + private String downloadPacContent(String url) throws IOException { + if (url == null) { + throw new IOException("Invalid PAC script URL: null"); + } + + setPacProxySelectorEnabled(false); + + HttpURLConnection con = null; + try { + con = setupHTTPConnection(url); + if (con.getResponseCode() != 200) { + throw new IOException("Server returned: "+con.getResponseCode()+" "+con.getResponseMessage()); + } + // Read expire date. + this.expireAtMillis = con.getExpiration(); + + BufferedReader r = getReader(con); + String result = readAllContent(r); + r.close(); + return result; + } finally { + setPacProxySelectorEnabled(true); + if (con != null) { + con.disconnect(); + } + } + } + + /************************************************************************* + * Enables/disables the PAC proxy selector while we download to prevent recursion. + * See issue: 26 in the change tracker. + ************************************************************************/ + + private void setPacProxySelectorEnabled(boolean enable) { + PacProxySelector.setEnabled(enable); + } + + /************************************************************************* + * Reads the whole content available into a String. + * @param r to read from. + * @return the complete PAC file content. + * @throws IOException + ************************************************************************/ + + private String readAllContent(BufferedReader r) throws IOException { + StringBuilder result = new StringBuilder(); + String line; + while ((line = r.readLine()) != null) { + result.append(line).append("\n"); + } + return result.toString(); + } + + /************************************************************************* + * Build a BufferedReader around the open HTTP connection. + * @param con to read from + * @return the BufferedReader. + * @throws UnsupportedEncodingException + * @throws IOException + ************************************************************************/ + + private BufferedReader getReader(HttpURLConnection con) + throws UnsupportedEncodingException, IOException { + String charsetName = parseCharsetFromHeader(con.getContentType()); + BufferedReader r = new BufferedReader(new InputStreamReader(con.getInputStream(), charsetName)); + return r; + } + + /************************************************************************* + * Configure the connection to download from. + * @param url to get the pac file content from + * @return a HTTPUrlConnecion to this url. + * @throws IOException + * @throws MalformedURLException + ************************************************************************/ + + private HttpURLConnection setupHTTPConnection(String url) + throws IOException, MalformedURLException { + HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(Proxy.NO_PROXY); + con.setConnectTimeout(getTimeOut(OVERRIDE_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT)); + con.setReadTimeout(getTimeOut(OVERRIDE_READ_TIMEOUT, DEFAULT_READ_TIMEOUT)); + con.setInstanceFollowRedirects(true); + con.setRequestProperty("accept", "application/x-ns-proxy-autoconfig, */*;q=0.8"); + return con; + } + + /************************************************************************* + * Gets the timeout value from a property or uses the given default value if + * the property cannot be parsed. + * @param overrideProperty the property to define the timeout value in milliseconds + * @param defaultValue the default timeout value in milliseconds. + * @return the value to use. + ************************************************************************/ + + protected int getTimeOut(String overrideProperty, int defaultValue) { + int timeout = defaultValue; + String prop = System.getProperty(overrideProperty); + if (prop != null && prop.trim().length() > 0) { + try { + timeout = Integer.parseInt(prop.trim()); + } catch (NumberFormatException e) { + Logger.log(getClass(), LogLevel.DEBUG, "Invalid override property : {0}={1}", overrideProperty, prop); + // In this case use the default value. + } + } + return timeout; + } + + /************************************************************************* + * Response Content-Type could be something like this: + * application/x-ns-proxy-autoconfig; charset=UTF-8 + * @param contentType header field. + * @return the extracted charset if set else a default charset. + ************************************************************************/ + + String parseCharsetFromHeader(String contentType) { + String result = "ISO-8859-1"; + if (contentType != null) { + String[] paramList = contentType.split(";"); + for (String param : paramList) { + if (param.toLowerCase().trim().startsWith("charset") && param.indexOf("=") != -1) { + result = param.substring(param.indexOf("=")+1).trim(); + } + } + } + return result; + } + + /*************************************************************************** + * @see java.lang.Object#toString() + **************************************************************************/ + @Override + public String toString() { + return this.scriptUrl; + } + + /************************************************************************* + * isScriptValid + * @see com.btr.proxy.selector.pac.PacScriptSource#isScriptValid() + ************************************************************************/ + + public boolean isScriptValid() { + try { + String script = getScriptContent(); + if (script == null || script.trim().length() == 0) { + Logger.log(getClass(), LogLevel.DEBUG, "PAC script is empty. Skipping script!"); + return false; + } + if (script.indexOf("FindProxyForURL") == -1) { + Logger.log(getClass(), LogLevel.DEBUG, "PAC script entry point FindProxyForURL not found. Skipping script!"); + return false; + } + return true; + } catch (IOException e) { + Logger.log(getClass(), LogLevel.DEBUG, "File reading error: {0}", e); + return false; + } + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/DefaultWhiteListParser.java b/src/main/java/com/btr/proxy/selector/whitelist/DefaultWhiteListParser.java new file mode 100644 index 0000000..0f949e1 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/DefaultWhiteListParser.java @@ -0,0 +1,78 @@ +package com.btr.proxy.selector.whitelist; + +import java.util.ArrayList; +import java.util.List; + +import com.btr.proxy.selector.whitelist.HostnameFilter.Mode; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Default implementation for an white list parser. This will support the most + * common forms of filters found in white lists. + * The white list is a comma (or space) separated list of domain names or IP addresses. + * The following section shows some examples. + * + * .mynet.com - Filters all host names ending with .mynet.com + * *.mynet.com - Filters all host names ending with .mynet.com + * www.mynet.* - Filters all host names starting with www.mynet. + * 123.12.32.1 - Filters the IP 123.12.32.1 + * 123.12.32.1/255 - Filters the IP range + * http://www.mynet.com - Filters only HTTP protocol not FTP and no HTTPS + * + * Example of a list: + * + * .mynet.com, *.my-other-net.org, 123.55.23.222, 123.55.23.0/24 + * + * Some info about this topic can be found here: + * http://kb.mozillazine.org/No_proxy_for + * http://technet.microsoft.com/en-us/library/dd361953.aspx + * + * Note that this implementation does not cover all variations of all browsers + * but should cover the most used formats. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class DefaultWhiteListParser implements WhiteListParser { + + /************************************************************************* + * parseWhiteList + * @see com.btr.proxy.selector.whitelist.WhiteListParser#parseWhiteList(java.lang.String) + ************************************************************************/ + + public List<UriFilter> parseWhiteList(String whiteList) { + List<UriFilter> result = new ArrayList<UriFilter>(); + + String[] token = whiteList.split("[, ]+"); + for (int i = 0; i < token.length; i++) { + String tkn = token[i].trim(); + if (isIP4SubnetFilter(tkn)) { + result.add(new IpRangeFilter(tkn)); + continue; + } else + if (tkn.endsWith("*")) { + tkn = tkn.substring(0, tkn.length()-1); + result.add(new HostnameFilter(Mode.BEGINS_WITH, tkn)); + continue; + } else + if (tkn.trim().startsWith("*")) { + tkn = tkn.substring(1); + result.add(new HostnameFilter(Mode.ENDS_WITH, tkn)); + } else { + result.add(new HostnameFilter(Mode.ENDS_WITH, tkn)); + } + } + + return result; + } + + /************************************************************************* + * @param token + * @return + ************************************************************************/ + + private boolean isIP4SubnetFilter(String token) { + return IPv4WithSubnetChecker.isValid(token); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/HostnameFilter.java b/src/main/java/com/btr/proxy/selector/whitelist/HostnameFilter.java new file mode 100644 index 0000000..176eaeb --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/HostnameFilter.java @@ -0,0 +1,93 @@ +package com.btr.proxy.selector.whitelist; + +import java.net.URI; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Tests if a host name of a given URI matches some criteria. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class HostnameFilter implements UriFilter { + + private static final String PROTOCOL_ENDING = "://"; + + public enum Mode {BEGINS_WITH, ENDS_WITH, REGEX} + + private String matchTo; + private String protocolFilter; + private Mode mode; + + /************************************************************************* + * Constructor + * @param mode the filter mode. + * @param matchTo the match criteria. + ************************************************************************/ + + public HostnameFilter(Mode mode, String matchTo) { + super(); + this.mode = mode; + this.matchTo = matchTo.toLowerCase(); + + extractProtocolFilter(); + } + + /************************************************************************* + * Extracts the protocol if one is given to initialize the protocol matcher. + ************************************************************************/ + + private void extractProtocolFilter() { + int protocolIndex = this.matchTo.indexOf(PROTOCOL_ENDING); + if (protocolIndex != -1) { + this.protocolFilter = this.matchTo.substring(0, protocolIndex); + this.matchTo = this.matchTo.substring(protocolIndex+PROTOCOL_ENDING.length()); + } + } + + /************************************************************************* + * accept + * @see com.btr.proxy.util.UriFilter#accept(java.net.URI) + ************************************************************************/ + + public boolean accept(URI uri) { + if (uri == null || uri.getAuthority() == null) { + return false; + } + + if (!isProtocolMatching(uri)) { + return false; + } + + String host = uri.getAuthority(); + + // Strip away port. + int index = host.indexOf(':'); + if (index != -1) { + host = host.substring(0, index); + } + + switch (this.mode) { + case BEGINS_WITH : + return host.toLowerCase().startsWith(this.matchTo); + case ENDS_WITH : + return host.toLowerCase().endsWith(this.matchTo); + case REGEX : + return host.toLowerCase().matches(this.matchTo); + } + return false; + } + + /************************************************************************* + * Applies the protocol filter if available to see if we have a match. + * @param uri to test for a correct protocol. + * @return true if passed else false. + ************************************************************************/ + + private boolean isProtocolMatching(URI uri) { + return this.protocolFilter == null + || uri.getScheme() == null + || uri.getScheme().equalsIgnoreCase(this.protocolFilter); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/IPv4WithSubnetChecker.java b/src/main/java/com/btr/proxy/selector/whitelist/IPv4WithSubnetChecker.java new file mode 100644 index 0000000..b8e713e --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/IPv4WithSubnetChecker.java @@ -0,0 +1,29 @@ +package com.btr.proxy.selector.whitelist; + +import java.util.regex.Pattern; + +/***************************************************************************** + * Checks if the given string is a IP4 range subnet definition + * of the format 192.168.0/24 + * Based on a contribution by Jan Engler + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class IPv4WithSubnetChecker { + + private static Pattern IP_SUB_PATTERN = Pattern.compile( + "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])/(\\d|([12]\\d|3[0-2]))$"); + + /************************************************************************* + * Tests if a given string is of in the correct format for an IP4 subnet mask. + * @param possibleIPAddress to test for valid format. + * @return true if valid else false. + ************************************************************************/ + + public static boolean isValid(String possibleIPAddress) { + return IP_SUB_PATTERN.matcher(possibleIPAddress).matches(); + } +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/IpRangeFilter.java b/src/main/java/com/btr/proxy/selector/whitelist/IpRangeFilter.java new file mode 100644 index 0000000..293f520 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/IpRangeFilter.java @@ -0,0 +1,83 @@ +package com.btr.proxy.selector.whitelist; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; + +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Filters an URI by inspecting it's IP address is in a given range. + * The range as must be defined in CIDR notation. + * e.g. 192.0.2.1/24, + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class IpRangeFilter implements UriFilter { + + private byte[] matchTo; + int numOfBits; + + /************************************************************************* + * Constructor + * @param matchTo the match subnet in CIDR notation. + ************************************************************************/ + + public IpRangeFilter(String matchTo) { + super(); + + String[] parts = matchTo.split("/"); + if (parts.length != 2) { + throw new IllegalArgumentException("IP range is not valid:"+matchTo); + } + + try { + InetAddress address = InetAddress.getByName(parts[0].trim()); + this.matchTo = address.getAddress(); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("IP range is not valid:"+matchTo); + } + + this.numOfBits = Integer.parseInt(parts[1].trim()); + } + + /************************************************************************* + * accept + * @see com.btr.proxy.util.UriFilter#accept(java.net.URI) + ************************************************************************/ + + public boolean accept(URI uri) { + if (uri == null || uri.getHost() == null) { + return false; + } + try { + InetAddress address = InetAddress.getByName(uri.getHost()); + byte[] addr = address.getAddress(); + + // Comparing IP6 against IP4? + if (addr.length != this.matchTo.length) { + return false; + } + + int bit = 0; + for (int nibble = 0; nibble < addr.length; nibble++) { + for (int nibblePos = 7; nibblePos >= 0; nibblePos--) { + int mask = 1 << nibblePos; + if ((this.matchTo[nibble] & mask) != (addr[nibble] & mask)) { + return false; + } + bit++; + if (bit >= this.numOfBits) { + return true; + } + } + } + + } catch (UnknownHostException e) { + // In this case we can not get the IP do not match. + } + return false; + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/ProxyBypassListSelector.java b/src/main/java/com/btr/proxy/selector/whitelist/ProxyBypassListSelector.java new file mode 100644 index 0000000..81e2a7d --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/ProxyBypassListSelector.java @@ -0,0 +1,84 @@ +package com.btr.proxy.selector.whitelist; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Special purpose ProxySelector used as Facade on top of a normal ProxySelector. + * A wrapper that will first check the URI against a white list and if it matches + * it will return DIRECT else it will pass the URI to an delegate for inspection. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxyBypassListSelector extends ProxySelector { + + private ProxySelector delegate; + private List<UriFilter> whiteListFilter; + + + /************************************************************************* + * Constructor + * @param whiteListFilter a list of filters for whitelist URLs. + * @param proxySelector the proxy selector to use. + ************************************************************************/ + + public ProxyBypassListSelector(List<UriFilter> whiteListFilter, ProxySelector proxySelector) { + super(); + if (whiteListFilter == null) { + throw new NullPointerException("Whitelist must not be null."); + } + if (proxySelector == null) { + throw new NullPointerException("ProxySelector must not be null."); + } + + this.delegate = proxySelector; + this.whiteListFilter = whiteListFilter; + } + + + /************************************************************************* + * Constructor + * @param whiteList a list of filters for whitelist URLs as comma/space separated string. + * @param proxySelector the proxy selector to use. + ************************************************************************/ + + public ProxyBypassListSelector(String whiteList, ProxySelector proxySelector) { + this(new DefaultWhiteListParser().parseWhiteList(whiteList), proxySelector); + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + this.delegate.connectFailed(uri, sa, ioe); + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + + // If in white list, use DIRECT connection. + for (UriFilter filter : this.whiteListFilter) { + if (filter.accept(uri)) { + return ProxyUtil.noProxyList(); + } + } + + return this.delegate.select(uri); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/UseProxyWhiteListSelector.java b/src/main/java/com/btr/proxy/selector/whitelist/UseProxyWhiteListSelector.java new file mode 100644 index 0000000..1aface9 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/UseProxyWhiteListSelector.java @@ -0,0 +1,74 @@ +package com.btr.proxy.selector.whitelist; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Special purpose ProxySelector used as Facade on top of a normal ProxySelector. + * A wrapper that will first check the URI against a white list and if it matches + * it will use a proxy as provided by the delegate ProxySelector else it will + * return DIRECT. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class UseProxyWhiteListSelector extends ProxySelector { + + private ProxySelector delegate; + private List<UriFilter> whiteListFilter; + + /************************************************************************* + * Constructor + * @param proxySelector the proxy selector to use. + ************************************************************************/ + + public UseProxyWhiteListSelector(String whiteList, ProxySelector proxySelector) { + super(); + if (whiteList == null) { + throw new NullPointerException("Whitelist must not be null."); + } + if (proxySelector == null) { + throw new NullPointerException("ProxySelector must not be null."); + } + + this.delegate = proxySelector; + + WhiteListParser parser = new DefaultWhiteListParser(); + this.whiteListFilter = parser.parseWhiteList(whiteList); + } + + /************************************************************************* + * connectFailed + * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException) + ************************************************************************/ + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + this.delegate.connectFailed(uri, sa, ioe); + } + + /************************************************************************* + * select + * @see java.net.ProxySelector#select(java.net.URI) + ************************************************************************/ + + @Override + public List<Proxy> select(URI uri) { + + // If in white list, use proxy selector. + for (UriFilter filter : this.whiteListFilter) { + if (filter.accept(uri)) { + return this.delegate.select(uri); + } + } + + return ProxyUtil.noProxyList(); + } + +} diff --git a/src/main/java/com/btr/proxy/selector/whitelist/WhiteListParser.java b/src/main/java/com/btr/proxy/selector/whitelist/WhiteListParser.java new file mode 100644 index 0000000..7cd5fd5 --- /dev/null +++ b/src/main/java/com/btr/proxy/selector/whitelist/WhiteListParser.java @@ -0,0 +1,24 @@ +package com.btr.proxy.selector.whitelist; + +import java.util.List; + +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Interface for an white list parser. This will take an white list string and + * parse it into a list of UriFilter rules. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public interface WhiteListParser { + + /************************************************************************* + * Parses a list of host name and IP filters into UriFilter objects. + * @param whiteList the string to parse. + * @return a list of UriFilters + ************************************************************************/ + + public List<UriFilter> parseWhiteList(String whiteList); + +} |