Browse Source

Improve regex support for URL path matching

Closes gh-28815
pull/28850/head
rstoyanchev 3 years ago
parent
commit
cdd4e8cd7f
  1. 6
      spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
  2. 3
      spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java
  3. 14
      spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java
  4. 10
      spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java
  5. 3
      spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java
  6. 19
      src/docs/asciidoc/web/webmvc.adoc

6
spring-core/src/main/java/org/springframework/util/AntPathMatcher.java

@ -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));
} }
} }

3
spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java

@ -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

14
spring-web/src/main/java/org/springframework/web/util/pattern/CaptureVariablePathElement.java

@ -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);
}
} }
} }

10
spring-web/src/main/java/org/springframework/web/util/pattern/RegexPathElement.java

@ -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() {

3
spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java

@ -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

19
src/docs/asciidoc/web/webmvc.adoc

@ -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]]

Loading…
Cancel
Save