From: <jbo...@li...> - 2005-10-19 16:00:26
|
Author: rem...@jb... Date: 2005-10-19 11:59:57 -0400 (Wed, 19 Oct 2005) New Revision: 1412 Added: trunk/labs/jbossweb/java/ trunk/labs/jbossweb/java/org/ trunk/labs/jbossweb/java/org/jboss/ trunk/labs/jbossweb/java/org/jboss/web/ trunk/labs/jbossweb/java/org/jboss/web/rewrite/ trunk/labs/jbossweb/java/org/jboss/web/rewrite/Resolver.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteCond.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteMap.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteRule.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteTestCase.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteValve.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/Substitution.java trunk/labs/jbossweb/java/org/jboss/web/rewrite/TomcatResolver.java Log: - Add the rewrite code for backup purposes (not functional yet, though). Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/Resolver.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/Resolver.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/Resolver.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,15 @@ +package org.jboss.web.rewrite; + +public abstract class Resolver { + + public abstract String resolve(String key); + + public String resolveEnv(String key) { + return System.getProperty(key); + } + + public abstract String resolveSsl(String key); + + public abstract String resolveHttp(String key); + +} \ No newline at end of file Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteCond.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteCond.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteCond.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,208 @@ +package org.jboss.web.rewrite; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RewriteCond { + + public abstract class Condition { + public abstract boolean evaluate(String value, Resolver resolver); + } + + public class PatternCondition extends Condition { + public Pattern pattern; + public Matcher matcher = null; + public boolean evaluate(String value, Resolver resolver) { + Matcher m = pattern.matcher(value); + if (m.matches()) { + matcher = m; + return true; + } else { + return false; + } + } + } + + public class LexicalCondition extends Condition { + /** + * -1: < + * 0: = + * 1: > + */ + public int type = 0; + public String condition; + public boolean evaluate(String value, Resolver resolver) { + int result = value.compareTo(condition); + switch (type) { + case -1: + return (result < 0); + case 0: + return (result == 0); + case 1: + return (result > 0); + default: + return false; + } + + } + } + + protected String testString = null; + protected String condPattern = null; + + public String getCondPattern() { + return condPattern; + } + + public void setCondPattern(String condPattern) { + this.condPattern = condPattern; + } + + public String getTestString() { + return testString; + } + + public void setTestString(String testString) { + this.testString = testString; + } + + public void parse(Map maps) { + test = new Substitution(); + test.setSub(testString); + test.parse(maps); + // FIXME: Support for the other special condition patterns + if (condPattern.startsWith("!")) { + positive = false; + condPattern = condPattern.substring(1); + } + if (condPattern.startsWith("<")) { + LexicalCondition condition = new LexicalCondition(); + condition.type = -1; + condition.condition = condPattern.substring(1); + } else if (condPattern.startsWith(">")) { + LexicalCondition condition = new LexicalCondition(); + condition.type = 1; + condition.condition = condPattern.substring(1); + } else if (condPattern.startsWith("=")) { + LexicalCondition condition = new LexicalCondition(); + condition.type = 0; + condition.condition = condPattern.substring(1); + } else { + PatternCondition condition = new PatternCondition(); + int flags = 0; + if (isNocase()) { + flags &= Pattern.CASE_INSENSITIVE; + } + condition.pattern = Pattern.compile(condPattern, flags); + } + } + + public Matcher getMatcher() { + Object condition = this.condition.get(); + if (condition instanceof PatternCondition) { + return ((PatternCondition) condition).matcher; + } + return null; + } + + /** + * String representation. + */ + public String toString() { + return "RewriteCond " + testString + " " + condPattern; + } + + + protected boolean positive = true; + + protected Substitution test = null; + + protected ThreadLocal condition = new ThreadLocal(); + + /** + * This makes the test case-insensitive, i.e., there is no difference between + * 'A-Z' and 'a-z' both in the expanded TestString and the CondPattern. This + * flag is effective only for comparisons between TestString and CondPattern. + * It has no effect on filesystem and subrequest checks. + */ + public boolean nocase = false; + + /** + * Use this to combine rule conditions with a local OR instead of the implicit AND. + */ + public boolean ornext = false; + + /** + * Evaluate the condition based on the context + * + * @param rule corresponding matched rule + * @param cond last matched condition + * @return + */ + public boolean evaluate(Matcher rule, Matcher cond, Resolver resolver) { + String value = test.evaluate(rule, cond, resolver); + if (nocase) { + value = value.toLowerCase(); + } + Condition condition = (Condition) this.condition.get(); + if (condition == null) { + // FIXME: Support for the other special condition patterns + if (condPattern.startsWith("<")) { + LexicalCondition ncondition = new LexicalCondition(); + ncondition.type = -1; + ncondition.condition = condPattern.substring(1); + condition = ncondition; + } else if (condPattern.startsWith(">")) { + LexicalCondition ncondition = new LexicalCondition(); + ncondition.type = 1; + ncondition.condition = condPattern.substring(1); + condition = ncondition; + } else if (condPattern.startsWith("=")) { + LexicalCondition ncondition = new LexicalCondition(); + ncondition.type = 0; + ncondition.condition = condPattern.substring(1); + condition = ncondition; + } else { + PatternCondition ncondition = new PatternCondition(); + int flags = 0; + if (isNocase()) { + flags &= Pattern.CASE_INSENSITIVE; + } + ncondition.pattern = Pattern.compile(condPattern, flags); + condition = ncondition; + } + this.condition.set(condition); + } + if (positive) { + return condition.evaluate(value, resolver); + } else { + return !condition.evaluate(value, resolver); + } + } + + public boolean isNocase() { + return nocase; + } + + public void setNocase(boolean nocase) { + this.nocase = nocase; + } + + public boolean isOrnext() { + return ornext; + } + + public void setOrnext(boolean ornext) { + this.ornext = ornext; + } + + public boolean isPositive() { + return positive; + } + + public void setPositive(boolean positive) { + this.positive = positive; + } + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteMap.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteMap.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteMap.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,7 @@ +package org.jboss.web.rewrite; + +public interface RewriteMap { + + public String lookup(String key); + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteRule.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteRule.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteRule.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,419 @@ +package org.jboss.web.rewrite; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RewriteRule { + + protected RewriteCond[] conditions = new RewriteCond[0]; + + protected ThreadLocal pattern = new ThreadLocal(); + //protected Matcher matcher = null; + protected Substitution substitution = null; + + protected String patternString = null; + protected String substitutionString = null; + + public void parse(Map maps) { + // Parse the substitution + substitution = new Substitution(); + substitution.setSub(substitutionString); + substitution.parse(maps); + // Parse the pattern + int flags = 0; + if (isNocase()) { + flags &= Pattern.CASE_INSENSITIVE; + } + Pattern.compile(patternString, flags); + // Parse conditions + for (int i = 0; i < conditions.length; i++) { + conditions[i].parse(maps); + } + } + + public void addCondition(RewriteCond condition) { + RewriteCond[] conditions = new RewriteCond[this.conditions.length + 1]; + for (int i = 0; i < this.conditions.length; i++) { + conditions[i] = this.conditions[i]; + } + conditions[this.conditions.length] = condition; + this.conditions = conditions; + } + + /** + * Evaluate the rule based on the context + * + * @return null if no rewrite took place + */ + public CharSequence evaluate(CharSequence url, Resolver resolver) { + Pattern pattern = (Pattern) this.pattern.get(); + if (pattern == null) { + // Parse the pattern + int flags = 0; + if (isNocase()) { + flags &= Pattern.CASE_INSENSITIVE; + } + pattern = Pattern.compile(patternString, flags); + this.pattern.set(pattern); + } + Matcher matcher = pattern.matcher(url); + if (!matcher.matches()) { + // FIXME: System.out.println("No match for: " + url + " pattern: " + pattern); + // Evaluation done + return null; + } + // Evaluate conditions + boolean done = true; + boolean rewrite = true; + Matcher lastMatcher = null; + int pos = 0; + while (!done) { + if (pos < conditions.length) { + rewrite = conditions[pos].evaluate(matcher, lastMatcher, resolver); + if (rewrite) { + Matcher lastMatcher2 = conditions[pos].getMatcher(); + if (lastMatcher2 != null) { + lastMatcher = lastMatcher2; + } + } else if (!conditions[pos].isOrnext()) { + done = true; + } + pos++; + } else { + done = true; + } + } + // Use the substitution to rewrite the url + if (rewrite) { + return substitution.evaluate(matcher, lastMatcher, resolver); + } else { + return null; + } + } + + + /** + * String representation. + */ + public String toString() { + return "RewriteRule " + patternString + " " + substitutionString; + } + + + /** + * This flag chains the current rule with the next rule (which itself + * can be chained with the following rule, etc.). This has the following + * effect: if a rule matches, then processing continues as usual, i.e., + * the flag has no effect. If the rule does not match, then all following + * chained rules are skipped. For instance, use it to remove the ``.www'' + * part inside a per-directory rule set when you let an external redirect + * happen (where the ``.www'' part should not to occur!). + */ + protected boolean chain = false; + + /** + * This sets a cookie on the client's browser. The cookie's name is + * specified by NAME and the value is VAL. The domain field is the domain + * of the cookie, such as '.apache.org',the optional lifetime + * is the lifetime of the cookie in minutes, and the optional path is the + * path of the cookie + */ + protected boolean cookie = false; + protected String cookieName = null; + protected String cookieValue = null; + + /** + * This forces an environment variable named VAR to be set to the value VAL, + * where VAL can contain regexp backreferences $N and %N which will be + * expanded. You can use this flag more than once to set more than one variable. + * The variables can be later dereferenced in many situations, but usually + * from within XSSI (via <!--#echo var="VAR"-->) or CGI (e.g. $ENV{'VAR'}). + * Additionally you can dereference it in a following RewriteCond pattern via + * %{ENV:VAR}. Use this to strip but remember information from URLs. + */ + protected boolean env = false; + protected String envName = null; + protected String envValue = null; + + /** + * This forces the current URL to be forbidden, i.e., it immediately sends + * back a HTTP response of 403 (FORBIDDEN). Use this flag in conjunction + * with appropriate RewriteConds to conditionally block some URLs. + */ + protected boolean forbidden = false; + + /** + * This forces the current URL to be gone, i.e., it immediately sends + * back a HTTP response of 410 (GONE). Use this flag to mark pages which + * no longer exist as gone. + */ + protected boolean gone = false; + + /** + * FIXME: Content handler ????? + */ + + /** + * Stop the rewriting process here and don't apply any more rewriting + * rules. This corresponds to the Perl last command or the break command + * from the C language. Use this flag to prevent the currently rewritten + * URL from being rewritten further by following rules. For example, use + * it to rewrite the root-path URL ('/') to a real one, e.g., '/e/www/'. + */ + protected boolean last = false; + + /** + * Re-run the rewriting process (starting again with the first rewriting + * rule). Here the URL to match is again not the original URL but the URL + * from the last rewriting rule. This corresponds to the Perl next + * command or the continue command from the C language. Use this flag to + * restart the rewriting process, i.e., to immediately go to the top of + * the loop. But be careful not to create an infinite loop! + */ + protected boolean next = false; + + /** + * This makes the Pattern case-insensitive, i.e., there is no difference + * between 'A-Z' and 'a-z' when Pattern is matched against the current + * URL. + */ + protected boolean nocase = false; + + /** + * This flag keeps mod_rewrite from applying the usual URI escaping rules + * to the result of a rewrite. Ordinarily, special characters (such as + * '%', '$', ';', and so on) will be escaped into their hexcode + * equivalents ('%25', '%24', and '%3B', respectively); this flag + * prevents this from being done. This allows percent symbols to appear + * in the output, as in + * RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE] + * which would turn '/foo/zed' into a safe request for '/bar?arg=P1=zed'. + */ + protected boolean noescape = false; + + /** + * This flag forces the rewriting engine to skip a rewriting rule if the + * current request is an internal sub-request. For instance, sub-requests + * occur internally in Apache when mod_include tries to find out + * information about possible directory default files (index.xxx). On + * sub-requests it is not always useful and even sometimes causes a + * failure to if the complete set of rules are applied. Use this flag to + * exclude some rules. Use the following rule for your decision: whenever + * you prefix some URLs with CGI-scripts to force them to be processed by + * the CGI-script, the chance is high that you will run into problems (or + * even overhead) on sub-requests. In these cases, use this flag. + */ + protected boolean nosubreq = false; + + /** + * This flag forces the substitution part to be internally forced as a proxy + * request and immediately (i.e., rewriting rule processing stops here) put + * through the proxy module. You have to make sure that the substitution string + * is a valid URI (e.g., typically starting with http://hostname) which can be + * handled by the Apache proxy module. If not you get an error from the proxy + * module. Use this flag to achieve a more powerful implementation of the + * ProxyPass directive, to map some remote stuff into the namespace of + * the local server. + * FIXME: Likely no impl for this, so replace with a redirect + */ + + /** + * FIMXE: No passthrough ? + */ + + /** + * This flag forces the rewriting engine to append a query string part in + * the substitution string to the existing one instead of replacing it. + * Use this when you want to add more data to the query string via + * a rewrite rule. + */ + protected boolean qsappend = false; + + /** + * Prefix Substitution with http://thishost[:thisport]/ (which makes the + * new URL a URI) to force a external redirection. If no code is given + * a HTTP response of 302 (MOVED TEMPORARILY) is used. If you want to + * use other response codes in the range 300-400 just specify them as + * a number or use one of the following symbolic names: temp (default), + * permanent, seeother. Use it for rules which should canonicalize the + * URL and give it back to the client, e.g., translate ``/~'' into ``/u/'' + * or always append a slash to /u/user, etc. Note: When you use this flag, + * make sure that the substitution field is a valid URL! If not, you are + * redirecting to an invalid location! And remember that this flag itself + * only prefixes the URL with http://thishost[:thisport]/, rewriting + * continues. Usually you also want to stop and do the redirection + * immediately. To stop the rewriting you also have to provide the + * 'L' flag. + */ + protected boolean redirect = false; + protected int redirectCode = 0; + + /** + * This flag forces the rewriting engine to skip the next num rules in + * sequence when the current rule matches. Use this to make pseudo + * if-then-else constructs: The last rule of the then-clause becomes + * skip=N where N is the number of rules in the else-clause. + * (This is not the same as the 'chain|C' flag!) + */ + protected int skip = 0; + + /** + * Force the MIME-type of the target file to be MIME-type. For instance, + * this can be used to setup the content-type based on some conditions. + * For example, the following snippet allows .php files to be displayed + * by mod_php if they are called with the .phps extension: + * RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source] + */ + protected boolean type = false; + protected String typeValue = null; + public boolean isChain() { + return chain; + } + public void setChain(boolean chain) { + this.chain = chain; + } + public RewriteCond[] getConditions() { + return conditions; + } + public void setConditions(RewriteCond[] conditions) { + this.conditions = conditions; + } + public boolean isCookie() { + return cookie; + } + public void setCookie(boolean cookie) { + this.cookie = cookie; + } + public String getCookieName() { + return cookieName; + } + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } + public String getCookieValue() { + return cookieValue; + } + public void setCookieValue(String cookieValue) { + this.cookieValue = cookieValue; + } + public boolean isEnv() { + return env; + } + public void setEnv(boolean env) { + this.env = env; + } + public String getEnvName() { + return envName; + } + public void setEnvName(String envName) { + this.envName = envName; + } + public String getEnvValue() { + return envValue; + } + public void setEnvValue(String envValue) { + this.envValue = envValue; + } + public boolean isForbidden() { + return forbidden; + } + public void setForbidden(boolean forbidden) { + this.forbidden = forbidden; + } + public boolean isGone() { + return gone; + } + public void setGone(boolean gone) { + this.gone = gone; + } + public boolean isLast() { + return last; + } + public void setLast(boolean last) { + this.last = last; + } + public boolean isNext() { + return next; + } + public void setNext(boolean next) { + this.next = next; + } + public boolean isNocase() { + return nocase; + } + public void setNocase(boolean nocase) { + this.nocase = nocase; + } + public boolean isNoescape() { + return noescape; + } + public void setNoescape(boolean noescape) { + this.noescape = noescape; + } + public boolean isNosubreq() { + return nosubreq; + } + public void setNosubreq(boolean nosubreq) { + this.nosubreq = nosubreq; + } + public boolean isQsappend() { + return qsappend; + } + public void setQsappend(boolean qsappend) { + this.qsappend = qsappend; + } + public boolean isRedirect() { + return redirect; + } + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + public int getRedirectCode() { + return redirectCode; + } + public void setRedirectCode(int redirectCode) { + this.redirectCode = redirectCode; + } + public int getSkip() { + return skip; + } + public void setSkip(int skip) { + this.skip = skip; + } + public Substitution getSubstitution() { + return substitution; + } + public void setSubstitution(Substitution substitution) { + this.substitution = substitution; + } + public boolean isType() { + return type; + } + public void setType(boolean type) { + this.type = type; + } + public String getTypeValue() { + return typeValue; + } + public void setTypeValue(String typeValue) { + this.typeValue = typeValue; + } + + public String getPatternString() { + return patternString; + } + + public void setPatternString(String patternString) { + this.patternString = patternString; + } + + public String getSubstitutionString() { + return substitutionString; + } + + public void setSubstitutionString(String substitutionString) { + this.substitutionString = substitutionString; + } + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteTestCase.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteTestCase.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteTestCase.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,145 @@ +package org.jboss.web.rewrite; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class RewriteTestCase extends TestCase { + + protected class TestResolver extends Resolver { + + @Override + public String resolve(String key) { + return "server_variable_value[" + key + "]"; + } + + @Override + public String resolveHttp(String key) { + return "http_header_value[" + key + "]"; + } + + @Override + public String resolveSsl(String key) { + return "ssl_property_value[" + key + "]"; + } + + } + + /** + * Construct a new instance of this test case. + * + * @param name Name of the test case + */ + public RewriteTestCase(String name) { + super(name); + } + + /** + * Set up instance variables required by this test case. + */ + public void setUp() { + } + + /** + * Return the tests included in this test suite. + */ + public static Test suite() { + return (new TestSuite(RewriteTestCase.class)); + } + + /** + * Tear down instance variables required by this test case. + */ + public void tearDown() { + } + + public void testCanonicalUrl() { + Resolver resolver = new TestResolver(); + + RewriteRule rule1 = new RewriteRule(); + rule1.setPatternString("^/~([^/]+)/?(.*)"); + rule1.setSubstitutionString("/u/$1/$2"); + + RewriteRule rule2 = new RewriteRule(); + rule2.setPatternString("^/([uge])/([^/]+)$"); + rule2.setSubstitutionString("/$1/$2/"); + + RewriteRule[] rules = new RewriteRule[2]; + rules[0] = rule1; + rules[1] = rule2; + for (int i = 0; i < rules.length; i++) { + rules[i].parse(null); + } + + String result = rewriteUrl("/~user/foo/bar", resolver, rules).toString(); + assertEquals("/u/user/foo/bar", result); + result = rewriteUrl("/u/user", resolver, rules).toString(); + assertEquals("/u/user/", result); + } + + public void testCanonicalHostname() { + Resolver resolver = new TestResolver(); + + RewriteRule rule = new RewriteRule(); + rule.setPatternString("^/(.*)"); + rule.setSubstitutionString("http://fully.qualified.domain.name:%{SERVER_PORT}/$1"); + RewriteRule[] rules = new RewriteRule[1]; + rules[0] = rule; + + RewriteCond cond1 = new RewriteCond(); + cond1.setTestString("%{HTTP_HOST}"); + cond1.setCondPattern("!^fully\\.qualified\\.domain\\.name"); + cond1.setNocase(true); + + RewriteCond cond2 = new RewriteCond(); + cond2.setTestString("%{HTTP_HOST}"); + cond2.setCondPattern("!^$"); + + RewriteCond cond3 = new RewriteCond(); + cond3.setTestString("%{SERVER_PORT}"); + cond3.setCondPattern("!^80$"); + + rule.addCondition(cond1); + rule.addCondition(cond2); + rule.addCondition(cond3); + + for (int i = 0; i < rules.length; i++) { + rules[i].parse(null); + } + + String result = rewriteUrl("/foo/bar", resolver, rules).toString(); + assertEquals("http://fully.qualified.domain.name:server_variable_value[SERVER_PORT]/foo/bar", result); + } + + public void testMovedDocumentRoot() { + Resolver resolver = new TestResolver(); + + RewriteRule rule = new RewriteRule(); + rule.setPatternString("^/$"); + rule.setSubstitutionString("/about/"); + + RewriteRule[] rules = new RewriteRule[1]; + rules[0] = rule; + for (int i = 0; i < rules.length; i++) { + rules[i].parse(null); + } + + String result = rewriteUrl("/", resolver, rules).toString(); + assertEquals("/about/", result); + } + + public static CharSequence rewriteUrl(CharSequence url, Resolver resolver, RewriteRule[] rules) { + if (rules == null) + return url; + for (int i = 0; i < rules.length; i++) { + CharSequence newurl = rules[i].evaluate(url, resolver); + if (newurl != null) { + // Check some extra flags, including redirect, proxying, etc + // FIXME + url = newurl; + } + } + return url; + } + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteValve.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteValve.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/RewriteValve.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,128 @@ +package org.jboss.web.rewrite; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.Engine; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.util.LifecycleSupport; +import org.apache.catalina.valves.ValveBase; + +public class RewriteValve extends ValveBase + implements Lifecycle { + + /** + * The lifecycle event support for this component. + */ + protected LifecycleSupport lifecycle = new LifecycleSupport(this); + + + /** + * The rewrite rules that the valve will use. + */ + protected RewriteRule[] rules = null; + + + /** + * Thread local Tomcat resolver. + * Note: this could also go to a request note. + */ + protected ThreadLocal resolver = new ThreadLocal(); + + + /** + * If rewriting occurs, the whole request will be processed again. + */ + protected ThreadLocal invoked = new ThreadLocal(); + + + public void addLifecycleListener(LifecycleListener listener) { + lifecycle.addLifecycleListener(listener); + } + + public LifecycleListener[] findLifecycleListeners() { + return lifecycle.findLifecycleListeners(); + } + + public void removeLifecycleListener(LifecycleListener listener) { + lifecycle.removeLifecycleListener(listener); + } + + public void start() throws LifecycleException { + + } + + public void stop() throws LifecycleException { + + } + + + public void invoke(Request request, Response response) + throws IOException, ServletException { + + if (rules == null) { + getNext().invoke(request, response); + return; + } + + if (invoked.get() == Boolean.TRUE) { + getNext().invoke(request, response); + invoked.set(null); + return; + } + + Resolver resolver = (Resolver) this.resolver.get(); + if (resolver == null) { + resolver = new TomcatResolver(request); + this.resolver.set(resolver); + } + + invoked.set(Boolean.TRUE); + + // As long as MB isn't a char sequence or affiliated, this has to be + // converted to a string + String url = request.getDecodedRequestURI(); + boolean rewritten = false; + boolean done = false; + for (int i = 0; i < rules.length; i++) { + CharSequence newurl = rules[i].evaluate(url, resolver); + if (newurl != null) { + // Check some extra flags, including redirect, proxying, etc + // FIXME + url = newurl.toString(); + rewritten = true; + } + } + + if (rewritten) { + if (!done) { + // Reinvoke the whole request recursively + Engine engine = (Engine) request.getHost().getParent(); + Connector[] connectors = engine.getService().findConnectors(); + // Find the right connector (note: this is the only real hack) + // FIXME + Connector connector = connectors[0]; + try { + request.getCoyoteRequest().decodedURI().setString(url); + connector.getProtocolHandler().getAdapter().service + (request.getCoyoteRequest(), response.getCoyoteResponse()); + } catch (Exception e) { + // This doesn't actually happen in Tomcat + } + } + } else { + getNext().invoke(request, response); + } + + invoked.set(null); + + } + + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/Substitution.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/Substitution.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/Substitution.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,219 @@ +package org.jboss.web.rewrite; + +import java.util.ArrayList; +import java.util.Map; +import java.util.regex.Matcher; + +public class Substitution { + + public abstract class SubstitutionElement { + public abstract String evaluate(Matcher rule, Matcher cond, Resolver resolver); + } + + public class StaticElement extends SubstitutionElement { + public String value; + + public String evaluate + (Matcher rule, Matcher cond, Resolver resolver) { + return value; + } + + } + + public class RewriteRuleBackReferenceElement extends SubstitutionElement { + public int n; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return rule.group(n); + } + } + + public class RewriteCondBackReferenceElement extends SubstitutionElement { + public int n; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return cond.group(n); + } + } + + public class ServerVariableElement extends SubstitutionElement { + public String key; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return resolver.resolve(key); + } + } + + public class ServerVariableEnvElement extends SubstitutionElement { + public String key; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return resolver.resolveEnv(key); + } + } + + public class ServerVariableSslElement extends SubstitutionElement { + public String key; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return resolver.resolveSsl(key); + } + } + + public class ServerVariableHttpElement extends SubstitutionElement { + public String key; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + return resolver.resolveHttp(key); + } + } + + public class MapElement extends SubstitutionElement { + public RewriteMap map = null; + public String key; + public String defaultValue = null; + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + String result = map.lookup(key); + if (result == null) { + result = defaultValue; + } + return result; + } + } + + protected SubstitutionElement[] elements = null; + + protected String sub = null; + public String getSub() { return sub; } + public void setSub(String sub) { this.sub = sub; } + + public void parse(Map maps) { + + ArrayList elements = new ArrayList(); + int pos = 0; + int percentPos = 0; + int dollarPos = 0; + + while (pos < sub.length()) { + percentPos = sub.indexOf('%', pos); + dollarPos = sub.indexOf('$', pos); + // FIXME: System.out.println("S: " + sub + " pos: " + pos + " L: " + sub.length() + " %: " + percentPos + " $: " + dollarPos); + if (percentPos == -1 && dollarPos == -1) { + // Static text + StaticElement newElement = new StaticElement(); + newElement.value = sub.substring(pos, sub.length()); + pos = sub.length(); + elements.add(newElement); + } else if (percentPos == -1 || ((dollarPos != -1) && (dollarPos < percentPos))) { + // $: backreference to rule or map lookup + if (dollarPos + 1 == sub.length()) { + throw new IllegalArgumentException(sub); + } + if (pos < dollarPos) { + // Static text + StaticElement newElement = new StaticElement(); + newElement.value = sub.substring(pos, dollarPos); + pos = dollarPos; + elements.add(newElement); + } + if (Character.isDigit(sub.charAt(dollarPos + 1))) { + // $: backreference to rule + RewriteRuleBackReferenceElement newElement = new RewriteRuleBackReferenceElement(); + newElement.n = Character.digit(sub.charAt(dollarPos + 1), 10); + pos = dollarPos + 2; + elements.add(newElement); + } else { + // $: map lookup as ${mapname:key|default} + MapElement newElement = new MapElement(); + int open = sub.indexOf('{', dollarPos); + int colon = sub.indexOf(':', dollarPos); + int def = sub.indexOf('|', dollarPos); + int close = sub.indexOf('}', dollarPos); + if (!(-1 < open && open < colon && colon < close)) { + throw new IllegalArgumentException(sub); + } + newElement.map = (RewriteMap) maps.get(sub.substring(open + 1, colon)); + if (newElement.map == null) { + throw new IllegalArgumentException(sub + ": No map: " + sub.substring(open + 1, colon)); + } + if (def > -1) { + if (!(colon < def && def < close)) { + throw new IllegalArgumentException(sub); + } + newElement.key = sub.substring(colon + 1, def); + newElement.defaultValue = sub.substring(def + 1, close); + } else { + newElement.key = sub.substring(colon + 1, close); + } + pos = close + 1; + elements.add(newElement); + } + } else { + // %: backreference to cond or server variable + if (percentPos + 1 == sub.length()) { + throw new IllegalArgumentException(sub); + } + if (pos < percentPos) { + // Static text + StaticElement newElement = new StaticElement(); + newElement.value = sub.substring(pos, percentPos); + pos = percentPos; + elements.add(newElement); + } + if (Character.isDigit(sub.charAt(percentPos + 1))) { + // %: backreference to cond + RewriteCondBackReferenceElement newElement = new RewriteCondBackReferenceElement(); + newElement.n = Character.digit(sub.charAt(percentPos + 1), 10); + pos = percentPos + 2; + elements.add(newElement); + } else { + // %: server variable as %{variable} + SubstitutionElement newElement = null; + int open = sub.indexOf('{', percentPos); + int colon = sub.indexOf(':', percentPos); + int close = sub.indexOf('}', percentPos); + if (!(-1 < open && open < close)) { + throw new IllegalArgumentException(sub); + } + if (colon > -1) { + if (!(open < colon && colon < close)) { + throw new IllegalArgumentException(sub); + } + String type = sub.substring(open + 1, colon); + if (type.equals("ENV")) { + newElement = new ServerVariableEnvElement(); + ((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close); + } else if (type.equals("SSL")) { + newElement = new ServerVariableSslElement(); + ((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close); + } else if (type.equals("HTTP")) { + newElement = new ServerVariableHttpElement(); + ((ServerVariableEnvElement) newElement).key = sub.substring(colon + 1, close); + } else { + throw new IllegalArgumentException(sub + ": Bad type: " + type); + } + } else { + newElement = new ServerVariableElement(); + ((ServerVariableElement) newElement).key = sub.substring(open + 1, close); + } + pos = close + 1; + elements.add(newElement); + } + } + } + + this.elements = (SubstitutionElement[]) elements.toArray(new SubstitutionElement[0]); + + } + + + /** + * Evaluate the substituation based on the context + * + * @param rule corresponding matched rule + * @param cond last matched condition + * @return + */ + public String evaluate(Matcher rule, Matcher cond, Resolver resolver) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < elements.length; i++) { + buf.append(elements[i].evaluate(rule, cond, resolver)); + } + return buf.toString(); + } + +} Added: trunk/labs/jbossweb/java/org/jboss/web/rewrite/TomcatResolver.java =================================================================== --- trunk/labs/jbossweb/java/org/jboss/web/rewrite/TomcatResolver.java 2005-10-19 15:57:42 UTC (rev 1411) +++ trunk/labs/jbossweb/java/org/jboss/web/rewrite/TomcatResolver.java 2005-10-19 15:59:57 UTC (rev 1412) @@ -0,0 +1,91 @@ +package org.jboss.web.rewrite; + +import org.apache.catalina.connector.Request; + +public class TomcatResolver extends Resolver { + + protected Request request = null; + + public TomcatResolver(Request request) { + this.request = request; + } + + /** + * The following are not implemented: + * - SERVER_ADMIN + * - API_VERSION + * - IS_SUBREQ + */ + public String resolve(String key) { + // FIXME: Time based stuff not implemented + if (key.equals("HTTP_USER_AGENT")) { + return request.getHeader("user-agent"); + } else if (key.equals("HTTP_USER_AGENT")) { + return request.getHeader("user-agent"); + } else if (key.equals("HTTP_REFERER")) { + return request.getHeader("referer"); + } else if (key.equals("HTTP_COOKIE")) { + return request.getHeader("cookie"); + } else if (key.equals("HTTP_FORWARDED")) { + return request.getHeader("forwarded"); + } else if (key.equals("HTTP_HOST")) { + return request.getHeader("host"); + } else if (key.equals("HTTP_PROXY_CONNECTION")) { + return request.getHeader("proxy-connection"); + } else if (key.equals("HTTP_ACCEPT")) { + return request.getHeader("accept"); + } else if (key.equals("REMOTE_ADDR")) { + return request.getRemoteAddr(); + } else if (key.equals("REMOTE_HOST")) { + return request.getRemoteHost(); + } else if (key.equals("REMOTE_PORT")) { + return String.valueOf(request.getRemotePort()); + } else if (key.equals("REMOTE_USER")) { + return request.getRemoteUser(); + } else if (key.equals("REMOTE_IDENT")) { + return request.getRemoteUser(); + } else if (key.equals("REQUEST_METHOD")) { + return request.getMethod(); + } else if (key.equals("SCRIPT_FILENAME")) { + return request.getRealPath(request.getServletPath()); //FIXME ? + } else if (key.equals("PATH_INFO")) { + return request.getPathInfo(); + } else if (key.equals("QUERY_STRING")) { + return request.getQueryString(); + } else if (key.equals("AUTH_TYPE")) { + return request.getAuthType(); + } else if (key.equals("DOCUMENT_ROOT")) { + return request.getRealPath("/"); + } else if (key.equals("SERVER_NAME")) { + return request.getLocalName(); + } else if (key.equals("SERVER_ADDR")) { + return request.getLocalAddr(); + } else if (key.equals("SERVER_PORT")) { + return String.valueOf(request.getLocalPort()); + } else if (key.equals("SERVER_PROTOCOL")) { + return request.getProtocol(); + } else if (key.equals("SERVER_SOFTWARE")) { + return "tomcat"; + } else if (key.equals("THE_REQUEST")) { + return request.getMethod() + " " + request.getRequestURI() + + " " + request.getProtocol(); + } else if (key.equals("REQUEST_URI")) { + return request.getRequestURI(); + } else if (key.equals("REQUEST_FILENAME")) { + return request.getPathTranslated(); + } else if (key.equals("HTTPS")) { + return request.isSecure() ? "on" : "off"; + } + return null; + } + + public String resolveSsl(String key) { + // FIXME: Implement SSL env variables + return null; + } + + public String resolveHttp(String key) { + return request.getHeader(key); + } + +} |