com.github.markusbernhardt.proxy.search.desktop.osx.OsxProxySearchStrategy Maven / Gradle / Ivy
package com.github.markusbernhardt.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.github.markusbernhardt.proxy.ProxySearchStrategy;
import com.github.markusbernhardt.proxy.search.browser.ie.IELocalByPassFilter;
import com.github.markusbernhardt.proxy.search.wpad.WpadProxySearchStrategy;
import com.github.markusbernhardt.proxy.selector.direct.NoProxySelector;
import com.github.markusbernhardt.proxy.selector.fixed.FixedProxySelector;
import com.github.markusbernhardt.proxy.selector.fixed.FixedSocksSelector;
import com.github.markusbernhardt.proxy.selector.misc.ProtocolDispatchSelector;
import com.github.markusbernhardt.proxy.selector.whitelist.ProxyBypassListSelector;
import com.github.markusbernhardt.proxy.util.Logger;
import com.github.markusbernhardt.proxy.util.Logger.LogLevel;
import com.github.markusbernhardt.proxy.util.PListParser;
import com.github.markusbernhardt.proxy.util.PListParser.Dict;
import com.github.markusbernhardt.proxy.util.PListParser.XmlParseException;
import com.github.markusbernhardt.proxy.util.ProxyException;
import com.github.markusbernhardt.proxy.util.ProxyUtil;
import com.github.markusbernhardt.proxy.util.UriFilter;
/*****************************************************************************
* Loads the OSX system proxy settings from the settings file.
*
* 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.
*
*
* To parse this file we use a parser that is derived from a plist parser that
* comes with the xmlwise XML parser package:
*
*
* http://code.google.com/p/xmlwise/
*
*
* I modified that parser to work with the default Java XML parsing library.
*
*
* The plist file is located on OSX at:
*
*
* /Library/Preferences/SystemConfiguration/preferences.plist
*
*
* @author Markus Bernhardt, Copyright 2016
* @author Bernd Rosstauscher, Copyright 2009
****************************************************************************/
public class OsxProxySearchStrategy implements ProxySearchStrategy {
public static final String OVERRIDE_SETTINGS_FILE = "com.github.markusbernhardt.proxy.osx.settingsFile";
public static final String OVERRIDE_ACCEPTED_DEVICES = "com.github.markusbernhardt.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.
************************************************************************/
@Override
public ProxySelector getProxySelector() throws ProxyException {
Logger.log(getClass(), LogLevel.TRACE, "Detecting OSX proxy settings");
try {
List 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));
// TODO 30.03.2015 bros Test for IP6 compatibility
List> serviceOrder = (List>) networkSet.getAtPath("/Network/Global/IPv4/ServiceOrder");
if (serviceOrder == null || serviceOrder.isEmpty()) {
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 | IOException e) {
throw new ProxyException(e);
}
}
/*************************************************************************
* Gets the printable name of the search strategy.
*
* @return the printable name of the search strategy
************************************************************************/
@Override
public String getName() {
return "osx";
}
/*************************************************************************
* 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);
if (result != null) {
result = installExceptionList(proxySettings, result);
result = installSimpleHostFilter(proxySettings, result);
}
return result;
}
/*************************************************************************
* Create a list of Ethernet interfaces that are connected
*
* @return a list of available interface names
* @throws SocketException
************************************************************************/
private List getNetworkInterfaces() throws SocketException {
String override = System.getProperty(OVERRIDE_ACCEPTED_DEVICES);
if (override != null && override.length() > 0) {
return Arrays.asList(override.split(";"));
}
List acceptedInterfaces = new ArrayList<>();
Enumeration 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();
}
/*************************************************************************
* Gets the settings file to parse the settings from.
*
* @return the settings file.
************************************************************************/
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;
}
/*************************************************************************
* Install a filter to ignore simple host names without domain name.
*
* @param proxySettings
* the dictionary containing all settings
* @param result
* the proxy selector that needs to be adapted.
* @return a wrapped proxy selector that will ignore simple names.
************************************************************************/
private ProxySelector installSimpleHostFilter(Dict proxySettings, ProxySelector result) {
if (isActive(proxySettings.get("ExcludeSimpleHostnames"))) {
List localBypassFilter = new ArrayList<>();
localBypassFilter.add(new IELocalByPassFilter());
result = new ProxyBypassListSelector(localBypassFilter, result);
}
return result;
}
/*************************************************************************
* Install a host name base filter to handle the proxy exclude list.
*
* @param proxySettings
* the dictionary containing all settings
* @param result
* the proxy selector that needs to be adapted.
* @return a wrapped proxy selector that will handle the exclude list.
************************************************************************/
private ProxySelector installExceptionList(Dict proxySettings, ProxySelector result) {
List> proxyExceptions = (List>) proxySettings.get("ExceptionsList");
if (proxyExceptions != null && !proxyExceptions.isEmpty()) {
Logger.log(getClass(), LogLevel.TRACE, "OSX uses proxy bypass list: {}", 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();
}
/*************************************************************************
* Invoke WPAD proxy detection if configured.
*
* @param proxySettings
* the settings to analyse.
* @param result
* the current proxy selector.
* @return a WPAD proxy selector or the passed in proxy selector.
* @throws ProxyException
* on automatic detection errors.
************************************************************************/
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;
}
/*************************************************************************
* Use a PAC based proxy selector if configured.
*
* @param proxySettings
* the settings to analyse.
* @param result
* the current proxy selector.
* @return a PAC proxy selector or the passed in proxy selector.
************************************************************************/
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 {}:{}", 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 {} the proxy {}:{}", 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);
}
}