Uploaded image for project: 'Undertow'
  1. Undertow
  2. UNDERTOW-431

Wrong PathMatch returned from ServletPathMatchesData.prefixMatches

XMLWordPrintable

    • Hide

      After the sample application mentioned in the description starts, attempt to access http://localhost:8080, Undertow will give a StringIndexOutOfBoundsException.

      Show
      After the sample application mentioned in the description starts, attempt to access http://localhost:8080 , Undertow will give a StringIndexOutOfBoundsException.

      Sample Application to Reproduce

      Here is a sample (albeit not the most reduced) project that reproduces the problem: https://github.com/shakuzen/spring-security-angular/tree/master/oauth2/ui
      (You can run the 'ui' application using mvn spring-boot:run from /oauth2/ui relative to the project root which will run the Spring Boot application using an embedded Undertow version 1.2.3.Final)

      Steps to Reproduce and Stacktrace

      After the application starts, when you attempt to go to http://localhost:8080, Undertow will give the following error:

      java.lang.StringIndexOutOfBoundsException: String index out of range: -4
      	at java.lang.String.substring(Unknown Source)
      	at io.undertow.servlet.handlers.ServletPathMatch.<init>(ServletPathMatch.java:47)
      	at io.undertow.servlet.handlers.ServletPathMatchesData.handleMatch(ServletPathMatchesData.java:88)
      	at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:75)
      	at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:83)
      	at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:125)
      	at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65)
      	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199)
      	at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:227)
      	at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:128)
      	at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:143)
      	at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:90)
      	at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:49)
      	at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
      	at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291)
      	at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286)
      	at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
      	at org.xnio.nio.NioTcpServerHandle.handleReady(NioTcpServerHandle.java:53)
      	at org.xnio.nio.WorkerThread.run(WorkerThread.java:539)
      

      Root Cause Analysis

      There are two servlets mapped by Spring as you can see in the log output:

      2015-05-05 15:55:34.970  INFO 12656 --- [main] o.s.b.c.e.ServletRegistrationBean: Mapping servlet: 'zuulServlet' to [/zuul/*]
      2015-05-05 15:55:34.977  INFO 12656 --- [main] o.s.b.c.e.ServletRegistrationBean: Mapping servlet: 'dispatcherServlet' to [/]
      

      Then when ServletPathMatchesData.getServletHandlerByPath(String) is called ServletPathMatchesData.prefixMatches will have data like the following:

      index contents
      0 /zuul
      1 io.undertow.util.SubstringMap$SubstringMatch@1001ea29
      2 ""
      3 io.undertow.util.SubstringMap$SubstringMatch@54ad5475

      Where indexes 1 and 3 are the SubstringMatch with PathMatch object containing the Zuul servlet and (default) dispatcherServlet respectively.

      So far, things look alright. next it will make the call prefixMatches.get("/", 1) . This will return null because nothing in the above table matches "/".

      Next it will make the call prefixMatches.get("/", 0, which will match the first thing in the table regardless of its key because it is a zero length substring match. It happens to be that the Zuul servlet is in the table before the default one with correct mapping "".

      Lastly it tries to instantiate a SerlvetPathMatch with a uri of "/" but target.getServletPath() returns "/zuul" (because it is the Zuul Servlet that is erroneously chosen as the target) which causes the StringIndexOutOfBoundsException mentioned above when it attempts to do remaining = uri.substring(matched.length()); (line 47 of ServletPathMatch in Undertow 1.2.3.Final)

      This issue does not happen on Undertow 1.1.x because it uses a regular Map (full string matches) instead of the custom-made SubstringMap:

      Unable to find source-code formatter for language: servletpathmatchesdata.java (1.1.3.final). Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      final String part = path.substring(0, i);
      match = prefixMatches.get(part);
      

      Which, in this described case, will do a full-string match (map lookup) for "" instead of a zero-length substring match that will match anything (thus returning the first thing it checks).

      Possible solution

      Perhaps for the case when length is 0 an exact check should be performed instead of returning the first key checked. This could be done internally in SubstringMap or by the places that call its methods. Either way, I think it would be good to document this behavior in the JavaDoc comments.

      Side note

      On 1.2.0.Final, you will not experience this issue but due to a different bug that appears to have been fixed. The table given above will only have one entry because the Zuul Servlet that originally gets input will be (partially) overwritten by the default dispatcherServlet. This is due to the logic in ServletPathMatchesData.Builder.addPrefixMatch() and again the zero-length substring match returning the first item it checks. It has been changed to use getExact in 1.2.3.Final.

              sdouglas1@redhat.com Stuart Douglas (Inactive)
              tommy.ludwig Tommy Ludwig (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated:
                Resolved: