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.ERROR, "Rhino JavaScript engine support dropped. javax JS should be available unless you're still running Java 1.5."); } } 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 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 URI to be evaluated. * @return Proxy-object list as result of the evaluation. ************************************************************************/ private List findProxy(URI uri) { try { List proxies = new ArrayList(); 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 Proxy 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_HTTP_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); } }