This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# 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.
rewriteValve.closeError=Error closing configuration
rewriteValve.invalidFlags=Invalid flag in [{0}] flags [{1}]
rewriteValve.invalidLine=Invalid line [{0}]
rewriteValve.invalidMapClassName=Invalid map class name [{0}]
rewriteValve.readError=Error reading configuration

View File

@@ -0,0 +1,20 @@
# 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.
rewriteValve.closeError=Erreur lors de la fermeture de la configuration
rewriteValve.invalidFlags=Indicateur invalide dans [{0}] indicateurs [{1}]
rewriteValve.invalidLine=Ligne invalide [{0}]
rewriteValve.invalidMapClassName=Le nom de la classe [{0}] de la structure est invalide
rewriteValve.readError=Erreur lors de la lecture de la configuration

View File

@@ -0,0 +1,20 @@
# 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.
rewriteValve.closeError=構成を閉じる際のエラー
rewriteValve.invalidFlags=[{0}]に無効なフラグ [{1}]があります。
rewriteValve.invalidLine=無効な行[{0}]
rewriteValve.invalidMapClassName=Mapクラス名[{0}]が無効です。
rewriteValve.readError=構成の読み取りエラー

View File

@@ -0,0 +1,20 @@
# 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.
rewriteValve.closeError=설정 리소스의 스트림을 닫는 중 오류 발생
rewriteValve.invalidFlags=[{0}] 내에 유효하지 않은 플래그입니다 (플래그들: [{1}]).
rewriteValve.invalidLine=유효하지 않은 행 [{0}]
rewriteValve.invalidMapClassName=유효하지 않은 Map 클래스 이름: [{0}]
rewriteValve.readError=설정을 읽는 도중 오류 발생

View File

@@ -0,0 +1,16 @@
# 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.
rewriteValve.readError=读取配置时发生异常

View File

@@ -0,0 +1,48 @@
/*
* 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.nio.charset.Charset;
/**
* Resolver abstract class.
*/
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);
public abstract boolean resolveResource(int type, String name);
/**
* @return The name of the encoding to use to %nn encode URIs
*
* @deprecated This will be removed in Tomcat 9.0.x
*/
@Deprecated
public abstract String getUriEncoding();
public abstract Charset getUriCharset();
}

View File

@@ -0,0 +1,191 @@
/*
* 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.nio.charset.Charset;
import java.util.Calendar;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Request;
import org.apache.tomcat.util.http.FastHttpDateFormat;
public class ResolverImpl extends Resolver {
protected Request request = null;
public ResolverImpl(Request request) {
this.request = request;
}
/**
* The following are not implemented:
* - SERVER_ADMIN
* - API_VERSION
* - IS_SUBREQ
*/
@Override
public String resolve(String key) {
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")) {
String host = request.getHeader("host");
if (host != null) {
int index = host.indexOf(':');
if (index != -1) {
host = host.substring(0, index);
}
}
return 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.getServletContext().getRealPath(request.getServletPath());
} else if (key.equals("REQUEST_PATH")) {
return request.getRequestPathMB().toString();
} else if (key.equals("CONTEXT_PATH")) {
return request.getContextPath();
} else if (key.equals("SERVLET_PATH")) {
return emptyStringIfNull(request.getServletPath());
} else if (key.equals("PATH_INFO")) {
return emptyStringIfNull(request.getPathInfo());
} else if (key.equals("QUERY_STRING")) {
return emptyStringIfNull(request.getQueryString());
} else if (key.equals("AUTH_TYPE")) {
return request.getAuthType();
} else if (key.equals("DOCUMENT_ROOT")) {
return request.getServletContext().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";
} else if (key.equals("TIME_YEAR")) {
return String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
} else if (key.equals("TIME_MON")) {
return String.valueOf(Calendar.getInstance().get(Calendar.MONTH));
} else if (key.equals("TIME_DAY")) {
return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
} else if (key.equals("TIME_HOUR")) {
return String.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY));
} else if (key.equals("TIME_MIN")) {
return String.valueOf(Calendar.getInstance().get(Calendar.MINUTE));
} else if (key.equals("TIME_SEC")) {
return String.valueOf(Calendar.getInstance().get(Calendar.SECOND));
} else if (key.equals("TIME_WDAY")) {
return String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_WEEK));
} else if (key.equals("TIME")) {
return FastHttpDateFormat.getCurrentDate();
}
return null;
}
@Override
public String resolveEnv(String key) {
Object result = request.getAttribute(key);
return (result != null) ? result.toString() : System.getProperty(key);
}
@Override
public String resolveSsl(String key) {
// FIXME: Implement SSL environment variables
return null;
}
@Override
public String resolveHttp(String key) {
String header = request.getHeader(key);
if (header == null) {
return "";
} else {
return header;
}
}
@Override
public boolean resolveResource(int type, String name) {
WebResourceRoot resources = request.getContext().getResources();
WebResource resource = resources.getResource(name);
if (!resource.exists()) {
return false;
} else {
switch (type) {
case 0:
return resource.isDirectory();
case 1:
return resource.isFile();
case 2:
return resource.isFile() && resource.getContentLength() > 0;
default:
return false;
}
}
}
private static final String emptyStringIfNull(String value) {
if (value == null) {
return "";
} else {
return value;
}
}
@Override
@Deprecated
public String getUriEncoding() {
return request.getConnector().getURIEncoding();
}
@Override
public Charset getUriCharset() {
return request.getConnector().getURICharset();
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RewriteCond {
public abstract static class Condition {
public abstract boolean evaluate(String value, Resolver resolver);
}
public static class PatternCondition extends Condition {
public Pattern pattern;
private ThreadLocal<Matcher> matcher = new ThreadLocal<>();
@Override
public boolean evaluate(String value, Resolver resolver) {
Matcher m = pattern.matcher(value);
if (m.matches()) {
matcher.set(m);
return true;
} else {
return false;
}
}
public Matcher getMatcher() {
return matcher.get();
}
}
public static class LexicalCondition extends Condition {
/**
* -1: &lt;
* 0: =
* 1: &gt;
*/
public int type = 0;
public String condition;
@Override
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;
}
}
}
public static class ResourceCondition extends Condition {
/**
* 0: -d (is directory ?)
* 1: -f (is regular file ?)
* 2: -s (is regular file with size ?)
*/
public int type = 0;
@Override
public boolean evaluate(String value, Resolver resolver) {
return resolver.resolveResource(type, value);
}
}
protected String testString = null;
protected String condPattern = null;
protected String flagsString = 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 final String getFlagsString() {
return flagsString;
}
public final void setFlagsString(String flagsString) {
this.flagsString = flagsString;
}
public void parse(Map<String, RewriteMap> maps) {
test = new Substitution();
test.setSub(testString);
test.parse(maps);
if (condPattern.startsWith("!")) {
positive = false;
condPattern = condPattern.substring(1);
}
if (condPattern.startsWith("<")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = -1;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.startsWith(">")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 1;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.startsWith("=")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 0;
ncondition.condition = condPattern.substring(1);
this.condition = ncondition;
} else if (condPattern.equals("-d")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 0;
this.condition = ncondition;
} else if (condPattern.equals("-f")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 1;
this.condition = ncondition;
} else if (condPattern.equals("-s")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 2;
this.condition = ncondition;
} else {
PatternCondition ncondition = new PatternCondition();
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
ncondition.pattern = Pattern.compile(condPattern, flags);
this.condition = ncondition;
}
}
public Matcher getMatcher() {
if (condition instanceof PatternCondition) {
return ((PatternCondition) condition).getMatcher();
}
return null;
}
/**
* String representation.
*/
@Override
public String toString() {
return "RewriteCond " + testString + " " + condPattern
+ ((flagsString != null) ? (" " + flagsString) : "");
}
protected boolean positive = true;
protected Substitution test = null;
protected Condition condition = null;
/**
* 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
* @param resolver Property resolver
* @return <code>true</code> if the condition matches
*/
public boolean evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String value = test.evaluate(rule, cond, resolver);
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;
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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;
public interface RewriteMap {
public String setParameters(String params);
public String lookup(String key);
}

View File

@@ -0,0 +1,563 @@
/*
* 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 (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);
// 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
* 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 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 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;
}
}

View File

@@ -0,0 +1,845 @@
/*
* 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.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Pipeline;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.URLEncoder;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.CharChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.http.RequestUtil;
/**
* Note: Extra caution should be used when adding a Rewrite Rule. When
* specifying a regex to match for in a Rewrite Rule, certain regex could allow
* an attacker to DoS your server, as Java's regex parsing is vulnerable to
* "catastrophic backtracking" (also known as "Regular expression Denial of
* Service", or ReDoS). There are some open source tools to help detect
* vulnerable regex, though in general it is a hard problem. A good defence is
* to use a regex debugger on your desired regex, and read more on the subject
* of catastrophic backtracking.
*
* @see <a href=
* "https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">OWASP
* ReDoS</a>
*/
public class RewriteValve extends ValveBase {
/**
* The rewrite rules that the valve will use.
*/
protected RewriteRule[] rules = null;
/**
* If rewriting occurs, the whole request will be processed again.
*/
protected ThreadLocal<Boolean> invoked = new ThreadLocal<>();
/**
* Relative path to the configuration file.
* Note: If the valve's container is a context, this will be relative to
* /WEB-INF/.
*/
protected String resourcePath = "rewrite.config";
/**
* Will be set to true if the valve is associated with a context.
*/
protected boolean context = false;
/**
* enabled this component
*/
protected boolean enabled = true;
/**
* Maps to be used by the rules.
*/
protected Map<String, RewriteMap> maps = new Hashtable<>();
/**
* Maps configuration.
*/
protected ArrayList<String> mapsConfiguration = new ArrayList<>();
public RewriteValve() {
super(true);
}
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
containerLog = LogFactory.getLog(getContainer().getLogName() + ".rewrite");
}
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
InputStream is = null;
// Process configuration file for this valve
if (getContainer() instanceof Context) {
context = true;
is = ((Context) getContainer()).getServletContext()
.getResourceAsStream("/WEB-INF/" + resourcePath);
if (containerLog.isDebugEnabled()) {
if (is == null) {
containerLog.debug("No configuration resource found: /WEB-INF/" + resourcePath);
} else {
containerLog.debug("Read configuration from: /WEB-INF/" + resourcePath);
}
}
} else if (getContainer() instanceof Host) {
String resourceName = getHostConfigPath(resourcePath);
File file = new File(getConfigBase(), resourceName);
try {
if (!file.exists()) {
// Use getResource and getResourceAsStream
is = getClass().getClassLoader()
.getResourceAsStream(resourceName);
if (is != null && containerLog.isDebugEnabled()) {
containerLog.debug("Read configuration from CL at " + resourceName);
}
} else {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Read configuration from " + file.getAbsolutePath());
}
is = new FileInputStream(file);
}
if ((is == null) && (containerLog.isDebugEnabled())) {
containerLog.debug("No configuration resource found: " + resourceName +
" in " + getConfigBase() + " or in the classloader");
}
} catch (Exception e) {
containerLog.error("Error opening configuration", e);
}
}
if (is == null) {
// Will use management operations to configure the valve dynamically
return;
}
try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr)) {
parse(reader);
} catch (IOException ioe) {
containerLog.error(sm.getString("rewriteValve.closeError"), ioe);
} finally {
try {
is.close();
} catch (IOException e) {
containerLog.error(sm.getString("rewriteValve.closeError"), e);
}
}
}
public void setConfiguration(String configuration)
throws Exception {
if (containerLog == null) {
containerLog = LogFactory.getLog(getContainer().getLogName() + ".rewrite");
}
maps.clear();
parse(new BufferedReader(new StringReader(configuration)));
}
public String getConfiguration() {
StringBuffer buffer = new StringBuffer();
for (String mapConfiguration : mapsConfiguration) {
buffer.append(mapConfiguration).append("\r\n");
}
if (mapsConfiguration.size() > 0) {
buffer.append("\r\n");
}
for (int i = 0; i < rules.length; i++) {
for (int j = 0; j < rules[i].getConditions().length; j++) {
buffer.append(rules[i].getConditions()[j].toString()).append("\r\n");
}
buffer.append(rules[i].toString()).append("\r\n").append("\r\n");
}
return buffer.toString();
}
protected void parse(BufferedReader reader) throws LifecycleException {
List<RewriteRule> rules = new ArrayList<>();
List<RewriteCond> conditions = new ArrayList<>();
while (true) {
try {
String line = reader.readLine();
if (line == null) {
break;
}
Object result = parse(line);
if (result instanceof RewriteRule) {
RewriteRule rule = (RewriteRule) result;
if (containerLog.isDebugEnabled()) {
containerLog.debug("Add rule with pattern " + rule.getPatternString()
+ " and substitution " + rule.getSubstitutionString());
}
for (int i = (conditions.size() - 1); i > 0; i--) {
if (conditions.get(i - 1).isOrnext()) {
conditions.get(i).setOrnext(true);
}
}
for (int i = 0; i < conditions.size(); i++) {
if (containerLog.isDebugEnabled()) {
RewriteCond cond = conditions.get(i);
containerLog.debug("Add condition " + cond.getCondPattern()
+ " test " + cond.getTestString() + " to rule with pattern "
+ rule.getPatternString() + " and substitution "
+ rule.getSubstitutionString() + (cond.isOrnext() ? " [OR]" : "")
+ (cond.isNocase() ? " [NC]" : ""));
}
rule.addCondition(conditions.get(i));
}
conditions.clear();
rules.add(rule);
} else if (result instanceof RewriteCond) {
conditions.add((RewriteCond) result);
} else if (result instanceof Object[]) {
String mapName = (String) ((Object[]) result)[0];
RewriteMap map = (RewriteMap) ((Object[]) result)[1];
maps.put(mapName, map);
// Keep the original configuration line as it is not possible to get
// the parameters back without an API change
mapsConfiguration.add(line);
if (map instanceof Lifecycle) {
((Lifecycle) map).start();
}
}
} catch (IOException e) {
containerLog.error(sm.getString("rewriteValve.readError"), e);
}
}
this.rules = rules.toArray(new RewriteRule[0]);
// Finish parsing the rules
for (int i = 0; i < this.rules.length; i++) {
this.rules[i].parse(maps);
}
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
super.stopInternal();
for (RewriteMap map : maps.values()) {
if (map instanceof Lifecycle) {
((Lifecycle) map).stop();
}
}
maps.clear();
rules = null;
}
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (!getEnabled() || rules == null || rules.length == 0) {
getNext().invoke(request, response);
return;
}
if (Boolean.TRUE.equals(invoked.get())) {
try {
getNext().invoke(request, response);
} finally {
invoked.set(null);
}
return;
}
try {
Resolver resolver = new ResolverImpl(request);
invoked.set(Boolean.TRUE);
// As long as MB isn't a char sequence or affiliated, this has to be
// converted to a string
Charset uriCharset = request.getConnector().getURICharset();
String originalQueryStringEncoded = request.getQueryString();
MessageBytes urlMB =
context ? request.getRequestPathMB() : request.getDecodedRequestURIMB();
urlMB.toChars();
CharSequence urlDecoded = urlMB.getCharChunk();
CharSequence host = request.getServerName();
boolean rewritten = false;
boolean done = false;
boolean qsa = false;
boolean qsd = false;
for (int i = 0; i < rules.length; i++) {
RewriteRule rule = rules[i];
CharSequence test = (rule.isHost()) ? host : urlDecoded;
CharSequence newtest = rule.evaluate(test, resolver);
if (newtest != null && !test.equals(newtest.toString())) {
if (containerLog.isDebugEnabled()) {
containerLog.debug("Rewrote " + test + " as " + newtest
+ " with rule pattern " + rule.getPatternString());
}
if (rule.isHost()) {
host = newtest;
} else {
urlDecoded = newtest;
}
rewritten = true;
}
// Check QSA before the final reply
if (!qsa && newtest != null && rule.isQsappend()) {
qsa = true;
}
if (!qsa && newtest != null && rule.isQsdiscard()) {
qsd = true;
}
// Final reply
// - forbidden
if (rule.isForbidden() && newtest != null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
done = true;
break;
}
// - gone
if (rule.isGone() && newtest != null) {
response.sendError(HttpServletResponse.SC_GONE);
done = true;
break;
}
// - redirect (code)
if (rule.isRedirect() && newtest != null) {
// Append the query string to the url if there is one and it
// hasn't been rewritten
String urlStringDecoded = urlDecoded.toString();
int index = urlStringDecoded.indexOf("?");
String rewrittenQueryStringDecoded;
if (index == -1) {
rewrittenQueryStringDecoded = null;
} else {
rewrittenQueryStringDecoded = urlStringDecoded.substring(index + 1);
urlStringDecoded = urlStringDecoded.substring(0, index);
}
StringBuffer urlStringEncoded =
new StringBuffer(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
if (!qsd && originalQueryStringEncoded != null
&& originalQueryStringEncoded.length() > 0) {
if (rewrittenQueryStringDecoded == null) {
urlStringEncoded.append('?');
urlStringEncoded.append(originalQueryStringEncoded);
} else {
if (qsa) {
// if qsa is specified append the query
urlStringEncoded.append('?');
urlStringEncoded.append(URLEncoder.QUERY.encode(
rewrittenQueryStringDecoded, uriCharset));
urlStringEncoded.append('&');
urlStringEncoded.append(originalQueryStringEncoded);
} else if (index == urlStringEncoded.length() - 1) {
// if the ? is the last character delete it, its only purpose was to
// prevent the rewrite module from appending the query string
urlStringEncoded.deleteCharAt(index);
} else {
urlStringEncoded.append('?');
urlStringEncoded.append(URLEncoder.QUERY.encode(
rewrittenQueryStringDecoded, uriCharset));
}
}
} else if (rewrittenQueryStringDecoded != null) {
urlStringEncoded.append('?');
urlStringEncoded.append(
URLEncoder.QUERY.encode(rewrittenQueryStringDecoded, uriCharset));
}
// Insert the context if
// 1. this valve is associated with a context
// 2. the url starts with a leading slash
// 3. the url isn't absolute
if (context && urlStringEncoded.charAt(0) == '/' &&
!UriUtil.hasScheme(urlStringEncoded)) {
urlStringEncoded.insert(0, request.getContext().getEncodedPath());
}
if (rule.isNoescape()) {
response.sendRedirect(
UDecoder.URLDecode(urlStringEncoded.toString(), uriCharset));
} else {
response.sendRedirect(urlStringEncoded.toString());
}
response.setStatus(rule.getRedirectCode());
done = true;
break;
}
// Reply modification
// - cookie
if (rule.isCookie() && newtest != null) {
Cookie cookie = new Cookie(rule.getCookieName(),
rule.getCookieResult());
cookie.setDomain(rule.getCookieDomain());
cookie.setMaxAge(rule.getCookieLifetime());
cookie.setPath(rule.getCookiePath());
cookie.setSecure(rule.isCookieSecure());
cookie.setHttpOnly(rule.isCookieHttpOnly());
response.addCookie(cookie);
}
// - env (note: this sets a request attribute)
if (rule.isEnv() && newtest != null) {
for (int j = 0; j < rule.getEnvSize(); j++) {
request.setAttribute(rule.getEnvName(j), rule.getEnvResult(j));
}
}
// - content type (note: this will not force the content type, use a filter
// to do that)
if (rule.isType() && newtest != null) {
request.setContentType(rule.getTypeValue());
}
// Control flow processing
// - chain (skip remaining chained rules if this one does not match)
if (rule.isChain() && newtest == null) {
for (int j = i; j < rules.length; j++) {
if (!rules[j].isChain()) {
i = j;
break;
}
}
continue;
}
// - last (stop rewriting here)
if (rule.isLast() && newtest != null) {
break;
}
// - next (redo again)
if (rule.isNext() && newtest != null) {
i = 0;
continue;
}
// - skip (n rules)
if (newtest != null) {
i += rule.getSkip();
}
}
if (rewritten) {
if (!done) {
// See if we need to replace the query string
String urlStringDecoded = urlDecoded.toString();
String queryStringDecoded = null;
int queryIndex = urlStringDecoded.indexOf('?');
if (queryIndex != -1) {
queryStringDecoded = urlStringDecoded.substring(queryIndex+1);
urlStringDecoded = urlStringDecoded.substring(0, queryIndex);
}
// Save the current context path before re-writing starts
String contextPath = null;
if (context) {
contextPath = request.getContextPath();
}
// Populated the encoded (i.e. undecoded) requestURI
request.getCoyoteRequest().requestURI().setString(null);
CharChunk chunk = request.getCoyoteRequest().requestURI().getCharChunk();
chunk.recycle();
if (context) {
// This is neither decoded nor normalized
chunk.append(contextPath);
}
chunk.append(URLEncoder.DEFAULT.encode(urlStringDecoded, uriCharset));
request.getCoyoteRequest().requestURI().toChars();
// Decoded and normalized URI
// Rewriting may have denormalized the URL
urlStringDecoded = RequestUtil.normalize(urlStringDecoded);
request.getCoyoteRequest().decodedURI().setString(null);
chunk = request.getCoyoteRequest().decodedURI().getCharChunk();
chunk.recycle();
if (context) {
// This is decoded and normalized
chunk.append(request.getServletContext().getContextPath());
}
chunk.append(urlStringDecoded);
request.getCoyoteRequest().decodedURI().toChars();
// Set the new Query if there is one
if (queryStringDecoded != null) {
request.getCoyoteRequest().queryString().setString(null);
chunk = request.getCoyoteRequest().queryString().getCharChunk();
chunk.recycle();
chunk.append(URLEncoder.QUERY.encode(queryStringDecoded, uriCharset));
if (qsa && originalQueryStringEncoded != null &&
originalQueryStringEncoded.length() > 0) {
chunk.append('&');
chunk.append(originalQueryStringEncoded);
}
if (!chunk.isNull()) {
request.getCoyoteRequest().queryString().toChars();
}
}
// Set the new host if it changed
if (!host.equals(request.getServerName())) {
request.getCoyoteRequest().serverName().setString(null);
chunk = request.getCoyoteRequest().serverName().getCharChunk();
chunk.recycle();
chunk.append(host.toString());
request.getCoyoteRequest().serverName().toChars();
}
request.getMappingData().recycle();
// Reinvoke the whole request recursively
Connector connector = request.getConnector();
try {
if (!connector.getProtocolHandler().getAdapter().prepare(
request.getCoyoteRequest(), response.getCoyoteResponse())) {
return;
}
} catch (Exception e) {
// This doesn't actually happen in the Catalina adapter implementation
}
Pipeline pipeline = connector.getService().getContainer().getPipeline();
request.setAsyncSupported(pipeline.isAsyncSupported());
pipeline.getFirst().invoke(request, response);
}
} else {
getNext().invoke(request, response);
}
} finally {
invoked.set(null);
}
}
/**
* @return config base.
*/
protected File getConfigBase() {
File configBase =
new File(System.getProperty("catalina.base"), "conf");
if (!configBase.exists()) {
return null;
} else {
return configBase;
}
}
/**
* Find the configuration path where the rewrite configuration file
* will be stored.
* @param resourceName The rewrite configuration file name
* @return the full rewrite configuration path
*/
protected String getHostConfigPath(String resourceName) {
StringBuffer result = new StringBuffer();
Container container = getContainer();
Container host = null;
Container engine = null;
while (container != null) {
if (container instanceof Host)
host = container;
if (container instanceof Engine)
engine = container;
container = container.getParent();
}
if (engine != null) {
result.append(engine.getName()).append('/');
}
if (host != null) {
result.append(host.getName()).append('/');
}
result.append(resourceName);
return result.toString();
}
/**
* This factory method will parse a line formed like:
*
* Example:
* RewriteCond %{REMOTE_HOST} ^host1.* [OR]
*
* @param line A line from the rewrite configuration
* @return The condition, rule or map resulting from parsing the line
*/
public static Object parse(String line) {
StringTokenizer tokenizer = new StringTokenizer(line);
if (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.equals("RewriteCond")) {
// RewriteCond TestString CondPattern [Flags]
RewriteCond condition = new RewriteCond();
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
condition.setTestString(tokenizer.nextToken());
condition.setCondPattern(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
String flags = tokenizer.nextToken();
condition.setFlagsString(flags);
if (flags.startsWith("[") && flags.endsWith("]")) {
flags = flags.substring(1, flags.length() - 1);
}
StringTokenizer flagsTokenizer = new StringTokenizer(flags, ",");
while (flagsTokenizer.hasMoreElements()) {
parseCondFlag(line, condition, flagsTokenizer.nextToken());
}
}
return condition;
} else if (token.equals("RewriteRule")) {
// RewriteRule Pattern Substitution [Flags]
RewriteRule rule = new RewriteRule();
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
rule.setPatternString(tokenizer.nextToken());
rule.setSubstitutionString(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
String flags = tokenizer.nextToken();
rule.setFlagsString(flags);
if (flags.startsWith("[") && flags.endsWith("]")) {
flags = flags.substring(1, flags.length() - 1);
}
StringTokenizer flagsTokenizer = new StringTokenizer(flags, ",");
while (flagsTokenizer.hasMoreElements()) {
parseRuleFlag(line, rule, flagsTokenizer.nextToken());
}
}
return rule;
} else if (token.equals("RewriteMap")) {
// RewriteMap name rewriteMapClassName whateverOptionalParameterInWhateverFormat
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
String name = tokenizer.nextToken();
String rewriteMapClassName = tokenizer.nextToken();
RewriteMap map = null;
try {
map = (RewriteMap) (Class.forName(
rewriteMapClassName).getConstructor().newInstance());
} catch (Exception e) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidMapClassName", line));
}
if (tokenizer.hasMoreTokens()) {
map.setParameters(tokenizer.nextToken());
}
Object[] result = new Object[2];
result[0] = name;
result[1] = map;
return result;
} else if (token.startsWith("#")) {
// it's a comment, ignore it
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidLine", line));
}
}
return null;
}
/**
* Parser for RewriteCond flags.
* @param line The configuration line being parsed
* @param condition The current condition
* @param flag The flag
*/
protected static void parseCondFlag(String line, RewriteCond condition, String flag) {
if (flag.equals("NC") || flag.equals("nocase")) {
condition.setNocase(true);
} else if (flag.equals("OR") || flag.equals("ornext")) {
condition.setOrnext(true);
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
}
/**
* Parser for RewriteRule flags.
* @param line The configuration line being parsed
* @param rule The current rule
* @param flag The flag
*/
protected static void parseRuleFlag(String line, RewriteRule rule, String flag) {
if (flag.equals("B")) {
rule.setEscapeBackReferences(true);
} else if (flag.equals("chain") || flag.equals("C")) {
rule.setChain(true);
} else if (flag.startsWith("cookie=") || flag.startsWith("CO=")) {
rule.setCookie(true);
if (flag.startsWith("cookie")) {
flag = flag.substring("cookie=".length());
} else if (flag.startsWith("CO=")) {
flag = flag.substring("CO=".length());
}
StringTokenizer tokenizer = new StringTokenizer(flag, ":");
if (tokenizer.countTokens() < 2) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
rule.setCookieName(tokenizer.nextToken());
rule.setCookieValue(tokenizer.nextToken());
if (tokenizer.hasMoreTokens()) {
rule.setCookieDomain(tokenizer.nextToken());
}
if (tokenizer.hasMoreTokens()) {
try {
rule.setCookieLifetime(Integer.parseInt(tokenizer.nextToken()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag), e);
}
}
if (tokenizer.hasMoreTokens()) {
rule.setCookiePath(tokenizer.nextToken());
}
if (tokenizer.hasMoreTokens()) {
rule.setCookieSecure(Boolean.parseBoolean(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
rule.setCookieHttpOnly(Boolean.parseBoolean(tokenizer.nextToken()));
}
} else if (flag.startsWith("env=") || flag.startsWith("E=")) {
rule.setEnv(true);
if (flag.startsWith("env=")) {
flag = flag.substring("env=".length());
} else if (flag.startsWith("E=")) {
flag = flag.substring("E=".length());
}
int pos = flag.indexOf(':');
if (pos == -1 || (pos + 1) == flag.length()) {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
rule.addEnvName(flag.substring(0, pos));
rule.addEnvValue(flag.substring(pos + 1));
} else if (flag.startsWith("forbidden") || flag.startsWith("F")) {
rule.setForbidden(true);
} else if (flag.startsWith("gone") || flag.startsWith("G")) {
rule.setGone(true);
} else if (flag.startsWith("host") || flag.startsWith("H")) {
rule.setHost(true);
} else if (flag.startsWith("last") || flag.startsWith("L")) {
rule.setLast(true);
} else if (flag.startsWith("nocase") || flag.startsWith("NC")) {
rule.setNocase(true);
} else if (flag.startsWith("noescape") || flag.startsWith("NE")) {
rule.setNoescape(true);
} else if (flag.startsWith("next") || flag.startsWith("N")) {
rule.setNext(true);
// Note: Proxy is not supported as Tomcat does not have proxy
// capabilities
} else if (flag.startsWith("qsappend") || flag.startsWith("QSA")) {
rule.setQsappend(true);
} else if (flag.startsWith("qsdiscard") || flag.startsWith("QSD")) {
rule.setQsdiscard(true);
} else if (flag.startsWith("redirect") || flag.startsWith("R")) {
rule.setRedirect(true);
int redirectCode = HttpServletResponse.SC_FOUND;
if (flag.startsWith("redirect=") || flag.startsWith("R=")) {
if (flag.startsWith("redirect=")) {
flag = flag.substring("redirect=".length());
} else if (flag.startsWith("R=")) {
flag = flag.substring("R=".length());
}
switch(flag) {
case "temp":
redirectCode = HttpServletResponse.SC_FOUND;
break;
case "permanent":
redirectCode = HttpServletResponse.SC_MOVED_PERMANENTLY;
break;
case "seeother":
redirectCode = HttpServletResponse.SC_SEE_OTHER;
break;
default:
redirectCode = Integer.parseInt(flag);
break;
}
}
rule.setRedirectCode(redirectCode);
} else if (flag.startsWith("skip") || flag.startsWith("S")) {
if (flag.startsWith("skip=")) {
flag = flag.substring("skip=".length());
} else if (flag.startsWith("S=")) {
flag = flag.substring("S=".length());
}
rule.setSkip(Integer.parseInt(flag));
} else if (flag.startsWith("type") || flag.startsWith("T")) {
if (flag.startsWith("type=")) {
flag = flag.substring("type=".length());
} else if (flag.startsWith("T=")) {
flag = flag.substring("T=".length());
}
rule.setType(true);
rule.setTypeValue(flag);
} else {
throw new IllegalArgumentException(sm.getString("rewriteValve.invalidFlags", line, flag));
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* 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.List;
import java.util.Map;
import java.util.regex.Matcher;
import org.apache.catalina.util.URLEncoder;
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;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return value;
}
}
public class RewriteRuleBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = rule.group(n);
if (result == null) {
result = "";
}
if (escapeBackReferences) {
// Note: This should be consistent with the way httpd behaves.
// We might want to consider providing a dedicated decoder
// with an option to add additional safe characters to
// provide users with more flexibility
return URLEncoder.DEFAULT.encode(result, resolver.getUriCharset());
} else {
return result;
}
}
}
public class RewriteCondBackReferenceElement extends SubstitutionElement {
public int n;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return (cond.group(n) == null ? "" : cond.group(n));
}
}
public class ServerVariableElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolve(key);
}
}
public class ServerVariableEnvElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveEnv(key);
}
}
public class ServerVariableSslElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveSsl(key);
}
}
public class ServerVariableHttpElement extends SubstitutionElement {
public String key;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return resolver.resolveHttp(key);
}
}
public class MapElement extends SubstitutionElement {
public RewriteMap map = null;
public SubstitutionElement[] defaultValue = null;
public SubstitutionElement[] key = null;
@Override
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String result = map.lookup(evaluateSubstitution(key, rule, cond, resolver));
if (result == null && defaultValue != null) {
result = evaluateSubstitution(defaultValue, rule, cond, resolver);
}
return result;
}
}
protected SubstitutionElement[] elements = null;
protected String sub = null;
public String getSub() { return sub; }
public void setSub(String sub) { this.sub = sub; }
private boolean escapeBackReferences;
void setEscapeBackReferences(boolean escapeBackReferences) {
this.escapeBackReferences = escapeBackReferences;
}
public void parse(Map<String, RewriteMap> maps) {
this.elements = parseSubtitution(sub, maps);
}
private SubstitutionElement[] parseSubtitution(String sub, Map<String, RewriteMap> maps) {
List<SubstitutionElement> elements = new ArrayList<>();
int pos = 0;
int percentPos = 0;
int dollarPos = 0;
int backslashPos = 0;
while (pos < sub.length()) {
percentPos = sub.indexOf('%', pos);
dollarPos = sub.indexOf('$', pos);
backslashPos = sub.indexOf('\\', pos);
if (percentPos == -1 && dollarPos == -1 && backslashPos == -1) {
// Static text
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, sub.length());
pos = sub.length();
elements.add(newElement);
} else if (isFirstPos(backslashPos, dollarPos, percentPos)) {
if (backslashPos + 1 == sub.length()) {
throw new IllegalArgumentException(sub);
}
StaticElement newElement = new StaticElement();
newElement.value = sub.substring(pos, backslashPos) + sub.substring(backslashPos + 1, backslashPos + 2);
pos = backslashPos + 2;
elements.add(newElement);
} else if (isFirstPos(dollarPos, percentPos)) {
// $: back reference 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))) {
// $: back reference to rule
RewriteRuleBackReferenceElement newElement = new RewriteRuleBackReferenceElement();
newElement.n = Character.digit(sub.charAt(dollarPos + 1), 10);
pos = dollarPos + 2;
elements.add(newElement);
} else if (sub.charAt(dollarPos + 1) == '{') {
// $: map lookup as ${mapname:key|default}
MapElement newElement = new MapElement();
int open = sub.indexOf('{', dollarPos);
int colon = findMatchingColonOrBar(true, sub, open);
int def = findMatchingColonOrBar(false, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < colon && colon < close)) {
throw new IllegalArgumentException(sub);
}
newElement.map = maps.get(sub.substring(open + 1, colon));
if (newElement.map == null) {
throw new IllegalArgumentException(sub + ": No map: " + sub.substring(open + 1, colon));
}
String key = null;
String defaultValue = null;
if (def > -1) {
if (!(colon < def && def < close)) {
throw new IllegalArgumentException(sub);
}
key = sub.substring(colon + 1, def);
defaultValue = sub.substring(def + 1, close);
} else {
key = sub.substring(colon + 1, close);
}
newElement.key = parseSubtitution(key, maps);
if (defaultValue != null) {
newElement.defaultValue = parseSubtitution(defaultValue, maps);
}
pos = close + 1;
elements.add(newElement);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
} else {
// %: back reference to condition 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))) {
// %: back reference to condition
RewriteCondBackReferenceElement newElement = new RewriteCondBackReferenceElement();
newElement.n = Character.digit(sub.charAt(percentPos + 1), 10);
pos = percentPos + 2;
elements.add(newElement);
} else if (sub.charAt(percentPos + 1) == '{') {
// %: server variable as %{variable}
SubstitutionElement newElement = null;
int open = sub.indexOf('{', percentPos);
int colon = findMatchingColonOrBar(true, sub, open);
int close = findMatchingBrace(sub, open);
if (!(-1 < open && open < close)) {
throw new IllegalArgumentException(sub);
}
if (colon > -1 && open < colon && colon < close) {
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();
((ServerVariableSslElement) newElement).key = sub.substring(colon + 1, close);
} else if (type.equals("HTTP")) {
newElement = new ServerVariableHttpElement();
((ServerVariableHttpElement) 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);
} else {
throw new IllegalArgumentException(sub + ": missing digit or curly brace.");
}
}
}
return elements.toArray(new SubstitutionElement[0]);
}
private static int findMatchingBrace(String sub, int start) {
int nesting = 1;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
if (nesting == 0) {
return i;
}
}
}
return -1;
}
private static int findMatchingColonOrBar(boolean colon, String sub, int start) {
int nesting = 0;
for (int i = start + 1; i < sub.length(); i++) {
char c = sub.charAt(i);
if (c == '{') {
char previousChar = sub.charAt(i-1);
if (previousChar == '$' || previousChar == '%') {
nesting++;
}
} else if (c == '}') {
nesting--;
} else if (colon ? c == ':' : c =='|') {
if (nesting == 0) {
return i;
}
}
}
return -1;
}
/**
* Evaluate the substitution based on the context.
* @param rule corresponding matched rule
* @param cond last matched condition
* @param resolver The property resolver
* @return The substitution result
*/
public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
return evaluateSubstitution(elements, rule, cond, resolver);
}
private String evaluateSubstitution(SubstitutionElement[] elements, 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();
}
/**
* Checks whether the first int is non negative and smaller than any non negative other int
* given with {@code others}.
*
* @param testPos
* integer to test against
* @param others
* list of integers that are paired against {@code testPos}. Any
* negative integer will be ignored.
* @return {@code true} if {@code testPos} is not negative and is less then any given other
* integer, {@code false} otherwise
*/
private boolean isFirstPos(int testPos, int... others) {
if (testPos < 0) {
return false;
}
for (int other : others) {
if (other >= 0 && other < testPos) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<mbeans-descriptors>
<mbean name="RewriteValve"
description="A URL rewrite valve"
domain="Catalina"
group="Valve"
type="org.apache.catalina.valves.rewrite.RewriteValve">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="configuration"
description="Rewrite configuration"
type="java.lang.String" />
</mbean>
</mbeans-descriptors>