Improve regex support for URL path matching
Closes gh-28815
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -701,8 +701,8 @@ public class AntPathMatcher implements PathMatcher {
|
|||||||
else {
|
else {
|
||||||
this.exactMatch = false;
|
this.exactMatch = false;
|
||||||
patternBuilder.append(quote(pattern, end, pattern.length()));
|
patternBuilder.append(quote(pattern, end, pattern.length()));
|
||||||
this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) :
|
this.pattern = Pattern.compile(patternBuilder.toString(),
|
||||||
Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));
|
Pattern.DOTALL | (this.caseSensitive ? 0 : Pattern.CASE_INSENSITIVE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -131,6 +131,7 @@ class AntPathMatcherTests {
|
|||||||
|
|
||||||
assertThat(pathMatcher.match("/{bla}.*", "/testing.html")).isTrue();
|
assertThat(pathMatcher.match("/{bla}.*", "/testing.html")).isTrue();
|
||||||
assertThat(pathMatcher.match("/{bla}", "//x\ny")).isTrue();
|
assertThat(pathMatcher.match("/{bla}", "//x\ny")).isTrue();
|
||||||
|
assertThat(pathMatcher.match("/{var:.*}", "/x\ny")).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -59,15 +59,9 @@ class CaptureVariablePathElement extends PathElement {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.variableName = new String(captureDescriptor, 1, colon - 1);
|
this.variableName = new String(captureDescriptor, 1, colon - 1);
|
||||||
if (caseSensitive) {
|
this.constraintPattern = Pattern.compile(
|
||||||
this.constraintPattern = Pattern.compile(
|
new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2),
|
||||||
new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2));
|
Pattern.DOTALL | (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE));
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.constraintPattern = Pattern.compile(
|
|
||||||
new String(captureDescriptor, colon + 1, captureDescriptor.length - colon - 2),
|
|
||||||
Pattern.CASE_INSENSITIVE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -108,12 +108,8 @@ class RegexPathElement extends PathElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patternBuilder.append(quote(text, end, text.length()));
|
patternBuilder.append(quote(text, end, text.length()));
|
||||||
if (this.caseSensitive) {
|
return Pattern.compile(patternBuilder.toString(),
|
||||||
return Pattern.compile(patternBuilder.toString());
|
Pattern.DOTALL | (this.caseSensitive ? 0 : Pattern.CASE_INSENSITIVE));
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getVariableNames() {
|
public List<String> getVariableNames() {
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ public class PathPatternTests {
|
|||||||
checkCapture("{var:f o}","f%20o","var","f o"); // constraint is expressed in non encoded form
|
checkCapture("{var:f o}","f%20o","var","f o"); // constraint is expressed in non encoded form
|
||||||
checkCapture("{var:f.o}","f%20o","var","f o");
|
checkCapture("{var:f.o}","f%20o","var","f o");
|
||||||
checkCapture("{var:f\\|o}","f%7co","var","f|o");
|
checkCapture("{var:f\\|o}","f%7co","var","f|o");
|
||||||
|
checkCapture("{var:.*}","x\ny","var","x\ny");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -319,6 +320,8 @@ public class PathPatternTests {
|
|||||||
checkCapture("/{var1}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
checkCapture("/{var1}_ _{var2}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||||
checkCapture("/{var1}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
checkCapture("/{var1}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||||
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||||
|
checkCapture("/{var1:f o}_ _{var2:f\\|o}","/f%20o_%20_f%7co","var1","f o","var2","f|o");
|
||||||
|
checkCapture("/{var1}_{var2}","/f\noo_foo","var1","f\noo","var2","foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -648,7 +648,7 @@ See <<mvc-config-interceptors>> in the section on MVC configuration for examples
|
|||||||
configure interceptors. You can also register them directly by using setters on individual
|
configure interceptors. You can also register them directly by using setters on individual
|
||||||
`HandlerMapping` implementations.
|
`HandlerMapping` implementations.
|
||||||
|
|
||||||
Note that `postHandle` is less useful with `@ResponseBody` and `ResponseEntity` methods for
|
`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for
|
||||||
which the response is written and committed within the `HandlerAdapter` and before
|
which the response is written and committed within the `HandlerAdapter` and before
|
||||||
`postHandle`. That means it is too late to make any changes to the response, such as adding
|
`postHandle`. That means it is too late to make any changes to the response, such as adding
|
||||||
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
|
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
|
||||||
@@ -657,6 +657,7 @@ declare it as an <<mvc-ann-controller-advice>> bean or configure it directly on
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[mvc-exceptionhandlers]]
|
[[mvc-exceptionhandlers]]
|
||||||
=== Exceptions
|
=== Exceptions
|
||||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-exceptions, WebFlux>>#
|
[.small]#<<web-reactive.adoc#webflux-dispatcher-exceptions, WebFlux>>#
|
||||||
@@ -5362,7 +5363,6 @@ the following example shows:
|
|||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(new LocaleChangeInterceptor());
|
registry.addInterceptor(new LocaleChangeInterceptor());
|
||||||
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
|
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
|
||||||
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@@ -5376,7 +5376,6 @@ the following example shows:
|
|||||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||||
registry.addInterceptor(LocaleChangeInterceptor())
|
registry.addInterceptor(LocaleChangeInterceptor())
|
||||||
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
||||||
registry.addInterceptor(SecurityInterceptor()).addPathPatterns("/secure/*")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@@ -5392,13 +5391,19 @@ The following example shows how to achieve the same configuration in XML:
|
|||||||
<mvc:exclude-mapping path="/admin/**"/>
|
<mvc:exclude-mapping path="/admin/**"/>
|
||||||
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
|
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
|
||||||
</mvc:interceptor>
|
</mvc:interceptor>
|
||||||
<mvc:interceptor>
|
|
||||||
<mvc:mapping path="/secure/*"/>
|
|
||||||
<bean class="org.example.SecurityInterceptor"/>
|
|
||||||
</mvc:interceptor>
|
|
||||||
</mvc:interceptors>
|
</mvc:interceptors>
|
||||||
----
|
----
|
||||||
|
|
||||||
|
NOTE: Mapped interceptors are not ideally suited as a security layer due to the potential
|
||||||
|
for a mismatch with annotated controller path matching, which can also match trailing
|
||||||
|
slashes and path extensions transparently, along with other path matching options. Many
|
||||||
|
of these options have been deprecated but the potential for a mismatch remains.
|
||||||
|
Generally, we recommend using Spring Security which includes a dedicated
|
||||||
|
https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher]
|
||||||
|
to align with Spring MVC path matching and also has a security firewall that blocks many
|
||||||
|
unwanted characters in URL paths.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[mvc-config-content-negotiation]]
|
[[mvc-config-content-negotiation]]
|
||||||
|
|||||||
Reference in New Issue
Block a user