Skip to content

Commit

Permalink
Move the functionality that provides redirects for context roots and …
Browse files Browse the repository at this point in the history
…directories where a trailing <code>/</code> is added from the Mapper to the DefaultServlet. This enables such requests to be processed by any configured Valves and Filters before the redirect is made. This behaviour is configurable via the mapperContextRootRedirectEnabled and mapperDirectoryRedirectEnabled attributes of the Context which may be used to restore the previous behaviour.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc7.0.x/trunk@1715213 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Nov 19, 2015
1 parent 74b3895 commit a273b5f
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 10 deletions.
40 changes: 40 additions & 0 deletions java/org/apache/catalina/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -1642,4 +1642,44 @@ Set<String> addServletSecurity(ApplicationServletRegistration registration,
* false}
*/
public boolean getValidateClientProvidedNewSessionId();

/**
* If enabled, requests for a web application context root will be
* redirected (adding a trailing slash) by the Mapper. This is more
* efficient but has the side effect of confirming that the context path is
* valid.
*
* @param mapperContextRootRedirectEnabled Should the redirects be enabled?
*/
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled);

/**
* Determines if requests for a web application context root will be
* redirected (adding a trailing slash) by the Mapper. This is more
* efficient but has the side effect of confirming that the context path is
* valid.
*
* @return {@code true} if the Mapper level redirect is enabled for this
* Context.
*/
public boolean getMapperContextRootRedirectEnabled();

/**
* If enabled, requests for a directory will be redirected (adding a
* trailing slash) by the Mapper. This is more efficient but has the
* side effect of confirming that the directory is valid.
*
* @param mapperDirectoryRedirectEnabled Should the redirects be enabled?
*/
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled);

/**
* Determines if requests for a directory will be redirected (adding a
* trailing slash) by the Mapper. This is more efficient but has the
* side effect of confirming that the directory is valid.
*
* @return {@code true} if the Mapper level redirect is enabled for this
* Context.
*/
public boolean getMapperDirectoryRedirectEnabled();
}
44 changes: 41 additions & 3 deletions java/org/apache/catalina/core/StandardContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -911,15 +911,53 @@ public StandardContext() {

private boolean validateClientProvidedNewSessionId = true;


boolean mapperContextRootRedirectEnabled = false;

boolean mapperDirectoryRedirectEnabled = false;


// ----------------------------------------------------- Context Properties

@Override
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
this.mapperContextRootRedirectEnabled = mapperContextRootRedirectEnabled;
}


/**
* {@inheritDoc}
* <p>
* The default value for this implementation is {@code false}.
*/
@Override
public boolean getMapperContextRootRedirectEnabled() {
return mapperContextRootRedirectEnabled;
}


@Override
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
this.mapperDirectoryRedirectEnabled = mapperDirectoryRedirectEnabled;
}


/**
* {@inheritDoc}
* <p>
* The default value for this implementation is {@code false}.
*/
@Override
public boolean getMapperDirectoryRedirectEnabled() {
return mapperDirectoryRedirectEnabled;
}


@Override
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
this.validateClientProvidedNewSessionId = validateClientProvidedNewSessionId;
}


/**
* {@inheritDoc}
* <p>
Expand All @@ -930,7 +968,7 @@ public boolean getValidateClientProvidedNewSessionId() {
return validateClientProvidedNewSessionId;
}


@Override
public void setContainerSciFilter(String containerSciFilter) {
this.containerSciFilter = containerSciFilter;
Expand Down
8 changes: 8 additions & 0 deletions java/org/apache/catalina/core/mbeans-descriptors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@
description="The object used for mapping"
type="java.lang.Object"/>

<attribute name="mapperContextRootRedirectEnabled"
description="Should the Mapper be used for context root redirects"
type="boolean" />

<attribute name="mapperDirectoryRedirectEnabled"
description="Should the Mapper be used for directory redirects"
type="boolean" />

<attribute name="namingContextListener"
description="Associated naming context listener."
type="org.apache.catalina.core.NamingContextListener" />
Expand Down
11 changes: 11 additions & 0 deletions java/org/apache/catalina/servlets/DefaultServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,17 @@ protected void serveResource(HttpServletRequest request,

if (cacheEntry.context != null) {

if (!path.endsWith("/")) {
StringBuilder location = new StringBuilder(request.getRequestURI());
location.append('/');
if (request.getQueryString() != null) {
location.append('?');
location.append(request.getQueryString());
}
response.sendRedirect(response.encodeRedirectURL(location.toString()));
return;
}

// Skip directory listings if we have been configured to
// suppress them
if (!listings) {
Expand Down
18 changes: 17 additions & 1 deletion java/org/apache/catalina/startup/FailedContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,25 @@ public void setContainerSciFilter(String containerSciFilter) { /* NO-OP */ }

@Override
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
//NO-OP
// NO-OP
}

@Override
public boolean getValidateClientProvidedNewSessionId() { return false; }

@Override
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
// NO-OP
}

@Override
public boolean getMapperContextRootRedirectEnabled() { return false; }

@Override
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
// NO-OP
}

@Override
public boolean getMapperDirectoryRedirectEnabled() { return false; }
}
42 changes: 38 additions & 4 deletions java/org/apache/tomcat/util/http/mapper/Mapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ public void setContext(String path, String[] welcomeResources,
* @param context Context object
* @param welcomeResources Welcome files defined for this context
* @param resources Static resources of the context
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[], javax.naming.Context, Collection)}
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[],
* javax.naming.Context, Collection, boolean, boolean)}
*/
@Deprecated
public void addContextVersion(String hostName, Object host, String path,
Expand All @@ -258,6 +259,7 @@ public void addContextVersion(String hostName, Object host, String path,
welcomeResources, resources, null);
}


/**
* Add a new Context to an existing Host.
*
Expand All @@ -269,10 +271,36 @@ public void addContextVersion(String hostName, Object host, String path,
* @param welcomeResources Welcome files defined for this context
* @param resources Static resources of the context
* @param wrappers Information on wrapper mappings
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[],
* javax.naming.Context, Collection, boolean, boolean)}
*/
@Deprecated
public void addContextVersion(String hostName, Object host, String path,
String version, Object context, String[] welcomeResources,
javax.naming.Context resources, Collection<WrapperMappingInfo> wrappers) {
addContextVersion(hostName, host, path, version, context, welcomeResources, resources,
wrappers, false, false);
}


/**
* Add a new Context to an existing Host.
*
* @param hostName Virtual host name this context belongs to
* @param host Host object
* @param path Context path
* @param version Context version
* @param context Context object
* @param welcomeResources Welcome files defined for this context
* @param resources Static resources of the context
* @param wrappers Information on wrapper mappings
* @param mapperContextRootRedirectEnabled Mapper does context root redirects
* @param mapperDirectoryRedirectEnabled Mapper does directory redirects
*/
public void addContextVersion(String hostName, Object host, String path,
String version, Object context, String[] welcomeResources,
javax.naming.Context resources, Collection<WrapperMappingInfo> wrappers,
boolean mapperContextRootRedirectEnabled, boolean mapperDirectoryRedirectEnabled) {

Host mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
Expand All @@ -294,6 +322,9 @@ public void addContextVersion(String hostName, Object host, String path,
newContextVersion.slashCount = slashCount;
newContextVersion.welcomeResources = welcomeResources;
newContextVersion.resources = resources;
newContextVersion.mapperContextRootRedirectEnabled = mapperContextRootRedirectEnabled;
newContextVersion.mapperDirectoryRedirectEnabled = mapperDirectoryRedirectEnabled;

if (wrappers != null) {
addWrappers(newContextVersion, wrappers);
}
Expand Down Expand Up @@ -904,7 +935,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
}
}

if(mappingData.wrapper == null && noServletPath) {
if(mappingData.wrapper == null && noServletPath &&
contextVersion.mapperContextRootRedirectEnabled) {
// The path is empty, redirect to "/"
mappingData.redirectPath.setChars
(path.getBuffer(), pathOffset, pathEnd-pathOffset);
Expand Down Expand Up @@ -1032,7 +1064,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
} catch(NamingException nex) {
// Swallow, since someone else handles the 404
}
if (file != null && file instanceof DirContext) {
if (file != null && file instanceof DirContext &&
contextVersion.mapperDirectoryRedirectEnabled) {
// Note: this mutates the path: do not do any processing
// after this (since we set the redirectPath, there
// shouldn't be any)
Expand All @@ -1049,7 +1082,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,

path.setOffset(pathOffset);
path.setEnd(pathEnd);

}


Expand Down Expand Up @@ -1684,6 +1716,8 @@ protected static final class ContextVersion extends MapElement {
public Wrapper[] wildcardWrappers = new Wrapper[0];
public Wrapper[] extensionWrappers = new Wrapper[0];
public int nesting = 0;
public boolean mapperContextRootRedirectEnabled = false;
public boolean mapperDirectoryRedirectEnabled = false;
private volatile boolean paused;

public ContextVersion() {
Expand Down
16 changes: 16 additions & 0 deletions test/org/apache/catalina/core/TesterContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -1227,4 +1227,20 @@ public void setValidateClientProvidedNewSessionId(boolean validateClientProvided

@Override
public boolean getValidateClientProvidedNewSessionId() { return false; }

@Override
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
// NO-OP
}

@Override
public boolean getMapperContextRootRedirectEnabled() { return false; }

@Override
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
// NO-OP
}

@Override
public boolean getMapperDirectoryRedirectEnabled() { return false; }
}
3 changes: 1 addition & 2 deletions test/org/apache/catalina/startup/TomcatBaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,7 @@ public static int methodUrl(String path, ByteChunk out, int readTimeout,
String method) throws IOException {

URL url = new URL(path);
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setUseCaches(false);
connection.setReadTimeout(readTimeout);
connection.setRequestMethod(method);
Expand Down
64 changes: 64 additions & 0 deletions test/org/apache/tomcat/util/http/mapper/TestMapperWebapps.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;

Expand All @@ -31,8 +32,11 @@

import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.catalina.valves.RemoteAddrValve;
import org.apache.tomcat.util.buf.ByteChunk;

/**
Expand Down Expand Up @@ -223,6 +227,66 @@ public void testWelcomeFileStrict() throws Exception {
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc);
}

@Test
public void testRedirect() throws Exception {
// Disable the following of redirects for this test only
boolean originalValue = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
try {
Tomcat tomcat = getTomcatInstance();

// Use standard test webapp as ROOT
File rootDir = new File("test/webapp-3.0");
org.apache.catalina.Context root =
tomcat.addWebapp(null, "", rootDir.getAbsolutePath());

// Add a security constraint
SecurityConstraint constraint = new SecurityConstraint();
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/welcome-files/*");
collection.addPattern("/welcome-files");
constraint.addCollection(collection);
constraint.addAuthRole("foo");
root.addConstraint(constraint);

// Also make examples available
File examplesDir = new File(getBuildDirectory(), "webapps/examples");
org.apache.catalina.Context examples = tomcat.addWebapp(
null, "/examples", examplesDir.getAbsolutePath());
// Then block access to the examples to test redirection
RemoteAddrValve rav = new RemoteAddrValve();
rav.setDeny(".*");
rav.setDenyStatus(404);
examples.getPipeline().addValve(rav);

tomcat.start();

// Redirects within a web application
doRedirectTest("/welcome-files", 401);
doRedirectTest("/welcome-files/", 401);

doRedirectTest("/jsp", 302);
doRedirectTest("/jsp/", 404);

doRedirectTest("/WEB-INF", 404);
doRedirectTest("/WEB-INF/", 404);

// Redirects between web applications
doRedirectTest("/examples", 404);
doRedirectTest("/examples/", 404);
} finally {
HttpURLConnection.setFollowRedirects(originalValue);
}
}


private void doRedirectTest(String path, int expected) throws IOException {
ByteChunk bc = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort() + path, bc, null);
Assert.assertEquals(expected, rc);
}


/**
* Prepare a string to search in messages that contain a timestamp, when it
* is known that the timestamp was printed between {@code timeA} and
Expand Down
Loading

0 comments on commit a273b5f

Please sign in to comment.