Skip to content
This repository was archived by the owner on Dec 4, 2018. It is now read-only.

Commit c15c2ab

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/tc8.0.x/trunk@1715207 13f79535-47bb-0310-9956-ffa450edef68
1 parent f7695d1 commit c15c2ab

File tree

12 files changed

+232
-8
lines changed

12 files changed

+232
-8
lines changed

java/org/apache/catalina/Context.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,4 +1698,44 @@ Set<String> addServletSecurity(ServletRegistration.Dynamic registration,
16981698
* false}
16991699
*/
17001700
public boolean getValidateClientProvidedNewSessionId();
1701+
1702+
/**
1703+
* If enabled, requests for a web application context root will be
1704+
* redirected (adding a trailing slash) by the Mapper. This is more
1705+
* efficient but has the side effect of confirming that the context path is
1706+
* valid.
1707+
*
1708+
* @param mapperContextRootRedirectEnabled Should the redirects be enabled?
1709+
*/
1710+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled);
1711+
1712+
/**
1713+
* Determines if requests for a web application context root will be
1714+
* redirected (adding a trailing slash) by the Mapper. This is more
1715+
* efficient but has the side effect of confirming that the context path is
1716+
* valid.
1717+
*
1718+
* @return {@code true} if the Mapper level redirect is enabled for this
1719+
* Context.
1720+
*/
1721+
public boolean getMapperContextRootRedirectEnabled();
1722+
1723+
/**
1724+
* If enabled, requests for a directory will be redirected (adding a
1725+
* trailing slash) by the Mapper. This is more efficient but has the
1726+
* side effect of confirming that the directory is valid.
1727+
*
1728+
* @param mapperDirectoryRedirectEnabled Should the redirects be enabled?
1729+
*/
1730+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled);
1731+
1732+
/**
1733+
* Determines if requests for a directory will be redirected (adding a
1734+
* trailing slash) by the Mapper. This is more efficient but has the
1735+
* side effect of confirming that the directory is valid.
1736+
*
1737+
* @return {@code true} if the Mapper level redirect is enabled for this
1738+
* Context.
1739+
*/
1740+
public boolean getMapperDirectoryRedirectEnabled();
17011741
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,13 +815,53 @@ public void unbind() {}
815815

816816
private boolean validateClientProvidedNewSessionId = true;
817817

818+
boolean mapperContextRootRedirectEnabled = false;
819+
820+
boolean mapperDirectoryRedirectEnabled = false;
821+
822+
818823
// ----------------------------------------------------- Context Properties
819824

825+
@Override
826+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
827+
this.mapperContextRootRedirectEnabled = mapperContextRootRedirectEnabled;
828+
}
829+
830+
831+
/**
832+
* {@inheritDoc}
833+
* <p>
834+
* The default value for this implementation is {@code false}.
835+
*/
836+
@Override
837+
public boolean getMapperContextRootRedirectEnabled() {
838+
return mapperContextRootRedirectEnabled;
839+
}
840+
841+
842+
@Override
843+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
844+
this.mapperDirectoryRedirectEnabled = mapperDirectoryRedirectEnabled;
845+
}
846+
847+
848+
/**
849+
* {@inheritDoc}
850+
* <p>
851+
* The default value for this implementation is {@code false}.
852+
*/
853+
@Override
854+
public boolean getMapperDirectoryRedirectEnabled() {
855+
return mapperDirectoryRedirectEnabled;
856+
}
857+
858+
820859
@Override
821860
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
822861
this.validateClientProvidedNewSessionId = validateClientProvidedNewSessionId;
823862
}
824863

864+
825865
/**
826866
* {@inheritDoc}
827867
* <p>
@@ -832,6 +872,7 @@ public boolean getValidateClientProvidedNewSessionId() {
832872
return validateClientProvidedNewSessionId;
833873
}
834874

875+
835876
@Override
836877
public void setCookieProcessor(CookieProcessor cookieProcessor) {
837878
if (cookieProcessor == null) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@
177177
description="Associated manager."
178178
type="org.apache.catalina.Manager" />
179179

180+
<attribute name="mapperContextRootRedirectEnabled"
181+
description="Should the Mapper be used for context root redirects"
182+
type="boolean" />
183+
184+
<attribute name="mapperDirectoryRedirectEnabled"
185+
description="Should the Mapper be used for directory redirects"
186+
type="boolean" />
187+
180188
<attribute name="namingContextListener"
181189
description="Associated naming context listener."
182190
type="org.apache.catalina.core.NamingContextListener" />

java/org/apache/catalina/mapper/Mapper.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
884884
}
885885
}
886886

887-
if(mappingData.wrapper == null && noServletPath) {
887+
if(mappingData.wrapper == null && noServletPath &&
888+
mappingData.context.getMapperContextRootRedirectEnabled()) {
888889
// The path is empty, redirect to "/"
889890
mappingData.redirectPath.setChars
890891
(path.getBuffer(), pathOffset, pathEnd-pathOffset);
@@ -1002,9 +1003,9 @@ private final void internalMapWrapper(ContextVersion contextVersion,
10021003
char[] buf = path.getBuffer();
10031004
if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
10041005
String pathStr = path.toString();
1005-
WebResource file =
1006-
contextVersion.resources.getResource(pathStr);
1007-
if (file != null && file.isDirectory()) {
1006+
WebResource file = contextVersion.resources.getResource(pathStr);
1007+
if (file != null && file.isDirectory() &&
1008+
mappingData.context.getMapperDirectoryRedirectEnabled()) {
10081009
// Note: this mutates the path: do not do any processing
10091010
// after this (since we set the redirectPath, there
10101011
// shouldn't be any)
@@ -1021,7 +1022,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
10211022

10221023
path.setOffset(pathOffset);
10231024
path.setEnd(pathEnd);
1024-
10251025
}
10261026

10271027

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,17 @@ protected void serveResource(HttpServletRequest request,
816816
long contentLength = -1L;
817817

818818
if (resource.isDirectory()) {
819+
if (!path.endsWith("/")) {
820+
StringBuilder location = new StringBuilder(request.getRequestURI());
821+
location.append('/');
822+
if (request.getQueryString() != null) {
823+
location.append('?');
824+
location.append(request.getQueryString());
825+
}
826+
response.sendRedirect(response.encodeRedirectURL(location.toString()));
827+
return;
828+
}
829+
819830
// Skip directory listings if we have been configured to
820831
// suppress them
821832
if (!listings) {

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,9 +764,25 @@ public void setCookieProcessor(CookieProcessor cookieProcessor) { /* NO-OP */ }
764764

765765
@Override
766766
public void setValidateClientProvidedNewSessionId(boolean validateClientProvidedNewSessionId) {
767-
//NO-OP
767+
// NO-OP
768768
}
769769

770770
@Override
771771
public boolean getValidateClientProvidedNewSessionId() { return false; }
772+
773+
@Override
774+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
775+
// NO-OP
776+
}
777+
778+
@Override
779+
public boolean getMapperContextRootRedirectEnabled() { return false; }
780+
781+
@Override
782+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
783+
// NO-OP
784+
}
785+
786+
@Override
787+
public boolean getMapperDirectoryRedirectEnabled() { return false; }
772788
}

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

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

12351235
@Override
12361236
public boolean getValidateClientProvidedNewSessionId() { return false; }
1237+
1238+
@Override
1239+
public void setMapperContextRootRedirectEnabled(boolean mapperContextRootRedirectEnabled) {
1240+
// NO-OP
1241+
}
1242+
1243+
@Override
1244+
public boolean getMapperContextRootRedirectEnabled() { return false; }
1245+
1246+
@Override
1247+
public void setMapperDirectoryRedirectEnabled(boolean mapperDirectoryRedirectEnabled) {
1248+
// NO-OP
1249+
}
1250+
1251+
@Override
1252+
public boolean getMapperDirectoryRedirectEnabled() { return false; }
12371253
}

test/org/apache/catalina/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

@@ -33,7 +34,10 @@
3334
import org.apache.catalina.core.StandardContext;
3435
import org.apache.catalina.startup.Tomcat;
3536
import org.apache.catalina.startup.TomcatBaseTest;
37+
import org.apache.catalina.valves.RemoteAddrValve;
3638
import org.apache.tomcat.util.buf.ByteChunk;
39+
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
40+
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
3741
import org.apache.tomcat.websocket.server.WsContextListener;
3842

3943
/**
@@ -225,6 +229,66 @@ public void testWelcomeFileStrict() throws Exception {
225229
Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc);
226230
}
227231

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

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

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

648648
URL url = new URL(path);
649-
HttpURLConnection connection =
650-
(HttpURLConnection) url.openConnection();
649+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
651650
connection.setUseCaches(false);
652651
connection.setReadTimeout(readTimeout);
653652
connection.setRequestMethod(method);

test/org/apache/catalina/valves/rewrite/TestRewriteValve.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.junit.Test;
2121

2222
import org.apache.catalina.Context;
23+
import org.apache.catalina.servlets.DefaultServlet;
2324
import org.apache.catalina.startup.Tomcat;
2425
import org.apache.catalina.startup.TomcatBaseTest;
2526
import org.apache.tomcat.util.buf.ByteChunk;
@@ -75,6 +76,8 @@ private void doTestRewrite(String config, String request, String expectedURI) th
7576
Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
7677
ctx.addServletMapping("/a/%255A", "snoop");
7778
ctx.addServletMapping("/c/*", "snoop");
79+
Tomcat.addServlet(ctx, "default", new DefaultServlet());
80+
ctx.addServletMapping("/", "default");
7881

7982
tomcat.start();
8083

0 commit comments

Comments
 (0)