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.
* Depends on js.jar of the Apache Rhino project. *

* More information about PAC can be found there:
* Proxy_auto-config
* web-browser-auto-proxy-configuration *

* @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.
* *
     * 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)
     * 
* * @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); } }