UrlHandlerFilter also removes query parameters and fragment when using HTTP redirect
Spring Module and Version
Spring Web – v7.0.1
Issue Description
When using the RedirectTrailingSlashHandler variant of the UrlHandlerFilter, the path portion of the request URI is stripped of it's trailing slash, and the result is used as the new Location header. The location becomes a relative URL to the original request.
E.g. for a request to https://www.example.com/foo/bar/////:
- the path of the request URI will be
/foo/bar///// - the
Locationheader will be set to/foo/bar - the client will redirect to
https://www.example.com/foo/bar
However for the same request with query parameters and/or a fragment, such as https://www.example.com/foo/bar/////?key=value#fragment:
- the path of the request URI will still be
/foo/bar///// - the
Locationheader will again be set to/foo/bar - the client will redirect to
https://www.example.com/foo/barand the query parameters and fragment will become lost
Expected Behaviour
The redirect should remove the trailing slashes from the path only, and should not remove the query parameters and fragment from the original request, as they are important parts of the original request.
E.g. for https://www.example.com/foo/bar/////?key=value#fragment, the Location header should be either of the following:
/foo/bar?key=value#fragment(relative)https://www.example.com/foo/bar?key=value#fragment(absolute)
Code Reference
This issue affects both the standard and reactive implementations of UrlHandlerFilter, so should be fixed for both classes:
-
Standard:
@Override public void handleInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException { response.resetBuffer(); response.setStatus(this.httpStatus.value()); response.setHeader(HttpHeaders.LOCATION, trimTrailingSlash(request.getRequestURI())); response.flushBuffer(); } -
Reactive:
@Override public Mono<Void> handleInternal(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(this.statusCode); response.getHeaders().set(HttpHeaders.LOCATION, trimTrailingSlash(exchange.getRequest())); return Mono.empty(); } As an aside, I think the reactive variant should return
response.setComplete()instead ofMono.empty()?