diff options
author | Björn Hagemeister | 2014-11-11 14:40:18 +0100 |
---|---|---|
committer | Björn Hagemeister | 2014-11-11 14:40:18 +0100 |
commit | e70ee5b59306ea37dd0c72603c61b33b1555def9 (patch) | |
tree | e6f09d76449da54463b9b4fa408b7dfba4b4b7bf /src/main/java/com | |
download | proxy-vole-e70ee5b59306ea37dd0c72603c61b33b1555def9.tar.gz proxy-vole-e70ee5b59306ea37dd0c72603c61b33b1555def9.tar.xz proxy-vole-e70ee5b59306ea37dd0c72603c61b33b1555def9.zip |
Added proxy java classes.
Diffstat (limited to 'src/main/java/com')
57 files changed, 8669 insertions, 0 deletions
diff --git a/src/main/java/com/btr/proxy/search/ProxySearch.java b/src/main/java/com/btr/proxy/search/ProxySearch.java new file mode 100644 index 0000000..19db95f --- /dev/null +++ b/src/main/java/com/btr/proxy/search/ProxySearch.java @@ -0,0 +1,258 @@ +package com.btr.proxy.search; + +import java.awt.GraphicsEnvironment; +import java.net.ProxySelector; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import com.btr.proxy.search.browser.firefox.FirefoxProxySearchStrategy; +import com.btr.proxy.search.browser.ie.IEProxySearchStrategy; +import com.btr.proxy.search.desktop.DesktopProxySearchStrategy; +import com.btr.proxy.search.desktop.gnome.GnomeProxySearchStrategy; +import com.btr.proxy.search.desktop.kde.KdeProxySearchStrategy; +import com.btr.proxy.search.desktop.win.WinProxySearchStrategy; +import com.btr.proxy.search.env.EnvProxySearchStrategy; +import com.btr.proxy.search.java.JavaProxySearchStrategy; +import com.btr.proxy.selector.misc.BufferedProxySelector; +import com.btr.proxy.selector.misc.ProxyListFallbackSelector; +import com.btr.proxy.selector.pac.PacProxySelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.PlatformUtil; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.Logger.LogBackEnd; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Main class to setup and initialize the proxy detection system.<br/> + * This class can be used to select a proxy discovery strategy.<br/> + * Implements the "Builder" pattern.<br/> + * Use <code>addStrategy</code> to add one or more search strategies.<br/> + * If you are done call the <code>getProxySelector</code> method. <br/> + * Then the strategies are asked one after the other for a ProxySelector until + * an valid selector is found. <br/> + * <p> + * Invoke the static <code>getDefaultProxySearch</code> method to use a default search strategy. + * </p> + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxySearch implements ProxySearchStrategy { + + private static final int DEFAULT_PAC_CACHE_SIZE = 20; + + private static final long DEFAULT_PAC_CACHE_TTL = 1000*60*10; // 10 Minutes + + private List<ProxySearchStrategy> strategies; + private int pacCacheSize; + private long pacCacheTTL; + + /***************************************************************************** + * Types of proxy detection supported by the builder. + ****************************************************************************/ + + public enum Strategy { + /// Use the platform settings. + OS_DEFAULT, + /// Use the settings of the platforms default browser. + BROWSER, + /// Use Firefox settings + FIREFOX, + /// Use InternetExplorer settings + IE, + /// Use environment variables for proxy settings. + ENV_VAR, + /// Use windows default proxy settings. + WIN, + /// Use KDE desktop default proxy settings. + KDE, + /// Use KDE desktop default proxy settings. + GNOME, + /// Use Java Networking system properties + JAVA + } + + /************************************************************************* + * Constructor + ************************************************************************/ + + public ProxySearch() { + super(); + this.strategies = new ArrayList<ProxySearchStrategy>(); + this.pacCacheSize = DEFAULT_PAC_CACHE_SIZE; + this.pacCacheTTL = DEFAULT_PAC_CACHE_TTL; + } + + /************************************************************************* + * Sets up a ProxySearch that uses a default search strategy suitable for + * every platform. + * @return a ProxySearch initialized with default settings. + ************************************************************************/ + + public static ProxySearch getDefaultProxySearch() { + ProxySearch s = new ProxySearch(); + + // Test if we are a server or a client. + boolean headless = GraphicsEnvironment.isHeadless(); + + if (headless) { + s.addStrategy(Strategy.JAVA); + s.addStrategy(Strategy.OS_DEFAULT); + s.addStrategy(Strategy.ENV_VAR); + } else { + s.addStrategy(Strategy.JAVA); + s.addStrategy(Strategy.BROWSER); + s.addStrategy(Strategy.OS_DEFAULT); + s.addStrategy(Strategy.ENV_VAR); + } + Logger.log(ProxySearch.class, LogLevel.TRACE, "Using default search priority: {0}", s); + + return s; + } + + /************************************************************************* + * Adds an search strategy to the list of proxy searches strategies. + * @param strategy the search strategy to add. + ************************************************************************/ + + public void addStrategy(Strategy strategy) { + switch (strategy) { + case OS_DEFAULT: + this.strategies.add(new DesktopProxySearchStrategy()); + break; + case BROWSER: + this.strategies.add(getDefaultBrowserStrategy()); + break; + case FIREFOX: + this.strategies.add(new FirefoxProxySearchStrategy()); + break; + case IE: + this.strategies.add(new IEProxySearchStrategy()); + break; + case ENV_VAR: + this.strategies.add(new EnvProxySearchStrategy()); + break; + case WIN: + this.strategies.add(new WinProxySearchStrategy()); + break; + case KDE: + this.strategies.add(new KdeProxySearchStrategy()); + break; + case GNOME: + this.strategies.add(new GnomeProxySearchStrategy()); + break; + case JAVA: + this.strategies.add(new JavaProxySearchStrategy()); + break; + default: + throw new IllegalArgumentException("Unknown strategy code!"); + } + } + + /************************************************************************* + * Sets the cache size of the PAC proxy selector cache. + * This defines the number of URLs that are cached together with the PAC + * script result. This improves performance because for URLs that are + * in the cache the script is not executed again. + * You have to set this before you add any strategies that may create a + * PAC script proxy selector. + * @param size of the cache. Set it to 0 to disable caching. + * @param ttl is the time to live of the cache entries as amount of milliseconds. + ************************************************************************/ + + public void setPacCacheSettings(int size, long ttl) { + this.pacCacheSize = size; + this.pacCacheTTL = ttl; + } + + /************************************************************************* + * Gets the search strategy for the platforms default browser. + * @return a ProxySearchStrategy, null if no supported browser was found. + ************************************************************************/ + + private ProxySearchStrategy getDefaultBrowserStrategy() { + switch (PlatformUtil.getDefaultBrowser()) { + case IE: + return new IEProxySearchStrategy(); + case FIREFOX: + return new FirefoxProxySearchStrategy(); + } + return null; + } + + /************************************************************************* + * Gets the proxy selector that will use the configured search order. + * @return a ProxySelector, null if none was found for the current + * builder configuration. + ************************************************************************/ + + public ProxySelector getProxySelector() { + Logger.log(getClass(), LogLevel.TRACE, "Executing search strategies to find proxy selector"); + for (ProxySearchStrategy strat : this.strategies) { + try { + ProxySelector selector = strat.getProxySelector(); + if (selector != null) { + selector = installBufferingAndFallbackBehaviour(selector); + return selector; + } + } catch (ProxyException e) { + Logger.log(getClass(), LogLevel.DEBUG, "Strategy {0} failed trying next one.", e); + // Ignore and try next strategy. + } + } + + return null; + } + + /************************************************************************* + * If it is PAC and we have caching enabled set it here. + * @param selector + * @return + ************************************************************************/ + + private ProxySelector installBufferingAndFallbackBehaviour(ProxySelector selector) { + if (selector instanceof PacProxySelector) { + if (this.pacCacheSize > 0) { + selector = new BufferedProxySelector(this.pacCacheSize, this.pacCacheTTL, selector); + } + selector = new ProxyListFallbackSelector(selector); + } + return selector; + } + + /************************************************************************* + * toString + * @see java.lang.Object#toString() + ************************************************************************/ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Proxy search: "); + for (ProxySearchStrategy strat : this.strategies) { + sb.append(strat); + sb.append(" "); + } + return sb.toString(); + } + + /************************************************************************* + * For testing only. Will print the logging & proxy information to the console. + * @param args the command line arguments. + ************************************************************************/ + + public static void main(String[] args) { + ProxySearch ps = ProxySearch.getDefaultProxySearch(); + Logger.setBackend(new LogBackEnd() { + + public void log(Class<?> clazz, LogLevel loglevel, String msg, + Object... params) { + System.out.println(MessageFormat.format(msg, params)); + } + + public boolean isLogginEnabled(LogLevel logLevel) { + return true; + } + }); + ps.getProxySelector(); + } + +} diff --git a/src/main/java/com/btr/proxy/search/ProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/ProxySearchStrategy.java new file mode 100644 index 0000000..6f14c28 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/ProxySearchStrategy.java @@ -0,0 +1,22 @@ +package com.btr.proxy.search; + +import java.net.ProxySelector; + +import com.btr.proxy.util.ProxyException; + +/***************************************************************************** + * Interface for a proxy search strategy. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public interface ProxySearchStrategy { + + /************************************************************************* + * Gets the a ProxySelector found by applying the search strategy. + * @return a ProxySelector, null if none is found. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException; + +} diff --git a/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProfileSource.java b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProfileSource.java new file mode 100644 index 0000000..8517074 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProfileSource.java @@ -0,0 +1,23 @@ +package com.btr.proxy.search.browser.firefox; + +import java.io.File; +import java.io.IOException; + +/***************************************************************************** + * A profile source for Firefox profiles. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +interface FirefoxProfileSource { + + /************************************************************************* + * Gets a profile folder found on the current system. + * If multiple profile folders are available the "default" profile is chosen. + * @return a profile folder. + * @throws IOException on error. + ************************************************************************/ + + public File getProfileFolder() throws IOException; + +} diff --git a/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProxySearchStrategy.java new file mode 100644 index 0000000..30c3be1 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxProxySearchStrategy.java @@ -0,0 +1,267 @@ +package com.btr.proxy.search.browser.firefox; + +import java.io.IOException; +import java.net.ProxySelector; +import java.util.Properties; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.desktop.DesktopProxySearchStrategy; +import com.btr.proxy.search.wpad.WpadProxySearchStrategy; +import com.btr.proxy.selector.direct.NoProxySelector; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.fixed.FixedSocksSelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.PlatformUtil; +import com.btr.proxy.util.PlatformUtil.Platform; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; + +/***************************************************************************** + * Loads the Firefox3 proxy settings from the users Firefox3 settings. + * This will load the file <i>prefs.js</i> that is located in the + * <p> + * <i>.mozilla/firefox/(profile)/</i> folder. + * </p> + * + * See <a href="https://developer.mozilla.org/En/Mozilla_Networking_Preferences">Mozilla_Networking_Preferences</a> + * for an explanation of the proxy settings. + * <p> + * The following settings are extracted from + * this file: + * </p> + * Some generic settings:<br/> + * <ul> + * <li><i>network.proxy.type</i> -> n/a = use system settings, 0 = direct, 1 = Fixed proxy settings, 2 = proxy script (PAC), 3 = also direct , 4 = auto detect (WPAD)</li> + * <li><i>network.proxy.share_proxy_settings</i> -> true = use same proxy for all protocols</li> + * <li><i>network.proxy.no_proxies_on</i> -> a comma separated ignore list. </li> + * <li><i>network.proxy.autoconfig_url</i> -> a URL to an proxy configuration script</li> + * </ul> + * Host names and ports per protocol are stored in the following settings: + * <ul> + * <li><i>network.proxy.http</i></li> + * <li><i>network.proxy.http_port</i></li> + * <li><i>network.proxy.ssl</i></li> + * <li><i>network.proxy.ssl_port</i></li> + * <li><i>network.proxy.ftp</i></li> + * <li><i>network.proxy.ftp_port</i></li> + * <li><i>network.proxy.gopher</i></li> + * <li><i>network.proxy.gopher_port</i></li> + * <li><i>network.proxy.socks</i></li> + * <li><i>network.proxy.socks_port</i></li> + * <li><i>network.proxy.socks_version</i> -> 4 or 5</li> + * </u> + * <p> + * Note that if there are more than one profile the first profile found will be used. + * </p> + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class FirefoxProxySearchStrategy implements ProxySearchStrategy { + + private FirefoxProfileSource profileScanner; + private FirefoxSettingParser settingsParser; + + /************************************************************************* + * ProxySelector + * @see java.net.ProxySelector#ProxySelector() + ************************************************************************/ + + public FirefoxProxySearchStrategy() { + super(); + if (PlatformUtil.getCurrentPlattform() == Platform.WIN) { + this.profileScanner = new WinFirefoxProfileSource(); + } else { + this.profileScanner = new LinuxFirefoxProfileSource(); + } + this.settingsParser = new FirefoxSettingParser(); + } + + /************************************************************************* + * Loads the proxy settings and initializes a proxy selector for the firefox + * proxy settings. + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException on file reading error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + Logger.log(getClass(), LogLevel.TRACE, "Detecting Firefox settings."); + + Properties settings = readSettings(); + + ProxySelector result = null; + int type = Integer.parseInt(settings.getProperty("network.proxy.type", "-1")); + switch (type) { + case -1: // Use system settings + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses system settings"); + result = new DesktopProxySearchStrategy().getProxySelector(); + break; + case 0: // Use no proxy + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses no proxy"); + result = NoProxySelector.getInstance(); + break; + case 1: // Fixed settings + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses manual settings"); + result = setupFixedProxySelector(settings); + break; + case 2: // PAC Script + String pacScriptUrl = settings.getProperty("network.proxy.autoconfig_url", ""); + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses script (PAC) {0}", pacScriptUrl); + result = ProxyUtil.buildPacSelectorForUrl(pacScriptUrl); + break; + case 3: // Backward compatibility to netscape. + Logger.log(getClass(), LogLevel.TRACE, "Netscape compability mode -> uses no proxy"); + result = NoProxySelector.getInstance(); + break; + case 4: // WPAD auto config + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses automatic detection (WPAD)"); + result = new WpadProxySearchStrategy().getProxySelector(); + break; + default: + break; + } + + // Wrap in white list filter. + String noProxyList = settings.getProperty("network.proxy.no_proxies_on", null); + if (result != null && noProxyList != null && noProxyList.trim().length() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox uses proxy bypass list for: {0}", noProxyList); + result = new ProxyBypassListSelector(noProxyList, result); + } + + return result; + } + + /************************************************************************* + * Reads the settings file and stores all settings in a Properties map. + * @return the parsed settings. + * @throws ProxyException on read error. + ************************************************************************/ + + public Properties readSettings() throws ProxyException { + try { + Properties settings = this.settingsParser.parseSettings(this.profileScanner); + return settings; + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Error parsing settings", e); + throw new ProxyException(e); + } + } + + /************************************************************************* + * Parse the fixed proxy settings and build an ProxySelector for this a + * chained configuration. + * @param settings the proxy settings to evaluate. + ************************************************************************/ + + private ProxySelector setupFixedProxySelector(Properties settings) { + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + installHttpProxy(ps, settings); + if (isProxyShared(settings)) { + installSharedProxy(ps); + } else { + installFtpProxy(ps, settings); + installSecureProxy(ps, settings); + installSocksProxy(ps, settings); + } + return ps; + } + + /************************************************************************* + * @param ps + * @param settings + * @throws NumberFormatException + ************************************************************************/ + + private void installFtpProxy(ProtocolDispatchSelector ps, + Properties settings) throws NumberFormatException { + installSelectorForProtocol(ps, settings, "ftp"); + } + + /************************************************************************* + * @param ps + * @param settings + * @throws NumberFormatException + ************************************************************************/ + + private void installHttpProxy(ProtocolDispatchSelector ps, + Properties settings) throws NumberFormatException { + installSelectorForProtocol(ps, settings, "http"); + } + + /************************************************************************* + * @param settings + * @return + ************************************************************************/ + + private boolean isProxyShared(Properties settings) { + return Boolean.TRUE.toString().equals(settings.getProperty("network.proxy.share_proxy_settings", "false").toLowerCase()); + } + + /************************************************************************* + * @param ps + ************************************************************************/ + + private void installSharedProxy(ProtocolDispatchSelector ps) { + ProxySelector httpProxy = ps.getSelector("http"); + if (httpProxy != null) { + ps.setFallbackSelector(httpProxy); + } + } + + /************************************************************************* + * @param ps + * @param settings + * @throws NumberFormatException + ************************************************************************/ + + private void installSocksProxy(ProtocolDispatchSelector ps, + Properties settings) throws NumberFormatException { + String proxyHost = settings.getProperty("network.proxy.socks", null); + int proxyPort = Integer.parseInt(settings.getProperty("network.proxy.socks_port", "0")); + if (proxyHost != null && proxyPort != 0) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox socks proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("socks", new FixedSocksSelector(proxyHost, proxyPort)); + } + } + + /************************************************************************* + * @param ps + * @param settings + * @throws NumberFormatException + ************************************************************************/ + + private void installSecureProxy(ProtocolDispatchSelector ps, + Properties settings) throws NumberFormatException { + String proxyHost = settings.getProperty("network.proxy.ssl", null); + int proxyPort = Integer.parseInt(settings.getProperty("network.proxy.ssl_port", "0")); + if (proxyHost != null && proxyPort != 0) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox secure proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("https", new FixedProxySelector(proxyHost, proxyPort)); + ps.setSelector("sftp", new FixedProxySelector(proxyHost, proxyPort)); + } + } + + /************************************************************************* + * Installs a proxy selector for the given protocol when settings are + * available. + * @param ps a ProtocolDispatchSelector to configure. + * @param settings to read the config from. + * @param protocol to configure. + * @throws NumberFormatException + ************************************************************************/ + + private void installSelectorForProtocol(ProtocolDispatchSelector ps, + Properties settings, String protocol) throws NumberFormatException { + + String proxyHost = settings.getProperty("network.proxy."+protocol, null); + int proxyPort = Integer.parseInt(settings.getProperty("network.proxy."+protocol+"_port", "0")); + if (proxyHost != null && proxyPort != 0) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox "+protocol+" proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector(protocol, new FixedProxySelector(proxyHost, proxyPort)); + } + } + + +} diff --git a/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxSettingParser.java b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxSettingParser.java new file mode 100644 index 0000000..b793299 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/firefox/FirefoxSettingParser.java @@ -0,0 +1,79 @@ +package com.btr.proxy.search.browser.firefox; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Properties; + +/***************************************************************************** + * Parser for the Firefox settings file. + * Will extract all relevant proxy settings form the configuration file. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +class FirefoxSettingParser { + + /************************************************************************* + * Constructor + ************************************************************************/ + + public FirefoxSettingParser() { + super(); + } + + /************************************************************************* + * Parse the settings file and extract all network.proxy.* settings from it. + * @param source of the Firefox profiles. + * @return the parsed properties. + * @throws IOException on read error. + ************************************************************************/ + + public Properties parseSettings(FirefoxProfileSource source) throws IOException { + // Search settings folder + File profileFolder = source.getProfileFolder(); + + // Read settings from file + File settingsFile = new File(profileFolder, "prefs.js"); + + BufferedReader fin = new BufferedReader( + new InputStreamReader( + new FileInputStream(settingsFile))); + + Properties result = new Properties(); + try { + String line = fin.readLine(); + while (line != null) { + line = line.trim(); + if (line.startsWith("user_pref(\"network.proxy")) { + line = line.substring(10, line.length()-2); + int index = line.indexOf(","); + String key = line.substring(0, index).trim(); + if (key.startsWith("\"")) { + key = key.substring(1); + } + if (key.endsWith("\"")) { + key = key.substring(0, key.length()-1); + } + String value = line.substring(index+1).trim(); + if (value.startsWith("\"")) { + value = value.substring(1); + } + if (value.endsWith("\"")) { + value = value.substring(0, value.length()-1); + } + result.put(key, value); + } + line = fin.readLine(); + } + } finally { + fin.close(); + } + + return result; + } + + +} diff --git a/src/main/java/com/btr/proxy/search/browser/firefox/LinuxFirefoxProfileSource.java b/src/main/java/com/btr/proxy/search/browser/firefox/LinuxFirefoxProfileSource.java new file mode 100644 index 0000000..38b1553 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/firefox/LinuxFirefoxProfileSource.java @@ -0,0 +1,46 @@ +package com.btr.proxy.search.browser.firefox; + +import java.io.File; + +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Searches for Firefox profile on an Linux / Unix base system. + * This will scan the <i>.mozilla</i> folder in the users home directory to find the + * profiles. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +class LinuxFirefoxProfileSource implements FirefoxProfileSource { + + /************************************************************************* + * Get profile folder for the Linux Firefox profile + ************************************************************************/ + + public File getProfileFolder() { + File userDir = new File(System.getProperty("user.home")); + File cfgDir = new File(userDir, ".mozilla"+File.separator+"firefox"+File.separator); + if (!cfgDir.exists()) { + Logger.log(getClass(), LogLevel.DEBUG, "Firefox settings folder not found!"); + return null; + } + File[] profiles = cfgDir.listFiles(); + if (profiles == null || profiles.length == 0) { + Logger.log(getClass(), LogLevel.DEBUG, "Firefox settings folder not found!"); + return null; + } + for (File p : profiles) { + if (p.getName().endsWith(".default")) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox settings folder is {0}", p); + return p; + } + } + + // Fall back -> take the first one found. + Logger.log(getClass(), LogLevel.TRACE, "Firefox settings folder is {0}", profiles[0]); + return profiles[0]; + } + +} diff --git a/src/main/java/com/btr/proxy/search/browser/firefox/WinFirefoxProfileSource.java b/src/main/java/com/btr/proxy/search/browser/firefox/WinFirefoxProfileSource.java new file mode 100644 index 0000000..9189585 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/firefox/WinFirefoxProfileSource.java @@ -0,0 +1,71 @@ +package com.btr.proxy.search.browser.firefox; + +import java.io.File; +import java.io.IOException; + +import com.btr.proxy.search.desktop.win.Win32ProxyUtils; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Finds the Firefox profile on Windows platforms. + * On Windows the profiles are located in the users appdata directory under: + * <p> + * <i>Mozilla\Firefox\Profiles\</i> + * </p> + * The location of the appdata folder is read from the windows registry. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +class WinFirefoxProfileSource implements FirefoxProfileSource { + + /************************************************************************* + * Constructor + ************************************************************************/ + + public WinFirefoxProfileSource() { + super(); + } + + /************************************************************************* + * Reads the current location of the app data folder from the registry. + * @return a path to the folder. + ************************************************************************/ + + private String getAppFolder() { + return new Win32ProxyUtils().readUserHomedir(); + } + + /************************************************************************* + * Get profile folder for the Windows Firefox profile + * @throws IOException on error. + ************************************************************************/ + + public File getProfileFolder() throws IOException { + + File appDataDir = new File(getAppFolder()); + File cfgDir = new File(appDataDir, "Mozilla"+File.separator+"Firefox"+File.separator+"Profiles"); + + if (!cfgDir.exists()) { + Logger.log(getClass(), LogLevel.DEBUG, "Firefox windows settings folder not found."); + return null; + } + File[] profiles = cfgDir.listFiles(); + if (profiles == null || profiles.length == 0) { + Logger.log(getClass(), LogLevel.DEBUG, "Firefox windows settings folder not found."); + return null; + } + for (File p : profiles) { + if (p.getName().endsWith(".default")) { + Logger.log(getClass(), LogLevel.TRACE, "Firefox windows settings folder is {0}.", p); + return p; + } + } + + // Fall back -> take the first one found. + Logger.log(getClass(), LogLevel.TRACE, "Firefox windows settings folder is {0}.", profiles[0]); + return profiles[0]; + } + +} diff --git a/src/main/java/com/btr/proxy/search/browser/ie/IELocalByPassFilter.java b/src/main/java/com/btr/proxy/search/browser/ie/IELocalByPassFilter.java new file mode 100644 index 0000000..fb81d6f --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/ie/IELocalByPassFilter.java @@ -0,0 +1,28 @@ +package com.btr.proxy.search.browser.ie; + +import java.net.URI; + +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class IELocalByPassFilter implements UriFilter { + + /************************************************************************* + * accept + * @see com.btr.proxy.util.UriFilter#accept(java.net.URI) + ************************************************************************/ + + public boolean accept(URI uri) { + if (uri == null) { + return false; + } + String host = uri.getAuthority(); + return host != null && !host.contains("."); + } + +} + diff --git a/src/main/java/com/btr/proxy/search/browser/ie/IEProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/browser/ie/IEProxySearchStrategy.java new file mode 100644 index 0000000..2a9b078 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/browser/ie/IEProxySearchStrategy.java @@ -0,0 +1,213 @@ +package com.btr.proxy.search.browser.ie; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.ProxySelector; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.desktop.win.Win32IESettings; +import com.btr.proxy.search.desktop.win.Win32ProxyUtils; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.pac.PacProxySelector; +import com.btr.proxy.selector.pac.UrlPacScriptSource; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Extracts the proxy settings for Microsoft Internet Explorer. + * The settings are read by invoking native Windows API methods. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class IEProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * getProxySelector + * @see com.btr.proxy.search.ProxySearchStrategy#getProxySelector() + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + + Logger.log(getClass(), LogLevel.TRACE, "Detecting IE proxy settings"); + + Win32IESettings ieSettings = readSettings(); + + ProxySelector result = createPacSelector(ieSettings); + if (result == null) { + result = createFixedProxySelector(ieSettings); + } + return result; + } + + /************************************************************************* + * Loads the settings from the windows registry. + * @return WinIESettings containing all proxy settings. + ************************************************************************/ + + public Win32IESettings readSettings() { + Win32IESettings ieSettings = new Win32ProxyUtils().winHttpGetIEProxyConfigForCurrentUser(); + return ieSettings; + } + + /************************************************************************* + * Parses the settings and creates an PAC ProxySelector for it. + * @param ieSettings the IE settings to use. + * @return a PacProxySelector the selector or null. + ************************************************************************/ + + private PacProxySelector createPacSelector(Win32IESettings ieSettings) { + String pacUrl = null; + + if (ieSettings.isAutoDetect()) { + Logger.log(getClass(), LogLevel.TRACE, "Autodetecting script URL."); + // This will take some time. + pacUrl = new Win32ProxyUtils().winHttpDetectAutoProxyConfigUrl( + Win32ProxyUtils.WINHTTP_AUTO_DETECT_TYPE_DHCP+ + Win32ProxyUtils.WINHTTP_AUTO_DETECT_TYPE_DNS_A); + } + if (pacUrl == null) { + pacUrl = ieSettings.getAutoConfigUrl(); + } + if (pacUrl != null && pacUrl.trim().length() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "IE uses script: "+pacUrl); + + // Fix for issue 9 + // If the IE has a file URL and it only starts has 2 slashes, + // add a third so it can be properly converted to the URL class + if (pacUrl.startsWith("file://") && !pacUrl.startsWith("file:///")) { + pacUrl = "file:///" + pacUrl.substring(7); + } + return ProxyUtil.buildPacSelectorForUrl(pacUrl); + } + + return null; + } + + /************************************************************************* + * Parses the proxy settings into an ProxySelector. + * @param ieSettings the settings to use. + * @return a ProxySelector, null if no settings are set. + * @throws ProxyException on error. + ************************************************************************/ + + private ProxySelector createFixedProxySelector(Win32IESettings ieSettings) throws ProxyException { + String proxyString = ieSettings.getProxy(); + String bypassList = ieSettings.getProxyBypass(); + if (proxyString == null) { + return null; + } + Logger.log(getClass(), LogLevel.TRACE, + "IE uses manual settings: {0} with bypass list: {1}", proxyString, bypassList); + + Properties p = parseProxyList(proxyString); + + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + addSelectorForProtocol(p, "http", ps); + addSelectorForProtocol(p, "https", ps); + addSelectorForProtocol(p, "ftp", ps); + addSelectorForProtocol(p, "gopher", ps); + addSelectorForProtocol(p, "socks", ps); + addFallbackSelector(p, ps); + + ProxySelector result = setByPassListOnSelector(bypassList, ps); + return result; + } + + /************************************************************************* + * @param bypassList + * @param ps + * @return + ************************************************************************/ + + private ProxySelector setByPassListOnSelector(String bypassList, + ProtocolDispatchSelector ps) { + if (bypassList != null && bypassList.trim().length() > 0) { + ProxyBypassListSelector result; + if ("<local>".equals(bypassList.trim())) { + result = buildLocalBypassSelector(ps); + } else { + bypassList = bypassList.replace(';', ','); + result = new ProxyBypassListSelector(bypassList, ps); + } + return result; + } + return ps; + } + + /************************************************************************* + * @param ps + * @return + ************************************************************************/ + + private ProxyBypassListSelector buildLocalBypassSelector( + ProtocolDispatchSelector ps) { + List<UriFilter> localBypassFilter = new ArrayList<UriFilter>(); + localBypassFilter.add(new IELocalByPassFilter()); + return new ProxyBypassListSelector(localBypassFilter, ps); + } + + /************************************************************************* + * Installs a fallback selector that is used whenever no protocol specific + * selector is defined. + * @param settings to take the proxy settings from. + * @param ps to install the created selector on. + ************************************************************************/ + + private void addFallbackSelector(Properties settings, ProtocolDispatchSelector ps) { + String proxy = settings.getProperty("default"); + if (proxy != null) { + ps.setFallbackSelector(ProxyUtil.parseProxySettings(proxy)); + } + } + + /************************************************************************* + * Creates a selector for a given protocol. The proxy will be taken + * from the settings and installed on the dispatch selector. + * @param settings to take the proxy settings from. + * @param protocol to create a selector for. + * @param ps to install the created selector on. + ************************************************************************/ + + private void addSelectorForProtocol(Properties settings, String protocol, ProtocolDispatchSelector ps) { + String proxy = settings.getProperty(protocol); + if (proxy != null) { + FixedProxySelector protocolSelector = ProxyUtil.parseProxySettings(proxy); + ps.setSelector(protocol, protocolSelector); + } + } + + /************************************************************************* + * Parses the proxy list and splits it by protocol. + * @param proxyString the proxy list string + * @return Properties with separated settings. + * @throws ProxyException on parse error. + ************************************************************************/ + + private Properties parseProxyList(String proxyString) throws ProxyException { + Properties p = new Properties(); + if (proxyString.indexOf('=') == -1) { + p.setProperty("default", proxyString); + } else { + try { + proxyString = proxyString.replace(';', '\n'); + p.load(new ByteArrayInputStream(proxyString.getBytes("ISO-8859-1"))); + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, + "Error reading IE settings as properties: {0}", e); + + throw new ProxyException(e); + } + } + return p; + } + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/DesktopProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/desktop/DesktopProxySearchStrategy.java new file mode 100644 index 0000000..8a5a5d8 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/DesktopProxySearchStrategy.java @@ -0,0 +1,69 @@ +package com.btr.proxy.search.desktop; + +import java.net.ProxySelector; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.desktop.gnome.GnomeProxySearchStrategy; +import com.btr.proxy.search.desktop.kde.KdeProxySearchStrategy; +import com.btr.proxy.search.desktop.osx.OsxProxySearchStrategy; +import com.btr.proxy.search.desktop.win.WinProxySearchStrategy; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.PlatformUtil; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.PlatformUtil.Desktop; +import com.btr.proxy.util.PlatformUtil.Platform; + +/***************************************************************************** + * This search provider will try to find out on which desktop platform we + * are running and then will initialize the default proxy search. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class DesktopProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * Gets the default ProxySelector for the current platform. + * @return a ProxySelector, null if none is found. + * @throws ProxyException on error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + ProxySearchStrategy strategy = findDesktopSpecificStrategy(); + return strategy == null? null : strategy.getProxySelector(); + } + + /************************************************************************* + * Determine the desktop and create a strategy for it. + * @return a desktop specific strategy, null if none was found. + ************************************************************************/ + + private ProxySearchStrategy findDesktopSpecificStrategy() { + Platform pf = PlatformUtil.getCurrentPlattform(); + Desktop dt = PlatformUtil.getCurrentDesktop(); + + Logger.log(getClass(), LogLevel.TRACE, "Detecting system settings."); + + ProxySearchStrategy strategy = null; + + if (pf == Platform.WIN) { + Logger.log(getClass(), LogLevel.TRACE, "We are running on Windows."); + strategy = new WinProxySearchStrategy(); + } else + if (dt == Desktop.KDE) { + Logger.log(getClass(), LogLevel.TRACE, "We are running on KDE."); + strategy = new KdeProxySearchStrategy(); + } else + if (dt == Desktop.GNOME) { + Logger.log(getClass(), LogLevel.TRACE, "We are running on Gnome."); + strategy = new GnomeProxySearchStrategy(); + } else + if (dt == Desktop.MAC_OS) { + Logger.log(getClass(), LogLevel.TRACE, "We are running on Mac OSX."); + strategy = new OsxProxySearchStrategy(); + } + return strategy; + } + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/gnome/GnomeProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/desktop/gnome/GnomeProxySearchStrategy.java new file mode 100644 index 0000000..240fb39 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/gnome/GnomeProxySearchStrategy.java @@ -0,0 +1,353 @@ +package com.btr.proxy.search.desktop.gnome; + +import java.io.File; +import java.io.IOException; +import java.net.ProxySelector; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.selector.direct.NoProxySelector; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.EmptyXMLResolver; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Loads the Gnome proxy settings from the Gnome GConf settings. + * <p> + * The following settings are extracted from the configuration that is stored + * in <i>.gconf</i> folder found in the user's home directory: + * </p> + * <ul> + * <li><i>/system/http_proxy/use_http_proxy</i> -> bool used only by gnome-vfs </li> + * <li><i>/system/http_proxy/host</i> -> string "my-proxy.example.com" without "http://"</li> + * <li><i>/system/http_proxy/port</i> -> int</li> + * <li><i>/system/http_proxy/use_authentication</i> -> bool</li> + * <li><i>/system/http_proxy/authentication_user</i> -> string</li> + * <li><i>/system/http_proxy/authentication_password</i> -> string</li> + * <li><i>/system/http_proxy/ignore_hosts</i> -> list-of-string</li> + * <li><i>/system/proxy/mode</i> -> string THIS IS THE CANONICAL KEY; SEE BELOW</li> + * <li><i>/system/proxy/secure_host</i> -> string "proxy-for-https.example.com"</li> + * <li><i>/system/proxy/secure_port</i> -> int</li> + * <li><i>/system/proxy/ftp_host</i> -> string "proxy-for-ftp.example.com"</li> + * <li><i>/system/proxy/ftp_port</i> -> int</li> + * <li><i>/system/proxy/socks_host</i> -> string "proxy-for-socks.example.com"</li> + * <li><i>/system/proxy/socks_port</i> -> int</li> + * <li><i>/system/proxy/autoconfig_url</i> -> string "http://proxy-autoconfig.example.com"</li> + * </ul> + * <i>/system/proxy/mode</i> can be either:<br/> + * "none" -> No proxy is used<br/> + * "manual" -> The user's configuration values are used (/system/http_proxy/{host,port,etc.})<br/> + * "auto" -> The "/system/proxy/autoconfig_url" key is used <br/> + * <p> + * GNOME Proxy_configuration settings are explained + * <a href="http://en.opensuse.org/GNOME/Proxy_configuration">here</a> in detail + * </p> + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class GnomeProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * ProxySelector + * @see java.net.ProxySelector#ProxySelector() + ************************************************************************/ + + public GnomeProxySearchStrategy() { + super(); + } + + /************************************************************************* + * Loads the proxy settings and initializes a proxy selector for the Gnome + * proxy settings. + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException on file reading error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + + Logger.log(getClass(), LogLevel.TRACE, "Detecting Gnome proxy settings"); + + Properties settings = readSettings(); + + String type = settings.getProperty("/system/proxy/mode"); + ProxySelector result = null; + if (type == null) { + String useProxy = settings.getProperty("/system/http_proxy/use_http_proxy"); + if (useProxy == null) { + return null; + } + type = Boolean.parseBoolean(useProxy)?"manual":"none"; + } + + if ("none".equals(type)) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome uses no proxy"); + result = NoProxySelector.getInstance(); + } + if ("manual".equals(type)) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome uses manual proxy settings"); + result = setupFixedProxySelector(settings); + } + if ("auto".equals(type)) { + String pacScriptUrl = settings.getProperty("/system/proxy/autoconfig_url", ""); + Logger.log(getClass(), LogLevel.TRACE, "Gnome uses autodetect script {0}", pacScriptUrl); + result = ProxyUtil.buildPacSelectorForUrl(pacScriptUrl); + } + + // Wrap into white-list filter? + String noProxyList = settings.getProperty("/system/http_proxy/ignore_hosts", null); + if (result != null && noProxyList != null && noProxyList.trim().length() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome uses proxy bypass list: {0}", noProxyList); + result = new ProxyBypassListSelector(noProxyList, result); + } + + return result; + } + + /************************************************************************* + * Load the proxy settings from the gconf settings XML file. + * @return the loaded settings stored in a properties object. + * @throws ProxyException on processing error. + ************************************************************************/ + + public Properties readSettings() throws ProxyException { + Properties settings = new Properties(); + try { + parseSettings("/system/proxy/", settings); + parseSettings("/system/http_proxy/", settings); + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Gnome settings file error.", e); + throw new ProxyException(e); + } + return settings; + } + + /************************************************************************* + * Finds the Gnome GConf settings file. + * @param context the gconf context to parse. + * @return a file or null if does not exist. + ************************************************************************/ + + private File findSettingsFile(String context) { + // Normally we should inspect /etc/gconf/<version>/path to find out where the actual file is. + // But for normal systems this is always stored in .gconf folder in the user's home directory. + File userDir = new File(System.getProperty("user.home")); + + // Build directory path for context + StringBuilder path = new StringBuilder(); + String[] parts = context.split("/"); + for (String part : parts) { + path.append(part); + path.append(File.separator); + } + + File settingsFile = new File(userDir, ".gconf"+File.separator+path.toString()+"%gconf.xml"); + if (!settingsFile.exists()) { + Logger.log(getClass(), LogLevel.WARNING, "Gnome settings: {0} not found.", settingsFile); + return null; + } + return settingsFile; + } + + /************************************************************************* + * Parse the fixed proxy settings and build an ProxySelector for this a + * chained configuration. + * @param settings the proxy settings to evaluate. + ************************************************************************/ + + private ProxySelector setupFixedProxySelector(Properties settings) { + if (!hasProxySettings(settings)) { + return null; + } + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + installHttpSelector(settings, ps); + + if (useForAllProtocols(settings)) { + ps.setFallbackSelector(ps.getSelector("http")); + } else { + installSecureSelector(settings, ps); + installFtpSelector(settings, ps); + installSocksSelector(settings, ps); + } + return ps; + } + + /************************************************************************* + * Check if the http proxy should also be used for all other protocols. + * @param settings to inspect. + * @return true if only one proxy is configured else false. + ************************************************************************/ + + private boolean useForAllProtocols(Properties settings) { + return Boolean.parseBoolean( + settings.getProperty("/system/http_proxy/use_same_proxy", "false")); + } + + /************************************************************************* + * Checks if we have Proxy configuration settings in the properties. + * @param settings to inspect. + * @return true if we have found Proxy settings. + ************************************************************************/ + + private boolean hasProxySettings(Properties settings) { + String proxyHost = settings.getProperty("/system/http_proxy/host", null); + return proxyHost != null && proxyHost.length() > 0; + } + + /************************************************************************* + * Install a http proxy from the given settings. + * @param settings to inspect + * @param ps the dispatch selector to configure. + * @throws NumberFormatException + ************************************************************************/ + + private void installHttpSelector(Properties settings, + ProtocolDispatchSelector ps) throws NumberFormatException { + String proxyHost = settings.getProperty("/system/http_proxy/host", null); + int proxyPort = Integer.parseInt(settings.getProperty("/system/http_proxy/port", "0").trim()); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome http proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("http", new FixedProxySelector(proxyHost.trim(), proxyPort)); + } + } + + /************************************************************************* + * Install a socks proxy from the given settings. + * @param settings to inspect + * @param ps the dispatch selector to configure. + * @throws NumberFormatException + ************************************************************************/ + + private void installSocksSelector(Properties settings, + ProtocolDispatchSelector ps) throws NumberFormatException { + String proxyHost = settings.getProperty("/system/proxy/socks_host", null); + int proxyPort = Integer.parseInt(settings.getProperty("/system/proxy/socks_port", "0").trim()); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome socks proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("socks", new FixedProxySelector(proxyHost.trim(), proxyPort)); + } + } + + /************************************************************************* + * @param settings + * @param ps + * @throws NumberFormatException + ************************************************************************/ + + private void installFtpSelector(Properties settings, + ProtocolDispatchSelector ps) throws NumberFormatException { + String proxyHost = settings.getProperty("/system/proxy/ftp_host", null); + int proxyPort = Integer.parseInt(settings.getProperty("/system/proxy/ftp_port", "0").trim()); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome ftp proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("ftp", new FixedProxySelector(proxyHost.trim(), proxyPort)); + } + } + + /************************************************************************* + * @param settings + * @param ps + * @throws NumberFormatException + ************************************************************************/ + + + private void installSecureSelector(Properties settings, + ProtocolDispatchSelector ps) throws NumberFormatException { + String proxyHost = settings.getProperty("/system/proxy/secure_host", null); + int proxyPort = Integer.parseInt(settings.getProperty("/system/proxy/secure_port", "0").trim()); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Gnome secure proxy is {0}:{1}", proxyHost, proxyPort); + ps.setSelector("https", new FixedProxySelector(proxyHost.trim(), proxyPort)); + ps.setSelector("sftp", new FixedProxySelector(proxyHost.trim(), proxyPort)); + } + } + + /************************************************************************* + * Parse the settings file and extract all network.proxy.* settings from it. + * @param context the gconf context to parse. + * @param settings the settings object to fill. + * @return the parsed properties. + * @throws IOException on read error. + ************************************************************************/ + + private Properties parseSettings(String context, Properties settings) throws IOException { + + // Read settings from file + File settingsFile = findSettingsFile(context); + if (settingsFile == null) { + return settings; + } + + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + documentBuilder.setEntityResolver(new EmptyXMLResolver()); + Document doc = documentBuilder.parse(settingsFile); + Element root = doc.getDocumentElement(); + Node entry = root.getFirstChild(); + while (entry != null) { + if ("entry".equals(entry.getNodeName()) && entry instanceof Element) { + String entryName = ((Element)entry).getAttribute("name"); + settings.setProperty(context+entryName, getEntryValue((Element) entry)); + } + entry = entry.getNextSibling(); + } + } catch (SAXException e) { + Logger.log(getClass(), LogLevel.ERROR, "Gnome settings parse error", e); + throw new IOException(e.getMessage()); + } catch (ParserConfigurationException e) { + Logger.log(getClass(), LogLevel.ERROR, "Gnome settings parse error", e); + throw new IOException(e.getMessage()); + } + + return settings; + } + + /************************************************************************* + * Parse an entry value from a given entry node. + * @param entry the XML node to inspect. + * @return the value, null if it has no value. + ************************************************************************/ + + private String getEntryValue(Element entry) { + String type = entry.getAttribute("type"); + + if ("int".equals(type) || "bool".equals(type)) { + return entry.getAttribute("value"); + } + if ("string".equals(type)) { + NodeList list = entry.getElementsByTagName("stringvalue"); + if (list.getLength() > 0) { + return list.item(0).getTextContent(); + } + } + if ("list".equals(type)) { + StringBuilder result = new StringBuilder(); + NodeList list = entry.getElementsByTagName("li"); + + // Build comma separated list of items + for (int i = 0; i < list.getLength(); i++) { + if (result.length() > 0) { + result.append(","); + } + result.append(getEntryValue((Element) list.item(i))); + } + return result.toString(); + } + return null; + } + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/gnome/ProxySchemasGSettingsAccess.java b/src/main/java/com/btr/proxy/search/desktop/gnome/ProxySchemasGSettingsAccess.java new file mode 100644 index 0000000..6e1b425 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/gnome/ProxySchemasGSettingsAccess.java @@ -0,0 +1,60 @@ +package com.btr.proxy.search.desktop.gnome; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +public class ProxySchemasGSettingsAccess { + + private static void closeStream(Closeable c) { + try { + c.close(); + } catch (IOException e) { + // Ignore cleanup errors + } + } + + static void copy(InputStream source, OutputStream dest) throws IOException { + try { + byte[] buffer = new byte[1024]; + int read = 0; + while (read >= 0) { + dest.write(buffer, 0, read); + read = source.read(buffer); + } + dest.flush(); + } finally { + closeStream(source); + closeStream(dest); + } + } + + static { + String arch = System.getProperty("os.arch"); + if (arch.equals("x86_64") || arch.equals("x64") || arch.equals("x86-64")) { + arch = "amd64"; + } + if (arch.equals("i386") || arch.equals("i686")) { + arch = "x86"; + } + String libName = "gsettings-" + arch + ".so"; + File libFile; + try { + InputStream source = ProxySchemasGSettingsAccess.class.getResourceAsStream("/lib/" + libName); + libFile = File.createTempFile("gsettings", ".so"); + libFile.deleteOnExit(); + FileOutputStream destination = new FileOutputStream(libFile); + copy(source, destination); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.load(libFile.getAbsolutePath()); + } + + public static native Map<String, Map<String, Object>> getValueByKeyBySchema(); + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/kde/KdeProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/desktop/kde/KdeProxySearchStrategy.java new file mode 100644 index 0000000..e05d72e --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/kde/KdeProxySearchStrategy.java @@ -0,0 +1,198 @@ +package com.btr.proxy.search.desktop.kde; + +import java.io.IOException; +import java.net.ProxySelector; +import java.util.Properties; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.env.EnvProxySearchStrategy; +import com.btr.proxy.search.wpad.WpadProxySearchStrategy; +import com.btr.proxy.selector.direct.NoProxySelector; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.pac.PacProxySelector; +import com.btr.proxy.selector.pac.UrlPacScriptSource; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.selector.whitelist.UseProxyWhiteListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; + +/***************************************************************************** + * Loads the KDE4 proxy settings from the KDE <i>kioslaverc</i> file. + * This will load properties from the file + * <p> + * <i>.kde/share/config/kioslaverc</i> + * </P> + * starting from the current users home directory. + * <p> + * The following settings are extracted from the section "[Proxy Settings]": + * </p> + * <ul> + * <li><i>AuthMode</i> -> 0 = no auth., 1 = use login.</li> + * <li><i>ProxyType</i> -> 0 = direct 1 = use fixed settings, 2 = use PAC, 3 = automatic (WPAD) , 4 = Use environment variables?</li> + * <li><i>Proxy Config</i> Script -> URL to PAC file</li> + * <li><i>ftpProxy</i> -> Fixed ftp proxy address e.g. http://www.ftp-proxy.com:8080</li> + * <li><i>httpProxy</i> -> Fixed http proxy e.g http://www.http-proxy.com:8080</li> + * <li><i>httpsProxy</i> -> Fixed https proxy e.g http://www.https-proxy.com:8080</li> + * <li><i>NoProxyFor</i> -> Proxy white list</li> + * <li><i>ReversedException</i> -> false = use NoProxyFor, true = revert meaning of the NoProxyFor list</li> + * </ul> + * + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class KdeProxySearchStrategy implements ProxySearchStrategy { + + private KdeSettingsParser settingsParser; + + /************************************************************************* + * ProxySelector using the given parser. + * @see java.net.ProxySelector#ProxySelector() + ************************************************************************/ + + public KdeProxySearchStrategy() { + this(new KdeSettingsParser()); + } + + /************************************************************************* + * ProxySelector + * @see java.net.ProxySelector#ProxySelector() + ************************************************************************/ + + public KdeProxySearchStrategy(KdeSettingsParser settingsParser) { + super(); + this.settingsParser = settingsParser; + } + + /************************************************************************* + * Loads the proxy settings and initializes a proxy selector for the firefox + * proxy settings. + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException on file reading error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + + Logger.log(getClass(), LogLevel.TRACE, "Detecting Kde proxy settings"); + + Properties settings = readSettings(); + if (settings == null) { + return null; + } + + ProxySelector result = null; + int type = Integer.parseInt(settings.getProperty("ProxyType", "-1")); + switch (type) { + case 0: // Use no proxy + Logger.log(getClass(), LogLevel.TRACE, "Kde uses no proxy"); + result = NoProxySelector.getInstance(); + break; + case 1: // Fixed settings + Logger.log(getClass(), LogLevel.TRACE, "Kde uses manual proxy settings"); + result = setupFixedProxySelector(settings); + break; + case 2: // PAC Script + String pacScriptUrl = settings.getProperty("Proxy Config Script", ""); + Logger.log(getClass(), LogLevel.TRACE, "Kde uses autodetect script {0}", pacScriptUrl); + result = ProxyUtil.buildPacSelectorForUrl(pacScriptUrl); + break; + case 3: // WPAD + Logger.log(getClass(), LogLevel.TRACE, "Kde uses WPAD to detect the proxy"); + result = new WpadProxySearchStrategy().getProxySelector(); + break; + case 4: // Use environment variables + Logger.log(getClass(), LogLevel.TRACE, "Kde reads proxy from environment"); + result = setupEnvVarSelector(settings); + break; + default: + break; + } + + return result; + } + + /************************************************************************* + * Reads the settings and stores them in a properties map. + * @return the parsed settings. + * @throws ProxyException + ************************************************************************/ + + private Properties readSettings() throws ProxyException { + try { + return this.settingsParser.parseSettings(); + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Can't parse settings.", e); + throw new ProxyException(e); + } + } + + /************************************************************************* + * Builds an environment variable selector. + * @param settings the settings to read from. + * @return the ProxySelector using environment variables. + ************************************************************************/ + + private ProxySelector setupEnvVarSelector(Properties settings) { + ProxySelector result; + result = new EnvProxySearchStrategy( + settings.getProperty("httpProxy"), + settings.getProperty("httpsProxy"), + settings.getProperty("ftpProxy"), + settings.getProperty("NoProxyFor") + ).getProxySelector(); + return result; + } + + /************************************************************************* + * Parse the fixed proxy settings and build an ProxySelector for this a + * chained configuration. + * @param settings the proxy settings to evaluate. + ************************************************************************/ + + private ProxySelector setupFixedProxySelector(Properties settings) { + String proxyVar = settings.getProperty("httpProxy", null); + FixedProxySelector httpPS = ProxyUtil.parseProxySettings(proxyVar); + if (httpPS == null) { + Logger.log(getClass(), LogLevel.TRACE, "Kde http proxy is {0}", proxyVar); + return null; + } + + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + ps.setSelector("http", httpPS); + + proxyVar = settings.getProperty("httpsProxy", null); + FixedProxySelector httpsPS = ProxyUtil.parseProxySettings(proxyVar); + if (httpsPS != null) { + Logger.log(getClass(), LogLevel.TRACE, "Kde https proxy is {0}", proxyVar); + ps.setSelector("https", httpsPS); + } + + proxyVar = settings.getProperty("ftpProxy", null); + FixedProxySelector ftpPS = ProxyUtil.parseProxySettings(proxyVar); + if (ftpPS != null) { + Logger.log(getClass(), LogLevel.TRACE, "Kde ftp proxy is {0}", proxyVar); + ps.setSelector("ftp", ftpPS); + } + + // Wrap in white list filter. + String noProxyList = settings.getProperty("NoProxyFor", null); + if (noProxyList != null && noProxyList.trim().length() > 0) { + boolean reverse = "true".equals(settings.getProperty("ReversedException", "false")); + if (reverse) { + Logger.log(getClass(), LogLevel.TRACE, "Kde proxy blacklist is {0}", noProxyList); + return new UseProxyWhiteListSelector(noProxyList, ps); + } else { + Logger.log(getClass(), LogLevel.TRACE, "Kde proxy whitelist is {0}", noProxyList); + return new ProxyBypassListSelector(noProxyList, ps); + } + } + + return ps; + } + + + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/kde/KdeSettingsParser.java b/src/main/java/com/btr/proxy/search/desktop/kde/KdeSettingsParser.java new file mode 100644 index 0000000..904528e --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/kde/KdeSettingsParser.java @@ -0,0 +1,132 @@ +package com.btr.proxy.search.desktop.kde; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Properties; + +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Parser for the KDE settings file. + * The KDE proxy settings are stored in the file: + * <p> + * <i>.kde/share/config/kioslaverc</i> + * </p> + * in the users home directory. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class KdeSettingsParser { + + private File settingsFile; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public KdeSettingsParser() { + this(null); + } + + /************************************************************************* + * Constructor + ************************************************************************/ + + public KdeSettingsParser(File settingsFile) { + super(); + this.settingsFile = settingsFile; + } + + /************************************************************************* + * Parse the settings file and extract all network.proxy.* settings from it. + * @return the parsed properties. + * @throws IOException on read error. + ************************************************************************/ + + public Properties parseSettings() throws IOException { + // Search for existing settings. + if (this.settingsFile == null) { + this.settingsFile = findSettingsFile(); + } + if (this.settingsFile == null) { + return null; + } + + // Read settings from file. + BufferedReader fin = new BufferedReader( + new InputStreamReader( + new FileInputStream(this.settingsFile))); + + Properties result = new Properties(); + try { + String line = fin.readLine(); + + // Find section start. + while (line != null && !"[Proxy Settings]".equals(line.trim())) { + line = fin.readLine(); + } + if (line == null) { + return result; + } + + // Read full section + line = ""; + while (line != null && !line.trim().startsWith("[")) { + line = line.trim(); + int index = line.indexOf('='); + if (index > 0) { + String key = line.substring(0, index).trim(); + String value = line.substring(index+1).trim(); + result.setProperty(key, value); + } + + line = fin.readLine(); + } + } finally { + fin.close(); + } + + return result; + } + + /************************************************************************* + * Finds all the KDE network settings file. + * @return a file or null if does not exist. + ************************************************************************/ + + private File findSettingsFile() { + File userDir = new File(System.getProperty("user.home")); + if ("4".equals(System.getenv("KDE_SESSION_VERSION"))) { + this.settingsFile = findSettingsFile( + new File(userDir, ".kde4"+File.separator+"share"+File.separator+"config"+File.separator+"kioslaverc")); + } + if (this.settingsFile == null) { + return findSettingsFile( + new File(userDir, ".kde"+File.separator+"share"+File.separator+"config"+File.separator+"kioslaverc")); + } else { + return this.settingsFile; + } + } + + /************************************************************************* + * Internal method to test if the settings file is at the given place. + * @param settingsFile the path to test. + * @return the file or null if it does not exist. + ************************************************************************/ + + private File findSettingsFile(File settingsFile) { + Logger.log(getClass(), LogLevel.TRACE, "Searching Kde settings in {0}", settingsFile); + if (!settingsFile.exists()) { + Logger.log(getClass(), LogLevel.DEBUG, "Settings not found"); + return null; + } + Logger.log(getClass(), LogLevel.TRACE, "Settings found"); + return settingsFile; + } + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/osx/OsxProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/desktop/osx/OsxProxySearchStrategy.java new file mode 100644 index 0000000..3d67ae7 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/osx/OsxProxySearchStrategy.java @@ -0,0 +1,325 @@ +package com.btr.proxy.search.desktop.osx; + +import java.io.File; +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.ProxySelector; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.browser.ie.IELocalByPassFilter; +import com.btr.proxy.search.wpad.WpadProxySearchStrategy; +import com.btr.proxy.selector.direct.NoProxySelector; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.fixed.FixedSocksSelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.PListParser; +import com.btr.proxy.util.PListParser.Dict; +import com.btr.proxy.util.PListParser.XmlParseException; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.UriFilter; + +/***************************************************************************** + * Loads the OSX system proxy settings from the settings file. + * <p> + * All settings are stored in OSX in a special XML file format. + * These settings file are named plist files and contain nested dictionaries, arrays and values. + * </p><p> + * To parse this file we use a parser that is derived from a plist parser that + * comes with the xmlwise XML parser package: + * </p><p> + * http://code.google.com/p/xmlwise/ + * </p><p> + * I modified that parser to work with the default Java XML parsing library. + * </p><p> + * The plist file is located on OSX at: + * </p><p> + * /Library/Preferences/SystemConfiguration/preferences.plist + * </p> + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2011 + ****************************************************************************/ + +public class OsxProxySearchStrategy implements ProxySearchStrategy { + + public static final String OVERRIDE_SETTINGS_FILE = "com.btr.proxy.osx.settingsFile"; + public static final String OVERRIDE_ACCEPTED_DEVICES = "com.btr.proxy.osx.acceptedDevices"; + + private static final String SETTINGS_FILE = "/Library/Preferences/SystemConfiguration/preferences.plist"; + + /************************************************************************* + * ProxySelector + * @see java.net.ProxySelector#ProxySelector() + ************************************************************************/ + + public OsxProxySearchStrategy() { + super(); + } + + /************************************************************************* + * Loads the proxy settings and initializes a proxy selector for the OSX + * proxy settings. + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException on file reading error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + + Logger.log(getClass(), LogLevel.TRACE, "Detecting OSX proxy settings"); + + try { + List<String> acceptedInterfaces = getNetworkInterfaces(); + + Dict settings = PListParser.load(getSettingsFile()); + Object currentSet = settings.getAtPath("/CurrentSet"); + if (currentSet == null) { + throw new ProxyException("CurrentSet not defined"); + } + + Dict networkSet = (Dict) settings.getAtPath(String.valueOf(currentSet)); + List<?> serviceOrder = (List<?>) networkSet.getAtPath("/Network/Global/IPv4/ServiceOrder"); + if (serviceOrder == null || serviceOrder.size() == 0) { + throw new ProxyException("ServiceOrder not defined"); + } + + // Look at the Services in priority order and pick the first one that was + // also accepted above + Dict proxySettings = null; + for (int i = 0; i < serviceOrder.size() && proxySettings == null; i++) { + Object candidateService = serviceOrder.get(i); + Object networkService = networkSet.getAtPath("/Network/Service/"+candidateService+"/__LINK__"); + if (networkService == null ) { + throw new ProxyException("NetworkService not defined."); + } + Dict selectedServiceSettings = (Dict) settings.getAtPath(""+networkService); + String interfaceName = (String) selectedServiceSettings.getAtPath("/Interface/DeviceName"); + if (acceptedInterfaces.contains(interfaceName)) { + Logger.log(getClass(), LogLevel.TRACE, "Looking up proxies for device " + interfaceName); + proxySettings = (Dict) selectedServiceSettings.getAtPath("/Proxies"); + } + } + if (proxySettings == null) { + return NoProxySelector.getInstance(); + } + + return buildSelector(proxySettings); + } catch (XmlParseException e) { + throw new ProxyException(e); + } catch (IOException e) { + throw new ProxyException(e); + } + } + + /************************************************************************* + * Build a selector from the given settings. + * @param proxySettings to parse + * @return the configured selector + * @throws ProxyException on error + ************************************************************************/ + + private ProxySelector buildSelector(Dict proxySettings) throws ProxyException { + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + installSelectorForProtocol(proxySettings, ps, "HTTP"); + installSelectorForProtocol(proxySettings, ps, "HTTPS"); + installSelectorForProtocol(proxySettings, ps, "FTP"); + installSelectorForProtocol(proxySettings, ps, "Gopher"); + installSelectorForProtocol(proxySettings, ps, "RTSP"); + installSocksProxy(proxySettings, ps); + + ProxySelector result = ps; + result = installPacProxyIfAvailable(proxySettings, result); + result = autodetectProxyIfAvailable(proxySettings, result); + + result = installExceptionList(proxySettings, result); + result = installSimpleHostFilter(proxySettings, result); + return result; + } + + /************************************************************************* + * Create a list of Ethernet interfaces that are connected + * @return + * @throws SocketException + ************************************************************************/ + + private List<String> getNetworkInterfaces() throws SocketException { + String override = System.getProperty(OVERRIDE_ACCEPTED_DEVICES); + if (override != null && override.length() > 0) { + return Arrays.asList(override.split(";")); + } + + List<String> acceptedInterfaces = new ArrayList<String>(); + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + if (isInterfaceAllowed(ni)) { + acceptedInterfaces.add(ni.getName()); + } + } + return acceptedInterfaces; + } + + /************************************************************************* + * Check if a given network interface is interesting for us. + * @param ni the interface to check + * @return true if accepted else false. + * @throws SocketException on error. + ************************************************************************/ + + private boolean isInterfaceAllowed(NetworkInterface ni) throws SocketException { + return !ni.isLoopback() && + !ni.isPointToPoint() && // Not sure if we should filter the point to point interfaces? + !ni.isVirtual() && + ni.isUp(); + } + + /************************************************************************* + * @return + ************************************************************************/ + + private File getSettingsFile() { + File result = new File(SETTINGS_FILE); + String overrideFile = System.getProperty(OVERRIDE_SETTINGS_FILE); + if (overrideFile != null) { + return new File(overrideFile); + } + return result; + } + + /************************************************************************* + * @param proxySettings + * @param result + * @return + ************************************************************************/ + + private ProxySelector installSimpleHostFilter( + Dict proxySettings, ProxySelector result) { + if (isActive(proxySettings.get("ExcludeSimpleHostnames"))) { + List<UriFilter> localBypassFilter = new ArrayList<UriFilter>(); + localBypassFilter.add(new IELocalByPassFilter()); + result = new ProxyBypassListSelector(localBypassFilter, result); + } + return result; + } + + /************************************************************************* + * @param proxySettings + * @param result + * @return + ************************************************************************/ + + private ProxySelector installExceptionList( + Dict proxySettings, ProxySelector result) { + List<?> proxyExceptions = (List<?>) proxySettings.get("ExceptionsList"); + if (proxyExceptions != null && proxyExceptions.size() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "OSX uses proxy bypass list: {0}", proxyExceptions); + String noProxyList = toCommaSeparatedString(proxyExceptions); + result = new ProxyBypassListSelector(noProxyList, result); + } + return result; + } + + /************************************************************************* + * Convert a list to a comma separated list. + * @param proxyExceptions list of elements. + * @return a comma separated string of the list's content. + ************************************************************************/ + + private String toCommaSeparatedString(List<?> proxyExceptions) { + StringBuilder result = new StringBuilder(); + for (Object object : proxyExceptions) { + if (result.length() > 0) { + result.append(","); + } + result.append(object); + } + return result.toString(); + } + + /************************************************************************* + * @param proxySettings + * @param result + * @return + * @throws ProxyException + ************************************************************************/ + + private ProxySelector autodetectProxyIfAvailable( + Dict proxySettings, ProxySelector result) + throws ProxyException { + if (isActive(proxySettings.get("ProxyAutoDiscoveryEnable"))) { + ProxySelector wp = new WpadProxySearchStrategy().getProxySelector(); + if (wp != null) { + result = wp; + } + } + return result; + } + + /************************************************************************* + * @param proxySettings + * @param result + * @return + ************************************************************************/ + + private ProxySelector installPacProxyIfAvailable(Dict proxySettings, + ProxySelector result) { + if (isActive(proxySettings.get("ProxyAutoConfigEnable"))) { + String url = (String) proxySettings.get("ProxyAutoConfigURLString"); + result = ProxyUtil.buildPacSelectorForUrl(url); + } + return result; + } + + /************************************************************************* + * Build a socks proxy and set it for the socks protocol. + * @param proxySettings to read the config values from. + * @param ps the ProtocolDispatchSelector to install the new proxy on. + ************************************************************************/ + + private void installSocksProxy(Dict proxySettings, + ProtocolDispatchSelector ps) { + if (isActive(proxySettings.get("SOCKSEnable"))) { + String proxyHost = (String) proxySettings.get("SOCKSProxy"); + int proxyPort = (Integer) proxySettings.get("SOCKSPort"); + ps.setSelector("socks", new FixedSocksSelector(proxyHost, proxyPort)); + Logger.log(getClass(), LogLevel.TRACE, "OSX socks proxy is {0}:{1}", proxyHost, proxyPort); + } + } + + /************************************************************************* + * Installs a proxy selector for the given protocoll on the ProtocolDispatchSelector + * @param proxySettings to read the config for the procotol from. + * @param ps the ProtocolDispatchSelector to install the new selector on. + * @param protocol to use. + ************************************************************************/ + + private void installSelectorForProtocol(Dict proxySettings, + ProtocolDispatchSelector ps, String protocol) { + String prefix = protocol.trim(); + if (isActive(proxySettings.get(prefix+"Enable"))) { + String proxyHost = (String) proxySettings.get(prefix+"Proxy"); + int proxyPort = (Integer) proxySettings.get(prefix+"Port"); + FixedProxySelector fp = new FixedProxySelector(proxyHost, proxyPort); + ps.setSelector(protocol.toLowerCase(), fp); + Logger.log(getClass(), LogLevel.TRACE, "OSX uses for {0} the proxy {1}:{2}", protocol, proxyHost, proxyPort); + } + } + + /************************************************************************* + * Checks if the given value is set to "on". + * @param value the value to test. + * @return true if it is set else false. + ************************************************************************/ + + private boolean isActive(Object value) { + return Integer.valueOf(1).equals(value); + } + +} diff --git a/src/main/java/com/btr/proxy/search/desktop/win/DLLManager.java b/src/main/java/com/btr/proxy/search/desktop/win/DLLManager.java new file mode 100644 index 0000000..8d67984 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/win/DLLManager.java @@ -0,0 +1,171 @@ +package com.btr.proxy.search.desktop.win; + +import java.io.Closeable; +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * This class provides some helper methods to work with the dll + * extracting /loading for windows. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +final class DLLManager { + + private static final class TempDLLFileFilter implements FileFilter { + public boolean accept(File pathname) { + String name = pathname.getName(); + return pathname.isFile() && + name.startsWith(TEMP_FILE_PREFIX) && + name.endsWith(DLL_EXTENSION); + } + } + + public static final String LIB_DIR_OVERRIDE = "proxy_vole_lib_dir"; + + static final String TEMP_FILE_PREFIX = "proxy_vole"; + static final String DLL_EXTENSION = ".dll"; + static String LIB_NAME_BASE = "proxy_util_"; + static final String DEFAULT_LIB_FOLDER = "lib"; + + /************************************************************************* + * Find the location of the native code dll file. + * @return the File pointing to the dll. + * @throws IOException on IO error. + ************************************************************************/ + + static File findLibFile() throws IOException { + String libName = buildLibName(); + File libFile = getOverrideLibFile(libName); + if (libFile == null || libFile.exists() == false) { + libFile = getDefaultLibFile(libName); + } + if (libFile == null || libFile.exists() == false) { + libFile = extractToTempFile(libName); + } + return libFile; + } + + /************************************************************************* + * Delete old temp files that may be there because they were extracted + * from the jar but could not be deleted on VM shutdown because they + * are still locked by windows. + * This is here to prevent a lot of temp dll files on disk. + ************************************************************************/ + + static void cleanupTempFiles() { + try { + String tempFolder = System.getProperty("java.io.tmpdir"); + if (tempFolder == null || tempFolder.trim().length() == 0) { + return; + } + File fldr = new File(tempFolder); + File[] oldFiles = fldr.listFiles(new TempDLLFileFilter()); + if (oldFiles == null) { + return; + } + for (File tmp : oldFiles) { + tmp.delete(); + } + } catch (Exception e) { + Logger.log(DLLManager.class, LogLevel.DEBUG, "Error cleaning up temporary dll files. ", e); + } + } + + /************************************************************************* + * @param libName + * @return + ************************************************************************/ + + private static File getDefaultLibFile(String libName) { + return new File(DEFAULT_LIB_FOLDER, libName); + } + + /************************************************************************* + * Gets the file name that was overriden via system property. + * @param libName + * @return the file, null if it is not existing. + ************************************************************************/ + + private static File getOverrideLibFile(String libName) { + String libDir = System.getProperty(LIB_DIR_OVERRIDE); + if (libDir == null || libDir.trim().length() == 0) { + return null; + } + return new File(libDir, libName); + } + + /************************************************************************* + * @param libName + * @return + * @throws IOException + * @throws FileNotFoundException + ************************************************************************/ + + static File extractToTempFile(String libName) throws IOException { + InputStream source = Win32ProxyUtils.class.getResourceAsStream("/"+DEFAULT_LIB_FOLDER+"/"+libName); + File tempFile = File.createTempFile(TEMP_FILE_PREFIX, DLL_EXTENSION); + tempFile.deleteOnExit(); + FileOutputStream destination = new FileOutputStream(tempFile); + copy(source, destination); + return tempFile; + } + + /************************************************************************* + * @param c a closeable to cleanup ignoring all errors. + ************************************************************************/ + + private static void closeStream(Closeable c) { + try { + c.close(); + } catch (IOException e) { + // Ignore cleanup errors + } + } + + /************************************************************************* + * Copies the content from source to destination. + * @param source + * @param dest + * @throws IOException + ************************************************************************/ + + static void copy(InputStream source, OutputStream dest) + throws IOException { + try { + byte[] buffer = new byte[1024]; + int read = 0; + while (read >= 0) { + dest.write(buffer, 0, read); + read = source.read(buffer); + } + dest.flush(); + } finally { + closeStream(source); + closeStream(dest); + } + } + + /************************************************************************* + * @return the name of the dll valid for the current architecture. + ************************************************************************/ + + private static String buildLibName() { + String arch = "w32"; + if(!System.getProperty("os.arch").equals("x86") ) { + arch = System.getProperty("os.arch"); + } + return LIB_NAME_BASE + arch + DLL_EXTENSION; + } + +} + diff --git a/src/main/java/com/btr/proxy/search/desktop/win/Win32IESettings.java b/src/main/java/com/btr/proxy/search/desktop/win/Win32IESettings.java new file mode 100644 index 0000000..ef91a22 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/win/Win32IESettings.java @@ -0,0 +1,68 @@ +package com.btr.proxy.search.desktop.win;
+
+/*****************************************************************************
+ * Proxy settings container used for the native methods.
+ * Will contain the Internet Explorer proxy settings as reported by windows
+ * WinHTTP API.
+ *
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ****************************************************************************/
+
+public class Win32IESettings {
+
+ private boolean autoDetect;
+ private String autoConfigUrl;
+ private String proxy;
+ private String proxyBypass;
+
+ /*************************************************************************
+ * Constructor
+ * @param autoDetect flag is autodetect is active or not.
+ * @param autoConfigUrl the URL for a PAC script
+ * @param proxy the proxy server selected
+ * @param proxyBypass the proxy bypass address list.
+ ************************************************************************/
+
+ public Win32IESettings(boolean autoDetect, String autoConfigUrl, String proxy, String proxyBypass) {
+ super();
+ this.autoDetect = autoDetect;
+ this.autoConfigUrl = autoConfigUrl;
+ this.proxy = proxy;
+ this.proxyBypass = proxyBypass;
+ }
+
+ /*************************************************************************
+ * @return Returns the autoDetect.
+ ************************************************************************/
+
+ public boolean isAutoDetect() {
+ return this.autoDetect;
+ }
+
+ /*************************************************************************
+ * @return Returns the autoConfigUrl.
+ ************************************************************************/
+
+ public String getAutoConfigUrl() {
+ return this.autoConfigUrl;
+ }
+
+ /*************************************************************************
+ * @return Returns the proxy.
+ ************************************************************************/
+
+ public String getProxy() {
+ return this.proxy;
+ }
+
+ /*************************************************************************
+ * @return Returns the proxyBypass.
+ ************************************************************************/
+
+ public String getProxyBypass() {
+ return this.proxyBypass;
+ }
+
+
+}
+
diff --git a/src/main/java/com/btr/proxy/search/desktop/win/Win32ProxyUtils.java b/src/main/java/com/btr/proxy/search/desktop/win/Win32ProxyUtils.java new file mode 100644 index 0000000..fb1f0b3 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/win/Win32ProxyUtils.java @@ -0,0 +1,88 @@ +package com.btr.proxy.search.desktop.win;
+
+import java.io.File;
+import java.io.IOException;
+
+/*****************************************************************************
+ * Defines the native methods used for windows to extract some system information.
+ * <p>
+ * This class will need some native code from the library proxy_util_"arch".dll.
+ * To load this library we use a three step algorithm as following:
+ * </p><P>
+ * First check the System property "proxy_vole_lib_dir" if it is set and
+ * it points to a folder where the dll is found than the dll from this
+ * folder is loaded as e.g. <i>"proxy_vole_lib_dir"/proxy_util_w32.dll</i>
+ * </p><p>
+ * Second we try to load the dll from the subfolder <i>lib</i> if that one exists.<br>
+ * Finally if we are inside of a jar file we need to extract the dll file
+ * to a temp-file because windows can not load dlls from a jar
+ * directly. This is a hack but it may work.
+ * </p><p>
+ * Please note that the file is named Win32ProxyUtils but has now also support
+ * for x64 architecture.
+ * </p>
+ * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
+ ****************************************************************************/
+
+public class Win32ProxyUtils {
+
+ public static final int WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001;
+ public static final int WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002;
+
+
+ // Code for loading the windows native dll
+ static {
+ try {
+ File libFile = DLLManager.findLibFile();
+ System.load(libFile.getAbsolutePath());
+ DLLManager.cleanupTempFiles();
+ } catch (IOException e) {
+ throw new RuntimeException("Error loading dll"+e.getMessage(), e);
+ }
+ }
+
+ /*************************************************************************
+ * Constructor
+ ************************************************************************/
+
+ public Win32ProxyUtils() {
+ super();
+ }
+
+ /*************************************************************************
+ * WinHTTP method to detect an PAC URL.
+ * @param mode the mode to use.
+ * @return the PAC URL, null if none was found.
+ ************************************************************************/
+
+ public native String winHttpDetectAutoProxyConfigUrl(int mode);
+
+ /*************************************************************************
+ * Gets the default windows proxy settings.
+ * The returned string will have the following format.
+ * TYPE PROXY | BYPASSLIST
+ * <p>
+ * e.g. DIRECT myproxy.mycompany.com:8080 | *.mycompany.com, localhost
+ * </p>
+ * @return a string containing all info, null if not found.
+ ************************************************************************/
+ // TODO Not implemented correctly in DLL yet.
+ native String winHttpGetDefaultProxyConfiguration();
+
+ /*************************************************************************
+ * Extracts the Internet Explorer proxy settings from the Windows system.
+ * @return a data structure containing all details, null on fail.
+ ************************************************************************/
+
+ public native Win32IESettings winHttpGetIEProxyConfigForCurrentUser();
+
+ /*************************************************************************
+ * Extracts the Internet Explorer proxy settings from the Windows system.
+ * @return a data structure containing all details, null on fail.
+ ************************************************************************/
+
+ public native String readUserHomedir();
+
+
+}
+
diff --git a/src/main/java/com/btr/proxy/search/desktop/win/WinProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/desktop/win/WinProxySearchStrategy.java new file mode 100644 index 0000000..1f26505 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/desktop/win/WinProxySearchStrategy.java @@ -0,0 +1,52 @@ +package com.btr.proxy.search.desktop.win; + +import java.net.ProxySelector; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.browser.ie.IEProxySearchStrategy; +import com.btr.proxy.util.ProxyException; + +/***************************************************************************** + * Extracts the proxy settings from the windows registry. + * This will read the windows system proxy settings. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class WinProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * Constructor + ************************************************************************/ + + public WinProxySearchStrategy() { + super(); + } + + /************************************************************************* + * getProxySelector + * @see com.btr.proxy.search.ProxySearchStrategy#getProxySelector() + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + // TODO Rossi 08.05.2009 Implement this by using Win API calls. + // new Win32ProxyUtils().winHttpGetDefaultProxyConfiguration() + // Current fallback is to use the IE settings. This is better + // because the registry settings are most of the time not set. + // Some Windows server installations may use it though. + return new IEProxySearchStrategy().getProxySelector(); + } + + /************************************************************************* + * Loads the settings. + * @return a WinIESettings object containing all proxy settings. + ************************************************************************/ + + public Win32IESettings readSettings() { + return new IEProxySearchStrategy().readSettings(); + } + + + + +} diff --git a/src/main/java/com/btr/proxy/search/env/EnvProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/env/EnvProxySearchStrategy.java new file mode 100644 index 0000000..37bd0a1 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/env/EnvProxySearchStrategy.java @@ -0,0 +1,130 @@ +package com.btr.proxy.search.env; + +import java.net.ProxySelector; +import java.util.Properties; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.ProxyUtil; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Reads some environment variables and extracts the proxy settings from them. + * These variables are mainly set on linux / unix environments. + * The following variables are read per default: + * <ul> + * <li><i>http_proxy</i> -> This will be used for http / https</li> + * <li><i>https_proxy</i> -> Will be used for https, if not set then http_proxy is used instead.</li> + * <li><i>ftp_proxy</i> -> Used for FTP.</li> + * <li><i>no_proxy</i> -> a no proxy white list.</li> + * </ul> + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class EnvProxySearchStrategy implements ProxySearchStrategy { + + private String httpEnv; + private String httpsEnv; + private String ftpEnv; + private String noProxyEnv; + + private String httpProxy; + private String httpsProxy; + private String ftpProxy; + private String noProxy; + + /************************************************************************* + * Constructor + * Will use the default environment variables. + ************************************************************************/ + + public EnvProxySearchStrategy() { + this("http_proxy", "https_proxy", "ftp_proxy", "no_proxy"); + } + + /************************************************************************* + * Constructor + * @param httpEnv name of environment variable + * @param httpsEnv name of environment variable + * @param ftpEnv name of environment variable + * @param noProxyEnv name of environment variable + ************************************************************************/ + + public EnvProxySearchStrategy(String httpEnv, String httpsEnv, String ftpEnv, String noProxyEnv) { + super(); + this.httpEnv = httpEnv; + this.httpsEnv = httpsEnv; + this.ftpEnv = ftpEnv; + this.noProxyEnv = noProxyEnv; + + loadProxySettings(); + } + + /************************************************************************* + * Loads the proxy settings from the system environment variables. + ************************************************************************/ + + private void loadProxySettings() { + this.httpProxy = System.getenv(this.httpEnv); + this.httpsProxy = System.getenv(this.httpsEnv); + this.ftpProxy = System.getenv(this.ftpEnv); + this.noProxy = System.getenv(this.noProxyEnv); + } + + /************************************************************************* + * Loads the settings and stores them in a properties map. + * @return the settings. + ************************************************************************/ + + public Properties readSettings() { + Properties result = new Properties(); + result.setProperty(this.httpEnv, this.httpProxy); + result.setProperty(this.httpsEnv, this.httpsProxy); + result.setProperty(this.ftpEnv, this.ftpProxy); + result.setProperty(this.noProxyEnv, this.noProxy); + return result; + } + + + /************************************************************************* + * Loads the proxy settings from environment variables. + * @return a configured ProxySelector, null if none is found. + ************************************************************************/ + + public ProxySelector getProxySelector() { + + Logger.log(getClass(), LogLevel.TRACE, "Inspecting environment variables."); + + // Check if http_proxy var is set. + ProxySelector httpPS = ProxyUtil.parseProxySettings(this.httpProxy); + if (httpPS == null) { + return null; + } + + Logger.log(getClass(), LogLevel.TRACE, "Http Proxy is {0}", this.httpProxy); + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + ps.setSelector("http", httpPS); + + ProxySelector httpsPS = ProxyUtil.parseProxySettings(this.httpsProxy); + Logger.log(getClass(), LogLevel.TRACE, "Https Proxy is {0}", httpsPS == null? this.httpsProxy: httpsPS); + ps.setSelector("https", httpsPS != null? httpsPS: httpPS); + + ProxySelector ftpPS = ProxyUtil.parseProxySettings(this.ftpProxy); + if (ftpPS != null) { + Logger.log(getClass(), LogLevel.TRACE, "Ftp Proxy is {0}", this.ftpProxy); + ps.setSelector("ftp", ftpPS); + } + + // Wrap with white list support + ProxySelector result = ps; + if (this.noProxy != null && this.noProxy.trim().length() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Using proxy bypass list: {0}", this.noProxy); + result = new ProxyBypassListSelector(this.noProxy, ps); + } + + return result; + } + +} diff --git a/src/main/java/com/btr/proxy/search/java/JavaProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/java/JavaProxySearchStrategy.java new file mode 100644 index 0000000..531ef94 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/java/JavaProxySearchStrategy.java @@ -0,0 +1,133 @@ +package com.btr.proxy.search.java; + +import java.net.ProxySelector; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.fixed.FixedSocksSelector; +import com.btr.proxy.selector.misc.ProtocolDispatchSelector; +import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Reads some java system properties and extracts the proxy settings from them. + * The following variables are read: + * <ul> + * <li><i>http.proxyHost</i> (default: none)</li> + * <li><i>http.proxyPort</i> (default: 80 if http.proxyHost specified)</li> + * <li><i>http.nonProxyHosts</i> (default: none)</li> + * </ul> + * <ul> + * <li><i>https.proxyHost</i> (default: none)</li> + * <li><i>https.proxyPort</i> (default: 443 if https.proxyHost specified)</li> + * </ul> + * <ul> + * <li><i>ftp.proxyHost</i> (default: none)</li> + * <li><i>ftp.proxyPort</i> (default: 80 if ftp.proxyHost specified)</li> + * <li><i>ftp.nonProxyHosts</i> (default: none)</li> + * </ul> + * <ul> + * <li><i>socksProxyHost</i></li> + * <li><i>socksProxyPort</i> (default: 1080)</li> + * </ul> + * <p> + * This is based on information found here: <br/> + * http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html + * </p> + * If the "http.proxyHost" property is not set then the no proxy selector is setup + * This property is used as marker to signal that the System settings should be used. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class JavaProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * Constructor + * Will use the default environment variables. + ************************************************************************/ + + public JavaProxySearchStrategy() { + super(); + } + + /************************************************************************* + * Loads the proxy settings from environment variables. + * @return a configured ProxySelector, null if none is found. + ************************************************************************/ + + public ProxySelector getProxySelector() { + ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); + + if (!proxyPropertyPresent()) { + return null; + } + Logger.log(getClass(), LogLevel.TRACE, "Using settings from Java System Properties"); + + + setupProxyForProtocol(ps, "http", 80); + setupProxyForProtocol(ps, "https", 443); + setupProxyForProtocol(ps, "ftp", 80); + setupProxyForProtocol(ps, "ftps", 80); + setupSocktProxy(ps); + + return ps; + } + + /************************************************************************* + * @return true if the http.proxyHost is available as system property. + ************************************************************************/ + + private boolean proxyPropertyPresent() { + return System.getProperty("http.proxyHost") != null + && System.getProperty("http.proxyHost").trim().length() > 0; + } + + /************************************************************************* + * Parse SOCKS settings + * @param ps + * @throws NumberFormatException + ************************************************************************/ + + + private void setupSocktProxy(ProtocolDispatchSelector ps) { + String host = System.getProperty("socksProxyHost"); + String port = System.getProperty("socksProxyPort", "1080"); + if (host != null && host.trim().length() > 0) { + Logger.log(getClass(), LogLevel.TRACE, "Socks proxy {0}:{1} found", host, port); + ps.setSelector("socks", new FixedSocksSelector(host, Integer.parseInt(port))); + } + } + + /************************************************************************* + * Parse properties for the given protocol. + * @param ps + * @param protocol + * @throws NumberFormatException + ************************************************************************/ + + private void setupProxyForProtocol(ProtocolDispatchSelector ps, String protocol, int defaultPort) { + String host = System.getProperty(protocol+".proxyHost"); + String port = System.getProperty(protocol+".proxyPort", ""+defaultPort); + String whiteList = System.getProperty(protocol+".nonProxyHosts", "").replace('|', ','); + + if ("https".equalsIgnoreCase(protocol)) { // This is dirty but https has no own property for it. + whiteList = System.getProperty("http.nonProxyHosts", "").replace('|', ','); + } + + if (host == null || host.trim().length() == 0) { + return; + } + + Logger.log(getClass(), LogLevel.TRACE, protocol.toUpperCase()+" proxy {0}:{1} found using whitelist: {2}", host, port, whiteList); + + ProxySelector protocolSelector = new FixedProxySelector(host, Integer.parseInt(port)); + if (whiteList.trim().length() > 0) { + protocolSelector = new ProxyBypassListSelector(whiteList, protocolSelector); + } + + ps.setSelector(protocol, protocolSelector); + } + +} diff --git a/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategy.java b/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategy.java new file mode 100644 index 0000000..cf01fd3 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategy.java @@ -0,0 +1,234 @@ +package com.btr.proxy.search.wpad; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Properties; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; + + +/***************************************************************************** + * Uses automatic proxy script search (WPAD) to find an PAC file automatically. + * <p> + * Note: at the moment only the DNS name guessing schema is implemented. + * All others are missing. + * </p><p> + * For more information about WPAD: + * <a href="http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol">Web_Proxy_Autodiscovery_Protocol</a> + * </p><p> + * Outdated RFC draft: + * <a href="http://www.web-cache.com/Writings/Internet-Drafts/draft-ietf-wrec-wpad-01.txt">draft-ietf-wrec-wpad-01.txt</a> + * </p> + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class WpadProxySearchStrategy implements ProxySearchStrategy { + + /************************************************************************* + * Constructor + ************************************************************************/ + + public WpadProxySearchStrategy() { + super(); + } + + /************************************************************************* + * Loads the proxy settings from a PAC file. + * The location of the PAC file is determined automatically. + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException on error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + try { + Logger.log(getClass(), LogLevel.TRACE, "Using WPAD to find a proxy"); + + String pacScriptUrl = detectScriptUrlPerDHCP(); + if (pacScriptUrl == null) { + pacScriptUrl = detectScriptUrlPerDNS(); + } + if (pacScriptUrl == null) { + return null; + } + Logger.log(getClass(), LogLevel.TRACE, "PAC script url found: {0}", pacScriptUrl); + return ProxyUtil.buildPacSelectorForUrl(pacScriptUrl); + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Error during WPAD search.", e); + throw new ProxyException(e); + } + } + + /************************************************************************* + * Loads the settings and stores them in a properties map. + * @return the settings. + ************************************************************************/ + + public Properties readSettings() { + try { + String pacScriptUrl = detectScriptUrlPerDHCP(); + if (pacScriptUrl == null) { + pacScriptUrl = detectScriptUrlPerDNS(); + } + if (pacScriptUrl == null) { + return null; + } + Properties result = new Properties(); + result.setProperty("url", pacScriptUrl); + return result; + } catch (IOException e) { + // Ignore and return empty properties. + return new Properties(); + } + } + + /************************************************************************* + * Uses DNS to find the script URL. + * Attention: this detection method is known to have some severe security issues. + * @return the URL, null if not found. + ************************************************************************/ + + private String detectScriptUrlPerDNS() throws IOException { + String result = null; + // String fqdn = InetAddress.getLocalHost().getCanonicalHostName(); + + Logger.log(getClass(), LogLevel.TRACE, "Searching per DNS guessing."); + // Logger.log(getClass(), LogLevel.INFO, "fqdn: ", fqdn); + + /** Reading address from "/etc/resolv.conf"; file looks like: + * + # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) + # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN + nameserver 127.0.1.1 + search ruf.uni-freiburg.de lp.ruf.uni-freiburg.de + */ + + FileReader fr = new FileReader("/etc/resolv.conf"); + BufferedReader br = new BufferedReader(fr); + + String input; + String[] addresses = null; + // using the 4th line of the file. + while ((input = br.readLine()) != null) { + if (input.startsWith("search")) { + // the first one is "search" and afterwards addresses are following. + addresses = input.substring(6).split(" "); + break; + } + } + + if (addresses == null) { + addresses = new String[]{""}; + } + + + for (int i = 0; i < addresses.length; ++i) { + String address = addresses[i]; + int index = -1; + do { + if (index != -1) { + address = address.substring(index); + } else { + address = ""; + } + + // Try to connect to URL + try { + address = ".uni-freiburg.de"; + URL lookupURL = new URL("http://wpad"+ address +"/wpad.dat"); + Logger.log(getClass(), LogLevel.TRACE, "Trying url: {0}", lookupURL); + + HttpURLConnection con = (HttpURLConnection) lookupURL.openConnection(Proxy.NO_PROXY); + con.setInstanceFollowRedirects(true); + con.setRequestProperty("accept", "application/x-ns-proxy-autoconfig"); + if (con.getResponseCode() == 200) { + result = lookupURL.toString(); + return result; + } + con.disconnect(); + } catch (UnknownHostException e) { + Logger.log(getClass(), LogLevel.DEBUG, "Not available!"); + // Not a real error, try next address + } + if (address.length() == 0) { + break; + } + index = address.indexOf('.', 1); + } while (true); + } + + return null; + } + + /************************************************************************* + * Uses DHCP to find the script URL. + * @return the URL, null if not found. + ************************************************************************/ + + private String detectScriptUrlPerDHCP() { + Logger.log(getClass(), LogLevel.DEBUG, "Searching per DHCP not supported yet."); + // TODO Rossi 28.04.2009 Not implemented yet. + return null; + } + + // Main method for testing. + public static void main( String[] args ) throws IOException { + WpadProxySearchStrategy wPSS = new WpadProxySearchStrategy(); +// System.setProperty("com.btr.proxy.pac.overrideLocalIP", "10.0.0.1"); + try { + ProxySelector pS = wPSS.getProxySelector(); + ProxySelector.setDefault(pS); + List<Proxy> proxyList = pS.select(new URI("http://www.google.de")); + + if (proxyList.isEmpty()) { + Logger.log(WpadProxySearchStrategy.class, LogLevel.INFO, "ProxyList is empty!"); + } else { + Logger.log(WpadProxySearchStrategy.class, LogLevel.INFO, "proxyList contains: {0}", proxyList.toString()); + } + } catch (ProxyException e) { + // TODO bjoern 28.10.2014 Auto-generated catch block + e.printStackTrace(); + } catch (URISyntaxException e) { + // TODO bjoern 28.10.2014 Auto-generated catch block + e.printStackTrace(); + } + + URL test = new URL("http://www.google.de"); + URLConnection uc = test.openConnection(); + BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); + + String inputLine; + while ((inputLine = br.readLine()) != null) { + System.out.println(inputLine); + } + + Socket socket = new Socket("www.google.de", 80); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + bw.write("GET / HTTP/1.1\r\nHost: www.google.de\r\nConnection: close\r\nAccept-Encoding: *\r\n\r\n"); + bw.flush(); + + br = new BufferedReader(new InputStreamReader(socket.getInputStream())); + while ((inputLine = br.readLine()) != null) { + System.out.println(inputLine); + } + + } + +} diff --git a/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategyWithDHPC.java b/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategyWithDHPC.java new file mode 100644 index 0000000..bb56cc0 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/wpad/WpadProxySearchStrategyWithDHPC.java @@ -0,0 +1,315 @@ +package com.btr.proxy.search.wpad; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Properties; +import java.util.Random; +import java.util.StringTokenizer; + +import com.btr.proxy.search.ProxySearchStrategy; +import com.btr.proxy.search.wpad.dhcp.DHCPMessage; +import com.btr.proxy.search.wpad.dhcp.DHCPOptions; +import com.btr.proxy.search.wpad.dhcp.DHCPSocket; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; +import com.btr.proxy.util.ProxyException; +import com.btr.proxy.util.ProxyUtil; + +/***************************************************************************** + * Uses automatic proxy script search (WPAD) to find an PAC file automatically. + * <p> + * Note: at the moment only the DNS name guessing schema is implemented. All + * others are missing. + * </p> + * <p> + * For more information about WPAD: <a + * href="http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol" + * >Web_Proxy_Autodiscovery_Protocol</a> + * </p> + * <p> + * Outdated RFC draft: <a href= + * "http://www.web-cache.com/Writings/Internet-Drafts/draft-ietf-wrec-wpad-01.txt" + * >draft-ietf-wrec-wpad-01.txt</a> + * </p> + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class WpadProxySearchStrategyWithDHPC implements ProxySearchStrategy { + DHCPSocket bindSocket = null; + byte hwaddr[] = new byte[16]; + InetAddress serverIP; + int portNum; + boolean gSentinel; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public WpadProxySearchStrategyWithDHPC() { + super(); + } + + /************************************************************************* + * Loads the proxy settings from a PAC file. The location of the PAC file is + * determined automatically. + * + * @return a configured ProxySelector, null if none is found. + * @throws ProxyException + * on error. + ************************************************************************/ + + public ProxySelector getProxySelector() throws ProxyException { + try { + Logger.log(getClass(), LogLevel.TRACE, "Using WPAD to find a proxy"); + + String pacScriptUrl = detectScriptUrlPerDHCP(); + if (pacScriptUrl == null) { + pacScriptUrl = detectScriptUrlPerDNS(); + } + if (pacScriptUrl == null) { + return null; + } + Logger.log(getClass(), LogLevel.TRACE, "PAC script url found: {0}", + pacScriptUrl); + return ProxyUtil.buildPacSelectorForUrl(pacScriptUrl); + } catch (IOException e) { + Logger.log(getClass(), LogLevel.ERROR, "Error during WPAD search.", + e); + throw new ProxyException(e); + } + } + + /************************************************************************* + * Loads the settings and stores them in a properties map. + * + * @return the settings. + ************************************************************************/ + + public Properties readSettings() { + try { + String pacScriptUrl = detectScriptUrlPerDHCP(); + if (pacScriptUrl == null) { + pacScriptUrl = detectScriptUrlPerDNS(); + } + if (pacScriptUrl == null) { + return null; + } + Properties result = new Properties(); + result.setProperty("url", pacScriptUrl); + return result; + } catch (IOException e) { + // Ignore and return empty properties. + return new Properties(); + } + } + + /************************************************************************* + * Uses DNS to find the script URL. Attention: this detection method is + * known to have some severe security issues. + * + * @return the URL, null if not found. + ************************************************************************/ + + private String detectScriptUrlPerDNS() throws IOException { + String result = null; + // String fqdn = InetAddress.getLocalHost().getCanonicalHostName(); + + Logger.log(getClass(), LogLevel.TRACE, "Searching per DNS guessing."); + // Logger.log(getClass(), LogLevel.INFO, "fqdn: ", fqdn); + + /** + * Reading address from "/etc/resolv.conf"; file looks like: + * + * # Dynamic resolv.conf(5) file for glibc resolver(3) generated by + * resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE + * OVERWRITTEN nameserver 127.0.1.1 search ruf.uni-freiburg.de + * lp.ruf.uni-freiburg.de + */ + + FileReader fr = new FileReader("/etc/resolv.conf"); + BufferedReader br = new BufferedReader(fr); + + String input; + // using the 4th line of the file. + br.readLine(); + br.readLine(); + br.readLine(); + input = br.readLine(); + + String[] addresses = input.split(" "); + // the first one is "search" and afterwards addresses are following. + + for (int i = 0; i < addresses.length; ++i) { + String address = addresses[i]; + int index = -1; + do { + address = address.substring(index + 1); + + // if we are already on TLD level then escape + if (address.indexOf('.') == -1) { + break; + } + + // Try to connect to URL + try { + URL lookupURL = new URL("http://wpad." + address + + "/wpad.dat"); + Logger.log(getClass(), LogLevel.TRACE, "Trying url: {0}", + lookupURL); + + HttpURLConnection con = (HttpURLConnection) lookupURL + .openConnection(Proxy.NO_PROXY); + con.setInstanceFollowRedirects(true); + con.setRequestProperty("accept", + "application/x-ns-proxy-autoconfig"); + if (con.getResponseCode() == 200) { + result = lookupURL.toString(); + return result; + } + con.disconnect(); + } catch (UnknownHostException e) { + Logger.log(getClass(), LogLevel.DEBUG, "Not available!"); + // Not a real error, try next address + } + index = address.indexOf('.'); + } while (index != -1); + } + + return null; + } + + /************************************************************************* + * Uses DHCP to find the script URL. + * + * @return the URL, null if not found. + ************************************************************************/ + + private String detectScriptUrlPerDHCP() { + Logger.log(getClass(), LogLevel.DEBUG, + "Searching per DHCP not supported yet."); + + // create socket. + try { + // bindSocket = new DHCPSocket(DHCPMessage.CLIENT_PORT); + // --> Not able to use ports under 1024 without being root. + this.bindSocket = new DHCPSocket(1068); + } catch (SocketException e) { + System.err.println(e); + return null; + } + + + + return null; + } + + // Sends DHCPDISCOVER Message and returns server message + private DHCPMessage SendDiscover() { + DHCPSocket bindSocket = null; + try { + bindSocket = new DHCPSocket(1068); + } catch (SocketException e1) { + // TODO bjoern 29.10.2014 Auto-generated catch block + e1.printStackTrace(); + return null; + } + + Random ranXid = new Random(); + DHCPMessage messageOut = new DHCPMessage(DHCPMessage.BROADCAST_ADDR, + DHCPMessage.SERVER_PORT); + DHCPMessage messageIn = new DHCPMessage(DHCPMessage.BROADCAST_ADDR, + DHCPMessage.SERVER_PORT); + try { + // Specify type of message: 1 = request - message, 2 = reply - message + messageOut.setOp((byte) 1); + // set Hardware type: 1 = Ethernet, 6 = IEEE 802 Networks, ... + messageOut.setHtype((byte) 1); // 1 = ethernet + // set hardware address length: 6 for MAC address + messageOut.setHlen((byte) 6); + messageOut.setHops((byte) 0); + // set session ID for later comparing with incoming message. + messageOut.setXid(ranXid.nextInt()); + messageOut.setSecs((short) 0); + messageOut.setFlags((short) 0x8000); + // set client hardware address, in this case my own hardware address of eth0. + messageOut.setChaddr(ChaddrToByte("D8:D3:85:80:8F:C9")); + + byte[] opt = new byte[1]; + byte[] wpadOpt = new byte[1]; + opt[0] = DHCPMessage.DHCPDISCOVER; + + // change message type + messageOut.setOption(DHCPOptions.OPTION_DHCP_MESSAGE_TYPE, opt); + wpadOpt[0] = DHCPMessage.OPTION_DHCP_WPAD; + messageOut.setOption(DHCPOptions.OPTION_DHCP_PARAMETER_REQUEST_LIST, wpadOpt); +// messageOut.setOption(DHCPOptions.OPTION_DHCP_IP_ADRESS_REQUESTED, +// offerMessageIn.getYiaddr()); + + bindSocket.send(messageOut); // send DHCPREQUEST + System.out.println("Sending DHCPDISCOVER ..."); + boolean sentinal = true; + int counter = 0; + while (sentinal) { + if (counter == 2) { return null; } + if (bindSocket.receive(messageIn)) { + if (messageOut.getXid() == messageIn.getXid()) { + sentinal = false; + } else { + bindSocket.send(messageOut); + counter++; + } + } else { + bindSocket.send(messageOut); + counter++; + } + } + } catch (SocketException e) { + System.err.println(e); + return null; + } catch (IOException e) { + System.err.println(e); + return null; + } finally { + try { + bindSocket.close(); + } catch (Exception e) { + // do nothing. + } + } // end catch + return messageIn; + } + + // Main method for testing. + public static void main(String[] args) throws UnsupportedEncodingException { + WpadProxySearchStrategyWithDHPC wPSSDHCP = new WpadProxySearchStrategyWithDHPC(); + DHCPMessage serverAnswer = wPSSDHCP.SendDiscover(); + + Logger.log(WpadProxySearchStrategyWithDHPC.class, LogLevel.INFO, + "DHCP serverAnswer: {0}", new String(serverAnswer.getOption(DHCPMessage.OPTION_DHCP_WPAD), "UTF-8")); + } + + private byte[] ChaddrToByte(String inChaddr) { + StringTokenizer token = new StringTokenizer(inChaddr, ":"); + Integer tempInt = new Integer(0); + byte outHwaddr[] = new byte[16]; + int temp; + int i = 0; + while (i < 6) { + temp = tempInt.parseInt(token.nextToken(), 16); + outHwaddr[i] = (byte) temp; + i++; + } + return outHwaddr; + } + +} diff --git a/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPMessage.java b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPMessage.java new file mode 100644 index 0000000..86323d5 --- /dev/null +++ b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPMessage.java @@ -0,0 +1,880 @@ +package com.btr.proxy.search.wpad.dhcp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * This class represents a DHCP Message. + * + * @author Jason Goldschmidt, Nick Stone and Simon Frankenberger + */ +public class DHCPMessage { + // ----------------------------------------------------------- + // Constants + // ----------------------------------------------------------- + /** + * Operation for a request + */ + public static final byte OP_REQUEST = 1; + + /** + * Operation for a reply + */ + public static final byte OP_REPLY = 2; + + /** + * Message Code representing a DHCPDISCOVER message + */ + public static final byte DHCPDISCOVER = 1; + + /** + * Message Code representing a DHCPOFFER message + */ + public static final byte DHCPOFFER = 2; + + /** + * Message Code representing a DHCPREQUEST message + */ + public static final byte DHCPREQUEST = 3; + + /** + * Message Code representing a DHCPDECLINE message + */ + public static final byte DHCPDECLINE = 4; + + /** + * Message Code representing a DHCPACK message + */ + public static final byte DHCPACK = 5; + + /** + * Message Code representing a DHCPNAK message + */ + public static final byte DHCPNAK = 6; + + /** + * Message Code representing a DHCPRELEASE message + */ + public static final byte DHCPRELEASE = 7; + + /** + * Message Code representing a DHCPINFORM message + */ + public static final byte DHCPINFORM = 8; + + /** + * Default DHCP client port + */ + public static final int CLIENT_PORT = 68; // client port (by default) + + /** + * Default DHCP server port + */ + public static final int SERVER_PORT = 67; // server port (by default) + + /** + * Broadcast Adress to send packets to + */ + public static InetAddress BROADCAST_ADDR = null; + + public static final byte OPTION_DHCP_WPAD = (byte) 252; + + + + // ----------------------------------------------------------- + // Fields defining a dhcp message + // ----------------------------------------------------------- + /** + * Operation Code.<br> + * <br> + * Can either be {@link #OP_REQUEST} or {@link #OP_REPLY}. + */ + private byte op; + + /** + * Networktype as defined by + * <a href="http://tools.ietf.org/html/rfc1340#page-54">RFC1340 page 54</a>. + */ + private byte htype; + + /** + * Hardware address length (e.g. '6' for ethernet). + */ + private byte hlen; + + /** + * Client sets to zero, optionally used by relay-agents + * when booting via a relay-agent. + */ + private byte hops; + + /** + * Transaction ID, a random number chosen by the + * client, used by the client and server to associate + * messages and responses between a client and a + * server. + */ + private int xid; + + /** + * Filled in by client, seconds elapsed since client + * started trying to boot. + */ + private short secs; + + /** + * Flags for this message.<br> + * The leftmost bit is defined as the BROADCAST (B) flag. + */ + private short flags; + + /** + * Client IP address; filled in by client in + * DHCPREQUEST if verifying previously allocated + * configuration parameters. + */ + private byte ciaddr[] = new byte[4]; + + /** + * 'your' (client) IP address. + */ + private byte yiaddr[] = new byte[4]; + + /** + * IP address of next server to use in bootstrap; + * returned in DHCPOFFER, DHCPACK and DHCPNAK by + * server. + */ + private byte siaddr[] = new byte[4]; + + /** + * Relay agent IP address, used in booting via a + * relay-agent. + */ + private byte giaddr[] = new byte[4]; + + /** + * Client hardware address. + */ + private byte chaddr[] = new byte[16]; + + /** + * Optional server host name, null terminated string. + */ + private byte sname[] = new byte[64]; + + /** + * Boot file name, null terminated string; "generic" + * name or null in DHCPDISCOVER, fully qualified + * directory-path name in DHCPOFFER. + */ + private byte file[] = new byte[128]; + + /** + * Internal representation of the given DHCP options. + */ + private DHCPOptions optionsList = null; + + /** + * global port variable for this message + */ + private int gPort; + + /** + * The destination IP-Adress of this message + */ + private InetAddress destination_IP; + + static { + try { + BROADCAST_ADDR = InetAddress.getByName("255.255.255.255"); + // broadcast address(by default) + } + catch (UnknownHostException e) { + // Broadcast address must always exist + } + } + + // ----------------------------------------------------------- + // Constructors + // ----------------------------------------------------------- + + /** + * Creates empty DHCPMessage object, + * initializes the object, sets the host to the broadcast address, + * the local subnet, binds to the default server port. + */ + public DHCPMessage() { + initialize(); + + this.destination_IP = BROADCAST_ADDR; + this.gPort = SERVER_PORT; + } + + /** + * Copy constructor + * creates DHCPMessage from inMessage + * + * @param inMessage The message to be copied + */ + public DHCPMessage(DHCPMessage inMessage) { + initialize(); + + this.destination_IP = BROADCAST_ADDR; + this.gPort = SERVER_PORT; + this.op = inMessage.getOp(); + this.htype = inMessage.getHtype(); + this.hlen = inMessage.getHlen(); + this.hops = inMessage.getHops(); + this.xid = inMessage.getXid(); + this.secs = inMessage.getSecs(); + this.flags = inMessage.getFlags(); + this.ciaddr = inMessage.getCiaddr(); + this.yiaddr = inMessage.getYiaddr(); + this.siaddr = inMessage.getSiaddr(); + this.giaddr = inMessage.getGiaddr(); + this.chaddr = inMessage.getChaddr(); + this.sname = inMessage.getSname(); + this.file = inMessage.getFile(); + this.optionsList.internalize(inMessage.getOptions()); + } + + /** + * Copy constructor + * creates DHCPMessage from inMessage and sets server and port. + * + * @param inMessage The message to be copied + * @param inServername The host name + * @param inPort The port number + */ + public DHCPMessage(DHCPMessage inMessage, InetAddress inServername, int inPort) { + initialize(); + + this.destination_IP = inServername; + this.gPort = inPort; + + this.op = inMessage.getOp(); + this.htype = inMessage.getHtype(); + this.hlen = inMessage.getHlen(); + this.hops = inMessage.getHops(); + this.xid = inMessage.getXid(); + this.secs = inMessage.getSecs(); + this.flags = inMessage.getFlags(); + this.ciaddr = inMessage.getCiaddr(); + this.yiaddr = inMessage.getYiaddr(); + this.siaddr = inMessage.getSiaddr(); + this.giaddr = inMessage.getGiaddr(); + this.chaddr = inMessage.getChaddr(); + this.sname = inMessage.getSname(); + this.file = inMessage.getFile(); + this.optionsList.internalize(inMessage.getOptions()); + } + + /** + * Copy constructor + * creates DHCPMessage from inMessage and sets server name. + * + * @param inMessage The message to be copied + * @param inServername The host name + */ + public DHCPMessage(DHCPMessage inMessage, InetAddress inServername) { + initialize(); + + this.destination_IP = inServername; + this.gPort = SERVER_PORT; + + this.op = inMessage.getOp(); + this.htype = inMessage.getHtype(); + this.hlen = inMessage.getHlen(); + this.hops = inMessage.getHops(); + this.xid = inMessage.getXid(); + this.secs = inMessage.getSecs(); + this.flags = inMessage.getFlags(); + this.ciaddr = inMessage.getCiaddr(); + this.yiaddr = inMessage.getYiaddr(); + this.siaddr = inMessage.getSiaddr(); + this.giaddr = inMessage.getGiaddr(); + this.chaddr = inMessage.getChaddr(); + this.sname = inMessage.getSname(); + this.file = inMessage.getFile(); + this.optionsList.internalize(inMessage.getOptions()); + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object, sets the host to a specified host name, + * and binds to a specified port. + * + * @param inServername The host name + * @param inPort The port number + */ + public DHCPMessage(InetAddress inServername, int inPort) { + initialize(); + + this.destination_IP = inServername; + this.gPort = inPort; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object, sets the host to a specified host name, + * and binds to the default port. + * + * @param inServername The host name + */ + public DHCPMessage(InetAddress inServername) { + initialize(); + + this.destination_IP = inServername; + this.gPort = SERVER_PORT; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object, sets the host to the broadcast address, + * and binds to a specified port. + * + * @param inPort The port number + */ + public DHCPMessage(int inPort) { + initialize(); + + this.destination_IP = BROADCAST_ADDR; + this.gPort = inPort; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object with a specified byte array containing + * DHCP message information, sets the host to default host name, the + * local subnet, and bind to the default server port. + * + * @param ibuf The byte array to initialize DHCPMessage object + */ + public DHCPMessage(byte[] ibuf) { + initialize(); + internalize(ibuf); + + this.destination_IP = BROADCAST_ADDR; + this.gPort = SERVER_PORT; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object with a specified byte array containing + * DHCP message information, sets the host to specified host name, + * and binds to the specified port. + * + * @param ibuf The byte array to initialize DHCPMessage object + * @param inServername The hostname + * @param inPort The port number + */ + public DHCPMessage(byte[] ibuf, InetAddress inServername, int inPort) { + initialize(); + internalize(ibuf); + + this.destination_IP = inServername; + this.gPort = inPort; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object with a specified byte array containing + * DHCP message information, sets the host to broadcast address, + * and binds to the specified port. + * + * @param ibuf The byte array to initialize DHCPMessage object + * @param inPort The port number + */ + public DHCPMessage(byte ibuf[], int inPort) { + initialize(); + internalize(ibuf); + + this.destination_IP = BROADCAST_ADDR; + this.gPort = inPort; + } + + /** + * Creates an empty DHCPMessage object, + * initializes the object with a specified byte array containing + * DHCP message information, sets the host to specified host name, + * and binds to the specified port. + * + * @param ibuf The byte array to initialize DHCPMessage object + * @param inServername The hostname + */ + public DHCPMessage(byte[] ibuf, InetAddress inServername) { + initialize(); + internalize(ibuf); + + this.destination_IP = inServername; + this.gPort = SERVER_PORT; + } + + /** + * Creates a new DHCPMessage object from the giben DataInputStream. + * + * @param inStream The stream to read from + */ + public DHCPMessage(DataInputStream inStream) { + initialize(); + + try { + this.op = inStream.readByte(); + this.htype = inStream.readByte(); + this.hlen = inStream.readByte(); + this.hops = inStream.readByte(); + this.xid = inStream.readInt(); + this.secs = inStream.readShort(); + this.flags = inStream.readShort(); + inStream.readFully(this.ciaddr, 0, 4); + inStream.readFully(this.yiaddr, 0, 4); + inStream.readFully(this.siaddr, 0, 4); + inStream.readFully(this.giaddr, 0, 4); + inStream.readFully(this.chaddr, 0, 16); + inStream.readFully(this.sname, 0, 64); + inStream.readFully(this.file, 0, 128); + byte[] options = new byte[312]; + inStream.readFully(options, 0, 312); + this.optionsList.internalize(options); + } + catch (IOException e) { + System.err.println(e); + } + } + + // ----------------------------------------------------------- + // Methods + // ----------------------------------------------------------- + /** + * Initializes datamembers in the constructors + * every empty DHCPMessage object will by default contain these params. + * Initializes options array from linked list form. + */ + private void initialize() { + this.optionsList = new DHCPOptions(); + } + + /** + * Converts a DHCPMessage object to a byte array. + * + * @return A byte array with information from DHCPMessage object, + * ready to send. + */ + public synchronized byte[] externalize() { + ByteArrayOutputStream outBStream = new ByteArrayOutputStream(); + DataOutputStream outStream = new DataOutputStream(outBStream); + + try { + outStream.writeByte(this.op); + outStream.writeByte(this.htype); + outStream.writeByte(this.hlen); + outStream.writeByte(this.hops); + outStream.writeInt(this.xid); + outStream.writeShort(this.secs); + outStream.writeShort(this.flags); + outStream.write(this.ciaddr, 0, 4); + outStream.write(this.yiaddr, 0, 4); + outStream.write(this.siaddr, 0, 4); + outStream.write(this.giaddr, 0, 4); + outStream.write(this.chaddr, 0, 16); + outStream.write(this.sname, 0, 64); + outStream.write(this.file, 0, 128); + + byte[] options = new byte[312]; + if (this.optionsList == null) { + initialize(); + } + + options = this.optionsList.externalize(); + outStream.write(options, 0, 312); + } catch (IOException e) { + System.err.println(e); + } + + // extract the byte array from the Stream + byte data[] = outBStream.toByteArray(); + + return data; + } + + /** + * Convert a specified byte array containing a DHCP message into a + * DHCPMessage object. + * + * @param ibuff Byte array to convert to a DHCPMessage object + * @return A DHCPMessage object with information from byte array. + */ + + public synchronized DHCPMessage internalize(byte[] ibuff) { + ByteArrayInputStream inBStream = + new ByteArrayInputStream(ibuff, 0, ibuff.length); + DataInputStream inStream = new DataInputStream(inBStream); + + try { + this.op = inStream.readByte(); + this.htype = inStream.readByte(); + this.hlen = inStream.readByte(); + this.hops = inStream.readByte(); + this.xid = inStream.readInt(); + this.secs = inStream.readShort(); + this.flags = inStream.readShort(); + inStream.readFully(this.ciaddr, 0, 4); + inStream.readFully(this.yiaddr, 0, 4); + inStream.readFully(this.siaddr, 0, 4); + inStream.readFully(this.giaddr, 0, 4); + inStream.readFully(this.chaddr, 0, 16); + inStream.readFully(this.sname, 0, 64); + inStream.readFully(this.file, 0, 128); + + byte[] options = new byte[312]; + inStream.readFully(options, 0, 312); + if (this.optionsList == null) { + initialize(); + } + + this.optionsList.internalize(options); + } + catch (IOException e) { + System.err.println(e); + } // end catch + + return this; + } + + /** + * Set message Op code / message type. + * + * @param inOp message Op code / message type + */ + public void setOp(byte inOp) { + this.op = inOp; + } + + /** + * Set hardware address type. + * + * @param inHtype hardware address type + */ + public void setHtype(byte inHtype) { + this.htype = inHtype; + } + + /** + * Set hardware address length. + * + * @param inHlen hardware address length + */ + public void setHlen(byte inHlen) { + this.hlen = inHlen; + } + + /** + * Set hops field. + * + * @param inHops hops field + */ + public void setHops(byte inHops) { + this.hops = inHops; + } + + /** + * Set transaction ID. + * + * @param inXid transactionID + */ + public void setXid(int inXid) { + this.xid = inXid; + } + + /** + * Set seconds elapsed since client began address acquisition or + * renewal process. + * + * @param inSecs Seconds elapsed since client began address acquisition + * or renewal process + */ + public void setSecs(short inSecs) { + this.secs = inSecs; + } + + /** + * Set flags field. + * + * @param inFlags flags field + */ + public void setFlags(short inFlags) { + this.flags = inFlags; + } + + /** + * Set client IP address. + * + * @param inCiaddr client IP address + */ + public void setCiaddr(byte[] inCiaddr) { + this.ciaddr = inCiaddr; + } + + /** + * Set 'your' (client) IP address. + * + * @param inYiaddr 'your' (client) IP address + */ + public void setYiaddr(byte[] inYiaddr) { + this.yiaddr = inYiaddr; + } + + /** + * Set address of next server to use in bootstrap. + * + * @param inSiaddr address of next server to use in bootstrap + */ + public void setSiaddr(byte[] inSiaddr) { + this.siaddr = inSiaddr; + } + + /** + * Set relay agent IP address. + * + * @param inGiaddr relay agent IP address + */ + public void setGiaddr(byte[] inGiaddr) { + this.giaddr = inGiaddr; + } + + /** + * Set client harware address. + * + * @param inChaddr client hardware address + */ + public void setChaddr(byte[] inChaddr) { + this.chaddr = inChaddr; + } + + /** + * Set optional server host name. + * + * @param inSname server host name + */ + public void setSname(byte[] inSname) { + this.sname = inSname; + } + + /** + * Set boot file name. + * + * @param inFile boot file name + */ + public void setFile(byte[] inFile) { + this.file = inFile; + } + + /** + * Set message destination port. + * + * @param inPortNum port on message destination host + */ + public void setPort(int inPortNum) { + this.gPort = inPortNum; + } + + /** + * Set message destination IP + * @param inHost string representation of message destination IP or + * hostname + */ + public void setDestinationHost(String inHost) { + try { + this.destination_IP = InetAddress.getByName(inHost); + } + catch (Exception e) { + System.err.println(e); + } + } + + /** + * @return message Op code / message type. + */ + public byte getOp() { + return this.op; + } + + /** + * @return hardware address type. + */ + public byte getHtype() { + return this.htype; + } + + /** + * @return hardware address length. + */ + public byte getHlen() { + return this.hlen; + } + + /** + * @return hops field. + */ + public byte getHops() { + return this.hops; + } + + /** + * @return transaction ID. + */ + public int getXid() { + return this.xid; + } + + /** + * @return seconds elapsed since client began address + * acquisition or renewal process. + */ + public short getSecs() { + return this.secs; + } + + /** + * @return flags field. + */ + public short getFlags() { + return this.flags; + } + + /** + * @return client IP address. + */ + public byte[] getCiaddr() { + return this.ciaddr; + } + + /** + * @return 'your' (client) IP address. + */ + public byte[] getYiaddr() { + return this.yiaddr; + } + + /** + * @return address of next server to use in bootstrap. + */ + public byte[] getSiaddr() { + return this.siaddr; + } + + /** + * @return relay agent IP address. + */ + public byte[] getGiaddr() { + return this.giaddr; + } + + /** + * @return client harware address. + */ + public byte[] getChaddr() { + return this.chaddr; + } + + /** + * @return optional server host name. + */ + public byte[] getSname() { + return this.sname; + } + + /** + * @return boot file name. + */ + public byte[] getFile() { + return this.file; + } + + /** + * @return a byte array containing options + */ + public byte[] getOptions() { + if (this.optionsList == null) { + initialize(); + } + return this.optionsList.externalize(); + } + + /** + * @return An interger representation of the message + * destination port + */ + public int getPort() { + return this.gPort; + } + + /** + * Get message destination hostname + * + * @return A string representing the hostname of the + * message destination server + */ + public String getDestinationAddress() { + return this.destination_IP.getHostAddress(); + } + + /** + * Sets DHCP options in DHCPMessage. If option already exists + * then remove old option and insert a new one. + * + * @param inOptNum option number + * @param inOptionData option data + */ + public void setOption(int inOptNum, byte[] inOptionData) { + this.optionsList.setOption((byte) inOptNum, inOptionData); + } + + /** + * Returns specified DHCP option that matches the input code. Null is + * returned if option is not set. + * + * @param inOptNum option number + * + * @return the option matching input code + */ + public byte[] getOption(int inOptNum) { + if (this.optionsList == null) { + initialize(); + } + return this.optionsList.getOption((byte) inOptNum); + } + + /** + * Removes the specified DHCP option that matches the input code. + * + * @param inOptNum option number + */ + public void removeOption(int inOptNum) { + if (this.optionsList == null) { + initialize(); + } + this.optionsList.removeOption((byte) inOptNum); + } + + /** + * Report whether or not the input option is set. + * + * @param inOptNum option number + * + * @return is the given option set? + */ + public boolean IsOptSet(int inOptNum) { + if (this.optionsList == null) { + initialize(); + } + + return this.optionsList.contains((byte) inOptNum); + } +} diff --git a/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPOptions.java b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPOptions.java new file mode 100644 index 0000000..1a07efb --- /dev/null +++ b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPOptions.java @@ -0,0 +1,235 @@ +package com.btr.proxy.search.wpad.dhcp; + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * This class represents a linked list of options for a DHCP message. + * Its purpose is to ease option handling such as add, remove or change. + * + * @author Jason Goldschmidt and Simon Frankenberger + */ +public class DHCPOptions { + public static final int OPTION_PAD = 0; + public static final int OPTION_NETMASK = 1; + public static final int OPTION_TIME_OFFSET = 2; + public static final int OPTION_ROUTERS = 3; + public static final int OPTION_TIME_SERVERS = 4; + public static final int OPTION_NAME_SERVERS = 5; + public static final int OPTION_DNS_SERVERS = 6; + public static final int OPTION_LOG_SERVERS = 7; + public static final int OPTION_COOKIE_SERVERS = 8; + public static final int OPTION_LPR_SERVERS = 9; + public static final int OPTION_IMPRESS_SERVERS = 10; + public static final int OPTION_RESSOURCE_LOCATION_SERVERS = 11; + public static final int OPTION_HOSTNAME = 12; + public static final int OPTION_BOOT_FILESIZE = 13; + public static final int OPTION_MERIT_DUMPFILE = 14; + public static final int OPTION_DOMAIN_NAME = 15; + public static final int OPTION_SWAP_SERVER = 16; + public static final int OPTION_ROOT_PATH = 17; + public static final int OPTION_EXTENSIONS_PATH = 18; + public static final int OPTION_END = 255; + + public static final int OPTION_IP_HOST_FORWARDING_ENABLE = 19; + public static final int OPTION_IP_HOST_NON_LOCAL_SOURCE_ROUTING_ENABLE = 20; + public static final int OPTION_IP_HOST_POLICY_FILTERS = 21; + public static final int OPTION_IP_HOST_MAXIMUM_DATAGRAM_REASSEMBLY_SIZE = 22; + public static final int OPTION_IP_HOST_DEFAULT_TTL = 23; + public static final int OPTION_IP_HOST_MTU_AGEING_TIMEOUT = 24; + public static final int OPTION_IP_HOST_MTU_PLATEAU_TABLE = 25; + + public static final int OPTION_IP_INTERFACE_MTU = 26; + public static final int OPTION_IP_INTERFACE_ALL_SUBNETS_LOCAL_ENABLE = 27; + public static final int OPTION_IP_INTERFACE_BROADCAST_ADDRESS = 28; + public static final int OPTION_IP_INTERFACE_PERFORM_MASK_DISCOVERY_ENABLE = 29; + public static final int OPTION_IP_INTERFACE_MASK_SUPPLIER_ENABLE = 30; + public static final int OPTION_IP_INTERFACE_PERFORM_ROUTER_DISCOVERY_ENABLE = 31; + public static final int OPTION_IP_INTERFACE_ROUTER_SOLICITATION_ADDRESS = 32; + public static final int OPTION_IP_INTERFACE_STATIC_ROUTES = 33; + + public static final int OPTION_LINK_TRAILER_ENCAPSULATION_ENABLE = 34; + public static final int OPTION_LINK_ARP_CACHE_TIMEOUT = 35; + public static final int OPTION_LINK_ETHERNET_ENCAPSULATION_ENABLE = 36; + + public static final int OPTION_TCP_DEFAULT_TTL = 37; + public static final int OPTION_TCP_KEEP_ALIVE_INTERVAL = 38; + public static final int OPTION_TCP_KEEP_ALIVE_GERBAGE_ENABLE = 39; + + public static final int OPTION_NIS_DOMAIN = 40; + public static final int OPTION_NIS_SERVERS = 41; + public static final int OPTION_NTP_SERVERS = 42; + + public static final int OPTION_SERVICE_VENDOR_SPECIFIC_INFORMATIONS = 43; + public static final int OPTION_SERVICE_NETBOIS_NAME_SERVERS = 44; + public static final int OPTION_SERVICE_NETBOIS_DATAGRAM_DISTRIBUTION_SERVERS = 45; + public static final int OPTION_SERVICE_NETBOIS_NODE_TYPE = 46; + public static final int OPTION_SERVICE_NETBOIS_SCOPE_TYPE = 47; + public static final int OPTION_SERVICE_X_FONT_SERVERS = 48; + public static final int OPTION_SERVICE_X_DISPLAY_MANAGERS = 49; + + public static final int OPTION_DHCP_IP_ADRESS_REQUESTED = 50; + public static final int OPTION_DHCP_IP_LEASE_TIME = 51; + public static final int OPTION_DHCP_OVERLOAD = 52; + public static final int OPTION_DHCP_MESSAGE_TYPE = 53; + public static final int OPTION_DHCP_SERVER_IDENTIFIER = 54; + public static final int OPTION_DHCP_PARAMETER_REQUEST_LIST = 55; + public static final int OPTION_DHCP_MESSAGE = 56; + public static final int OPTION_DHCP_MAXIMUM_MESSAGE_SIZE = 57; + public static final int OPTION_DHCP_RENEWAL_TIME = 58; + public static final int OPTION_DHCP_REBIND_TIME = 59; + public static final int OPTION_DHCP_CLASS_IDENTIFIER = 60; + public static final int OPTION_DHCP_CLIENT_IDENTIFIER = 61; + + + /** + *This inner class represent an entry in the Option Table + */ + + class DHCPOptionsEntry { + protected byte code; + protected byte length; + protected byte content[]; + + public DHCPOptionsEntry(byte entryCode, byte entryLength, + byte entryContent[]) { + this.code = entryCode; + this.length = entryLength; + this.content = entryContent; + } + + @Override + public String toString() { + return "Code: " + this.code + "\nContent: " + new String(this.content); + } + } + + private Hashtable<Byte, DHCPOptionsEntry> optionsTable = null; + + public DHCPOptions() { + this.optionsTable = new Hashtable<Byte, DHCPOptionsEntry>(); + } + + /** + * Removes option with specified bytecode + * @param entryCode The code of option to be removed + */ + + public void removeOption(byte entryCode) { + this.optionsTable.remove(new Byte(entryCode)); + } + + /** + * Returns true if option code is set in list; false otherwise + * @param entryCode The node's option code + * @return true if option is set, otherwise false + */ + public boolean contains(byte entryCode) { + return this.optionsTable.containsKey(new Byte(entryCode)); + } + + /** + * Determines if list is empty + * @return true if there are no options set, otherwise false + */ + public boolean isEmpty() { + return this.optionsTable.isEmpty(); + } + + /** + * Fetches value of option by its option code + * @param entryCode The node's option code + * @return byte array containing the value of option entryCode. + * null is returned if option is not set. + */ + public byte[] getOption(byte entryCode) { + if (this.contains(entryCode)) { + DHCPOptionsEntry ent = this.optionsTable.get(new Byte(entryCode)); + return ent.content; + } + else { + return null; + } + } + + /** + * Changes an existing option to new value + * @param entryCode The node's option code + * @param value Content of node option + */ + public void setOption(byte entryCode, byte value[]) { + DHCPOptionsEntry opt = new DHCPOptionsEntry(entryCode, (byte) value.length, value); + this.optionsTable.put(new Byte(entryCode), opt); + } + + /** + * Returns the option value of a specified option code in a byte array + * @param length Length of option content + * @param position Location in array of option node + * @param options The byte array of options + * @return byte array containing the value for the option + */ + private byte[] getArrayOption(int length, int position, byte options[]) { + byte value[] = new byte[length]; + for (int i = 0; i < length; i++) { + value[i] = options[position + i]; + } + return value; + } + + /** + * Converts an options byte array to a linked list + * @param optionsArray The byte array representation of the options list + */ + public void internalize(byte[] optionsArray) { + + /* Assume options valid and correct */ + int pos = 4; // ignore vendor magic cookie + byte code, length; + byte value[]; + + while (optionsArray[pos] != (byte) 255) { // until end option + code = optionsArray[pos++]; + length = optionsArray[pos++]; + value = getArrayOption(length, pos, optionsArray); + setOption(code, value); + pos += length; // increment position pointer + } + } + + /** + * Converts a linked options list to a byte array + * @return array representation of optionsTable + */ + // todo provide overflow return + public byte[] externalize() { + byte[] options = new byte[312]; + + options[0] = (byte) 99; // insert vendor magic cookie + options[1] = (byte) 130; + options[2] = (byte) 83; + options[3] = (byte) 99; + + int position = 4; + Enumeration<DHCPOptionsEntry> e = this.optionsTable.elements(); + + while (e.hasMoreElements()) { + DHCPOptionsEntry entry = e.nextElement(); + options[position++] = entry.code; + options[position++] = entry.length; + for (int i = 0; i < entry.length; ++i) { + options[position++] = entry.content[i]; + } + } + + options[position] = (byte) 255; // insert end option + return options; + } + + /** + * Prints the options linked list: For testing only. + */ + public void printList() { + System.out.println(this.optionsTable.toString()); + } +} diff --git a/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPSocket.java b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPSocket.java new file mode 100644 index 0000000..6dbe2bf --- /dev/null +++ b/src/main/java/com/btr/proxy/search/wpad/dhcp/DHCPSocket.java @@ -0,0 +1,107 @@ +package com.btr.proxy.search.wpad.dhcp; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; + +/** + * This class represents a Socket for sending DHCP Messages + * + * @author Jason Goldschmidt and Simon Frankenberger + * + * @see java.net.DatagramSocket + */ + +public class DHCPSocket extends DatagramSocket { + /** + * Default socket timeout (1 second) + */ + private int SOCKET_TIMEOUT = 5000; + + /** + * Default MTU (Maximum Transmission Unit) for ethernet (in bytes) + */ + private int mtu = 1500; + + /** + * Constructor for creating DHCPSocket on a specific port on the local + * machine. + * + * @param inPort The port for the application to bind. + * + * @throws SocketException As thrown by the {@link Socket} constructor + */ + public DHCPSocket(int inPort) throws SocketException { + super(inPort); + setSoTimeout(this.SOCKET_TIMEOUT); + } + + /** + * Sets the Maximum Transfer Unit for the UDP DHCP Packets to be set. + * + * @param inSize Integer representing desired MTU + */ + public void setMTU(int inSize) { + this.mtu = inSize; + } + + /** + * Returns the set MTU for this socket + * + * @return The Maximum Transfer Unit set for this socket + */ + public int getMTU() { + return this.mtu; + } + + /** + * Sends a DHCPMessage object to a predifined host. + * + * @param inMessage Well-formed DHCPMessage to be sent to a server + * + * @throws IOException If the message could not be sent. + */ + public synchronized void send(DHCPMessage inMessage) throws IOException { + byte data[] = new byte[this.mtu]; + data = inMessage.externalize(); + InetAddress dest = null; + try { + dest = InetAddress.getByName(inMessage.getDestinationAddress()); + } + catch (UnknownHostException e) { + } + + DatagramPacket outgoing = new DatagramPacket(data, data.length, dest, + inMessage.getPort()); + + send(outgoing); // send outgoing message + } + + /** + * Receives a datagram packet containing a DHCP Message into + * a DHCPMessage object. + * + * @return <code>true</code> if message is received, + * <code>false</code> if timeout occurs. + * @param outMessage DHCPMessage object to receive new message into + */ + public synchronized boolean receive(DHCPMessage outMessage) { + try { + DatagramPacket incoming = new DatagramPacket(new byte[this.mtu], + this.mtu); + //gSocket. + receive(incoming); // block on receive for SOCKET_TIMEOUT + + outMessage.internalize(incoming.getData()); + } + catch (java.io.IOException e) { + e.printStackTrace(); + return false; + } // end catch + return true; + } +} 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); + +} diff --git a/src/main/java/com/btr/proxy/test/ProxyTester.java b/src/main/java/com/btr/proxy/test/ProxyTester.java new file mode 100644 index 0000000..c01c7bb --- /dev/null +++ b/src/main/java/com/btr/proxy/test/ProxyTester.java @@ -0,0 +1,176 @@ +package com.btr.proxy.test; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URL; +import java.text.MessageFormat; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import com.btr.proxy.search.ProxySearch; +import com.btr.proxy.search.ProxySearch.Strategy; +import com.btr.proxy.util.Logger; +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Small test application that allows you to select a proxy search strategy + * and then validate URLs against it. + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxyTester extends JFrame { + + private static final long serialVersionUID = 1L; + + private JComboBox modes; + private JButton testButton; + private JTextField urlField; + + private JTextArea logArea; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public ProxyTester() { + super(); + init(); + } + + /************************************************************************* + * Initializes the GUI. + ************************************************************************/ + + private void init() { + setTitle("Proxy Vole Tester"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JPanel p = new JPanel(); + + p.add(new JLabel("Mode:")); + + this.modes = new JComboBox(ProxySearch.Strategy.values()); + p.add(this.modes); + + p.add(new JLabel("URL:")); + this.urlField = new JTextField(30); + this.urlField.setText("http://code.google.com/p/proxy-vole/"); + p.add(this.urlField); + + this.testButton = new JButton("Test"); + this.testButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + testUrl(); + } + }); + p.add(this.testButton); + + this.logArea = new JTextArea(5, 50); + JPanel contenPane = new JPanel(new BorderLayout()); + contenPane.add(p, BorderLayout.NORTH); + contenPane.add(new JScrollPane(this.logArea), BorderLayout.CENTER); + setContentPane(contenPane); + + pack(); + setLocationRelativeTo(null); + installLogger(); + } + + /************************************************************************* + * Install the framework logger. + ************************************************************************/ + + private void installLogger() { + Logger.setBackend(new Logger.LogBackEnd() { + public void log(Class<?> clazz, LogLevel loglevel, String msg, Object... params) { + ProxyTester.this.logArea.append(loglevel+"\t"+MessageFormat.format(msg, params)+"\n"); + } + public boolean isLogginEnabled(LogLevel logLevel) { + return true; + } + }); + } + + /************************************************************************* + * Test the given URL with the given Proxy Search. + ************************************************************************/ + + protected void testUrl() { + try { + if (this.urlField.getText().trim().length() == 0) { + JOptionPane.showMessageDialog(this, "Please enter an URL first."); + return; + } + + this.logArea.setText(""); + + Strategy pss = (Strategy) this.modes.getSelectedItem(); + ProxySearch ps = new ProxySearch(); + ps.addStrategy(pss); + ProxySelector psel = ps.getProxySelector(); + if (psel == null) { + JOptionPane.showMessageDialog(this, "No proxy settings available for this mode."); + return; + } + ProxySelector.setDefault(psel); + + URL url = new URL(this.urlField.getText().trim()); + List<Proxy> result = psel.select(url.toURI()); + if (result == null || result.size() == 0) { + JOptionPane.showMessageDialog(this, "No proxy found for this url."); + return; + } + + JOptionPane.showMessageDialog(this, + "Proxy Settings found using "+pss+" strategy.\n" + + "Proxy used for URL is: "+result.get(0)); + + } catch (Exception e) { + JOptionPane.showMessageDialog(this, "Error:"+e.getMessage(), "Error checking URL.", JOptionPane.ERROR_MESSAGE); + } + + } + + /************************************************************************* + * Main entry point for the application. + * @param args command line arguments. + ************************************************************************/ + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setLookAndFeel(); + + ProxyTester mainFrame = new ProxyTester(); + mainFrame.setVisible(true); + } + + }); + } + + /************************************************************************* + * Change the L&F to the system default. + ************************************************************************/ + + private static void setLookAndFeel() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + // Use default + } + } +} + diff --git a/src/main/java/com/btr/proxy/util/EmptyXMLResolver.java b/src/main/java/com/btr/proxy/util/EmptyXMLResolver.java new file mode 100644 index 0000000..9330556 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/EmptyXMLResolver.java @@ -0,0 +1,26 @@ +package com.btr.proxy.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/***************************************************************************** + * This resolver is used to prevent network lookups of DTD or XML schemas. + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class EmptyXMLResolver implements EntityResolver { + + /************************************************************************* + * Overwritten to return an empty entity. + * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String) + ************************************************************************/ + + public InputSource resolveEntity( String arg0, String arg1 ) throws SAXException, IOException { + return new InputSource( new ByteArrayInputStream("".getBytes())); + } + +} diff --git a/src/main/java/com/btr/proxy/util/Logger.java b/src/main/java/com/btr/proxy/util/Logger.java new file mode 100644 index 0000000..f434699 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/Logger.java @@ -0,0 +1,87 @@ +package com.btr.proxy.util; + +import java.text.MessageFormat; + +/***************************************************************************** + * Simple logging support for the framework. + * You need to add an logging listener that needs to send the logging events + * to an backend. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class Logger { + + public enum LogLevel {ERROR, WARNING, INFO, TRACE, DEBUG} + + /***************************************************************************** + * Interface for an logging backend that can be attached to the logger. + ****************************************************************************/ + + public interface LogBackEnd { + + /************************************************************************* + * Invoked for every logging event. + * @param clazz the class that sends the log message. + * @param loglevel the logging level. + * @param msg the message format string. + * @param params the message parameters for the format string. + ************************************************************************/ + + public void log(Class<?> clazz, LogLevel loglevel, String msg, Object ...params); + + /************************************************************************* + * Can be used to test if a given logging level is enabled. + * @param logLevel the loglevel to test. + * @return true if enabled, else false. + ************************************************************************/ + + public boolean isLogginEnabled(LogLevel logLevel); + } + + private static LogBackEnd backend; + + /************************************************************************* + * Gets the currently attached logging backend. + * @return Returns the backend. + ************************************************************************/ + + public static LogBackEnd getBackend() { + return backend; + } + + /************************************************************************* + * Attaches a new logging backend replacing the existing one. + * @param backend The backend to set. + ************************************************************************/ + + public static void setBackend(LogBackEnd backend) { + Logger.backend = backend; + } + + /************************************************************************* + * Logs a message. + * @param clazz the class that sends the log message. + * @param loglevel the logging level. + * @param msg the message format string. + * @param params the message parameters for the format string. + ************************************************************************/ + + public static void log(Class<?> clazz, LogLevel loglevel, String msg, Object ...params) { + System.out.println(MessageFormat.format(msg, params)); + } + + /************************************************************************* + * Can be used to test if a given logging level is enabled. + * @param logLevel the loglevel to test. + * @return true if enabled, else false. + ************************************************************************/ + + public static boolean isLogginEnabled(LogLevel logLevel) { + if (backend != null) { + return backend.isLogginEnabled(logLevel); + } + return false; + } + +} diff --git a/src/main/java/com/btr/proxy/util/PListParser.java b/src/main/java/com/btr/proxy/util/PListParser.java new file mode 100644 index 0000000..b678900 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/PListParser.java @@ -0,0 +1,544 @@ +package com.btr.proxy.util; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Plist xml handling (serialization and deserialization) + * <p> + * <em>The xml plist dtd can be found at http://www.apple.com/DTDs/PropertyList-1.0.dtd</em> + * <p> + * The plist spec handles 8 types of objects: booleans, real, integers, dates, binary data, + * strings, arrays (lists) and dictionaries (maps). + * <p> + * The java Plist lib handles converting xml plists to a nested {@code Map<String, Object>} + * that can be trivially read from java. It also provides a simple way to convert a nested + * {@code Map<String, Object>} into an xml plist representation. + * <p> + * The following mapping will be done when converting from plist to <tt>Map</tt>: + * <pre> + * true/false -> Boolean + * real -> Double + * integer -> Integer/Long (depends on size, values exceeding an int will be rendered as longs) + * data -> byte[] + * string -> String + * array -> List + * dict -> Map + * </pre> + * <p> + * When converting from Map -> plist the conversion is as follows: + * <pre> + * Boolean -> true/false + * Float/Double -> real + * Byte/Short/Integer/Long -> integer + * byte[] -> data + * List -> array + * Map -> dict + * </pre> + * + * @author Christoffer Lerno / Modified by Bernd Rosstauscher + */ +public final class PListParser +{ + /***************************************************************************** + * Exception is used for XML parse problems. + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + + public static class XmlParseException extends Exception { + + /** Comment for <code>serialVersionUID</code>*/ + private static final long serialVersionUID = 1L; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public XmlParseException() { + super(); + } + + /************************************************************************* + * Constructor + * @param msg the error message + ************************************************************************/ + + public XmlParseException(String msg) { + super(msg); + } + + /************************************************************************* + * Constructor + * @param msg error message + * @param e the cause. + ************************************************************************/ + + public XmlParseException(String msg, Exception e) { + super(msg, e); + } + + } + + /***************************************************************************** + * Small helper class representing a tree node. + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + + public static class Dict implements Iterable<Map.Entry<String, Object>> { + private Map<String, Object> children; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public Dict() { + super(); + this.children = new HashMap<String, Object>(); + } + + /************************************************************************* + * @param key of the child node. + * @return the child node, null if not existing. + ************************************************************************/ + + public Object get(String key) { + return this.children.get(key); + } + + /************************************************************************* + * iterator + * @see java.lang.Iterable#iterator() + ************************************************************************/ + + public Iterator<Entry<String, Object>> iterator() { + return this.children.entrySet().iterator(); + } + + /************************************************************************* + * @return the size of this dictionary. + ************************************************************************/ + + public int size() { + return this.children.size(); + } + + /************************************************************************* + * Dumps a dictionary with all sub-nodes to the console. + ************************************************************************/ + + public void dump() { + System.out.println("PList"); + dumpInternal(this, 1); + } + + /************************************************************************* + * @param plist + * @param indent + ************************************************************************/ + + private static void dumpInternal(Dict plist, int indent) { + for (Map.Entry<String, Object> child : plist) { + if (child.getValue() instanceof Dict) { + for (int j = 0; j < indent; j++) { + System.out.print(" "); + } + System.out.println(child.getKey()); + dumpInternal((Dict) child.getValue(), indent+1); + } else { + for (int j = 0; j < indent; j++) { + System.out.print(" "); + } + System.out.println(child.getKey()+" = "+child.getValue()); + } + } + + } + + /************************************************************************* + * Get a node at a given path. + * @param path a / separated path into the plist hirarchy. + * @return the object located at the given path, null if it does not exist. + ************************************************************************/ + + public Object getAtPath(String path) { + Dict currentNode = this; + + String[] pathSegments = path.trim().split("/"); + for (int i = 0; i < pathSegments.length; i++) { + String segment = pathSegments[i].trim(); + if (segment.length() == 0) { + continue; + } + Object o = currentNode.get(segment); + if (i >= pathSegments.length-1) { + return o; + } + if (o == null || !(o instanceof Dict)){ + break; + } + currentNode = (Dict) o; + } + return null; + } + + } + + /** + * Singleton instance. + */ + private final static PListParser PLIST = new PListParser(); + + /** + * All element types possible for a plist. + */ + private static enum ElementType + { + INTEGER, + STRING, + REAL, + DATA, + DATE, + DICT, + ARRAY, + TRUE, + FALSE, + } + + private static final String BASE64_STRING + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final char[] BASE64_CHARS = BASE64_STRING.toCharArray(); + private final DateFormat m_dateFormat; + private final Map<Class<?>, ElementType> m_simpleTypes; + + /** + * Utility method to close a closeable. + * + * @param closeable or null. + */ + static void silentlyClose(Closeable closeable) + { + try + { + if (closeable != null) { + closeable.close(); + } + } + catch (IOException e) + { + // Ignore + } + } + + /************************************************************************* + * @param input + * @return + * @throws XmlParseException + ************************************************************************/ + + private static Dict parse(InputSource input) + throws XmlParseException { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + documentBuilder.setEntityResolver(new EmptyXMLResolver()); + Document doc = documentBuilder.parse(input); + Element element = doc.getDocumentElement(); + return PLIST.parse(element); + } catch (ParserConfigurationException e) { + throw new XmlParseException("Error reading input", e); + } catch (SAXException e) { + throw new XmlParseException("Error reading input", e); + } catch (IOException e) { + throw new XmlParseException("Error reading input", e); + } + } + + /** + * Create a nested {@code map<String, Object>} from a plist xml file using the default mapping. + * + * @param file the File containing the the plist xml. + * @return the resulting map as read from the plist data. + * @throws XmlParseException if the plist could not be properly parsed. + * @throws IOException if there was an issue reading the plist file. + */ + public static Dict load(File file) throws XmlParseException, IOException + { + FileInputStream byteStream = new FileInputStream(file); + try { + InputSource input = new InputSource(byteStream); + return parse(input); + } finally { + silentlyClose(byteStream); + } + } + + /** + * Create a plist handler. + */ + PListParser() + { + this.m_dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + this.m_dateFormat.setTimeZone(TimeZone.getTimeZone("Z")); + this.m_simpleTypes = new HashMap<Class<?>, ElementType>(); + this.m_simpleTypes.put(Integer.class, ElementType.INTEGER); + this.m_simpleTypes.put(Byte.class, ElementType.INTEGER); + this.m_simpleTypes.put(Short.class, ElementType.INTEGER); + this.m_simpleTypes.put(Short.class, ElementType.INTEGER); + this.m_simpleTypes.put(Long.class, ElementType.INTEGER); + this.m_simpleTypes.put(String.class, ElementType.STRING); + this.m_simpleTypes.put(Float.class, ElementType.REAL); + this.m_simpleTypes.put(Double.class, ElementType.REAL); + this.m_simpleTypes.put(byte[].class, ElementType.DATA); + this.m_simpleTypes.put(Boolean.class, ElementType.TRUE); + this.m_simpleTypes.put(Date.class, ElementType.DATE); + } + + /** + * Parses a plist top element into a map dictionary containing all the data + * in the plist. + * + * @param element the top plist element. + * @return the resulting data tree structure. + * @throws XmlParseException if there was any error parsing the xml. + */ + Dict parse(Element element) throws XmlParseException + { + if (!"plist".equalsIgnoreCase(element.getNodeName())) { + throw new XmlParseException("Expected plist top element, was: " + element.getNodeName()); + } + + Node n = element.getFirstChild(); + while (n != null && !n.getNodeName().equals("dict")) { + n = n.getNextSibling(); + } + + Dict result = (Dict) parseElement(n); + return result; + } + + /** + * Parses a (non-top) xml element. + * + * @param element the element to parse. + * @return the resulting object. + * @throws XmlParseException if there was some error in the xml. + */ + private Object parseElement(Node element) throws XmlParseException + { + try + { + return parseElementRaw(element); + } + catch (Exception e) + { + throw new XmlParseException("Failed to parse: " + element.getNodeName(), e); + } + } + + + /** + * Parses a (non-top) xml element. + * + * @param element the element to parse. + * @return the resulting object. + * @throws ParseException if there was some error parsing the xml. + */ + private Object parseElementRaw(Node element) throws ParseException + { + ElementType type = ElementType.valueOf(element.getNodeName().toUpperCase()); + switch (type) + { + case INTEGER: + return parseInt(getValue(element)); + case REAL: + return Double.valueOf(getValue(element)); + case STRING: + return getValue(element); + case DATE: + return this.m_dateFormat.parse(getValue(element)); + case DATA: + return base64decode(getValue(element)); + case ARRAY: + return parseArray(element.getChildNodes()); + case TRUE: + return Boolean.TRUE; + case FALSE: + return Boolean.FALSE; + case DICT: + return parseDict(element.getChildNodes()); + default: + throw new RuntimeException("Unexpected type: " + element.getNodeName()); + } + } + + /************************************************************************* + * @param n + * @return + ************************************************************************/ + + private String getValue(Node n) { + StringBuilder sb = new StringBuilder(); + Node c = n.getFirstChild(); + while (c != null) { + if (c.getNodeType() == Node.TEXT_NODE) { + sb.append(c.getNodeValue()); + } + c = c.getNextSibling(); + } + return sb.toString(); + } + + /** + * Parses a string into a Long or Integer depending on size. + * + * @param value the value as a string. + * @return the long value of this string is the value doesn't fit in an integer, + * otherwise the int value of the string. + */ + private Number parseInt(String value) + { + Long l = Long.valueOf(value); + if (l.intValue() == l) { + return l.intValue(); + } + return l; + } + + /** + * Parse a list of xml elements as a plist dict. + * + * @param elements the elements to parse. + * @return the dict deserialized as a map. + * @throws ParseException if there are any problems deserializing the map. + */ + private Dict parseDict(NodeList elements) throws ParseException + { + Dict dict = new Dict(); + for (int i = 0; i < elements.getLength(); i++) { + Node key = elements.item(i); + if (key.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + if (!"key".equals(key.getNodeName())) { + throw new ParseException("Expected key but was " + key.getNodeName(), -1); + } + i++; + Node value = elements.item(i); + while (value.getNodeType() != Node.ELEMENT_NODE) { + i++; + value = elements.item(i); + } + Object o = parseElementRaw(value); + String dictName = getValue(key); + dict.children.put(dictName, o); + } + return dict; + } + + /** + * Parse a list of xml elements as a plist array. + * + * @param elements the elements to parse. + * @return the array deserialized as a list. + * @throws ParseException if there are any problems deserializing the list. + */ + private List<Object> parseArray(NodeList elements) throws ParseException + { + ArrayList<Object> list = new ArrayList<Object>(); + for (int i = 0; i < elements.getLength(); i++) { + Node o = elements.item(i); + if (o.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + list.add(parseElementRaw(o)); + } + return list; + } + + /** + * Encode an array of bytes to a string using base64 encoding. + * + * @param bytes the bytes to convert. + * @return the base64 representation of the bytes. + */ + static String base64encode(byte[] bytes) + { + StringBuilder builder = new StringBuilder(((bytes.length + 2)/ 3) * 4); + for (int i = 0; i < bytes.length; i += 3) + { + byte b0 = bytes[i]; + byte b1 = i < bytes.length - 1 ? bytes[i + 1] : 0; + byte b2 = i < bytes.length - 2 ? bytes[i + 2] : 0; + builder.append(BASE64_CHARS[(b0 & 0xFF) >> 2]); + builder.append(BASE64_CHARS[((b0 & 0x03) << 4) | ((b1 & 0xF0) >> 4)]); + builder.append(i < bytes.length - 1 ? BASE64_CHARS[((b1 & 0x0F) << 2) | ((b2 & 0xC0) >> 6)] : "="); + builder.append(i < bytes.length - 2 ? BASE64_CHARS[b2 & 0x3F] : "="); + } + return builder.toString(); + } + + /** + * Converts a string to a byte array assuming the string uses base64-encoding. + * + * @param base64 the string to convert. + * @return the resulting byte array. + */ + static byte[] base64decode(String base64) + { + base64 = base64.trim(); + int endTrim = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0; + int length = (base64.length() / 4) * 3 - endTrim; + base64 = base64.replace('=', 'A'); + byte[] result = new byte[length]; + int stringLength = base64.length(); + int index = 0; + for (int i = 0; i < stringLength; i += 4) + { + int i0 = BASE64_STRING.indexOf(base64.charAt(i)); + int i1 = BASE64_STRING.indexOf(base64.charAt(i + 1)); + int i2 = BASE64_STRING.indexOf(base64.charAt(i + 2)); + int i3 = BASE64_STRING.indexOf(base64.charAt(i + 3)); + byte b0 = (byte) ((i0 << 2) | (i1 >> 4)); + byte b1 = (byte) ((i1 << 4) | (i2 >> 2)); + byte b2 = (byte) ((i2 << 6) | i3); + result[index++] = b0; + if (index < length) + { + result[index++] = b1; + if (index < length) + { + result[index++] = b2; + } + } + } + return result; + } + + + + +} + diff --git a/src/main/java/com/btr/proxy/util/PlatformUtil.java b/src/main/java/com/btr/proxy/util/PlatformUtil.java new file mode 100644 index 0000000..39de5d2 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/PlatformUtil.java @@ -0,0 +1,114 @@ +package com.btr.proxy.util; + +import com.btr.proxy.util.Logger.LogLevel; + +/***************************************************************************** + * Defines some helper methods to find the correct platform. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class PlatformUtil { + + public enum Platform {WIN, LINUX, MAC_OS, SOLARIS, OTHER} + public enum Desktop {WIN, KDE, GNOME, MAC_OS, OTHER} + public enum Browser {IE, FIREFOX} + + /************************************************************************* + * Gets the platform we are currently running on. + * @return a platform code. + ************************************************************************/ + + public static Platform getCurrentPlattform() { + String osName = System.getProperty("os.name"); + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detecting platform. Name is: {0}", osName); + + if (osName.toLowerCase().contains("windows")) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Windows platform: {0}", osName); + return Platform.WIN; + } + if (osName.toLowerCase().contains("linux")) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Linux platform: {0}", osName); + return Platform.LINUX; + } + if (osName.startsWith("Mac OS")) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Mac OS platform: {0}", osName); + return Platform.MAC_OS; + } + if (osName.startsWith("SunOS")) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Solaris platform: {0}", osName); + return Platform.SOLARIS; + } + + return Platform.OTHER; + } + + /************************************************************************* + * Gets the ID for the platform default browser. + * @return a browser ID, null if no supported browser was detected. + ************************************************************************/ + + public static Browser getDefaultBrowser() { + // Use better logic to detect default browser? + if (getCurrentPlattform() == Platform.WIN) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Browser is InternetExplorer"); + return Browser.IE; + } else { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Browser Firefox. Fallback?"); + return Browser.FIREFOX; + } + } + + /************************************************************************* + * Gets the desktop that we are running on. + * @return the desktop identifier. + ************************************************************************/ + + public static Desktop getCurrentDesktop() { + Platform platf = getCurrentPlattform(); + + if (platf == Platform.WIN) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Windows desktop"); + return Desktop.WIN; + } + if (platf == Platform.MAC_OS) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Mac OS desktop"); + return Desktop.MAC_OS; + } + + if (platf == Platform.LINUX + || platf == Platform.SOLARIS + || platf == Platform.OTHER) { + + if (isKDE()) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected KDE desktop"); + return Desktop.KDE; + } + if (isGnome()) { + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Gnome desktop"); + return Desktop.GNOME; + } + } + Logger.log(PlatformUtil.class, LogLevel.TRACE, "Detected Unknown desktop"); + return Desktop.OTHER; + } + + /************************************************************************* + * Checks if we are currently running under Gnome desktop. + * @return true if it is a Gnome else false. + ************************************************************************/ + + private static boolean isGnome() { + return System.getenv("GNOME_DESKTOP_SESSION_ID") != null; + } + + /************************************************************************* + * Checks if we are currently running under KDE desktop. + * @return true if it is a KDE else false. + ************************************************************************/ + + private static boolean isKDE() { + return System.getenv("KDE_SESSION_VERSION") != null; + } + +} diff --git a/src/main/java/com/btr/proxy/util/ProxyException.java b/src/main/java/com/btr/proxy/util/ProxyException.java new file mode 100644 index 0000000..a3c1424 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/ProxyException.java @@ -0,0 +1,50 @@ +package com.btr.proxy.util; + +/***************************************************************************** + * Indicates an exception in the proxy framework. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxyException extends Exception { + + private static final long serialVersionUID = 1L; + + /************************************************************************* + * Constructor + ************************************************************************/ + + public ProxyException() { + super(); + } + + /************************************************************************* + * Constructor + * @param message the error message + * @param cause the causing exception for chaining exceptions. + ************************************************************************/ + + public ProxyException(String message, Throwable cause) { + super(message, cause); + } + + /************************************************************************* + * Constructor + * @param message the error message + ************************************************************************/ + + public ProxyException(String message) { + super(message); + } + + /************************************************************************* + * Constructor + * @param cause the causing exception for chaining exceptions. + ************************************************************************/ + + public ProxyException(Throwable cause) { + super(cause); + } + + +} diff --git a/src/main/java/com/btr/proxy/util/ProxyUtil.java b/src/main/java/com/btr/proxy/util/ProxyUtil.java new file mode 100644 index 0000000..aad9293 --- /dev/null +++ b/src/main/java/com/btr/proxy/util/ProxyUtil.java @@ -0,0 +1,84 @@ +package com.btr.proxy.util; + +import java.net.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.btr.proxy.selector.fixed.FixedProxySelector; +import com.btr.proxy.selector.pac.PacProxySelector; +import com.btr.proxy.selector.pac.PacScriptSource; +import com.btr.proxy.selector.pac.UrlPacScriptSource; + +/***************************************************************************** + * Small helper class for some common utility methods. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public class ProxyUtil { + + public static final int DEFAULT_PROXY_PORT = 80; + + private static List<Proxy> noProxyList; + private static Pattern pattern = Pattern.compile("\\w*?:?/*([^:/]+):?(\\d*)/?"); + + /************************************************************************* + * Parse host and port out of a proxy variable. + * @param proxyVar the proxy string. example: http://192.168.10.9:8080/ + * @return a FixedProxySelector using this settings, null on parse error. + ************************************************************************/ + + public static FixedProxySelector parseProxySettings(String proxyVar) { + if (proxyVar == null || proxyVar.trim().length() == 0) { + return null; + } + Matcher matcher = pattern.matcher(proxyVar); + if (matcher.matches()) { + String host = matcher.group(1); + int port; + if (!"".equals(matcher.group(2))) { + port = Integer.parseInt(matcher.group(2)); + } else { + port = DEFAULT_PROXY_PORT; + } + return new FixedProxySelector(host.trim(), port); + } else { + return null; + } + } + + /************************************************************************* + * Gets an unmodifiable proxy list that will have as it's only entry an DIRECT proxy. + * @return a list with a DIRECT proxy in it. + ************************************************************************/ + + public static synchronized List<Proxy> noProxyList() { + if (noProxyList == null) { + ArrayList<Proxy> list = new ArrayList<Proxy>(1); + list.add(Proxy.NO_PROXY); + noProxyList = Collections.unmodifiableList(list); + } + return noProxyList; + } + + /************************************************************************* + * Build a PAC proxy selector for the given URL. + * @param url to fetch the PAC script from. + * @return a PacProxySelector or null if it is not possible to build a working + * selector. + ************************************************************************/ + + public static PacProxySelector buildPacSelectorForUrl(String url) { + PacProxySelector result = null; + PacScriptSource pacSource = new UrlPacScriptSource(url); + if (pacSource.isScriptValid()) { + result = new PacProxySelector(pacSource); + } + return result; + } + + +} diff --git a/src/main/java/com/btr/proxy/util/UriFilter.java b/src/main/java/com/btr/proxy/util/UriFilter.java new file mode 100644 index 0000000..8145e0e --- /dev/null +++ b/src/main/java/com/btr/proxy/util/UriFilter.java @@ -0,0 +1,21 @@ +package com.btr.proxy.util; + +import java.net.URI; + +/***************************************************************************** + * Interface for an URI filter. + * + * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 + ****************************************************************************/ + +public interface UriFilter { + + /************************************************************************* + * Tests an URI against a given matching criteria. + * @param uri the URI to test. + * @return true if it matches the criteria else false. + ************************************************************************/ + + public abstract boolean accept(URI uri); + +} |