/*
 * 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 = 0;
        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<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
Params:
  • url – The char sequence
  • resolver – Property resolver
Returns:null if no rewrite took place
/** * 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); // 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.
/** * 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!).
/** * 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
/** * 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.
/** * 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.
/** * 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.
/** * 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).
/** * 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/'.
/** * 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!
/** * 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.
/** * 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'.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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!)
/** * 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]
/** * 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 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; } }