RewriteRule.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.valves.rewrite;
import java.util.ArrayList;
import java.util.Arrays;
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> pattern = new ThreadLocal<>();
protected Substitution substitution = null;
protected String patternString = null;
protected String substitutionString = null;
protected String flagsString = null;
protected boolean positive = true;
public void parse(Map<String,RewriteMap> maps) {
// Parse the substitution
if (!"-".equals(substitutionString)) {
substitution = new Substitution();
substitution.setSub(substitutionString);
substitution.parse(maps);
substitution.setEscapeBackReferences(isEscapeBackReferences());
}
// Parse the pattern
if (patternString.startsWith("!")) {
positive = false;
patternString = patternString.substring(1);
}
int flags = Pattern.DOTALL;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
Pattern.compile(patternString, flags);
// Parse conditions
for (RewriteCond condition : conditions) {
condition.parse(maps);
}
// Parse flag which have substitution values
if (isEnv()) {
for (String s : envValue) {
Substitution newEnvSubstitution = new Substitution();
newEnvSubstitution.setSub(s);
newEnvSubstitution.parse(maps);
envSubstitution.add(newEnvSubstitution);
envResult.add(new ThreadLocal<>());
}
}
if (isCookie()) {
cookieSubstitution = new Substitution();
cookieSubstitution.setSub(cookieValue);
cookieSubstitution.parse(maps);
}
}
public void addCondition(RewriteCond condition) {
RewriteCond[] conditions = Arrays.copyOf(this.conditions, this.conditions.length + 1);
conditions[this.conditions.length] = condition;
this.conditions = conditions;
}
/**
* Evaluate the rule based on the context
*
* @param url The char sequence
* @param resolver Property resolver
*
* @return <code>null</code> if no rewrite took place
*/
public CharSequence evaluate(CharSequence url, Resolver resolver) {
Pattern pattern = this.pattern.get();
if (pattern == null) {
// Parse the pattern
int flags = Pattern.DOTALL;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
pattern = Pattern.compile(patternString, flags);
this.pattern.set(pattern);
}
Matcher matcher = pattern.matcher(url);
// Use XOR
if (positive ^ matcher.matches()) {
// Evaluation done
return null;
}
// Evaluate conditions
boolean done = false;
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;
}
while (pos < conditions.length && conditions[pos].isOrnext()) {
pos++;
}
} else if (!conditions[pos].isOrnext()) {
done = true;
}
pos++;
} else {
done = true;
}
}
// Use the substitution to rewrite the url
if (rewrite) {
if (isEnv()) {
for (int i = 0; i < envSubstitution.size(); i++) {
envResult.get(i).set(envSubstitution.get(i).evaluate(matcher, lastMatcher, resolver));
}
}
if (isCookie()) {
cookieResult.set(cookieSubstitution.evaluate(matcher, lastMatcher, resolver));
}
if (substitution != null) {
return substitution.evaluate(matcher, lastMatcher, resolver);
} else {
return url;
}
} else {
return null;
}
}
/**
* String representation.
*/
@Override
public String toString() {
return "RewriteRule " + patternString + " " + substitutionString +
((flagsString != null) ? (" " + flagsString) : "");
}
private boolean escapeBackReferences = false;
/**
* 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;
protected String cookieDomain = null;
protected int cookieLifetime = -1;
protected String cookiePath = null;
protected boolean cookieSecure = false;
protected boolean cookieHttpOnly = false;
protected Substitution cookieSubstitution = null;
protected ThreadLocal<String> cookieResult = new ThreadLocal<>();
/**
* This forces a request attribute named VAR to be set to the value VAL, where VAL can contain regexp back
* references $N and %N which will be expanded. Multiple env flags are allowed.
*/
protected boolean env = false;
protected ArrayList<String> envName = new ArrayList<>();
protected ArrayList<String> envValue = new ArrayList<>();
protected ArrayList<Substitution> envSubstitution = new ArrayList<>();
protected ArrayList<ThreadLocal<String>> envResult = new ArrayList<>();
/**
* This forces the current URL to be forbidden, i.e., it immediately sends back an 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 an HTTP response of 410 (GONE). Use this
* flag to mark pages which no longer exist as gone.
*/
protected boolean gone = false;
/**
* Host. This means this rule and its associated conditions will apply to host, allowing host rewriting (ex:
* redirecting internally *.foo.com to bar.foo.com).
*/
protected boolean host = false;
/**
* 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 {@code 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;
/*
* Note: No proxy
*/
/*
* Note: 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;
/**
* When the requested URI contains a query string, and the target URI does not, the default behavior of RewriteRule
* is to copy that query string to the target URI. Using the [QSD] flag causes the query string to be discarded.
* Using [QSD] and [QSA] together will result in [QSD] taking precedence.
*/
protected boolean qsdiscard = false;
/**
* Prefix Substitution with http://thishost[:thisport]/ (which makes the new URL a URI) to force an external
* redirection. If no code is given an HTTP response of 302 (FOUND, previously MOVED TEMPORARILY) is used. If you
* want to use other response codes in the range 300-399 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;
/**
* Allows skipping the next valve in the Catalina pipeline.
*/
protected boolean valveSkip = false;
public boolean isEscapeBackReferences() {
return escapeBackReferences;
}
public void setEscapeBackReferences(boolean escapeBackReferences) {
this.escapeBackReferences = escapeBackReferences;
}
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 String getCookieResult() {
return cookieResult.get();
}
public boolean isEnv() {
return env;
}
public int getEnvSize() {
return envName.size();
}
public void setEnv(boolean env) {
this.env = env;
}
public String getEnvName(int i) {
return envName.get(i);
}
public void addEnvName(String envName) {
this.envName.add(envName);
}
public String getEnvValue(int i) {
return envValue.get(i);
}
public void addEnvValue(String envValue) {
this.envValue.add(envValue);
}
public String getEnvResult(int i) {
return envResult.get(i).get();
}
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 final boolean isQsdiscard() {
return qsdiscard;
}
public final void setQsdiscard(boolean qsdiscard) {
this.qsdiscard = qsdiscard;
}
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;
}
public final String getFlagsString() {
return flagsString;
}
public final void setFlagsString(String flagsString) {
this.flagsString = flagsString;
}
public boolean isHost() {
return host;
}
public void setHost(boolean host) {
this.host = host;
}
public String getCookieDomain() {
return cookieDomain;
}
public void setCookieDomain(String cookieDomain) {
this.cookieDomain = cookieDomain;
}
public int getCookieLifetime() {
return cookieLifetime;
}
public void setCookieLifetime(int cookieLifetime) {
this.cookieLifetime = cookieLifetime;
}
public String getCookiePath() {
return cookiePath;
}
public void setCookiePath(String cookiePath) {
this.cookiePath = cookiePath;
}
public boolean isCookieSecure() {
return cookieSecure;
}
public void setCookieSecure(boolean cookieSecure) {
this.cookieSecure = cookieSecure;
}
public boolean isCookieHttpOnly() {
return cookieHttpOnly;
}
public void setCookieHttpOnly(boolean cookieHttpOnly) {
this.cookieHttpOnly = cookieHttpOnly;
}
public boolean isValveSkip() {
return this.valveSkip;
}
public void setValveSkip(boolean valveSkip) {
this.valveSkip = valveSkip;
}
}