summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/btr/proxy/selector/pac/JavaxPacScriptParser.java
blob: 5d293fa0f97d892d788e8c09e5c474868091d14e (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
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);
		}

	}
}