Skip to content

Commit

Permalink
Add support for apache/tomcat style rewite rules
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas committed Oct 26, 2015
1 parent 1b48515 commit 5703c38
Show file tree
Hide file tree
Showing 11 changed files with 2,052 additions and 15 deletions.
33 changes: 18 additions & 15 deletions core/src/main/java/io/undertow/util/DateUtils.java
Expand Up @@ -232,23 +232,26 @@ public static boolean handleIfUnmodifiedSince(final String modifiedSince, final
public static void addDateHeaderIfRequired(HttpServerExchange exchange) {
HeaderMap responseHeaders = exchange.getResponseHeaders();
if (exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALWAYS_SET_DATE, true) && !responseHeaders.contains(Headers.DATE)) {
String dateString = cachedDateString.get();
if (dateString != null) {
responseHeaders.put(Headers.DATE, dateString);
} else {
//set the time and register a timer to invalidate it
//note that this is racey, it does not matter if multiple threads do this
//the perf cost of synchronizing would be more than the perf cost of multiple threads running it
long realTime = System.currentTimeMillis();
long mod = realTime % 1000;
long toGo = 1000 - mod;
dateString = DateUtils.toDateString(new Date(realTime));
if (cachedDateString.compareAndSet(null, dateString)) {
exchange.getConnection().getIoThread().executeAfter(INVALIDATE_TASK, toGo, TimeUnit.MILLISECONDS);
}
responseHeaders.put(Headers.DATE, dateString);
String dateString = getCurrentDateTime(exchange);
responseHeaders.put(Headers.DATE, dateString);
}
}

public static String getCurrentDateTime(HttpServerExchange exchange) {
String dateString = cachedDateString.get();
if (dateString == null) {
//set the time and register a timer to invalidate it
//note that this is racey, it does not matter if multiple threads do this
//the perf cost of synchronizing would be more than the perf cost of multiple threads running it
long realTime = System.currentTimeMillis();
long mod = realTime % 1000;
long toGo = 1000 - mod;
dateString = DateUtils.toDateString(new Date(realTime));
if (cachedDateString.compareAndSet(null, dateString)) {
exchange.getConnection().getIoThread().executeAfter(INVALIDATE_TASK, toGo, TimeUnit.MILLISECONDS);
}
}
return dateString;
}

private DateUtils() {
Expand Down
Expand Up @@ -96,4 +96,23 @@ public interface UndertowServletLogger extends BasicLogger {
@Message(id = 15012, value = "Failed to generate error page %s for original exception: %s. Generating error page resulted in a %s.")
void errorGeneratingErrorPage(String originalErrorPage, Object originalException, int code, @Cause Throwable cause);

@Message(id = 15013, value = "Error opening rewrite configuration")
String errorOpeningRewriteConfiguration();

@Message(id = 15014, value = "Error reading rewrite configuration")
@LogMessage(level = Logger.Level.ERROR)
void errorReadingRewriteConfiguration(@Cause IOException e);

@Message(id = 15015, value = "Error reading rewrite configuration: %s")
IllegalArgumentException invalidRewriteConfiguration(String line);

@Message(id = 15016, value = "Invalid rewrite map class: %s")
IllegalArgumentException invalidRewriteMap(String className);

@Message(id = 15017, value = "Error reading rewrite flags in line %s as %s")
IllegalArgumentException invalidRewriteFlags(String line, String flags);

@Message(id = 15018, value = "Error reading rewrite flags in line %s")
IllegalArgumentException invalidRewriteFlags(String line);

}
@@ -0,0 +1,40 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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 io.undertow.servlet.compat.rewrite;

/**
* Resolver abstract class.
*
* @author Remy Maucherat
*/
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);

}
@@ -0,0 +1,275 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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 io.undertow.servlet.compat.rewrite;

import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author Remy Maucherat
*/
public class RewriteCond {

public abstract class Condition {
public abstract boolean evaluate(String value, Resolver resolver);
}

public class PatternCondition extends Condition {
public Pattern pattern;
public Matcher matcher = null;

public boolean evaluate(String value, Resolver resolver) {
Matcher m = pattern.matcher(value);
if (m.matches()) {
matcher = m;
return true;
} else {
return false;
}
}
}

public class LexicalCondition extends Condition {
/**
* -1: <
* 0: =
* 1: >
*/
public int type = 0;
public String condition;

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 class ResourceCondition extends Condition {
/**
* 0: -d (is directory ?)
* 1: -f (is regular file ?)
* 2: -s (is regular file with size ?)
*/
public int type = 0;

public boolean evaluate(String value, Resolver resolver) {
switch (type) {
case 0:
return true;
case 1:
return true;
case 2:
return true;
default:
return false;
}

}
}

protected String testString = null;
protected String condPattern = 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 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 condition = new LexicalCondition();
condition.type = -1;
condition.condition = condPattern.substring(1);
} else if (condPattern.startsWith(">")) {
LexicalCondition condition = new LexicalCondition();
condition.type = 1;
condition.condition = condPattern.substring(1);
} else if (condPattern.startsWith("=")) {
LexicalCondition condition = new LexicalCondition();
condition.type = 0;
condition.condition = condPattern.substring(1);
} else if (condPattern.equals("-d")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 0;
} else if (condPattern.equals("-f")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 1;
} else if (condPattern.equals("-s")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 2;
} else {
PatternCondition condition = new PatternCondition();
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
condition.pattern = Pattern.compile(condPattern, flags);
}
}

public Matcher getMatcher() {
Object condition = this.condition.get();
if (condition instanceof PatternCondition) {
return ((PatternCondition) condition).matcher;
}
return null;
}

/**
* String representation.
*/
public String toString() {
// FIXME: Add flags if possible
return "RewriteCond " + testString + " " + condPattern;
}


protected boolean positive = true;

protected Substitution test = null;

protected ThreadLocal<Condition> condition = new ThreadLocal<Condition>();

/**
* 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
* @return
*/
public boolean evaluate(Matcher rule, Matcher cond, Resolver resolver) {
String value = test.evaluate(rule, cond, resolver);
if (nocase) {
value = value.toLowerCase(Locale.ENGLISH);
}
Condition condition = this.condition.get();
if (condition == null) {
if (condPattern.startsWith("<")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = -1;
ncondition.condition = condPattern.substring(1);
condition = ncondition;
} else if (condPattern.startsWith(">")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 1;
ncondition.condition = condPattern.substring(1);
condition = ncondition;
} else if (condPattern.startsWith("=")) {
LexicalCondition ncondition = new LexicalCondition();
ncondition.type = 0;
ncondition.condition = condPattern.substring(1);
condition = ncondition;
} else if (condPattern.equals("-d")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 0;
condition = ncondition;
} else if (condPattern.equals("-f")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 1;
condition = ncondition;
} else if (condPattern.equals("-s")) {
ResourceCondition ncondition = new ResourceCondition();
ncondition.type = 2;
condition = ncondition;
} else {
PatternCondition ncondition = new PatternCondition();
int flags = 0;
if (isNocase()) {
flags |= Pattern.CASE_INSENSITIVE;
}
ncondition.pattern = Pattern.compile(condPattern, flags);
condition = ncondition;
}
this.condition.set(condition);
}
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;
}

}

0 comments on commit 5703c38

Please sign in to comment.