Skip to content

Commit e9fb5eb

Browse files
committed
Respect forwarded path in UrlHandlerFilter
Closes gh-35509
1 parent 5a85891 commit e9fb5eb

File tree

2 files changed

+54
-23
lines changed

2 files changed

+54
-23
lines changed

spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Map;
2323
import java.util.function.Consumer;
2424

25+
import jakarta.servlet.DispatcherType;
2526
import jakarta.servlet.FilterChain;
2627
import jakarta.servlet.ServletException;
2728
import jakarta.servlet.http.HttpServletRequest;
@@ -43,8 +44,8 @@
4344
import org.springframework.web.util.pattern.PathPatternParser;
4445

4546
/**
46-
* {@link jakarta.servlet.Filter} that modifies the URL, and then redirects or
47-
* wraps the request to apply the change.
47+
* {@link jakarta.servlet.Filter} that modifies the URL, and then either
48+
* redirects or wraps the request to effect the change.
4849
*
4950
* <p>To create an instance, you can use the following:
5051
*
@@ -55,8 +56,8 @@
5556
* .build();
5657
* </pre>
5758
*
58-
* <p>This {@code Filter} should be ordered after {@link ForwardedHeaderFilter}
59-
* and before any security filters.
59+
* <p>This {@code Filter} should be ordered after {@link ForwardedHeaderFilter},
60+
* before {@link ServletRequestPathFilter}, and before security filters.
6061
*
6162
* @author Rossen Stoyanchev
6263
* @since 6.2
@@ -332,9 +333,7 @@ public void handleInternal(HttpServletRequest request, HttpServletResponse respo
332333
hasPathInfo ? trimTrailingSlash(pathInfo) : pathInfo);
333334

334335
RequestPath previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
335-
if (previousPath != null) {
336-
ServletRequestPathUtils.parseAndCache(request);
337-
}
336+
ServletRequestPathUtils.clearParsedRequestPath(request);
338337
try {
339338
chain.doFilter(request, response);
340339
}
@@ -372,22 +371,30 @@ private static class TrailingSlashHttpServletRequest extends HttpServletRequestW
372371

373372
@Override
374373
public String getRequestURI() {
375-
return this.requestURI;
374+
return (isForward() ? getDelegate().getRequestURI() : this.requestURI);
376375
}
377376

378377
@Override
379378
public StringBuffer getRequestURL() {
380-
return this.requestURL;
379+
return (isForward() ? getDelegate().getRequestURL() : this.requestURL);
381380
}
382381

383382
@Override
384383
public String getServletPath() {
385-
return this.servletPath;
384+
return (isForward() ? getDelegate().getServletPath() : this.servletPath);
386385
}
387386

388387
@Override
389388
public String getPathInfo() {
390-
return this.pathInfo;
389+
return (isForward() ? getDelegate().getPathInfo() : this.pathInfo);
390+
}
391+
392+
private boolean isForward() {
393+
return (getDispatcherType() == DispatcherType.FORWARD);
394+
}
395+
396+
private HttpServletRequest getDelegate() {
397+
return (HttpServletRequest) getRequest();
391398
}
392399
}
393400

spring-web/src/test/java/org/springframework/web/filter/UrlHandlerFilterTests.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020

21+
import jakarta.servlet.DispatcherType;
2122
import jakarta.servlet.ServletException;
2223
import jakarta.servlet.http.HttpServlet;
2324
import jakarta.servlet.http.HttpServletRequest;
@@ -127,20 +128,20 @@ private static void testNoUrlHandling(String pattern, String contextPath, String
127128
assertThat(response.isCommitted()).isFalse();
128129
}
129130

130-
@Test
131+
@Test // gh-35538
131132
void shouldNotFilterErrorAndAsyncDispatches() {
132133
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/**").wrapRequest().build();
133134

134135
assertThat(filter.shouldNotFilterAsyncDispatch())
135-
.as("Should not filter async dispatch: wrapped request is reused")
136+
.as("Should not filter ASYNC dispatch as wrapped request is reused")
136137
.isTrue();
137138

138139
assertThat(filter.shouldNotFilterErrorDispatch())
139-
.as("Should not filter error dispatch: it's a different path")
140+
.as("Should not filter ERROR dispatch as it's an internal, fixed path")
140141
.isTrue();
141142
}
142143

143-
@Test
144+
@Test // gh-35538
144145
void shouldNotCacheParsedPath() throws Exception {
145146
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/*").wrapRequest().build();
146147

@@ -155,8 +156,8 @@ void shouldNotCacheParsedPath() throws Exception {
155156
.isFalse();
156157
}
157158

158-
@Test
159-
void shouldReplaceCachedPath() throws Exception {
159+
@Test // gh-35538
160+
void shouldClearPreviouslyCachedPath() throws Exception {
160161
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/*").wrapRequest().build();
161162

162163
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path/123/");
@@ -165,16 +166,38 @@ void shouldReplaceCachedPath() throws Exception {
165166
ServletRequestPathUtils.parseAndCache(request);
166167
assertThat(ServletRequestPathUtils.getParsedRequestPath(request).value()).isEqualTo("/path/123/");
167168

168-
PathSavingServlet servlet = new PathSavingServlet();
169+
PathServlet servlet = new PathServlet();
169170
MockFilterChain chain = new MockFilterChain(servlet);
170171
filter.doFilterInternal(request, new MockHttpServletResponse(), chain);
171172

172-
assertThat(servlet.getParsedPath()).isEqualTo("/path/123");
173-
assertThat(ServletRequestPathUtils.getParsedRequestPath(request).value()).isEqualTo("/path/123/");
173+
assertThat(servlet.getParsedPath()).isNull();
174+
}
175+
176+
@Test // gh-35509
177+
void shouldRespectForwardedPath() throws Exception {
178+
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/requestURI/*").wrapRequest().build();
179+
180+
String requestURI = "/requestURI/123/";
181+
MockHttpServletRequest originalRequest = new MockHttpServletRequest("GET", requestURI);
182+
originalRequest.setServletPath(requestURI);
183+
184+
MockFilterChain chain = new MockFilterChain();
185+
filter.doFilterInternal(originalRequest, new MockHttpServletResponse(), chain);
186+
187+
HttpServletRequest wrapped = (HttpServletRequest) chain.getRequest();
188+
assertThat(wrapped).isNotNull().isNotSameAs(originalRequest);
189+
assertThat(wrapped.getRequestURI()).isEqualTo("/requestURI/123");
190+
191+
// Change dispatcher type of underlying requests
192+
originalRequest.setDispatcherType(DispatcherType.FORWARD);
193+
assertThat(wrapped.getRequestURI())
194+
.as("Should delegate to underlying request for the requestURI on FORWARD")
195+
.isEqualTo(requestURI);
174196
}
175197

176198

177-
private static class PathSavingServlet extends HttpServlet {
199+
@SuppressWarnings("serial")
200+
private static class PathServlet extends HttpServlet {
178201

179202
private String parsedPath;
180203

@@ -183,8 +206,9 @@ public String getParsedPath() {
183206
}
184207

185208
@Override
186-
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
187-
this.parsedPath = ServletRequestPathUtils.getParsedRequestPath(request).value();
209+
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
210+
this.parsedPath = (ServletRequestPathUtils.hasParsedRequestPath(request) ?
211+
ServletRequestPathUtils.getParsedRequestPath(request).value() : null);
188212
}
189213
}
190214

0 commit comments

Comments
 (0)