jetty/jetty.project

Issue with whitespace in header on Jetty 12.x

Closed this issue · 6 comments

Jetty Version
12.0.10

Jetty Environment
ee10

Java Version
22

Question

I have a question, since I noticed a difference in behaviour between Jetty 12.0.10 and 11.0.20 and I'm not sure how to resolve it. I have a following multipart request which I realize isn't formatted as it should be. Essentially this is an example of badly formatted request that we receive on our endpoint.

This request was processed successfully on Jetty 11.0.20, however after upgrade I'm getting following exception invalid leading whitespace before header

Is there a way to override this behavior since I'm not finding any way.

POST http://localhost:11185/test
Content-Type: multipart/form-data; boundary=boundary
Authorization: App **

--boundary
Content-Disposition: form-data; 
           name="from"

test@test.com
--boundary--

@magdalynv can you post the stacktrace?

I've attached the stacktrace. Thanks
stacktrace.txt

org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:131)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:110)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:86)
	at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:112)
	at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1227)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:547)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
	at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:736)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1614)
	at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1547)
	at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:824)
	at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:436)
	at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:464)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
	at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:703)
	at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:858)
	at org.eclipse.jetty.server.Server.handle(Server.java:181)
	at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:648)
	at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:403)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
	at java.base/java.lang.Thread.run(Thread.java:1623)
Caused by: jakarta.servlet.ServletException: org.eclipse.jetty.http.BadMessageException: 400: bad multipart
	at org.eclipse.jetty.ee10.servlet.ServletApiRequest.getParts(ServletApiRequest.java:720)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:93)
	... 44 more
Caused by: org.eclipse.jetty.http.BadMessageException: 400: bad multipart
	... 46 more
Caused by: org.eclipse.jetty.http.BadMessageException: 400: invalid leading whitespace before header
	at org.eclipse.jetty.http.MultiPart$Parser.parseHeaderStart(MultiPart.java:1256)
	at org.eclipse.jetty.http.MultiPart$Parser.parse(MultiPart.java:1080)
	at org.eclipse.jetty.http.MultiPartFormData$Parser$1.parse(MultiPartFormData.java:311)
	at org.eclipse.jetty.http.MultiPartFormData$Parser$1.parse(MultiPartFormData.java:301)
	at org.eclipse.jetty.io.content.ContentSourceCompletableFuture.parse(ContentSourceCompletableFuture.java:104)
	at org.eclipse.jetty.http.MultiPartFormData$Parser.parse(MultiPartFormData.java:326)
	at org.eclipse.jetty.http.MultiPartFormData.from(MultiPartFormData.java:109)
	at org.eclipse.jetty.ee10.servlet.ServletMultiPartFormData.from(ServletMultiPartFormData.java:138)
	at org.eclipse.jetty.ee10.servlet.ServletMultiPartFormData.from(ServletMultiPartFormData.java:62)
	at org.eclipse.jetty.ee10.servlet.ServletApiRequest.getParts(ServletApiRequest.java:637)
	... 45 more

--boundary
Content-Disposition: form-data;
name="from"

The multipart/form-data spec (RFC7578) says these 3 lines are ...

  1. A boundary line
  2. A Content-Disposition header with a single attribute form-data (which has no value)
  3. Invalid line that doesn't fit either a Header field or a end of headers line.

The failure "invalid leading whitespace before header" (line 3) is telling you that after the Content-Disposition field, the parser didn't see a end-of-headers line, so it is expecting another header, but the parser didn't see the mandated non-whitespace ASCII character at the start of the line for a header field, hence the exception.

The form of header you are using is whats known as "line folding".
Line folding has never been supported for the multipart/form-data or multipart/byteranges content types (both declared in 1998) seen in HTTP.
Line folding is a concept for multipart/related (RFC2110 declared in 1997) seen in email and MHTML where there are line length restrictions.
Note that content types multipart/mixed, multipart/alternative, multipart/digest, and multipart/parallel (RFC1341 declared in 1992) also do not support header "line folding".

You have a few choices in front of you ...

  1. (The Best Choice) - fix the multipart/form-data syntax that is being generated before submitting it to Jetty.
  2. Fall back to ee8 / ee9 and use the LEGACY (buggy and SLOW) multipart/form-data parser via the MultiPartCompliance configuration in HttpConfiguration (not available on ee10 and newer). - (to give an idea on how slow, a 1MB multipart/form-data takes about 800ms to parse on the ee10 spec compliant parser, or about 3 minutes on the LEGACY parser)
  3. Stick with ee10 and reject bad multipart/form-data syntax properly, dropping and ignoring bad multipart/form-data

Hi,

sorry for a later response. Thanks for the explanation.

I realize that the request itself is a bad request due to folding lines, but this is a request that was handled fine on the previous version of Jetty. I just wanted to check what caused this difference in behavior.

Thank you for the hint how to use legacy option if it comes to that.

I realize that the request itself is a bad request due to folding lines, but this is a request that was handled fine on the previous version of Jetty. I just wanted to check what caused this difference in behavior.

We constantly update the implementation to expose less attack surface, and folding lines are one such attack vectors, and that's why by default they now result in a 400 response.

If you really must support the old behavior, you can configure so.

Perfect, thank you for the reply and your help