1616
1717package org .springframework .security .web .servlet .util .matcher ;
1818
19- import java .util .Collection ;
20- import java .util .LinkedHashMap ;
21- import java .util .Map ;
2219import java .util .Objects ;
23- import java .util .concurrent .atomic .AtomicReference ;
2420
25- import jakarta .servlet .ServletContext ;
26- import jakarta .servlet .ServletRegistration ;
2721import jakarta .servlet .http .HttpServletRequest ;
2822
2923import org .springframework .http .HttpMethod ;
4034
4135/**
4236 * A {@link RequestMatcher} that uses {@link PathPattern}s to match against each
43- * {@link HttpServletRequest}. The provided path should be relative to the servlet (that
44- * is, it should exclude any context or servlet path).
37+ * {@link HttpServletRequest}. The provided path should be relative to the context path
38+ * (that is, it should exclude any context path).
4539 *
4640 * <p>
47- * To also match the servlet, please see {@link PathPatternRequestMatcher#servletPath}
41+ * You can provide the servlet path in {@link PathPatternRequestMatcher#servletPath} and
42+ * reuse for multiple matchers.
4843 *
4944 * <p>
5045 * Note that the {@link org.springframework.web.servlet.HandlerMapping} that contains the
@@ -113,7 +108,7 @@ public MatchResult matcher(HttpServletRequest request) {
113108 if (!this .method .matches (request )) {
114109 return MatchResult .notMatch ();
115110 }
116- PathContainer path = getRequestPath (request ). pathWithinApplication ( );
111+ PathContainer path = getPathContainer (request );
117112 PathPattern .PathMatchInfo info = this .pattern .matchAndExtract (path );
118113 return (info != null ) ? MatchResult .match (info .getUriVariables ()) : MatchResult .notMatch ();
119114 }
@@ -122,11 +117,7 @@ void setMethod(RequestMatcher method) {
122117 this .method = method ;
123118 }
124119
125- void setServletPath (RequestMatcher servletPath ) {
126- this .servletPath = servletPath ;
127- }
128-
129- private RequestPath getRequestPath (HttpServletRequest request ) {
120+ private PathContainer getPathContainer (HttpServletRequest request ) {
130121 RequestPath path ;
131122 if (ServletRequestPathUtils .hasParsedRequestPath (request )) {
132123 path = ServletRequestPathUtils .getParsedRequestPath (request );
@@ -135,7 +126,8 @@ private RequestPath getRequestPath(HttpServletRequest request) {
135126 path = ServletRequestPathUtils .parseAndCache (request );
136127 ServletRequestPathUtils .clearParsedRequestPath (request );
137128 }
138- return path ;
129+ PathContainer contextPath = path .contextPath ();
130+ return path .subPath (contextPath .elements ().size ());
139131 }
140132
141133 /**
@@ -166,9 +158,6 @@ public String toString() {
166158 if (this .method instanceof HttpMethodRequestMatcher m ) {
167159 request .append (m .method .name ()).append (' ' );
168160 }
169- if (this .servletPath instanceof ServletPathRequestMatcher s ) {
170- request .append (s .path );
171- }
172161 return "PathPattern [" + request + this .pattern + "]" ;
173162 }
174163
@@ -194,17 +183,17 @@ public static final class Builder {
194183
195184 private final PathPatternParser parser ;
196185
197- private final RequestMatcher servletPath ;
186+ private final String servletPath ;
198187
199188 Builder () {
200189 this (PathPatternParser .defaultInstance );
201190 }
202191
203192 Builder (PathPatternParser parser ) {
204- this (parser , AnyRequestMatcher . INSTANCE );
193+ this (parser , "" );
205194 }
206195
207- Builder (PathPatternParser parser , RequestMatcher servletPath ) {
196+ Builder (PathPatternParser parser , String servletPath ) {
208197 this .parser = parser ;
209198 this .servletPath = servletPath ;
210199 }
@@ -215,7 +204,11 @@ public static final class Builder {
215204 * @return the {@link Builder} for more configuration
216205 */
217206 public Builder servletPath (String servletPath ) {
218- return new Builder (this .parser , new ServletPathRequestMatcher (servletPath ));
207+ Assert .notNull (servletPath , "servletPath cannot be null" );
208+ Assert .isTrue (servletPath .startsWith ("/" ), "servletPath must start with '/'" );
209+ Assert .isTrue (!servletPath .endsWith ("/" ), "servletPath must not end with a slash" );
210+ Assert .isTrue (!servletPath .contains ("*" ), "servletPath must not contain a star" );
211+ return new Builder (this .parser , servletPath );
219212 }
220213
221214 /**
@@ -286,14 +279,11 @@ public PathPatternRequestMatcher matcher(String path) {
286279 public PathPatternRequestMatcher matcher (@ Nullable HttpMethod method , String path ) {
287280 Assert .notNull (path , "pattern cannot be null" );
288281 Assert .isTrue (path .startsWith ("/" ), "pattern must start with a /" );
289- PathPattern pathPattern = this .parser .parse (path );
282+ PathPattern pathPattern = this .parser .parse (this . servletPath + path );
290283 PathPatternRequestMatcher requestMatcher = new PathPatternRequestMatcher (pathPattern );
291284 if (method != null ) {
292285 requestMatcher .setMethod (new HttpMethodRequestMatcher (method ));
293286 }
294- if (this .servletPath != AnyRequestMatcher .INSTANCE ) {
295- requestMatcher .setServletPath (this .servletPath );
296- }
297287 return requestMatcher ;
298288 }
299289
@@ -319,60 +309,4 @@ public String toString() {
319309
320310 }
321311
322- private static final class ServletPathRequestMatcher implements RequestMatcher {
323-
324- private final String path ;
325-
326- private final AtomicReference <Boolean > servletExists = new AtomicReference <>();
327-
328- ServletPathRequestMatcher (String servletPath ) {
329- Assert .notNull (servletPath , "servletPath cannot be null" );
330- Assert .isTrue (servletPath .startsWith ("/" ), "servletPath must start with '/'" );
331- Assert .isTrue (!servletPath .endsWith ("/" ), "servletPath must not end with a slash" );
332- Assert .isTrue (!servletPath .contains ("*" ), "servletPath must not contain a star" );
333- this .path = servletPath ;
334- }
335-
336- @ Override
337- public boolean matches (HttpServletRequest request ) {
338- Assert .isTrue (servletExists (request ), () -> this .path + "/* does not exist in your servlet registration "
339- + registrationMappings (request ));
340- return Objects .equals (this .path , ServletRequestPathUtils .getServletPathPrefix (request ));
341- }
342-
343- private boolean servletExists (HttpServletRequest request ) {
344- return this .servletExists .updateAndGet ((value ) -> {
345- if (value != null ) {
346- return value ;
347- }
348- if (request .getAttribute ("org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE" ) != null ) {
349- return true ;
350- }
351- for (ServletRegistration registration : request .getServletContext ()
352- .getServletRegistrations ()
353- .values ()) {
354- if (registration .getMappings ().contains (this .path + "/*" )) {
355- return true ;
356- }
357- }
358- return false ;
359- });
360- }
361-
362- private Map <String , Collection <String >> registrationMappings (HttpServletRequest request ) {
363- Map <String , Collection <String >> map = new LinkedHashMap <>();
364- ServletContext servletContext = request .getServletContext ();
365- for (ServletRegistration registration : servletContext .getServletRegistrations ().values ()) {
366- map .put (registration .getName (), registration .getMappings ());
367- }
368- return map ;
369- }
370-
371- @ Override
372- public String toString () {
373- return "ServletPath [" + this .path + "]" ;
374- }
375-
376- }
377-
378312}
0 commit comments