summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/btr/proxy/selector/pac/PacProxySelector.java
blob: 40469b00895e0eb4b0e3966ad22bd9b4b56ecd5e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package com.btr.proxy.selector.pac;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import com.btr.proxy.util.Logger;
import com.btr.proxy.util.Logger.LogLevel;
import com.btr.proxy.util.ProxyUtil;


/*****************************************************************************
 * ProxySelector that will use a PAC script to find an proxy for a given URI.
 *
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ****************************************************************************/
public class PacProxySelector extends ProxySelector {

	private final boolean JAVAX_PARSER = ScriptAvailability.isJavaxScriptingAvailable();

	// private static final String PAC_PROXY = "PROXY";
	private static final String PAC_SOCKS = "SOCKS";
	private static final String PAC_DIRECT = "DIRECT";

	private PacScriptParser pacScriptParser;

	private static volatile boolean enabled = true;

	/*************************************************************************
	 * Constructor
	 * @param pacSource the source for the PAC file. 
	 ************************************************************************/

	public PacProxySelector(PacScriptSource pacSource) {
		super();
		selectEngine(pacSource);
	}
	
	/*************************************************************************
	 * Can be used to enable / disable the proxy selector.
	 * If disabled it will return DIRECT for all urls.
	 * @param enable the new status to set.
	 ************************************************************************/
	
	public static void setEnabled(boolean enable) {
		enabled = enable;
	}
	
	/*************************************************************************
	 * Checks if the selector is currently enabled.
	 * @return true if enabled else false.
	 ************************************************************************/
	
	public static boolean isEnabled() {
		return enabled;
	}

	/*************************************************************************
	 * Selects one of the available PAC parser engines.
	 * @param pacSource to use as input.
	 ************************************************************************/
	
	private void selectEngine(PacScriptSource pacSource) {
		try {
			if (this.JAVAX_PARSER) {
				Logger.log(getClass(), LogLevel.INFO,
						"Using javax.script JavaScript engine.");
				this.pacScriptParser = new JavaxPacScriptParser(pacSource);
			} else {
				Logger.log(getClass(), LogLevel.ERROR,
						"Rhino JavaScript engine support dropped. javax JS should be available unless you're still running Java 1.5.");
			}
		} catch (Exception e) {
			Logger.log(getClass(), LogLevel.ERROR, "PAC parser error.", e);
		}
	}

	/*************************************************************************
	 * connectFailed
	 * @see java.net.ProxySelector#connectFailed(java.net.URI, java.net.SocketAddress, java.io.IOException)
	 ************************************************************************/
	@Override
	public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
		// Not used.
	}

	/*************************************************************************
	 * select
	 * @see java.net.ProxySelector#select(java.net.URI)
	 ************************************************************************/
	@Override
	public List<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);
	}
	
}