/*
* 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;
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
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);
}
// Parse flag which have substitution values
if (isEnv()) {
for (int i = 0; i < envValue.size(); i++) {
Substitution newEnvSubstitution = new Substitution();
newEnvSubstitution.setSub(envValue.get(i));
newEnvSubstitution.parse(maps);
envSubstitution.add(newEnvSubstitution);
envResult.add(new ThreadLocal<String>());
}
}
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 = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
pattern = Pattern.compile(patternString, flags);
this.pattern.set(pattern);
}
Matcher matcher = pattern.matcher(url);
if (!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() {
// FIXME: Add flags if possible
return "RewriteRule " + patternString + " " + substitutionString;
}
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 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;
/**
* 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
* 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.
* 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;
/**
* Prefix Substitution with http://thishost[:thisport]/ (which makes the
* new URL a URI) to force a 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;
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 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 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;
}
}