summaryrefslogblamecommitdiffstats
path: root/src/main/java/com/btr/proxy/search/desktop/osx/OsxProxySearchStrategy.java
blob: 71d7a8f758e5b05a51d8f768504080b19c9c024a (plain) (tree)
1
2
3
4
5
6




                                         
                      










                                                           















































































































































































































































































                                                                                                                   
                                                                                                                         
















                                                                                                                   
                                                                                                                      















                                                                                                                                     
package com.btr.proxy.search.desktop.osx;

import java.io.File;
import java.io.IOException;
import java.net.NetworkInterface;
import java.net.Proxy;
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.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", ProxyUtil.parseProxySettings(proxyHost.trim(), Proxy.Type.SOCKS, 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");
			ProxySelector fp = ProxyUtil.parseProxySettings(proxyHost.trim(), Proxy.Type.HTTP, 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);
	}

}