in java/org/apache/catalina/valves/rewrite/RewriteValve.java [296:602]
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();
/*
* The URL presented to the rewrite valve is the URL that is used for request mapping. That URL has been
* processed to: remove path parameters; remove the query string; decode; and normalize the URL. It may
* contain literal '%', '?' and/or ';' characters at this point.
*
* The re-write rules need to be able to process URLs with literal '?' characters and add query strings
* without the two becoming confused. The re-write rules also need to be able to insert literal '%'
* characters without them being confused with %nn encoding.
*
* To meet these requirement, the URL is processed as follows.
*
* Step 1. The URL is partially re-encoded by encodeForRewrite(). This method encodes any literal '%', ';'
* and/or '?' characters in the URL using the standard %nn form.
*
* Step 2. The re-write processing runs with the provided re-write rules against the partially encoded URL.
* If a re-write rule needs to insert a literal '%', ';' or '?', it must do so in %nn encoded form.
*
* Step 3. The URL (and query string if present) is re-encoded using the re-write specific encoders
* (REWRITE_DEFAULT_ENCODER and REWRITE_QUERY_ENCODER) that behave the same was as the standard encoders
* apart from '%' being treated as a safe character. This prevents double encoding of any '%' characters
* present in the URL from steps 1 or 2.
*/
// Step 1. Encode URL for processing by the re-write rules.
CharSequence urlRewriteEncoded = encodeForRewrite(urlDecoded);
CharSequence host = request.getServerName();
boolean rewritten = false;
boolean done = false;
boolean qsa = false;
boolean qsd = false;
boolean valveSkip = false;
// Step 2. Process the URL using the re-write rules.
for (int i = 0; i < rules.length; i++) {
RewriteRule rule = rules[i];
CharSequence test = (rule.isHost()) ? host : urlRewriteEncoded;
CharSequence newtest = rule.evaluate(test, resolver);
if (newtest != null && !Objects.equals(test.toString(), newtest.toString())) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(
"Rewrote " + test + " as " + newtest + " with rule pattern " + rule.getPatternString());
}
if (rule.isHost()) {
host = newtest;
} else {
urlRewriteEncoded = newtest;
}
rewritten = true;
}
// Check QSA before the final reply
if (!qsa && newtest != null && rule.isQsappend()) {
qsa = true;
}
if (!qsd && newtest != null && rule.isQsdiscard()) {
qsd = true;
}
if (!valveSkip && newtest != null && rule.isValveSkip()) {
valveSkip = 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 urlStringRewriteEncoded = urlRewriteEncoded.toString();
int index = urlStringRewriteEncoded.indexOf('?');
String rewrittenQueryStringRewriteEncoded;
if (index == -1) {
rewrittenQueryStringRewriteEncoded = null;
} else {
rewrittenQueryStringRewriteEncoded = urlStringRewriteEncoded.substring(index + 1);
urlStringRewriteEncoded = urlStringRewriteEncoded.substring(0, index);
}
// Step 3. Complete the 2nd stage to encoding.
StringBuilder urlStringEncoded =
new StringBuilder(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
if (!qsd && originalQueryStringEncoded != null && !originalQueryStringEncoded.isEmpty()) {
if (rewrittenQueryStringRewriteEncoded == null) {
urlStringEncoded.append('?');
urlStringEncoded.append(originalQueryStringEncoded);
} else {
if (qsa) {
// if qsa is specified append the query
urlStringEncoded.append('?');
urlStringEncoded.append(
REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, 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(
REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, uriCharset));
}
}
} else if (rewrittenQueryStringRewriteEncoded != null) {
urlStringEncoded.append('?');
urlStringEncoded
.append(REWRITE_QUERY_ENCODER.encode(rewrittenQueryStringRewriteEncoded, 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) {
response.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 urlStringRewriteEncoded = urlRewriteEncoded.toString();
String queryStringRewriteEncoded = null;
int queryIndex = urlStringRewriteEncoded.indexOf('?');
if (queryIndex != -1) {
queryStringRewriteEncoded = urlStringRewriteEncoded.substring(queryIndex + 1);
urlStringRewriteEncoded = urlStringRewriteEncoded.substring(0, queryIndex);
}
// Parse path parameters from rewrite production and populate request path parameters
urlStringRewriteEncoded = org.apache.catalina.util.RequestUtil.stripPathParams(urlStringRewriteEncoded, request);
// 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().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
CharChunk chunk = request.getCoyoteRequest().requestURI().getCharChunk();
if (context) {
// This is neither decoded nor normalized
chunk.append(contextPath);
}
// Step 3. Complete the 2nd stage to encoding.
chunk.append(REWRITE_DEFAULT_ENCODER.encode(urlStringRewriteEncoded, uriCharset));
// Decoded and normalized URI
// Rewriting may have denormalized the URL
urlStringRewriteEncoded = RequestUtil.normalize(urlStringRewriteEncoded);
request.getCoyoteRequest().decodedURI().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
chunk = request.getCoyoteRequest().decodedURI().getCharChunk();
if (context) {
// This is decoded and normalized
chunk.append(request.getServletContext().getContextPath());
}
chunk.append(URLDecoder.decode(urlStringRewriteEncoded, uriCharset));
// Set the new Query if there is one
if (queryStringRewriteEncoded != null) {
request.getCoyoteRequest().queryString().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
chunk = request.getCoyoteRequest().queryString().getCharChunk();
chunk.append(REWRITE_QUERY_ENCODER.encode(queryStringRewriteEncoded, uriCharset));
if (qsa && originalQueryStringEncoded != null && !originalQueryStringEncoded.isEmpty()) {
chunk.append('&');
chunk.append(originalQueryStringEncoded);
}
}
// Set the new host if it changed
if (!host.equals(request.getServerName())) {
request.getCoyoteRequest().serverName().setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
chunk = request.getCoyoteRequest().serverName().getCharChunk();
chunk.append(host.toString());
}
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 {
Valve next = getNext();
if (valveSkip) {
next = next.getNext();
if (next == null) {
// Ignore and invoke the next valve normally
next = getNext();
}
}
next.invoke(request, response);
}
} finally {
invoked.set(null);
}
}