--- /dev/null
+// ProxyDispatcher.java\r
+// $Id: ProxyDispatcher.java,v 1.1 2010/06/15 12:28:31 smhuang Exp $\r
+// (c) COPYRIGHT MIT and INRIA, 1996.\r
+// please first read the full copyright statement in file COPYRIGHT.HTML\r
+\r
+package org.w3c.www.protocol.http.proxy ;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.PrintStream;\r
+\r
+import java.net.URL;\r
+import java.net.URLConnection;\r
+\r
+import org.w3c.util.ObservableProperties;\r
+import org.w3c.util.PropertyMonitoring;\r
+\r
+import org.w3c.www.protocol.http.HttpException;\r
+import org.w3c.www.protocol.http.HttpManager;\r
+import org.w3c.www.protocol.http.PropRequestFilter;\r
+import org.w3c.www.protocol.http.Reply;\r
+import org.w3c.www.protocol.http.Request;\r
+\r
+import org.w3c.www.http.HttpRequestMessage;\r
+\r
+/**\r
+ * The proxy dispatcher applies some <em>rules</em> to a request.\r
+ * The goal of that filter is to allow special pre-processing of requests\r
+ * based, on their target host, before sending them off the net.\r
+ * <p>The filter is configured through a <em>rule file</em> whose format\r
+ * is described by the following BNF:\r
+ * <code>\r
+ * rule-file=(<em>record</em>)*<br>\r
+ * record=<strong>EOL</strong>|<em>comment</em>|<em>rule</em><br>\r
+ * comment=<strong>#</strong>(<strong>^EOL</strong>)*<strong>EOL</strong><br>\r
+ * rule=<em>rule-lhs</em>(<strong>SPACE</strong>)*<em>rule-rhs</em><br>\r
+ * rule-lhs=(<strong>token</strong>)\r
+ * |(<strong>token</strong> (<strong>.</strong> <strog>token</strong>)*<br>\r
+ * rule-lhr=<em>forbid</em>|<em>direct</em>|<em>redirect</em>\r
+ * |<em>proxy</em>|<em>authorization</em>|<em>proxyauth</em><br>\r
+ * forbid=<strong>FORBID</strong>|<strong>forbid</strong><br>\r
+ * direct=<strong>DIRECT</strong>|<strong>direct</strong><br>\r
+ * redirect=(<strong>REDIRECT</strong>|<strong>proxy</strong>) <em>url</em><br>\r
+ * proxy=(<strong>PROXY</strong>|<strong>proxy</strong>) <em>url</em><br>\r
+ * url=<strong>any valid URL</strong></br>\r
+ * authorization=(<strong>AUTHORIZATION</strong>|<strong>authorization</strong>\r
+ * <em>user</em> <em>password</em><br>\r
+ * proxyauth=(<strong>PROXYAUTH</strong>|<strong>proxyauth</strong>\r
+ * <em>user</em> <em>password</em> <em>url</em><br>\r
+ * </code>\r
+ * <p>A sample rule file looks like this:\r
+ * <code>\r
+ * # Some comments\r
+ * \r
+ * edu proxy http://class.w3.org:8001/\r
+ * org proxy http://class.w3.org:8001/\r
+ * fr direct\r
+ * www.evilsite.com redirect http://www.goodsite.com/warning.html\r
+ * www.w3.org direct\r
+ * 138.96.24 direct\r
+ * www.playboy.com forbid\r
+ * default proxy http://cache.inria.fr:8080/\r
+ * </code>\r
+ * <p>The algorithm used to lookup rules is the following: \r
+ * <ul>\r
+ * <li>Split all rules <em>left hand side</em> into its components, eg \r
+ * H1.H2.H3 is splitted into { H1, H2, H3 }, then reverse the components and \r
+ * map that to the rule. In our example above, { org, w3, www} would be mapped\r
+ * to <em>direct</em>.\r
+ * <li>Split the fully qualified host name into its components, eg, A.B.C is\r
+ * splitted into { A, B, C } and reverse it.\r
+ * <li>Find the longest match in the mapping table of rules, and get\r
+ * apply the given rule.\r
+ * </ul>\r
+ * <p>In our example, a request to <strong>www.isi.edu</strong> would match\r
+ * the <em>edu</em> rule, and a request for <strong>www.w3.org</strong>\r
+ * would match the <em>direct</em> rule, for example.\r
+ * <p>Three rules are defined:\r
+ * <dl>\r
+ * <dt>direct<dd>Run that request directly against the target host.\r
+ * <dt>forbid<dd>Emit a forbid message, indicating that the user is not\r
+ * allowed to contact this host.\r
+ * <dt>proxy<dd>Run that request through the given <em>proxy</em>.\r
+ * <dt>proxyauth<dd>Run that request through a proxy with the right proxy\r
+ * credentials.\r
+ * </dl>\r
+ * <p>For numeric IP addresses, the most significant part is the beginning,\r
+ * so {A, B, C} are deducted directly. In the example { 138, 96, 24 } is mapped\r
+ * to direct.\r
+ * <p>If no rules are applied, then the default rule (root rule) is applied.\r
+ * See the example.\r
+ */\r
+\r
+public class ProxyDispatcher\r
+ implements PropRequestFilter, PropertyMonitoring \r
+{\r
+ /**\r
+ * Name of the property giving the rule file URL.\r
+ */\r
+ public static final \r
+ String RULE_P = "org.w3c.www.protocol.http.proxy.rules";\r
+\r
+ /**\r
+ * Name of the property turning that filter in debug mode.\r
+ */\r
+ public static final\r
+ String DEBUG_P = "org.w3c.www.protocol.http.proxy.debug";\r
+\r
+ /**\r
+ * Name of the property turning that filter in debug mode.\r
+ */\r
+ public static final\r
+ String CHECK_RULES_LAST_MODIFIED_P = \r
+ "org.w3c.www.protocol.http.proxy.rules.check.lastmodified";\r
+\r
+ /**\r
+ * The properties we initialized ourself from.\r
+ */\r
+ protected ObservableProperties props = null; \r
+ /**\r
+ * The current set of rules to apply.\r
+ */\r
+ protected RuleNode rules = null;\r
+ /**\r
+ * Are we in debug mode ?\r
+ */\r
+ protected boolean debug = false;\r
+\r
+ protected boolean check_rules = false;\r
+\r
+ protected static final String disabled = "disabled";\r
+\r
+ protected long lastParsingTime = -1;\r
+\r
+ /**\r
+ * Parse the given input stream as a rule file.\r
+ * @param in The input stream to parse.\r
+ * @exception IOException if an IO error occurs.\r
+ * @exception RuleParserException if parsing failed.\r
+ */\r
+\r
+ protected void parseRules(InputStream in)\r
+ throws IOException, RuleParserException\r
+ {\r
+ RuleParser parser = new RuleParser(in);\r
+ RuleNode nroot = parser.parse();\r
+ rules = nroot;\r
+ lastParsingTime = System.currentTimeMillis();\r
+ }\r
+\r
+ /**\r
+ * Parse the default set of rules.\r
+ * <p>IOf the rules cannot be parsed, the filter emits an error\r
+ * message to standard error, and turn itself into transparent mode.\r
+ */\r
+\r
+ protected void parseRules() {\r
+ if ( debug )\r
+ System.out.println("PARSING RULES...");\r
+ String ruleurl = props.getString(RULE_P, null);\r
+ InputStream in = null;\r
+ // Try opening the rule file as a URL:\r
+ try {\r
+ URL url = new URL(ruleurl);\r
+ in = url.openStream();\r
+ } catch (Exception ex) {\r
+ // If this fails, it may be just a file name:\r
+ try {\r
+ in = (new BufferedInputStream\r
+ (new FileInputStream\r
+ (new File(ruleurl))));\r
+ } catch (Exception nex) {\r
+ System.err.println("* ProxyDispatcher: unable to open rule "\r
+ + "file \"" + ruleurl + "\"");\r
+ rules = null;\r
+ return;\r
+ }\r
+ } \r
+ // Parse that input stream as a rule file:\r
+ try {\r
+ parseRules(in);\r
+ } catch (Exception ex) {\r
+ System.err.println("Error parsing rules from: "+ruleurl);\r
+ ex.printStackTrace();\r
+ rules = null; \r
+ } finally {\r
+ if ( in != null ) {\r
+ try {\r
+ in.close();\r
+ } catch (IOException ex) {\r
+ }\r
+ }\r
+ }\r
+ if ( debug )\r
+ System.out.println("DONE.");\r
+ }\r
+\r
+ protected boolean needsParsing() {\r
+ if (rules == null) \r
+ return true;\r
+ if (! check_rules)\r
+ return false;\r
+ long rulesStamp = -1;\r
+ String ruleurl = props.getString(RULE_P, null);\r
+ try {\r
+ URL url = new URL(ruleurl);\r
+ if (url.getProtocol().equalsIgnoreCase("file")) {\r
+ File file = new File(url.getFile());\r
+ rulesStamp = file.lastModified();\r
+ } else {\r
+ URLConnection con = url.openConnection();\r
+ rulesStamp = con.getLastModified();\r
+ }\r
+ } catch (Exception ex) {\r
+ File file = new File(ruleurl);\r
+ rulesStamp = file.lastModified();\r
+ }\r
+ System.out.println("rulesStamp : "+rulesStamp);\r
+ return (lastParsingTime < rulesStamp);\r
+ }\r
+\r
+ /**\r
+ * Filter requests before they are emitted.\r
+ * Look for a matching rule, and if found apply it before continuing\r
+ * the process. If a forbid rule was apply, this method will return\r
+ * with a <em>forbidden</em> message.\r
+ * @param request The request to filter.\r
+ * @return A Reply instance, if processing is not to be continued,\r
+ * <strong>false</strong>otherwise.\r
+ */\r
+\r
+ public Reply ingoingFilter(Request request) {\r
+ if (needsParsing())\r
+ parseRules();\r
+ if ( rules != null ) {\r
+ URL url = request.getURL();\r
+ String host = url.getHost();\r
+ Rule rule = rules.lookupRule(host);\r
+ if ( rule != null ) {\r
+ if ( debug ) {\r
+ String args = rule.getRuleArgs();\r
+ if (args == null) {\r
+ args = "";\r
+ } else {\r
+ args = " "+args;\r
+ }\r
+ System.out.println("["+ getClass().getName() +\r
+ "]: applying rule <"+rule.getRuleName()+\r
+ args +"> to " + request.getURL());\r
+ }\r
+ return rule.apply(request);\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Filter requests when an error occurs during the process.\r
+ * This filter tries to do a direct connection if it is needed\r
+ * @param reques The request to filter.\r
+ * @param reply It's associated reply.\r
+ * @return Always <strong>null</strong>.\r
+ */\r
+\r
+ public boolean exceptionFilter(Request request, HttpException ex) {\r
+ // if it was a proxy connection, try a direct one\r
+ // add test for exception here\r
+ if(request.hasProxy()) {\r
+ Reply reply = null;\r
+ HttpManager hm = HttpManager.getManager();\r
+ request.setProxy(null);\r
+ if ( debug )\r
+ System.out.println("["+getClass().getName()+"]: direct fetch "\r
+ +"for " + request.getURL());\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Filter requests after processing.\r
+ * This filter doesn't do any post-processing.\r
+ * @param reques The request to filter.\r
+ * @param reply It's associated reply.\r
+ * @return Always <strong>null</strong>.\r
+ */\r
+\r
+ public Reply outgoingFilter(Request request, Reply reply) {\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * PropertyMonitoring implementation - Commit property changes.\r
+ * @param name The name of the property that has changed.\r
+ * @return A boolean <strong>true</strong> if change was commited, \r
+ * <strong>false</strong> otherwise.\r
+ */\r
+\r
+ public boolean propertyChanged(String name) {\r
+ if(name.equals(RULE_P)) {\r
+ try {\r
+ parseRules();\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ return false;\r
+ }\r
+ } else if (name.equals(DEBUG_P)) {\r
+ debug = props.getBoolean(DEBUG_P, false);\r
+ } else if (name.equals(CHECK_RULES_LAST_MODIFIED_P)) {\r
+ check_rules = props.getBoolean(CHECK_RULES_LAST_MODIFIED_P, false);\r
+ }\r
+ return true;\r
+ }\r
+\r
+ public void initialize(HttpManager manager) {\r
+ // Prepare empty entry list:\r
+ props = manager.getProperties();\r
+ props.registerObserver(this);\r
+ // Initialize from properties:\r
+ parseRules();\r
+ if (debug = props.getBoolean(DEBUG_P, false)) \r
+ System.out.println("["+getClass().getName()+": debuging on.");\r
+ check_rules = props.getBoolean(CHECK_RULES_LAST_MODIFIED_P, false);\r
+ // Install ourself\r
+ manager.setFilter(this);\r
+ }\r
+\r
+ /**\r
+ * We don't maintain cached infos.\r
+ */\r
+\r
+ public void sync() {\r
+ }\r
+\r
+ /**\r
+ * Empty constructor, for dynamic instantiation.\r
+ */\r
+\r
+ public ProxyDispatcher() {\r
+ super();\r
+ }\r
+}\r