Skip to content

Commit a273b5f

Browse files
committed
Move the functionality that provides redirects for context roots and 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
1 parent 74b3895 commit a273b5f

File tree

11 files changed

+262
-10
lines changed

11 files changed

+262
-10
lines changed

java/org/apache/catalina/Context.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,4 +1642,44 @@ Set<String> addServletSecurity(ApplicationServletRegistration registration,
16421642
* false}
16431643
*/
16441644
public boolean getValidateClientProvidedNewSessionId();
1645+
1646+
/**
1647+
* If enabled, requests for a web application context root will be
1648+
* redirected (adding a trailing slash) by the Mapper. This is more
1649+
* efficient but has the side effect of confirming that the context path is
1650+
* valid.
1651+
*
1652+
* @param mapperContextRootRedirectEnabled Should the redirects be enabled?
1653+
*/
1654+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled);
1655+
1656+
/**
1657+
* Determines if requests for a web application context root will be
1658+
* redirected (adding a trailing slash) by the Mapper. This is more
1659+
* efficient but has the side effect of confirming that the context path is
1660+
* valid.
1661+
*
1662+
* @return {@code true} if the Mapper level redirect is enabled for this
1663+
* Context.
1664+
*/
1665+
public boolean getMapperContextRootRedirectEnabled();
1666+
1667+
/**
1668+
* If enabled, requests for a directory will be redirected (adding a
1669+
* trailing slash) by the Mapper. This is more efficient but has the
1670+
* side effect of confirming that the directory is valid.
1671+
*
1672+
* @param mapperDirectoryRedirectEnabled Should the redirects be enabled?
1673+
*/
1674+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled);
1675+
1676+
/**
1677+
* Determines if requests for a directory will be redirected (adding a
1678+
* trailing slash) by the Mapper. This is more efficient but has the
1679+
* side effect of confirming that the directory is valid.
1680+
*
1681+
* @return {@code true} if the Mapper level redirect is enabled for this
1682+
* Context.
1683+
*/
1684+
public boolean getMapperDirectoryRedirectEnabled();
16451685
}

java/org/apache/catalina/core/StandardContext.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,15 +911,53 @@ public StandardContext() {
911911

912912
private boolean validateClientProvidedNewSessionId = true;
913913

914-
914+
boolean mapperContextRootRedirectEnabled = false;
915+
916+
boolean mapperDirectoryRedirectEnabled = false;
917+
918+
915919
// ----------------------------------------------------- Context Properties
916920

921+
@Override
922+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
923+
this.mapperContextRootRedirectEnabled = mapperContextRootRedirectEnabled;
924+
}
925+
926+
927+
/**
928+
* {@inheritDoc}
929+
* <p>
930+
* The default value for this implementation is {@code false}.
931+
*/
932+
@Override
933+
public boolean getMapperContextRootRedirectEnabled() {
934+
return mapperContextRootRedirectEnabled;
935+
}
936+
937+
938+
@Override
939+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
940+
this.mapperDirectoryRedirectEnabled = mapperDirectoryRedirectEnabled;
941+
}
942+
943+
944+
/**
945+
* {@inheritDoc}
946+
* <p>
947+
* The default value for this implementation is {@code false}.
948+
*/
949+
@Override
950+
public boolean getMapperDirectoryRedirectEnabled() {
951+
return mapperDirectoryRedirectEnabled;
952+
}
953+
954+
917955
@Override
918956
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
919957
this.validateClientProvidedNewSessionId = validateClientProvidedNewSessionId;
920958
}
921959

922-
960+
923961
/**
924962
* {@inheritDoc}
925963
* <p>
@@ -930,7 +968,7 @@ public boolean getValidateClientProvidedNewSessionId() {
930968
return validateClientProvidedNewSessionId;
931969
}
932970

933-
971+
934972
@Override
935973
public void setContainerSciFilter(String containerSciFilter) {
936974
this.containerSciFilter = containerSciFilter;

java/org/apache/catalina/core/mbeans-descriptors.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@
221221
description="The object used for mapping"
222222
type="java.lang.Object"/>
223223

224+
<attribute name="mapperContextRootRedirectEnabled"
225+
description="Should the Mapper be used for context root redirects"
226+
type="boolean" />
227+
228+
<attribute name="mapperDirectoryRedirectEnabled"
229+
description="Should the Mapper be used for directory redirects"
230+
type="boolean" />
231+
224232
<attribute name="namingContextListener"
225233
description="Associated naming context listener."
226234
type="org.apache.catalina.core.NamingContextListener" />

java/org/apache/catalina/servlets/DefaultServlet.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,17 @@ protected void serveResource(HttpServletRequest request,
862862

863863
if (cacheEntry.context != null) {
864864

865+
if (!path.endsWith("/")) {
866+
StringBuilder location = new StringBuilder(request.getRequestURI());
867+
location.append('/');
868+
if (request.getQueryString() != null) {
869+
location.append('?');
870+
location.append(request.getQueryString());
871+
}
872+
response.sendRedirect(response.encodeRedirectURL(location.toString()));
873+
return;
874+
}
875+
865876
// Skip directory listings if we have been configured to
866877
// suppress them
867878
if (!listings) {

java/org/apache/catalina/startup/FailedContext.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,9 +706,25 @@ public void setContainerSciFilter(String containerSciFilter) { /* NO-OP */ }
706706

707707
@Override
708708
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
709-
//NO-OP
709+
// NO-OP
710710
}
711711

712712
@Override
713713
public boolean getValidateClientProvidedNewSessionId() { return false; }
714+
715+
@Override
716+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
717+
// NO-OP
718+
}
719+
720+
@Override
721+
public boolean getMapperContextRootRedirectEnabled() { return false; }
722+
723+
@Override
724+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
725+
// NO-OP
726+
}
727+
728+
@Override
729+
public boolean getMapperDirectoryRedirectEnabled() { return false; }
714730
}

java/org/apache/tomcat/util/http/mapper/Mapper.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ public void setContext(String path, String[] welcomeResources,
248248
* @param context Context object
249249
* @param welcomeResources Welcome files defined for this context
250250
* @param resources Static resources of the context
251-
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[], javax.naming.Context, Collection)}
251+
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[],
252+
* javax.naming.Context, Collection, boolean, boolean)}
252253
*/
253254
@Deprecated
254255
public void addContextVersion(String hostName, Object host, String path,
@@ -258,6 +259,7 @@ public void addContextVersion(String hostName, Object host, String path,
258259
welcomeResources, resources, null);
259260
}
260261

262+
261263
/**
262264
* Add a new Context to an existing Host.
263265
*
@@ -269,10 +271,36 @@ public void addContextVersion(String hostName, Object host, String path,
269271
* @param welcomeResources Welcome files defined for this context
270272
* @param resources Static resources of the context
271273
* @param wrappers Information on wrapper mappings
274+
* @deprecated Use {@link #addContextVersion(String, Object, String, String, Object, String[],
275+
* javax.naming.Context, Collection, boolean, boolean)}
272276
*/
277+
@Deprecated
273278
public void addContextVersion(String hostName, Object host, String path,
274279
String version, Object context, String[] welcomeResources,
275280
javax.naming.Context resources, Collection<WrapperMappingInfo> wrappers) {
281+
addContextVersion(hostName, host, path, version, context, welcomeResources, resources,
282+
wrappers, false, false);
283+
}
284+
285+
286+
/**
287+
* Add a new Context to an existing Host.
288+
*
289+
* @param hostName Virtual host name this context belongs to
290+
* @param host Host object
291+
* @param path Context path
292+
* @param version Context version
293+
* @param context Context object
294+
* @param welcomeResources Welcome files defined for this context
295+
* @param resources Static resources of the context
296+
* @param wrappers Information on wrapper mappings
297+
* @param mapperContextRootRedirectEnabled Mapper does context root redirects
298+
* @param mapperDirectoryRedirectEnabled Mapper does directory redirects
299+
*/
300+
public void addContextVersion(String hostName, Object host, String path,
301+
String version, Object context, String[] welcomeResources,
302+
javax.naming.Context resources, Collection<WrapperMappingInfo> wrappers,
303+
boolean mapperContextRootRedirectEnabled, boolean mapperDirectoryRedirectEnabled) {
276304

277305
Host mappedHost = exactFind(hosts, hostName);
278306
if (mappedHost == null) {
@@ -294,6 +322,9 @@ public void addContextVersion(String hostName, Object host, String path,
294322
newContextVersion.slashCount = slashCount;
295323
newContextVersion.welcomeResources = welcomeResources;
296324
newContextVersion.resources = resources;
325+
newContextVersion.mapperContextRootRedirectEnabled = mapperContextRootRedirectEnabled;
326+
newContextVersion.mapperDirectoryRedirectEnabled = mapperDirectoryRedirectEnabled;
327+
297328
if (wrappers != null) {
298329
addWrappers(newContextVersion, wrappers);
299330
}
@@ -904,7 +935,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
904935
}
905936
}
906937

907-
if(mappingData.wrapper == null && noServletPath) {
938+
if(mappingData.wrapper == null && noServletPath &&
939+
contextVersion.mapperContextRootRedirectEnabled) {
908940
// The path is empty, redirect to "/"
909941
mappingData.redirectPath.setChars
910942
(path.getBuffer(), pathOffset, pathEnd-pathOffset);
@@ -1032,7 +1064,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
10321064
} catch(NamingException nex) {
10331065
// Swallow, since someone else handles the 404
10341066
}
1035-
if (file != null && file instanceof DirContext) {
1067+
if (file != null && file instanceof DirContext &&
1068+
contextVersion.mapperDirectoryRedirectEnabled) {
10361069
// Note: this mutates the path: do not do any processing
10371070
// after this (since we set the redirectPath, there
10381071
// shouldn't be any)
@@ -1049,7 +1082,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
10491082

10501083
path.setOffset(pathOffset);
10511084
path.setEnd(pathEnd);
1052-
10531085
}
10541086

10551087

@@ -1684,6 +1716,8 @@ protected static final class ContextVersion extends MapElement {
16841716
public Wrapper[] wildcardWrappers = new Wrapper[0];
16851717
public Wrapper[] extensionWrappers = new Wrapper[0];
16861718
public int nesting = 0;
1719+
public boolean mapperContextRootRedirectEnabled = false;
1720+
public boolean mapperDirectoryRedirectEnabled = false;
16871721
private volatile boolean paused;
16881722

16891723
public ContextVersion() {

test/org/apache/catalina/core/TesterContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,4 +1227,20 @@ public void setValidateClientProvidedNewSessionId(boolean validateClientProvided
12271227

12281228
@Override
12291229
public boolean getValidateClientProvidedNewSessionId() { return false; }
1230+
1231+
@Override
1232+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
1233+
// NO-OP
1234+
}
1235+
1236+
@Override
1237+
public boolean getMapperContextRootRedirectEnabled() { return false; }
1238+
1239+
@Override
1240+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
1241+
// NO-OP
1242+
}
1243+
1244+
@Override
1245+
public boolean getMapperDirectoryRedirectEnabled() { return false; }
12301246
}

test/org/apache/catalina/startup/TomcatBaseTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,7 @@ public static int methodUrl(String path, ByteChunk out, int readTimeout,
631631
String method) throws IOException {
632632

633633
URL url = new URL(path);
634-
HttpURLConnection connection =
635-
(HttpURLConnection) url.openConnection();
634+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
636635
connection.setUseCaches(false);
637636
connection.setReadTimeout(readTimeout);
638637
connection.setRequestMethod(method);

test/org/apache/tomcat/util/http/mapper/TestMapperWebapps.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.net.HttpURLConnection;
2122
import java.util.HashMap;
2223
import java.util.List;
2324

@@ -31,8 +32,11 @@
3132

3233
import org.apache.catalina.Context;
3334
import org.apache.catalina.core.StandardContext;
35+
import org.apache.catalina.deploy.SecurityCollection;
36+
import org.apache.catalina.deploy.SecurityConstraint;
3437
import org.apache.catalina.startup.Tomcat;
3538
import org.apache.catalina.startup.TomcatBaseTest;
39+
import org.apache.catalina.valves.RemoteAddrValve;
3640
import org.apache.tomcat.util.buf.ByteChunk;
3741

3842
/**
@@ -223,6 +227,66 @@ public void testWelcomeFileStrict() throws Exception {
223227
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc);
224228
}
225229

230+
@Test
231+
public void testRedirect() throws Exception {
232+
// Disable the following of redirects for this test only
233+
boolean originalValue = HttpURLConnection.getFollowRedirects();
234+
HttpURLConnection.setFollowRedirects(false);
235+
try {
236+
Tomcat tomcat = getTomcatInstance();
237+
238+
// Use standard test webapp as ROOT
239+
File rootDir = new File("test/webapp-3.0");
240+
org.apache.catalina.Context root =
241+
tomcat.addWebapp(null, "", rootDir.getAbsolutePath());
242+
243+
// Add a security constraint
244+
SecurityConstraint constraint = new SecurityConstraint();
245+
SecurityCollection collection = new SecurityCollection();
246+
collection.addPattern("/welcome-files/*");
247+
collection.addPattern("/welcome-files");
248+
constraint.addCollection(collection);
249+
constraint.addAuthRole("foo");
250+
root.addConstraint(constraint);
251+
252+
// Also make examples available
253+
File examplesDir = new File(getBuildDirectory(), "webapps/examples");
254+
org.apache.catalina.Context examples = tomcat.addWebapp(
255+
null, "/examples", examplesDir.getAbsolutePath());
256+
// Then block access to the examples to test redirection
257+
RemoteAddrValve rav = new RemoteAddrValve();
258+
rav.setDeny(".*");
259+
rav.setDenyStatus(404);
260+
examples.getPipeline().addValve(rav);
261+
262+
tomcat.start();
263+
264+
// Redirects within a web application
265+
doRedirectTest("/welcome-files", 401);
266+
doRedirectTest("/welcome-files/", 401);
267+
268+
doRedirectTest("/jsp", 302);
269+
doRedirectTest("/jsp/", 404);
270+
271+
doRedirectTest("/WEB-INF", 404);
272+
doRedirectTest("/WEB-INF/", 404);
273+
274+
// Redirects between web applications
275+
doRedirectTest("/examples", 404);
276+
doRedirectTest("/examples/", 404);
277+
} finally {
278+
HttpURLConnection.setFollowRedirects(originalValue);
279+
}
280+
}
281+
282+
283+
private void doRedirectTest(String path, int expected) throws IOException {
284+
ByteChunk bc = new ByteChunk();
285+
int rc = getUrl("http://localhost:" + getPort() + path, bc, null);
286+
Assert.assertEquals(expected, rc);
287+
}
288+
289+
226290
/**
227291
* Prepare a string to search in messages that contain a timestamp, when it
228292
* is known that the timestamp was printed between {@code timeA} and

0 commit comments

Comments
 (0)