Uploaded image for project: 'JBoss Enterprise Application Platform'
  1. JBoss Enterprise Application Platform
  2. JBEAP-21387

[GSS](7.4.z) UNDERTOW-1873 - JSP file does not recompile when forwarding a request path is not canonicalized in exploded deployment

    XMLWordPrintable

    Details

    • Target Release:
    • Steps to Reproduce:
      Hide

      Normal Servlet/JSP reproducer (example.war.zip)

      1. Extract example.war.zip
      2. Deploy example.war as an unmanaged deployment

      deploy /path/to/example.war --unmanaged
      

      (or put example.war under your $JBOSS_HOME/standalone/deployments/ and create an empty example.war.dodeploy)
      3. Send a request

      curl -v http://localhost:8080/example/index.jsp
      

      4. Update /path/to/example.war/test/example.jsp
      5. Send a new request

      curl -v http://localhost:8080/example/index.jsp
      

      The update is not reflected as recompile is not triggered.

      Spring Boot based reproducer (sb-app-reproducer.zip)

      1. Extract sb-app-reproducer.zip
      2. Deploy demo.war as an unmanaged deployment

      deploy /path/to/demo.war --unmanaged
      

      (or put demo.war under your $JBOSS_HOME/standalone/deployments/ and create an empty demo.war.dodeploy)
      3. Send a request

      curl -v http://localhost:8080/demo/test2
      

      4. Update /path/to/demo.war/WEB-INF/jsp/test/example.jsp
      5. Send a new request

      curl -v http://localhost:8080/demo/test2
      

      The update is not reflected as recompile is not triggered.

      Show
      Normal Servlet/JSP reproducer (example.war.zip) 1. Extract example.war.zip 2. Deploy example.war as an unmanaged deployment deploy /path/to/example.war --unmanaged (or put example.war under your $JBOSS_HOME/standalone/deployments/ and create an empty example.war.dodeploy) 3. Send a request curl -v http: //localhost:8080/example/index.jsp 4. Update /path/to/example.war/test/example.jsp 5. Send a new request curl -v http: //localhost:8080/example/index.jsp The update is not reflected as recompile is not triggered. — Spring Boot based reproducer (sb-app-reproducer.zip) 1. Extract sb-app-reproducer.zip 2. Deploy demo.war as an unmanaged deployment deploy /path/to/demo.war --unmanaged (or put demo.war under your $JBOSS_HOME/standalone/deployments/ and create an empty demo.war.dodeploy) 3. Send a request curl -v http: //localhost:8080/demo/test2 4. Update /path/to/demo.war/WEB-INF/jsp/test/example.jsp 5. Send a new request curl -v http: //localhost:8080/demo/test2 The update is not reflected as recompile is not triggered.
    • Workaround Description:
      Hide

      You can workaround this issue by using the following servlet filter that can utilize Undertow's CanonicalPathUtils API to canonicalize a forward path.

      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 {
          }
      
      }
      
      Show
      You can workaround this issue by using the following servlet filter that can utilize Undertow's CanonicalPathUtils API to canonicalize a forward path. 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 { } }

      Description

      JSP files can be updated and changes can automatically be detected through XNIO FilySystemWatcher for the exploded deployment. So, the next request can basically trigger recompiling of the updated JSP file without redeployment.
      However, JSP recompile does not happen when a request is forward to the JSP file with a non-canonicalize path.

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

      index.jsp
      <%
      // 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 triggerd even after this is updated
      └── WEB-INF
          └── web.xml
      

      Of course, we can easily notice that the forwarded path is not canonicalized and not correct with the above simple example.

      However, it's sometimes not easy to notice. For example, if the customer's application is Sping Boot based application and their controller has the following controller:

      DemoJspController.java
      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
          }
      
          @RequestMapping("/test2")
          public String test2() {
              return "/test/example"; // recompile is not triggered even after WEB-INF/jsp/test/example.jsp is updated
          }
      
      }
      

      with JSP support:

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

      The above "/test1" and "/test2" forward to the same "WEB-INF/jsp/test/example.jsp", but recompile is not triggered eve after example.jsp is updated in the latter one.

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              soul2zimate Chao Wang
              Reporter:
              soul2zimate Chao Wang
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: