Skip to content

Commit dd2f20f

Browse files
committed
Polish "Add support for configuring Tomcat's relaxed path and query chars"
See gh-17510
1 parent 1fee797 commit dd2f20f

File tree

5 files changed

+123
-33
lines changed

5 files changed

+123
-33
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -397,28 +397,26 @@ public static class Tomcat {
397397
private List<String> additionalTldSkipPatterns = new ArrayList<>();
398398

399399
/**
400-
* Static resource configuration.
400+
* Comma-separated list of additional unencoded characters that should be allowed
401+
* in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.
401402
*/
402-
private final Resource resource = new Resource();
403+
private List<Character> relaxedPathChars = new ArrayList<>();
403404

404405
/**
405-
* Modeler MBean Registry configuration.
406+
* Comma-separated list of additional unencoded characters that should be allowed
407+
* in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed.
406408
*/
407-
private final Mbeanregistry mbeanregistry = new Mbeanregistry();
409+
private List<Character> relaxedQueryChars = new ArrayList<>();
408410

409411
/**
410-
* Specify additional unencoded characters that Tomcat should allow in a
411-
* querystring. The value may be any combination of the following characters: " <
412-
* > [ \ ] ^ ` { | } . Any other characters present in the value will be ignored.
412+
* Static resource configuration.
413413
*/
414-
private String relaxedQueryChars;
414+
private final Resource resource = new Resource();
415415

416416
/**
417-
* Specify additional unencoded characters that Tomcat should allow in the path.
418-
* The value may be any combination of the following characters: " < > [ \ ] ^ ` {
419-
* | } . Any other characters present in the value will be ignored.
417+
* Modeler MBean Registry configuration.
420418
*/
421-
private String relaxedPathChars;
419+
private final Mbeanregistry mbeanregistry = new Mbeanregistry();
422420

423421
public int getMaxThreads() {
424422
return this.maxThreads;
@@ -568,30 +566,30 @@ public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns)
568566
this.additionalTldSkipPatterns = additionalTldSkipPatterns;
569567
}
570568

571-
public Resource getResource() {
572-
return this.resource;
573-
}
574-
575-
public Mbeanregistry getMbeanregistry() {
576-
return this.mbeanregistry;
577-
}
578-
579-
public String getRelaxedPathChars() {
569+
public List<Character> getRelaxedPathChars() {
580570
return this.relaxedPathChars;
581571
}
582572

583-
public void setRelaxedPathChars(String relaxedPathChars) {
573+
public void setRelaxedPathChars(List<Character> relaxedPathChars) {
584574
this.relaxedPathChars = relaxedPathChars;
585575
}
586576

587-
public String getRelaxedQueryChars() {
577+
public List<Character> getRelaxedQueryChars() {
588578
return this.relaxedQueryChars;
589579
}
590580

591-
public void setRelaxedQueryChars(String relaxedQueryChars) {
581+
public void setRelaxedQueryChars(List<Character> relaxedQueryChars) {
592582
this.relaxedQueryChars = relaxedQueryChars;
593583
}
594584

585+
public Resource getResource() {
586+
return this.resource;
587+
}
588+
589+
public Mbeanregistry getMbeanregistry() {
590+
return this.mbeanregistry;
591+
}
592+
595593
/**
596594
* Tomcat access log properties.
597595
*/

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.boot.autoconfigure.web.embedded;
1818

1919
import java.time.Duration;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
2022

2123
import org.apache.catalina.Lifecycle;
2224
import org.apache.catalina.valves.AccessLogValve;
@@ -103,10 +105,10 @@ public void customize(ConfigurableTomcatWebServerFactory factory) {
103105
.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
104106
propertyMapper.from(tomcatProperties::getProcessorCache)
105107
.to((processorCache) -> customizeProcessorCache(factory, processorCache));
106-
propertyMapper.from(tomcatProperties::getRelaxedQueryChars)
107-
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
108-
propertyMapper.from(tomcatProperties::getRelaxedPathChars)
108+
propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText()
109109
.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
110+
propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText()
111+
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
110112
customizeStaticResources(factory);
111113
customizeErrorReportValve(properties.getError(), factory);
112114
}
@@ -154,12 +156,16 @@ private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory facto
154156
});
155157
}
156158

159+
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
160+
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedPathChars", relaxedChars));
161+
}
162+
157163
private void customizeRelaxedQueryChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
158164
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedQueryChars", relaxedChars));
159165
}
160166

161-
private void customizeRelaxedPathChars(ConfigurableTomcatWebServerFactory factory, String relaxedChars) {
162-
factory.addConnectorCustomizers((connector) -> connector.setAttribute("relaxedPathChars", relaxedChars));
167+
private String joinCharacters(List<Character> content) {
168+
return content.stream().map(String::valueOf).collect(Collectors.joining());
163169
}
164170

165171
private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,76 @@
20892089
}
20902090
],
20912091
"hints": [
2092+
{
2093+
"name": "server.tomcat.relaxed-query-chars",
2094+
"values": [
2095+
{
2096+
"value": "<"
2097+
},
2098+
{
2099+
"value": ">"
2100+
},
2101+
{
2102+
"value": "["
2103+
},
2104+
{
2105+
"value": "\\"
2106+
},
2107+
{
2108+
"value": "]"
2109+
},
2110+
{
2111+
"value": "^"
2112+
},
2113+
{
2114+
"value": "`"
2115+
},
2116+
{
2117+
"value": "{"
2118+
},
2119+
{
2120+
"value": "|"
2121+
},
2122+
{
2123+
"value": "}"
2124+
}
2125+
]
2126+
},
2127+
{
2128+
"name": "server.tomcat.relaxed-path-chars",
2129+
"values": [
2130+
{
2131+
"value": "<"
2132+
},
2133+
{
2134+
"value": ">"
2135+
},
2136+
{
2137+
"value": "["
2138+
},
2139+
{
2140+
"value": "\\"
2141+
},
2142+
{
2143+
"value": "]"
2144+
},
2145+
{
2146+
"value": "^"
2147+
},
2148+
{
2149+
"value": "`"
2150+
},
2151+
{
2152+
"value": "{"
2153+
},
2154+
{
2155+
"value": "|"
2156+
},
2157+
{
2158+
"value": "}"
2159+
}
2160+
]
2161+
},
20922162
{
20932163
"name": "spring.liquibase.change-log",
20942164
"providers": [

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ void testTomcatBinding() {
127127
map.put("server.tomcat.remote-ip-header", "Remote-Ip");
128128
map.put("server.tomcat.internal-proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
129129
map.put("server.tomcat.background-processor-delay", "10");
130-
map.put("server.tomcat.relaxed-path-chars", "|");
131-
map.put("server.tomcat.relaxed-query-chars", "^^");
130+
map.put("server.tomcat.relaxed-path-chars", "|,<");
131+
map.put("server.tomcat.relaxed-query-chars", "^ , | ");
132132
bind(map);
133133
ServerProperties.Tomcat tomcat = this.properties.getTomcat();
134134
Accesslog accesslog = tomcat.getAccesslog();
@@ -148,8 +148,8 @@ void testTomcatBinding() {
148148
assertThat(tomcat.getProtocolHeader()).isEqualTo("X-Forwarded-Protocol");
149149
assertThat(tomcat.getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
150150
assertThat(tomcat.getBackgroundProcessorDelay()).isEqualTo(Duration.ofSeconds(10));
151-
assertThat(tomcat.getRelaxedPathChars()).isEqualTo("|");
152-
assertThat(tomcat.getRelaxedQueryChars()).isEqualTo("^^");
151+
assertThat(tomcat.getRelaxedPathChars()).containsExactly('|', '<');
152+
assertThat(tomcat.getRelaxedQueryChars()).containsExactly('^', '|');
153153
}
154154

155155
@Test

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,22 @@ void customStaticResourceCacheTtl() {
198198
});
199199
}
200200

201+
@Test
202+
void customRelaxedPathChars() {
203+
bind("server.tomcat.relaxed-path-chars=|,^");
204+
customizeAndRunServer((server) -> assertThat(
205+
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
206+
.getRelaxedPathChars()).isEqualTo("|^"));
207+
}
208+
209+
@Test
210+
void customRelaxedQueryChars() {
211+
bind("server.tomcat.relaxed-query-chars=^ , | ");
212+
customizeAndRunServer((server) -> assertThat(
213+
((AbstractHttp11Protocol<?>) server.getTomcat().getConnector().getProtocolHandler())
214+
.getRelaxedQueryChars()).isEqualTo("^|"));
215+
}
216+
201217
@Test
202218
void deduceUseForwardHeaders() {
203219
this.environment.setProperty("DYNO", "-");

0 commit comments

Comments
 (0)