When forwarding a request path is not canonicalized, JSP recompile is not trigger even after JSP file is updated in exploded deployments on JBoss EAP 7

Solution Unverified - Updated

Environment

  • Red Hat JBoss Enterprise Application Platform (EAP)
    • 7.x

Issue

When forwarding a request path is not canonicalized, JSP recompile is not triggered even after the JSP file is updated in the exploded deployment on JBoss EAP 7.

For example, when my application contains the following JSP file that forwards to another JSP with multiple slashes // in the path:

<%
// RequestDispatcher dispatcher = request.getRequestDispatcher("test/example.jsp"); // OK
// RequestDispatcher dispatcher = request.getRequestDispatcher("test//example.jsp"); // OK
// RequestDispatcher dispatcher = request.getRequestDispatcher("/test/example.jsp"); // OK
// RequestDispatcher dispatcher = request.getRequestDispatcher("//test/example.jsp"); // Recompile is not triggered even after example.jsp is updated
RequestDispatcher dispatcher = request.getRequestDispatcher("/test//example.jsp"); // Recompile is not triggered even after example.jsp is updated
dispatcher.forward(request, response);
%>

in the following directory structure:

example.war
├── index.jsp
├── test
│   └── example.jsp // <- Recompile is not triggered even after this JSP file is updated
└── WEB-INF
    └── web.xml

The same issue can happen with Sping Web MVC based application that has the following:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class DemoJspController {

    @RequestMapping("/test1")
    public String test1() {
        return "test/example"; // OK. Forwarinding to WEB-INF/jsp/test/example.jsp. Recompile works after the example.jsp is updated.
    }

    @RequestMapping("/test2")
    public String test2() {
        return "/test/example"; // forwarinding to WEB-INF/jsp//test/example.jsp. Recompile is not triggered even after the example.jsp is updated.
    }

}

with enabling JSP support in application.properties:

spring.mvc.view.prefix:/WEB-INF/jsp/
spring.mvc.view.suffix:.jsp

Resolution

This is caused by a bug that has been reported as This content is not included.JBEAP-21383/This content is not included.JBEAP-21387 and this has been fixed in JBoss EAP 7.3.7+ and JBoss EAP 7.4.1+.

Until the fix is incorporated, you need to fix your application not to forward a request to a non-canonicalized path.

Or, if it's difficult to fix all forward paths in your application, you can workaround this issue by using the following servlet filter that utilizes Undertow's internal CanonicalPathUtils API to canonicalize a forward path. See also the attached workaround-filter.zip for a concrete example maven project that you can build locally.

import java.io.IOException;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import io.undertow.util.CanonicalPathUtils;

@WebFilter("/*")
public class ExampleServletFilter implements Filter {

    private static final Logger log = Logger.getLogger(ExampleServletFilter.class.getName());
    protected FilterConfig filterConfig = null;

    public void destroy() {
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

        if (!(request instanceof HttpServletRequest)) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
            @Override
            public RequestDispatcher getRequestDispatcher(final String path) {
                final String canonicalPath = CanonicalPathUtils.canonicalize(path);
                // log.info("specified path = " + path + " -> canonicalized path = " + canonicalPath);
                return httpRequest.getRequestDispatcher(canonicalPath);
            }
        };

        chain.doFilter(wrappedRequest, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

}

If you want to enable the above servlet filter without packaging in your application, you can use the deployment overlay feature. For example, you can enable like the following JBoss-CLI command:

deployment-overlay add --name=workaround-canonicalize-forwarding-path-filter --content=/WEB-INF/lib/workaround-filter.jar=/path/to/location/of/workaround-filter/target/example-servlet-filter.jar --deployments=*.war --redeploy-affected

Note: after you update to the version that contains the fix forThis content is not included.JBEAP-21383, you can remove the above deployment overlay with the following CLI command:

deployment-overlay remove --name=workaround-canonicalize-forwarding-path-filter --redeploy-affected
Components
Category

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.