init
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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=構成の読み取りエラー
|
||||
@@ -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=설정을 읽는 도중 오류 발생
|
||||
@@ -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=读取配置时发生异常
|
||||
48
java/org/apache/catalina/valves/rewrite/Resolver.java
Normal file
48
java/org/apache/catalina/valves/rewrite/Resolver.java
Normal 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();
|
||||
}
|
||||
191
java/org/apache/catalina/valves/rewrite/ResolverImpl.java
Normal file
191
java/org/apache/catalina/valves/rewrite/ResolverImpl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
239
java/org/apache/catalina/valves/rewrite/RewriteCond.java
Normal file
239
java/org/apache/catalina/valves/rewrite/RewriteCond.java
Normal 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: <
|
||||
* 0: =
|
||||
* 1: >
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
24
java/org/apache/catalina/valves/rewrite/RewriteMap.java
Normal file
24
java/org/apache/catalina/valves/rewrite/RewriteMap.java
Normal 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);
|
||||
}
|
||||
563
java/org/apache/catalina/valves/rewrite/RewriteRule.java
Normal file
563
java/org/apache/catalina/valves/rewrite/RewriteRule.java
Normal 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;
|
||||
}
|
||||
}
|
||||
845
java/org/apache/catalina/valves/rewrite/RewriteValve.java
Normal file
845
java/org/apache/catalina/valves/rewrite/RewriteValve.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
347
java/org/apache/catalina/valves/rewrite/Substitution.java
Normal file
347
java/org/apache/catalina/valves/rewrite/Substitution.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user