Commit dd2f20fc authored by Stephane Nicoll's avatar Stephane Nicoll

Polish "Add support for configuring Tomcat's relaxed path and query chars"

See gh-17510
parent 1fee797a
...@@ -397,28 +397,26 @@ public class ServerProperties { ...@@ -397,28 +397,26 @@ public class ServerProperties {
private List<String> additionalTldSkipPatterns = new ArrayList<>(); private List<String> additionalTldSkipPatterns = new ArrayList<>();
/** /**
* Static resource configuration. * Comma-separated list of additional unencoded characters that should be allowed
* in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.
*/ */
private final Resource resource = new Resource(); private List<Character> relaxedPathChars = new ArrayList<>();
/** /**
* Modeler MBean Registry configuration. * Comma-separated list of additional unencoded characters that should be allowed
* in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed.
*/ */
private final Mbeanregistry mbeanregistry = new Mbeanregistry(); private List<Character> relaxedQueryChars = new ArrayList<>();
/** /**
* Specify additional unencoded characters that Tomcat should allow in a * Static resource configuration.
* querystring. The value may be any combination of the following characters: " <
* > [ \ ] ^ ` { | } . Any other characters present in the value will be ignored.
*/ */
private String relaxedQueryChars; private final Resource resource = new Resource();
/** /**
* Specify additional unencoded characters that Tomcat should allow in the path. * Modeler MBean Registry configuration.
* The value may be any combination of the following characters: " < > [ \ ] ^ ` {
* | } . Any other characters present in the value will be ignored.
*/ */
private String relaxedPathChars; private final Mbeanregistry mbeanregistry = new Mbeanregistry();
public int getMaxThreads() { public int getMaxThreads() {
return this.maxThreads; return this.maxThreads;
...@@ -568,30 +566,30 @@ public class ServerProperties { ...@@ -568,30 +566,30 @@ public class ServerProperties {
this.additionalTldSkipPatterns = additionalTldSkipPatterns; this.additionalTldSkipPatterns = additionalTldSkipPatterns;
} }
public Resource getResource() { public List<Character> getRelaxedPathChars() {
return this.resource;
}
public Mbeanregistry getMbeanregistry() {
return this.mbeanregistry;
}
public String getRelaxedPathChars() {
return this.relaxedPathChars; return this.relaxedPathChars;
} }
public void setRelaxedPathChars(String relaxedPathChars) { public void setRelaxedPathChars(List<Character> relaxedPathChars) {
this.relaxedPathChars = relaxedPathChars; this.relaxedPathChars = relaxedPathChars;
} }
public String getRelaxedQueryChars() { public List<Character> getRelaxedQueryChars() {
return this.relaxedQueryChars; return this.relaxedQueryChars;
} }
public void setRelaxedQueryChars(String relaxedQueryChars) { public void setRelaxedQueryChars(List<Character> relaxedQueryChars) {
this.relaxedQueryChars = relaxedQueryChars; this.relaxedQueryChars = relaxedQueryChars;
} }
public Resource getResource() {
return this.resource;
}
public Mbeanregistry getMbeanregistry() {
return this.mbeanregistry;
}
/** /**
* Tomcat access log properties. * Tomcat access log properties.
*/ */
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
package org.springframework.boot.autoconfigure.web.embedded; package org.springframework.boot.autoconfigure.web.embedded;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.catalina.Lifecycle; import org.apache.catalina.Lifecycle;
import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.AccessLogValve;
...@@ -103,10 +105,10 @@ public class TomcatWebServerFactoryCustomizer ...@@ -103,10 +105,10 @@ public class TomcatWebServerFactoryCustomizer
.to((acceptCount) -> customizeAcceptCount(factory, acceptCount)); .to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
propertyMapper.from(tomcatProperties::getProcessorCache) propertyMapper.from(tomcatProperties::getProcessorCache)
.to((processorCache) -> customizeProcessorCache(factory, processorCache)); .to((processorCache) -> customizeProcessorCache(factory, processorCache));
propertyMapper.from(tomcatProperties::getRelaxedQueryChars) propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText()
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
propertyMapper.from(tomcatProperties::getRelaxedPathChars)
.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars)); .to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText()
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
customizeStaticResources(factory); customizeStaticResources(factory);
customizeErrorReportValve(properties.getError(), factory); customizeErrorReportValve(properties.getError(), factory);
} }
...@@ -154,12 +156,16 @@ public class TomcatWebServerFactoryCustomizer ...@@ -154,12 +156,16 @@ public class TomcatWebServerFactoryCustomizer
}); });
} }
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedPathChars", relaxedChars));
}
private void customizeRelaxedQueryChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) { private void customizeRelaxedQueryChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedQueryChars", relaxedChars)); factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedQueryChars", relaxedChars));
} }
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) { private String joinCharacters(List<Character> content) {
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedPathChars", relaxedChars)); return content.stream().map(String::valueOf).collect(Collectors.joining());
} }
private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) { private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
......
...@@ -2089,6 +2089,76 @@ ...@@ -2089,6 +2089,76 @@
} }
], ],
"hints": [ "hints": [
{
"name": "server.tomcat.relaxed-query-chars",
"values": [
{
"value": "<"
},
{
"value": ">"
},
{
"value": "["
},
{
"value": "\\"
},
{
"value": "]"
},
{
"value": "^"
},
{
"value": "`"
},
{
"value": "{"
},
{
"value": "|"
},
{
"value": "}"
}
]
},
{
"name": "server.tomcat.relaxed-path-chars",
"values": [
{
"value": "<"
},
{
"value": ">"
},
{
"value": "["
},
{
"value": "\\"
},
{
"value": "]"
},
{
"value": "^"
},
{
"value": "`"
},
{
"value": "{"
},
{
"value": "|"
},
{
"value": "}"
}
]
},
{ {
"name": "spring.liquibase.change-log", "name": "spring.liquibase.change-log",
"providers": [ "providers": [
......
...@@ -127,8 +127,8 @@ class ServerPropertiesTests { ...@@ -127,8 +127,8 @@ class ServerPropertiesTests {
map.put("server.tomcat.remote-ip-header", "Remote-Ip"); map.put("server.tomcat.remote-ip-header", "Remote-Ip");
map.put("server.tomcat.internal-proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); map.put("server.tomcat.internal-proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
map.put("server.tomcat.background-processor-delay", "10"); map.put("server.tomcat.background-processor-delay", "10");
map.put("server.tomcat.relaxed-path-chars", "|"); map.put("server.tomcat.relaxed-path-chars", "|,<");
map.put("server.tomcat.relaxed-query-chars", "^^"); map.put("server.tomcat.relaxed-query-chars", "^ , | ");
bind(map); bind(map);
ServerProperties.Tomcat tomcat = this.properties.getTomcat(); ServerProperties.Tomcat tomcat = this.properties.getTomcat();
Accesslog accesslog = tomcat.getAccesslog(); Accesslog accesslog = tomcat.getAccesslog();
...@@ -148,8 +148,8 @@ class ServerPropertiesTests { ...@@ -148,8 +148,8 @@ class ServerPropertiesTests {
assertThat(tomcat.getProtocolHeader()).isEqualTo("X-Forwarded-Protocol"); assertThat(tomcat.getProtocolHeader()).isEqualTo("X-Forwarded-Protocol");
assertThat(tomcat.getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); assertThat(tomcat.getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
assertThat(tomcat.getBackgroundProcessorDelay()).isEqualTo(Duration.ofSeconds(10)); assertThat(tomcat.getBackgroundProcessorDelay()).isEqualTo(Duration.ofSeconds(10));
assertThat(tomcat.getRelaxedPathChars()).isEqualTo("|"); assertThat(tomcat.getRelaxedPathChars()).containsExactly('|', '<');
assertThat(tomcat.getRelaxedQueryChars()).isEqualTo("^^"); assertThat(tomcat.getRelaxedQueryChars()).containsExactly('^', '|');
} }
@Test @Test
......
...@@ -198,6 +198,22 @@ class TomcatWebServerFactoryCustomizerTests { ...@@ -198,6 +198,22 @@ class TomcatWebServerFactoryCustomizerTests {
}); });
} }
@Test
void customRelaxedPathChars() {
bind("server.tomcat.relaxed-path-chars=|,^");
customizeAndRunServer((server) -> assertThat(
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
.getRelaxedPathChars()).isEqualTo("|^"));
}
@Test
void customRelaxedQueryChars() {
bind("server.tomcat.relaxed-query-chars=^ , | ");
customizeAndRunServer((server) -> assertThat(
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
.getRelaxedQueryChars()).isEqualTo("^|"));
}
@Test @Test
void deduceUseForwardHeaders() { void deduceUseForwardHeaders() {
this.environment.setProperty("DYNO", "-"); this.environment.setProperty("DYNO", "-");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment