1616
1717package org .springframework .security .web .servlet .util .matcher ;
1818
19- import java .nio .charset .StandardCharsets ;
2019import java .util .Collection ;
2120import java .util .LinkedHashMap ;
2221import java .util .Map ;
2322import java .util .Objects ;
2423import java .util .concurrent .atomic .AtomicReference ;
2524
26- import jakarta .servlet .RequestDispatcher ;
2725import jakarta .servlet .ServletContext ;
2826import jakarta .servlet .ServletRegistration ;
29- import jakarta .servlet .http .HttpServletMapping ;
3027import jakarta .servlet .http .HttpServletRequest ;
31- import jakarta .servlet .http .MappingMatch ;
3228
3329import org .springframework .http .HttpMethod ;
3430import org .springframework .http .server .PathContainer ;
3935import org .springframework .security .web .util .matcher .MethodPatternRequestMatcherFactory ;
4036import org .springframework .security .web .util .matcher .RequestMatcher ;
4137import org .springframework .util .Assert ;
42- import org .springframework .util .ObjectUtils ;
4338import org .springframework .web .util .ServletRequestPathUtils ;
44- import org .springframework .web .util .UriUtils ;
45- import org .springframework .web .util .WebUtils ;
4639import org .springframework .web .util .pattern .PathPattern ;
4740import org .springframework .web .util .pattern .PathPatternParser ;
4841
@@ -88,9 +81,7 @@ private PathPatternRequestMatcher(PathPattern pattern) {
8881 * prefix
8982 * <p>
9083 * When there is no context path, then these URIs are effectively absolute.
91- * @return a {@link PathPatternRequestMatcher.Builder} that treats URIs as relative to
92- * the context path, if any
93- * @since 6.5
84+ * @return a {@link Builder} that treats URIs as relative to the context path, if any
9485 */
9586 public static Builder path () {
9687 return new Builder ();
@@ -109,17 +100,25 @@ public static Builder path() {
109100 * {@link HttpServletRequest#getServletPath()} would return {@code /path} and so
110101 * {@code /path} is what is specified here.
111102 *
103+ * <p>
112104 * Specify the path here without the trailing {@code /*}.
113- * @return a {@link PathPatternRequestMatcher.Builder} that treats URIs as relative to
114- * the given {@code servletPath}
115- * @since 6.5
105+ * @return a {@link Builder} that treats URIs as relative to the given
106+ * {@code servletPath}
116107 */
117108 public static Builder servletPath (String servletPath ) {
118- Assert .notNull (servletPath , "servletPath cannot be null" );
119- Assert .isTrue (servletPath .startsWith ("/" ), "servletPath must start with '/'" );
120- Assert .isTrue (!servletPath .endsWith ("/" ), "servletPath must not end with a slash" );
121- Assert .isTrue (!servletPath .contains ("*" ), "servletPath must not contain a star" );
122- return new Builder (new ServletPathRequestMatcher (servletPath ));
109+ return new Builder ().servletPath (servletPath );
110+ }
111+
112+ /**
113+ * Use this {@link PathPatternParser} to parse path patterns. Uses
114+ * {@link PathPatternParser#defaultInstance} by default.
115+ * @param parser the {@link PathPatternParser} to use
116+ * @return a {@link Builder} that treats URIs as relative to the given
117+ * {@code servletPath}
118+ */
119+ public static Builder withPathPatternParser (PathPatternParser parser ) {
120+ Assert .notNull (parser , "pathPatternParser cannot be null" );
121+ return new Builder (parser );
123122 }
124123
125124 /**
@@ -212,16 +211,27 @@ public String toString() {
212211 */
213212 public static final class Builder implements MethodPatternRequestMatcherFactory {
214213
215- private PathPatternParser parser = PathPatternParser . defaultInstance ;
214+ private final PathPatternParser parser ;
216215
217- private final RequestMatcher servletPath ;
216+ private RequestMatcher servletPath = AnyRequestMatcher . INSTANCE ;
218217
219- Builder () {
220- this ( AnyRequestMatcher . INSTANCE ) ;
218+ private Builder () {
219+ this . parser = PathPatternParser . defaultInstance ;
221220 }
222221
223- Builder (RequestMatcher servletPath ) {
224- this .servletPath = servletPath ;
222+ private Builder (PathPatternParser parser ) {
223+ this .parser = parser ;
224+ }
225+
226+ /**
227+ * Match requests starting with this {@code servletPath}.
228+ * @param servletPath the servlet path prefix
229+ * @see PathPatternRequestMatcher#servletPath
230+ * @return the {@link Builder} for more configuration
231+ */
232+ public Builder servletPath (String servletPath ) {
233+ this .servletPath = new ServletPathRequestMatcher (servletPath );
234+ return this ;
225235 }
226236
227237 /**
@@ -298,14 +308,18 @@ private static final class ServletPathRequestMatcher implements RequestMatcher {
298308 private final AtomicReference <Boolean > servletExists = new AtomicReference <>();
299309
300310 ServletPathRequestMatcher (String servletPath ) {
311+ Assert .notNull (servletPath , "servletPath cannot be null" );
312+ Assert .isTrue (servletPath .startsWith ("/" ), "servletPath must start with '/'" );
313+ Assert .isTrue (!servletPath .endsWith ("/" ), "servletPath must not end with a slash" );
314+ Assert .isTrue (!servletPath .contains ("*" ), "servletPath must not contain a star" );
301315 this .path = servletPath ;
302316 }
303317
304318 @ Override
305319 public boolean matches (HttpServletRequest request ) {
306320 Assert .isTrue (servletExists (request ), () -> this .path + "/* does not exist in your servlet registration "
307321 + registrationMappings (request ));
308- return Objects .equals (this .path , getServletPathPrefix (request ));
322+ return Objects .equals (this .path , ServletRequestPathUtils . getServletPathPrefix (request ));
309323 }
310324
311325 private boolean servletExists (HttpServletRequest request ) {
@@ -336,20 +350,6 @@ private Map<String, Collection<String>> registrationMappings(HttpServletRequest
336350 return map ;
337351 }
338352
339- @ Nullable
340- private static String getServletPathPrefix (HttpServletRequest request ) {
341- HttpServletMapping mapping = (HttpServletMapping ) request .getAttribute (RequestDispatcher .INCLUDE_MAPPING );
342- mapping = (mapping != null ) ? mapping : request .getHttpServletMapping ();
343- if (ObjectUtils .nullSafeEquals (mapping .getMappingMatch (), MappingMatch .PATH )) {
344- String servletPath = (String ) request .getAttribute (WebUtils .INCLUDE_SERVLET_PATH_ATTRIBUTE );
345- servletPath = (servletPath != null ) ? servletPath : request .getServletPath ();
346- servletPath = servletPath .endsWith ("/" ) ? servletPath .substring (0 , servletPath .length () - 1 )
347- : servletPath ;
348- return UriUtils .encodePath (servletPath , StandardCharsets .UTF_8 );
349- }
350- return null ;
351- }
352-
353353 @ Override
354354 public String toString () {
355355 return "ServletPath [" + this .path + "]" ;
0 commit comments