VYPR
Medium severity5.3NVD Advisory· Published Feb 25, 2016· Updated May 6, 2026

CVE-2015-5345

CVE-2015-5345

Description

The Mapper component in Apache Tomcat 6.x before 6.0.45, 7.x before 7.0.68, 8.x before 8.0.30, and 9.x before 9.0.0.M2 processes redirects before considering security constraints and Filters, which allows remote attackers to determine the existence of a directory via a URL that lacks a trailing / (slash) character.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.tomcat:tomcatMaven
>= 9.0.0.M1, < 9.0.0.M29.0.0.M2
org.apache.tomcat:tomcatMaven
>= 8.0.0-RC1, < 8.0.308.0.30
org.apache.tomcat:tomcatMaven
>= 7.0.0, < 7.0.687.0.68
org.apache.tomcat:tomcatMaven
>= 6.0.0, < 6.0.456.0.45

Affected products

102
  • Apache/Tomcat96 versions
    cpe:2.3:a:apache:tomcat:6.0.0:*:*:*:*:*:*:*+ 95 more
    • cpe:2.3:a:apache:tomcat:6.0.0:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.0:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.10:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.13:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.16:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.18:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.1:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.24:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.30:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.32:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.33:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.35:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.36:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.37:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.39:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.4:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.41:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.43:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.44:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.4:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.0:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.10:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.12:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.16:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.19:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.21:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.22:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.23:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.25:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.27:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.2:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.30:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.32:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.33:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.34:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.35:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.37:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.39:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.40:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.41:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.42:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.47:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.4:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.50:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.52:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.53:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.54:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.55:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.56:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.57:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.59:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.5:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.6:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.61:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.62:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.63:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.64:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.65:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc1:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc10:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc3:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc5:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.12:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.15:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.17:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.18:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.21:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.22:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.23:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.24:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.27:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.3:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:9.0.0:milestone1:*:*:*:*:*:*
  • cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*+ 3 more
    • cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:15.10:*:*:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:16.04:*:*:*:lts:*:*:*
  • cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*
    • cpe:2.3:o:debian:debian_linux:8.0:*:*:*:*:*:*:*

Patches

11
7288bc70a14e

Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58660

https://github.com/apache/tomcatMark ThomasNov 30, 2015via ghsa
5 files changed · +62 20
  • java/org/apache/catalina/authenticator/FormAuthenticator.java+14 0 modified
    @@ -236,6 +236,20 @@ public boolean authenticate(Request request,
     
             // No -- Save this request and redirect to the form login page
             if (!loginAction) {
    +            // If this request was to the root of the context without a trailing
    +            // '/', need to redirect to add it else the submit of the login form
    +            // may not go to the correct web application
    +            if (request.getServletPath().length() == 0 && request.getPathInfo() == null) {
    +                StringBuilder location = new StringBuilder(requestURI);
    +                location.append('/');
    +                if (request.getQueryString() != null) {
    +                    location.append('?');
    +                    location.append(request.getQueryString());
    +                }
    +                response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +                return false;
    +            }
    +
                 session = request.getSessionInternal(true);
                 if (log.isDebugEnabled()) {
                     log.debug("Save request in session '" + session.getIdInternal() + "'");
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+24 9 modified
    @@ -375,6 +375,10 @@ public void init() throws ServletException {
          * @param request The servlet request we are processing
          */
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always
             // serves resources from the web app root with context rooted paths.
             // i.e. it can not be used to mount the web app root under a sub-path
    @@ -400,7 +404,7 @@ protected String getRelativePath(HttpServletRequest request) {
             if (pathInfo != null) {
                 result.append(pathInfo);
             }
    -        if (result.length() == 0) {
    +        if (result.length() == 0 && !allowEmptyPath) {
                 result.append('/');
             }
     
    @@ -778,7 +782,8 @@ protected void serveResource(HttpServletRequest request,
             boolean serveContent = content;
     
             // Identify the requested resource path
    -        String path = getRelativePath(request);
    +        String path = getRelativePath(request, true);
    +
             if (debug > 0) {
                 if (serveContent)
                     log("DefaultServlet.serveResource:  Serving resource '" +
    @@ -788,6 +793,12 @@ protected void serveResource(HttpServletRequest request,
                         path + "' headers only");
             }
     
    +        if (path.length() == 0) {
    +            // Context root redirect
    +            doDirectoryRedirect(request, response);
    +            return;
    +        }
    +
             CacheEntry cacheEntry = resources.lookupCache(path);
     
             if (!cacheEntry.exists) {
    @@ -857,13 +868,7 @@ 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()));
    +                doDirectoryRedirect(request, response);
                     return;
                 }
     
    @@ -1074,6 +1079,16 @@ protected void serveResource(HttpServletRequest request,
     
         }
     
    +    private void doDirectoryRedirect(HttpServletRequest request, HttpServletResponse response)
    +            throws IOException {
    +        StringBuilder location = new StringBuilder(request.getRequestURI());
    +        location.append('/');
    +        if (request.getQueryString() != null) {
    +            location.append('?');
    +            location.append(request.getQueryString());
    +        }
    +        response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +    }
     
         /**
          * Parse the content-range header.
    
  • java/org/apache/catalina/servlets/WebdavServlet.java+5 0 modified
    @@ -430,6 +430,11 @@ protected boolean checkIfHeaders(HttpServletRequest request,
          */
         @Override
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    @Override
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             String pathInfo;
     
             if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
    
  • java/org/apache/tomcat/util/http/mapper/Mapper.java+10 11 modified
    @@ -887,20 +887,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             int pathOffset = path.getOffset();
             int pathEnd = path.getEnd();
    -        int servletPath = pathOffset;
             boolean noServletPath = false;
     
             int length = contextVersion.path.length();
    -        if (length != (pathEnd - pathOffset)) {
    -            servletPath = pathOffset + length;
    -        } else {
    +        if (length == (pathEnd - pathOffset)) {
                 noServletPath = true;
    -            path.append('/');
    -            pathOffset = path.getOffset();
    -            pathEnd = path.getEnd();
    -            servletPath = pathOffset+length;
             }
    -
    +        int servletPath = pathOffset + length;
             path.setOffset(servletPath);
     
             // Rule 1 -- Exact Match
    @@ -938,8 +931,10 @@ private final void internalMapWrapper(ContextVersion contextVersion,
             if(mappingData.wrapper == null && noServletPath &&
                     contextVersion.mapperContextRootRedirectEnabled) {
                 // The path is empty, redirect to "/"
    +            path.append('/');
    +            pathEnd = path.getEnd();
                 mappingData.redirectPath.setChars
    -                (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    +                (path.getBuffer(), pathOffset, pathEnd - pathOffset);
                 path.setEnd(pathEnd - 1);
                 return;
             }
    @@ -1060,7 +1055,11 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                     Object file = null;
                     String pathStr = path.toString();
                     try {
    -                    file = contextVersion.resources.lookup(pathStr);
    +                    if (pathStr.length() == 0) {
    +                        file = contextVersion.resources.lookup("/");
    +                    } else {
    +                        file = contextVersion.resources.lookup(pathStr);
    +                    }
                     } catch(NamingException nex) {
                         // Swallow, since someone else handles the 404
                     }
    
  • webapps/docs/changelog.xml+9 0 modified
    @@ -58,6 +58,15 @@
       issues do not "pop up" wrt. others).
     -->
     <section name="Tomcat 7.0.67 (violetagg)">
    +  <subsection name="Catalina">
    +    <changelog>
    +      <fix>
    +        <bug>58660</bug>: Correct a regression in 8.0.29 caused by the change
    +        that moved the redirection for context roots from the Mapper to the
    +        Default Servlet. (markt)
    +      </fix>
    +    </changelog>
    +  </subsection>
       <subsection name="WebSocket">
         <changelog>
           <fix>
    
2b643a4e36d3

Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58660

https://github.com/apache/tomcat80Mark ThomasNov 30, 2015via ghsa
5 files changed · +60 20
  • java/org/apache/catalina/authenticator/FormAuthenticator.java+14 0 modified
    @@ -214,6 +214,20 @@ public boolean authenticate(Request request, HttpServletResponse response)
     
             // No -- Save this request and redirect to the form login page
             if (!loginAction) {
    +            // If this request was to the root of the context without a trailing
    +            // '/', need to redirect to add it else the submit of the login form
    +            // may not go to the correct web application
    +            if (request.getServletPath().length() == 0 && request.getPathInfo() == null) {
    +                StringBuilder location = new StringBuilder(requestURI);
    +                location.append('/');
    +                if (request.getQueryString() != null) {
    +                    location.append('?');
    +                    location.append(request.getQueryString());
    +                }
    +                response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +                return false;
    +            }
    +
                 session = request.getSessionInternal(true);
                 if (log.isDebugEnabled()) {
                     log.debug("Save request in session '" + session.getIdInternal() + "'");
    
  • java/org/apache/catalina/mapper/Mapper.java+12 11 modified
    @@ -836,20 +836,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             int pathOffset = path.getOffset();
             int pathEnd = path.getEnd();
    -        int servletPath = pathOffset;
             boolean noServletPath = false;
     
             int length = contextVersion.path.length();
    -        if (length != (pathEnd - pathOffset)) {
    -            servletPath = pathOffset + length;
    -        } else {
    +        if (length == (pathEnd - pathOffset)) {
                 noServletPath = true;
    -            path.append('/');
    -            pathOffset = path.getOffset();
    -            pathEnd = path.getEnd();
    -            servletPath = pathOffset+length;
             }
    -
    +        int servletPath = pathOffset + length;
             path.setOffset(servletPath);
     
             // Rule 1 -- Exact Match
    @@ -887,8 +880,10 @@ private final void internalMapWrapper(ContextVersion contextVersion,
             if(mappingData.wrapper == null && noServletPath &&
                     mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
    +            path.append('/');
    +            pathEnd = path.getEnd();
                 mappingData.redirectPath.setChars
    -                (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    +                (path.getBuffer(), pathOffset, pathEnd - pathOffset);
                 path.setEnd(pathEnd - 1);
                 return;
             }
    @@ -1003,7 +998,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file = contextVersion.resources.getResource(pathStr);
    +                WebResource file;
    +                // Handle context root
    +                if (pathStr.length() == 0) {
    +                    file = contextVersion.resources.getResource("/");
    +                } else {
    +                    file = contextVersion.resources.getResource(pathStr);
    +                }
                     if (file != null && file.isDirectory() &&
                             mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+24 9 modified
    @@ -331,6 +331,10 @@ public void init() throws ServletException {
          * @param request The servlet request we are processing
          */
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always
             // serves resources from the web app root with context rooted paths.
             // i.e. it can not be used to mount the web app root under a sub-path
    @@ -356,7 +360,7 @@ protected String getRelativePath(HttpServletRequest request) {
             if (pathInfo != null) {
                 result.append(pathInfo);
             }
    -        if (result.length() == 0) {
    +        if (result.length() == 0 && !allowEmptyPath) {
                 result.append('/');
             }
     
    @@ -686,7 +690,8 @@ protected void serveResource(HttpServletRequest request,
             boolean serveContent = content;
     
             // Identify the requested resource path
    -        String path = getRelativePath(request);
    +        String path = getRelativePath(request, true);
    +
             if (debug > 0) {
                 if (serveContent)
                     log("DefaultServlet.serveResource:  Serving resource '" +
    @@ -696,6 +701,12 @@ protected void serveResource(HttpServletRequest request,
                         path + "' headers only");
             }
     
    +        if (path.length() == 0) {
    +            // Context root redirect
    +            doDirectoryRedirect(request, response);
    +            return;
    +        }
    +
             WebResource resource = resources.getResource(path);
     
             if (!resource.exists()) {
    @@ -811,13 +822,7 @@ protected void serveResource(HttpServletRequest request,
     
             if (resource.isDirectory()) {
                 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()));
    +                doDirectoryRedirect(request, response);
                     return;
                 }
     
    @@ -1026,6 +1031,16 @@ protected void serveResource(HttpServletRequest request,
             }
         }
     
    +    private void doDirectoryRedirect(HttpServletRequest request, HttpServletResponse response)
    +            throws IOException {
    +        StringBuilder location = new StringBuilder(request.getRequestURI());
    +        location.append('/');
    +        if (request.getQueryString() != null) {
    +            location.append('?');
    +            location.append(request.getQueryString());
    +        }
    +        response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +    }
     
         /**
          * Parse the content-range header.
    
  • java/org/apache/catalina/servlets/WebdavServlet.java+5 0 modified
    @@ -375,6 +375,11 @@ protected boolean checkIfHeaders(HttpServletRequest request,
          */
         @Override
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    @Override
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             String pathInfo;
     
             if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
    
  • webapps/docs/changelog.xml+5 0 modified
    @@ -61,6 +61,11 @@
             running Tomcat with a Java agent. Based on a patch by Huxing Zhang.
             (markt)
           </fix>
    +      <fix>
    +        <bug>58660</bug>: Correct a regression in 8.0.29 caused by the change
    +        that moved the redirection for context roots from the Mapper to the
    +        Default Servlet. (markt)
    +      </fix>
         </changelog>
       </subsection>
       <subsection name="Coyote">
    
c584c7c4ab06

Additional fix for BZ 58660

https://github.com/apache/tomcatMark ThomasNov 27, 2015via ghsa
1 file changed · +3 1
  • java/org/apache/catalina/mapper/Mapper.java+3 1 modified
    @@ -879,8 +879,10 @@ private final void internalMapWrapper(ContextVersion contextVersion,
             if(mappingData.wrapper == null && noServletPath &&
                     mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
    +            path.append('/');
    +            pathEnd = path.getEnd();
                 mappingData.redirectPath.setChars
    -                (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    +                (path.getBuffer(), pathOffset, pathEnd - pathOffset);
                 path.setEnd(pathEnd - 1);
                 return;
             }
    
127d8ea86d24

Additional fix for BZ 58660

https://github.com/apache/tomcatMark ThomasNov 27, 2015via ghsa
1 file changed · +3 1
  • java/org/apache/catalina/mapper/Mapper.java+3 1 modified
    @@ -879,8 +879,10 @@ private final void internalMapWrapper(ContextVersion contextVersion,
             if(mappingData.wrapper == null && noServletPath &&
                     mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
    +            path.append('/');
    +            pathEnd = path.getEnd();
                 mappingData.redirectPath.setChars
    -                (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    +                (path.getBuffer(), pathOffset, pathEnd - pathOffset);
                 path.setEnd(pathEnd - 1);
                 return;
             }
    
816552abf673

Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58660

https://github.com/apache/tomcatMark ThomasNov 27, 2015via ghsa
3 files changed · +47 19
  • java/org/apache/catalina/authenticator/FormAuthenticator.java+14 0 modified
    @@ -219,6 +219,20 @@ public boolean authenticate(Request request, HttpServletResponse response)
     
             // No -- Save this request and redirect to the form login page
             if (!loginAction) {
    +            // If this request was to the root of the context without a trailing
    +            // '/', need to redirect to add it else the submit of the login form
    +            // may not go to the correct web application
    +            if (request.getServletPath().length() == 0 && request.getPathInfo() == null) {
    +                StringBuilder location = new StringBuilder(requestURI);
    +                location.append('/');
    +                if (request.getQueryString() != null) {
    +                    location.append('?');
    +                    location.append(request.getQueryString());
    +                }
    +                response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +                return false;
    +            }
    +
                 session = request.getSessionInternal(true);
                 if (log.isDebugEnabled()) {
                     log.debug("Save request in session '" + session.getIdInternal() + "'");
    
  • java/org/apache/catalina/mapper/Mapper.java+9 10 modified
    @@ -835,20 +835,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             int pathOffset = path.getOffset();
             int pathEnd = path.getEnd();
    -        int servletPath = pathOffset;
             boolean noServletPath = false;
     
             int length = contextVersion.path.length();
    -        if (length != (pathEnd - pathOffset)) {
    -            servletPath = pathOffset + length;
    -        } else {
    +        if (length == (pathEnd - pathOffset)) {
                 noServletPath = true;
    -            path.append('/');
    -            pathOffset = path.getOffset();
    -            pathEnd = path.getEnd();
    -            servletPath = pathOffset+length;
             }
    -
    +        int servletPath = pathOffset + length;
             path.setOffset(servletPath);
     
             // Rule 1 -- Exact Match
    @@ -1002,7 +995,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file = contextVersion.resources.getResource(pathStr);
    +                WebResource file;
    +                // Handle context root
    +                if (pathStr.length() == 0) {
    +                    file = contextVersion.resources.getResource("/");
    +                } else {
    +                    file = contextVersion.resources.getResource(pathStr);
    +                }
                     if (file != null && file.isDirectory() &&
                             mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+24 9 modified
    @@ -331,6 +331,10 @@ public void init() throws ServletException {
          * @param request The servlet request we are processing
          */
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always
             // serves resources from the web app root with context rooted paths.
             // i.e. it cannot be used to mount the web app root under a sub-path
    @@ -356,7 +360,7 @@ protected String getRelativePath(HttpServletRequest request) {
             if (pathInfo != null) {
                 result.append(pathInfo);
             }
    -        if (result.length() == 0) {
    +        if (result.length() == 0 && !allowEmptyPath) {
                 result.append('/');
             }
     
    @@ -686,7 +690,8 @@ protected void serveResource(HttpServletRequest request,
             boolean serveContent = content;
     
             // Identify the requested resource path
    -        String path = getRelativePath(request);
    +        String path = getRelativePath(request, true);
    +
             if (debug > 0) {
                 if (serveContent)
                     log("DefaultServlet.serveResource:  Serving resource '" +
    @@ -696,6 +701,12 @@ protected void serveResource(HttpServletRequest request,
                         path + "' headers only");
             }
     
    +        if (path.length() == 0) {
    +            // Context root redirect
    +            doDirectoryRedirect(request, response);
    +            return;
    +        }
    +
             WebResource resource = resources.getResource(path);
     
             if (!resource.exists()) {
    @@ -811,13 +822,7 @@ protected void serveResource(HttpServletRequest request,
     
             if (resource.isDirectory()) {
                 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()));
    +                doDirectoryRedirect(request, response);
                     return;
                 }
     
    @@ -1026,6 +1031,16 @@ protected void serveResource(HttpServletRequest request,
             }
         }
     
    +    private void doDirectoryRedirect(HttpServletRequest request, HttpServletResponse response)
    +            throws IOException {
    +        StringBuilder location = new StringBuilder(request.getRequestURI());
    +        location.append('/');
    +        if (request.getQueryString() != null) {
    +            location.append('?');
    +            location.append(request.getQueryString());
    +        }
    +        response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +    }
     
         /**
          * Parse the content-range header.
    
66daa4adc14b

Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58660

https://github.com/apache/tomcatMark ThomasNov 27, 2015via ghsa
3 files changed · +47 19
  • java/org/apache/catalina/authenticator/FormAuthenticator.java+14 0 modified
    @@ -219,6 +219,20 @@ public boolean authenticate(Request request, HttpServletResponse response)
     
             // No -- Save this request and redirect to the form login page
             if (!loginAction) {
    +            // If this request was to the root of the context without a trailing
    +            // '/', need to redirect to add it else the submit of the login form
    +            // may not go to the correct web application
    +            if (request.getServletPath().length() == 0 && request.getPathInfo() == null) {
    +                StringBuilder location = new StringBuilder(requestURI);
    +                location.append('/');
    +                if (request.getQueryString() != null) {
    +                    location.append('?');
    +                    location.append(request.getQueryString());
    +                }
    +                response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +                return false;
    +            }
    +
                 session = request.getSessionInternal(true);
                 if (log.isDebugEnabled()) {
                     log.debug("Save request in session '" + session.getIdInternal() + "'");
    
  • java/org/apache/catalina/mapper/Mapper.java+9 10 modified
    @@ -835,20 +835,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             int pathOffset = path.getOffset();
             int pathEnd = path.getEnd();
    -        int servletPath = pathOffset;
             boolean noServletPath = false;
     
             int length = contextVersion.path.length();
    -        if (length != (pathEnd - pathOffset)) {
    -            servletPath = pathOffset + length;
    -        } else {
    +        if (length == (pathEnd - pathOffset)) {
                 noServletPath = true;
    -            path.append('/');
    -            pathOffset = path.getOffset();
    -            pathEnd = path.getEnd();
    -            servletPath = pathOffset+length;
             }
    -
    +        int servletPath = pathOffset + length;
             path.setOffset(servletPath);
     
             // Rule 1 -- Exact Match
    @@ -1002,7 +995,13 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file = contextVersion.resources.getResource(pathStr);
    +                WebResource file;
    +                // Handle context root
    +                if (pathStr.length() == 0) {
    +                    file = contextVersion.resources.getResource("/");
    +                } else {
    +                    file = contextVersion.resources.getResource(pathStr);
    +                }
                     if (file != null && file.isDirectory() &&
                             mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+24 9 modified
    @@ -331,6 +331,10 @@ public void init() throws ServletException {
          * @param request The servlet request we are processing
          */
         protected String getRelativePath(HttpServletRequest request) {
    +        return getRelativePath(request, false);
    +    }
    +
    +    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
             // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always
             // serves resources from the web app root with context rooted paths.
             // i.e. it cannot be used to mount the web app root under a sub-path
    @@ -356,7 +360,7 @@ protected String getRelativePath(HttpServletRequest request) {
             if (pathInfo != null) {
                 result.append(pathInfo);
             }
    -        if (result.length() == 0) {
    +        if (result.length() == 0 && !allowEmptyPath) {
                 result.append('/');
             }
     
    @@ -686,7 +690,8 @@ protected void serveResource(HttpServletRequest request,
             boolean serveContent = content;
     
             // Identify the requested resource path
    -        String path = getRelativePath(request);
    +        String path = getRelativePath(request, true);
    +
             if (debug > 0) {
                 if (serveContent)
                     log("DefaultServlet.serveResource:  Serving resource '" +
    @@ -696,6 +701,12 @@ protected void serveResource(HttpServletRequest request,
                         path + "' headers only");
             }
     
    +        if (path.length() == 0) {
    +            // Context root redirect
    +            doDirectoryRedirect(request, response);
    +            return;
    +        }
    +
             WebResource resource = resources.getResource(path);
     
             if (!resource.exists()) {
    @@ -811,13 +822,7 @@ protected void serveResource(HttpServletRequest request,
     
             if (resource.isDirectory()) {
                 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()));
    +                doDirectoryRedirect(request, response);
                     return;
                 }
     
    @@ -1026,6 +1031,16 @@ protected void serveResource(HttpServletRequest request,
             }
         }
     
    +    private void doDirectoryRedirect(HttpServletRequest request, HttpServletResponse response)
    +            throws IOException {
    +        StringBuilder location = new StringBuilder(request.getRequestURI());
    +        location.append('/');
    +        if (request.getQueryString() != null) {
    +            location.append('?');
    +            location.append(request.getQueryString());
    +        }
    +        response.sendRedirect(response.encodeRedirectURL(location.toString()));
    +    }
     
         /**
          * Parse the content-range header.
    
58c09b6217c5

Fix the issue that meant the work-around for BZ 58660 would not work.

https://github.com/apache/tomcatMark ThomasNov 27, 2015via ghsa
1 file changed · +2 1
  • java/org/apache/catalina/connector/MapperListener.java+2 1 modified
    @@ -384,7 +384,8 @@ private void registerContext(Context context) {
     
             mapper.addContextVersion(host.getName(), host, contextPath,
                     context.getWebappVersion(), context, welcomeFiles, resources,
    -                wrappers);
    +                wrappers, context.getMapperContextRootRedirectEnabled(),
    +                context.getMapperDirectoryRedirectEnabled());
     
             if(log.isDebugEnabled()) {
                 log.debug(sm.getString("mapperListener.registerContext",
    
a273b5f45cb4

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.

https://github.com/apache/tomcatMark ThomasNov 19, 2015via ghsa
11 files changed · +262 10
  • java/org/apache/catalina/Context.java+40 0 modified
    @@ -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();
     }
    
  • java/org/apache/catalina/core/mbeans-descriptors.xml+8 0 modified
    @@ -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" />
    
  • java/org/apache/catalina/core/StandardContext.java+41 3 modified
    @@ -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>
    @@ -930,7 +968,7 @@ public boolean getValidateClientProvidedNewSessionId() {
             return validateClientProvidedNewSessionId;
         }
     
    -    
    +
         @Override
         public void setContainerSciFilter(String containerSciFilter) {
             this.containerSciFilter = containerSciFilter;
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+11 0 modified
    @@ -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) {
    
  • java/org/apache/catalina/startup/FailedContext.java+17 1 modified
    @@ -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; }
     }
    \ No newline at end of file
    
  • java/org/apache/tomcat/util/http/mapper/Mapper.java+38 4 modified
    @@ -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,
    @@ -258,6 +259,7 @@ public void addContextVersion(String hostName, Object host, String path,
                     welcomeResources, resources, null);
         }
     
    +    
         /**
          * Add a new Context to an existing Host.
          *
    @@ -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) {
    @@ -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);
                 }
    @@ -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);
    @@ -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)
    @@ -1049,7 +1082,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             path.setOffset(pathOffset);
             path.setEnd(pathEnd);
    -
         }
     
     
    @@ -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() {
    
  • test/org/apache/catalina/core/TesterContext.java+16 0 modified
    @@ -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; }
     }
    
  • test/org/apache/catalina/startup/TomcatBaseTest.java+1 2 modified
    @@ -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);
    
  • test/org/apache/tomcat/util/http/mapper/TestMapperWebapps.java+64 0 modified
    @@ -18,6 +18,7 @@
     
     import java.io.File;
     import java.io.IOException;
    +import java.net.HttpURLConnection;
     import java.util.HashMap;
     import java.util.List;
     
    @@ -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;
     
     /**
    @@ -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
    
  • webapps/docs/changelog.xml+10 0 modified
    @@ -171,6 +171,16 @@
             Ensure that in an embedded Tomcat the logging configuration is
             not lost during garbage collection. (violetagg)
           </fix>
    +      <add>
    +        Move the functionality that provides redirects for context roots and
    +        directories where a trailing <code>/</code> is added from the Mapper to
    +        the <code>DefaultServlet</code>. This enables such requests to be
    +        processed by any configured Valves and Filters before the redirect is
    +        made. This behaviour is configurable via the
    +        <code>mapperContextRootRedirectEnabled</code> and
    +        <code>mapperDirectoryRedirectEnabled</code> attributes of the Context
    +        which may be used to restore the previous behaviour. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Cluster">
    
  • webapps/docs/config/context.xml+16 0 modified
    @@ -360,6 +360,22 @@
             default value of <code>false</code> is used.</p>
           </attribute>
     
    +      <attribute name="mapperContextRootRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application context root will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the context path exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
    +      <attribute name="mapperDirectoryRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application directory will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the directory is exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
           <attribute name="override" required="false">
             <p>Set to <code>true</code> to ignore any settings in both the global
             or <a href="host.html">Host</a> default contexts.  By default, settings
    
c15c2aba8eb4

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.

https://github.com/apache/tomcat80Mark ThomasNov 19, 2015via ghsa
12 files changed · +232 8
  • java/org/apache/catalina/Context.java+40 0 modified
    @@ -1698,4 +1698,44 @@ Set<String> addServletSecurity(ServletRegistration.Dynamic 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();
     }
    
  • java/org/apache/catalina/core/mbeans-descriptors.xml+8 0 modified
    @@ -177,6 +177,14 @@
                    description="Associated manager."
                    type="org.apache.catalina.Manager" />
     
    +    <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" />
    
  • java/org/apache/catalina/core/StandardContext.java+41 0 modified
    @@ -815,13 +815,53 @@ public void unbind() {}
     
         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>
    @@ -832,6 +872,7 @@ public boolean getValidateClientProvidedNewSessionId() {
             return validateClientProvidedNewSessionId;
         }
     
    +
         @Override
         public void setCookieProcessor(CookieProcessor cookieProcessor) {
             if (cookieProcessor == null) {
    
  • java/org/apache/catalina/mapper/Mapper.java+5 5 modified
    @@ -884,7 +884,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 }
             }
     
    -        if(mappingData.wrapper == null && noServletPath) {
    +        if(mappingData.wrapper == null && noServletPath &&
    +                mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
                 mappingData.redirectPath.setChars
                     (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    @@ -1002,9 +1003,9 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file =
    -                        contextVersion.resources.getResource(pathStr);
    -                if (file != null && file.isDirectory()) {
    +                WebResource file = contextVersion.resources.getResource(pathStr);
    +                if (file != null && file.isDirectory() &&
    +                        mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
                         // after this (since we set the redirectPath, there
                         // shouldn't be any)
    @@ -1021,7 +1022,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             path.setOffset(pathOffset);
             path.setEnd(pathEnd);
    -
         }
     
     
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+11 0 modified
    @@ -816,6 +816,17 @@ protected void serveResource(HttpServletRequest request,
             long contentLength = -1L;
     
             if (resource.isDirectory()) {
    +            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) {
    
  • java/org/apache/catalina/startup/FailedContext.java+17 1 modified
    @@ -764,9 +764,25 @@ public void setCookieProcessor(CookieProcessor cookieProcessor) { /* 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; }
     }
    \ No newline at end of file
    
  • test/org/apache/catalina/core/TesterContext.java+16 0 modified
    @@ -1234,4 +1234,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; }
     }
    
  • test/org/apache/catalina/mapper/TestMapperWebapps.java+64 0 modified
    @@ -18,6 +18,7 @@
     
     import java.io.File;
     import java.io.IOException;
    +import java.net.HttpURLConnection;
     import java.util.HashMap;
     import java.util.List;
     
    @@ -33,7 +34,10 @@
     import org.apache.catalina.core.StandardContext;
     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;
    +import org.apache.tomcat.util.descriptor.web.SecurityCollection;
    +import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
     import org.apache.tomcat.websocket.server.WsContextListener;
     
     /**
    @@ -225,6 +229,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");
    +            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
    
  • test/org/apache/catalina/startup/TomcatBaseTest.java+1 2 modified
    @@ -646,8 +646,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);
    
  • test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+3 0 modified
    @@ -20,6 +20,7 @@
     import org.junit.Test;
     
     import org.apache.catalina.Context;
    +import org.apache.catalina.servlets.DefaultServlet;
     import org.apache.catalina.startup.Tomcat;
     import org.apache.catalina.startup.TomcatBaseTest;
     import org.apache.tomcat.util.buf.ByteChunk;
    @@ -75,6 +76,8 @@ private void doTestRewrite(String config, String request, String expectedURI) th
             Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
             ctx.addServletMapping("/a/%255A", "snoop");
             ctx.addServletMapping("/c/*", "snoop");
    +        Tomcat.addServlet(ctx, "default", new DefaultServlet());
    +        ctx.addServletMapping("/", "default");
     
             tomcat.start();
     
    
  • webapps/docs/changelog.xml+10 0 modified
    @@ -188,6 +188,16 @@
             Ensure that in an embedded Tomcat the logging configuration is
             not lost during garbage collection. (violetagg)
           </fix>
    +      <add>
    +        Move the functionality that provides redirects for context roots and
    +        directories where a trailing <code>/</code> is added from the Mapper to
    +        the <code>DefaultServlet</code>. This enables such requests to be
    +        processed by any configured Valves and Filters before the redirect is
    +        made. This behaviour is configurable via the
    +        <code>mapperContextRootRedirectEnabled</code> and
    +        <code>mapperDirectoryRedirectEnabled</code> attributes of the Context
    +        which may be used to restore the previous behaviour. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Coyote">
    
  • webapps/docs/config/context.xml+16 0 modified
    @@ -360,6 +360,22 @@
             default value of <code>false</code> is used.</p>
           </attribute>
     
    +      <attribute name="mapperContextRootRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application context root will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the context path exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
    +      <attribute name="mapperDirectoryRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application directory will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the directory is exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
           <attribute name="override" required="false">
             <p>Set to <code>true</code> to ignore any settings in both the global
             or <a href="host.html">Host</a> default contexts.  By default, settings
    
89cd0cf33a99

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.

https://github.com/apache/tomcatMark ThomasNov 19, 2015via ghsa
12 files changed · +232 8
  • java/org/apache/catalina/Context.java+40 0 modified
    @@ -1714,4 +1714,44 @@ Set<String> addServletSecurity(ServletRegistration.Dynamic 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();
     }
    
  • java/org/apache/catalina/core/mbeans-descriptors.xml+8 0 modified
    @@ -177,6 +177,14 @@
                    description="Associated manager."
                    type="org.apache.catalina.Manager" />
     
    +    <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" />
    
  • java/org/apache/catalina/core/StandardContext.java+41 0 modified
    @@ -816,13 +816,53 @@ public void unbind() {}
     
         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>
    @@ -833,6 +873,7 @@ public boolean getValidateClientProvidedNewSessionId() {
             return validateClientProvidedNewSessionId;
         }
     
    +
         @Override
         public void setCookieProcessor(CookieProcessor cookieProcessor) {
             if (cookieProcessor == null) {
    
  • java/org/apache/catalina/mapper/Mapper.java+5 5 modified
    @@ -883,7 +883,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 }
             }
     
    -        if(mappingData.wrapper == null && noServletPath) {
    +        if(mappingData.wrapper == null && noServletPath &&
    +                mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
                 mappingData.redirectPath.setChars
                     (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    @@ -1001,9 +1002,9 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file =
    -                        contextVersion.resources.getResource(pathStr);
    -                if (file != null && file.isDirectory()) {
    +                WebResource file = contextVersion.resources.getResource(pathStr);
    +                if (file != null && file.isDirectory() &&
    +                        mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
                         // after this (since we set the redirectPath, there
                         // shouldn't be any)
    @@ -1020,7 +1021,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             path.setOffset(pathOffset);
             path.setEnd(pathEnd);
    -
         }
     
     
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+11 0 modified
    @@ -816,6 +816,17 @@ protected void serveResource(HttpServletRequest request,
             long contentLength = -1L;
     
             if (resource.isDirectory()) {
    +            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) {
    
  • java/org/apache/catalina/startup/FailedContext.java+17 1 modified
    @@ -764,9 +764,25 @@ public void setCookieProcessor(CookieProcessor cookieProcessor) { /* 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; }
     }
    \ No newline at end of file
    
  • test/org/apache/catalina/core/TesterContext.java+16 0 modified
    @@ -1234,4 +1234,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; }
     }
    
  • test/org/apache/catalina/mapper/TestMapperWebapps.java+64 0 modified
    @@ -18,6 +18,7 @@
     
     import java.io.File;
     import java.io.IOException;
    +import java.net.HttpURLConnection;
     import java.util.HashMap;
     import java.util.List;
     
    @@ -33,7 +34,10 @@
     import org.apache.catalina.core.StandardContext;
     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;
    +import org.apache.tomcat.util.descriptor.web.SecurityCollection;
    +import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
     import org.apache.tomcat.websocket.server.WsContextListener;
     
     /**
    @@ -225,6 +229,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");
    +            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
    
  • test/org/apache/catalina/startup/TomcatBaseTest.java+1 2 modified
    @@ -647,8 +647,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);
    
  • test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+3 0 modified
    @@ -20,6 +20,7 @@
     import org.junit.Test;
     
     import org.apache.catalina.Context;
    +import org.apache.catalina.servlets.DefaultServlet;
     import org.apache.catalina.startup.Tomcat;
     import org.apache.catalina.startup.TomcatBaseTest;
     import org.apache.tomcat.util.buf.ByteChunk;
    @@ -75,6 +76,8 @@ private void doTestRewrite(String config, String request, String expectedURI) th
             Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
             ctx.addServletMapping("/a/%255A", "snoop");
             ctx.addServletMapping("/c/*", "snoop");
    +        Tomcat.addServlet(ctx, "default", new DefaultServlet());
    +        ctx.addServletMapping("/", "default");
     
             tomcat.start();
     
    
  • webapps/docs/changelog.xml+10 0 modified
    @@ -52,6 +52,16 @@
             <code>Mapper</code> used is the <code>Mapper</code> associated with the
             <code>Service</code> for which the listener was created. (markt)
           </scode>
    +      <add>
    +        Move the functionality that provides redirects for context roots and
    +        directories where a trailing <code>/</code> is added from the Mapper to
    +        the <code>DefaultServlet</code>. This enables such requests to be
    +        processed by any configured Valves and Filters before the redirect is
    +        made. This behaviour is configurable via the
    +        <code>mapperContextRootRedirectEnabled</code> and
    +        <code>mapperDirectoryRedirectEnabled</code> attributes of the Context
    +        which may be used to restore the previous behaviour. (markt)
    +      </add>
         </changelog>
       </subsection>
     </section>
    
  • webapps/docs/config/context.xml+16 0 modified
    @@ -360,6 +360,22 @@
             default value of <code>false</code> is used.</p>
           </attribute>
     
    +      <attribute name="mapperContextRootRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application context root will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the context path exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
    +      <attribute name="mapperDirectoryRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application directory will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the directory is exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
           <attribute name="override" required="false">
             <p>Set to <code>true</code> to ignore any settings in both the global
             or <a href="host.html">Host</a> default contexts.  By default, settings
    
8437193708e4

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.

https://github.com/apache/tomcatMark ThomasNov 19, 2015via ghsa
12 files changed · +232 8
  • java/org/apache/catalina/Context.java+40 0 modified
    @@ -1714,4 +1714,44 @@ Set<String> addServletSecurity(ServletRegistration.Dynamic 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();
     }
    
  • java/org/apache/catalina/core/mbeans-descriptors.xml+8 0 modified
    @@ -177,6 +177,14 @@
                    description="Associated manager."
                    type="org.apache.catalina.Manager" />
     
    +    <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" />
    
  • java/org/apache/catalina/core/StandardContext.java+41 0 modified
    @@ -816,13 +816,53 @@ public void unbind() {}
     
         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>
    @@ -833,6 +873,7 @@ public boolean getValidateClientProvidedNewSessionId() {
             return validateClientProvidedNewSessionId;
         }
     
    +
         @Override
         public void setCookieProcessor(CookieProcessor cookieProcessor) {
             if (cookieProcessor == null) {
    
  • java/org/apache/catalina/mapper/Mapper.java+5 5 modified
    @@ -883,7 +883,8 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 }
             }
     
    -        if(mappingData.wrapper == null && noServletPath) {
    +        if(mappingData.wrapper == null && noServletPath &&
    +                mappingData.context.getMapperContextRootRedirectEnabled()) {
                 // The path is empty, redirect to "/"
                 mappingData.redirectPath.setChars
                     (path.getBuffer(), pathOffset, pathEnd-pathOffset);
    @@ -1001,9 +1002,9 @@ private final void internalMapWrapper(ContextVersion contextVersion,
                 char[] buf = path.getBuffer();
                 if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                     String pathStr = path.toString();
    -                WebResource file =
    -                        contextVersion.resources.getResource(pathStr);
    -                if (file != null && file.isDirectory()) {
    +                WebResource file = contextVersion.resources.getResource(pathStr);
    +                if (file != null && file.isDirectory() &&
    +                        mappingData.context.getMapperDirectoryRedirectEnabled()) {
                         // Note: this mutates the path: do not do any processing
                         // after this (since we set the redirectPath, there
                         // shouldn't be any)
    @@ -1020,7 +1021,6 @@ private final void internalMapWrapper(ContextVersion contextVersion,
     
             path.setOffset(pathOffset);
             path.setEnd(pathEnd);
    -
         }
     
     
    
  • java/org/apache/catalina/servlets/DefaultServlet.java+11 0 modified
    @@ -816,6 +816,17 @@ protected void serveResource(HttpServletRequest request,
             long contentLength = -1L;
     
             if (resource.isDirectory()) {
    +            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) {
    
  • java/org/apache/catalina/startup/FailedContext.java+17 1 modified
    @@ -764,9 +764,25 @@ public void setCookieProcessor(CookieProcessor cookieProcessor) { /* 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; }
     }
    \ No newline at end of file
    
  • test/org/apache/catalina/core/TesterContext.java+16 0 modified
    @@ -1234,4 +1234,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; }
     }
    
  • test/org/apache/catalina/mapper/TestMapperWebapps.java+64 0 modified
    @@ -18,6 +18,7 @@
     
     import java.io.File;
     import java.io.IOException;
    +import java.net.HttpURLConnection;
     import java.util.HashMap;
     import java.util.List;
     
    @@ -33,7 +34,10 @@
     import org.apache.catalina.core.StandardContext;
     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;
    +import org.apache.tomcat.util.descriptor.web.SecurityCollection;
    +import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
     import org.apache.tomcat.websocket.server.WsContextListener;
     
     /**
    @@ -225,6 +229,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");
    +            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
    
  • test/org/apache/catalina/startup/TomcatBaseTest.java+1 2 modified
    @@ -647,8 +647,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);
    
  • test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+3 0 modified
    @@ -20,6 +20,7 @@
     import org.junit.Test;
     
     import org.apache.catalina.Context;
    +import org.apache.catalina.servlets.DefaultServlet;
     import org.apache.catalina.startup.Tomcat;
     import org.apache.catalina.startup.TomcatBaseTest;
     import org.apache.tomcat.util.buf.ByteChunk;
    @@ -75,6 +76,8 @@ private void doTestRewrite(String config, String request, String expectedURI) th
             Tomcat.addServlet(ctx, "snoop", new SnoopServlet());
             ctx.addServletMapping("/a/%255A", "snoop");
             ctx.addServletMapping("/c/*", "snoop");
    +        Tomcat.addServlet(ctx, "default", new DefaultServlet());
    +        ctx.addServletMapping("/", "default");
     
             tomcat.start();
     
    
  • webapps/docs/changelog.xml+10 0 modified
    @@ -52,6 +52,16 @@
             <code>Mapper</code> used is the <code>Mapper</code> associated with the
             <code>Service</code> for which the listener was created. (markt)
           </scode>
    +      <add>
    +        Move the functionality that provides redirects for context roots and
    +        directories where a trailing <code>/</code> is added from the Mapper to
    +        the <code>DefaultServlet</code>. This enables such requests to be
    +        processed by any configured Valves and Filters before the redirect is
    +        made. This behaviour is configurable via the
    +        <code>mapperContextRootRedirectEnabled</code> and
    +        <code>mapperDirectoryRedirectEnabled</code> attributes of the Context
    +        which may be used to restore the previous behaviour. (markt)
    +      </add>
         </changelog>
       </subsection>
     </section>
    
  • webapps/docs/config/context.xml+16 0 modified
    @@ -360,6 +360,22 @@
             default value of <code>false</code> is used.</p>
           </attribute>
     
    +      <attribute name="mapperContextRootRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application context root will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the context path exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
    +      <attribute name="mapperDirectoryRedirectEnabled" required="false">
    +        <p>If enabled, requests for a web application directory will be
    +        redirected (adding a trailing slash) if necessary by the Mapper rather
    +        than the default Servlet. This is more efficient but has the side effect
    +        of confirming that the directory is exists. If not specified, the
    +        default value of <code>false</code> is used.</p>
    +      </attribute>
    +
           <attribute name="override" required="false">
             <p>Set to <code>true</code> to ignore any settings in both the global
             or <a href="host.html">Host</a> default contexts.  By default, settings
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

74

News mentions

0

No linked articles in our index yet.