Skip to content

Commit 186b1fa

Browse files
rhamedywilkinsona
authored andcommitted
Add support for configuring remaining Undertow server options
This commit adds support for configuring Undertow's server options that were previously not configurable via application properties. The additions are the following: - allow-encoded-slash - always-set-keep-alive - decode-url - max-cookies - max-headers - max-parameters, - url-charset See gh-16278
1 parent 13b356a commit 186b1fa

File tree

3 files changed

+182
-24
lines changed

3 files changed

+182
-24
lines changed

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

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
* @author Chentao Qu
5959
* @author Artsiom Yudovin
6060
* @author Andrew McGhie
61+
* @author Rafiullah Hamedy
62+
*
6163
*/
6264
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
6365
public class ServerProperties {
@@ -1114,6 +1116,49 @@ public static class Undertow {
11141116
*/
11151117
private boolean eagerFilterInit = true;
11161118

1119+
/**
1120+
* The maximum number of query or path parameters that are allowed. This limit
1121+
* exists to prevent hash collision based DOS attacks.
1122+
*/
1123+
private Integer maxParameters;
1124+
1125+
/**
1126+
* The maximum number of headers that are allowed. This limit exists to prevent
1127+
* hash collision based DOS attacks.
1128+
*/
1129+
private Integer maxHeaders;
1130+
1131+
/**
1132+
* The maximum number of cookies that are allowed. This limit exists to prevent
1133+
* hash collision based DOS attacks.
1134+
*/
1135+
private Integer maxCookies;
1136+
1137+
/**
1138+
* Set this to true if you want the server to decode percent encoded slash
1139+
* characters. This is probably a bad idea, as it can have security implications,
1140+
* due to different servers interpreting the slash differently. Only enable this
1141+
* if you have a legacy application that requires it.
1142+
*/
1143+
private Boolean allowEncodedSlash;
1144+
1145+
/**
1146+
* If the URL should be decoded. If this is not set to true then percent encoded
1147+
* characters in the URL will be left as is.
1148+
*/
1149+
private Boolean decodeUrl;
1150+
1151+
/**
1152+
* The charset to decode the URL to.
1153+
*/
1154+
private String urlCharset;
1155+
1156+
/**
1157+
* If the 'Connection: keep-alive' header should be added to all responses, even
1158+
* if not required by spec.
1159+
*/
1160+
private Boolean alwaysSetKeepAlive;
1161+
11171162
private final Accesslog accesslog = new Accesslog();
11181163

11191164
public DataSize getMaxHttpPostSize() {
@@ -1164,6 +1209,62 @@ public void setEagerFilterInit(boolean eagerFilterInit) {
11641209
this.eagerFilterInit = eagerFilterInit;
11651210
}
11661211

1212+
public Integer getMaxParameters() {
1213+
return this.maxParameters;
1214+
}
1215+
1216+
public void setMaxParameters(Integer maxParameters) {
1217+
this.maxParameters = maxParameters;
1218+
}
1219+
1220+
public Integer getMaxHeaders() {
1221+
return this.maxHeaders;
1222+
}
1223+
1224+
public void setMaxHeaders(Integer maxHeaders) {
1225+
this.maxHeaders = maxHeaders;
1226+
}
1227+
1228+
public Integer getMaxCookies() {
1229+
return this.maxCookies;
1230+
}
1231+
1232+
public void setMaxCookies(Integer maxCookies) {
1233+
this.maxCookies = maxCookies;
1234+
}
1235+
1236+
public Boolean isAllowEncodedSlash() {
1237+
return this.allowEncodedSlash;
1238+
}
1239+
1240+
public void setAllowEncodedSlash(Boolean allowEncodedSlash) {
1241+
this.allowEncodedSlash = allowEncodedSlash;
1242+
}
1243+
1244+
public Boolean isDecodeUrl() {
1245+
return this.decodeUrl;
1246+
}
1247+
1248+
public void setDecodeUrl(Boolean decodeUrl) {
1249+
this.decodeUrl = decodeUrl;
1250+
}
1251+
1252+
public String getUrlCharset() {
1253+
return this.urlCharset;
1254+
}
1255+
1256+
public void setUrlCharset(String urlCharset) {
1257+
this.urlCharset = urlCharset;
1258+
}
1259+
1260+
public Boolean isAlwaysSetKeepAlive() {
1261+
return this.alwaysSetKeepAlive;
1262+
}
1263+
1264+
public void setAlwaysSetKeepAlive(Boolean alwaysSetKeepAlive) {
1265+
this.alwaysSetKeepAlive = alwaysSetKeepAlive;
1266+
}
1267+
11671268
public Accesslog getAccesslog() {
11681269
return this.accesslog;
11691270
}

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

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
package org.springframework.boot.autoconfigure.web.embedded;
1818

19-
import java.time.Duration;
20-
2119
import io.undertow.UndertowOptions;
20+
import org.xnio.Option;
2221

2322
import org.springframework.boot.autoconfigure.web.ServerProperties;
2423
import org.springframework.boot.cloud.CloudPlatform;
@@ -38,6 +37,7 @@
3837
* @author Stephane Nicoll
3938
* @author Phillip Webb
4039
* @author Arstiom Yudovin
40+
* @author Rafiullah Hamedy
4141
* @since 2.0.0
4242
*/
4343
public class UndertowWebServerFactoryCustomizer implements
@@ -86,17 +86,50 @@ public void customize(ConfigurableUndertowWebServerFactory factory) {
8686
.to(factory::setAccessLogRotate);
8787
propertyMapper.from(this::getOrDeduceUseForwardHeaders)
8888
.to(factory::setUseForwardHeaders);
89+
8990
propertyMapper.from(properties::getMaxHttpHeaderSize).whenNonNull()
9091
.asInt(DataSize::toBytes).when(this::isPositive)
91-
.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory,
92-
maxHttpHeaderSize));
92+
.to((maxHttpHeaderSize) -> customizeProperties(factory,
93+
UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
94+
9395
propertyMapper.from(undertowProperties::getMaxHttpPostSize)
9496
.asInt(DataSize::toBytes).when(this::isPositive)
95-
.to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory,
96-
maxHttpPostSize));
97+
.to((maxHttpPostSize) -> customizeProperties(factory,
98+
UndertowOptions.MAX_HEADER_SIZE, maxHttpPostSize));
99+
97100
propertyMapper.from(properties::getConnectionTimeout)
98-
.to((connectionTimeout) -> customizeConnectionTimeout(factory,
99-
connectionTimeout));
101+
.to((connectionTimeout) -> customizeProperties(factory,
102+
UndertowOptions.NO_REQUEST_TIMEOUT,
103+
(int) connectionTimeout.toMillis()));
104+
105+
propertyMapper.from(undertowProperties::getMaxParameters)
106+
.to((maxParameters) -> customizeProperties(factory,
107+
UndertowOptions.MAX_PARAMETERS, maxParameters));
108+
109+
propertyMapper.from(undertowProperties::getMaxHeaders)
110+
.to((maxHeaders) -> customizeProperties(factory,
111+
UndertowOptions.MAX_HEADERS, maxHeaders));
112+
113+
propertyMapper.from(undertowProperties::getMaxCookies)
114+
.to((maxCookies) -> customizeProperties(factory,
115+
UndertowOptions.MAX_COOKIES, maxCookies));
116+
117+
propertyMapper.from(undertowProperties::isAllowEncodedSlash)
118+
.to((allowEncodedSlash) -> customizeProperties(factory,
119+
UndertowOptions.ALLOW_ENCODED_SLASH, allowEncodedSlash));
120+
121+
propertyMapper.from(undertowProperties::isDecodeUrl)
122+
.to((isDecodeUrl) -> customizeProperties(factory,
123+
UndertowOptions.DECODE_URL, isDecodeUrl));
124+
125+
propertyMapper.from(undertowProperties::getUrlCharset)
126+
.to((urlCharset) -> customizeProperties(factory,
127+
UndertowOptions.URL_CHARSET, urlCharset));
128+
129+
propertyMapper.from(undertowProperties::isAlwaysSetKeepAlive)
130+
.to((alwaysSetKeepAlive) -> customizeProperties(factory,
131+
UndertowOptions.ALWAYS_SET_KEEP_ALIVE, alwaysSetKeepAlive));
132+
100133
factory.addDeploymentInfoCustomizers((deploymentInfo) -> deploymentInfo
101134
.setEagerFilterInit(undertowProperties.isEagerFilterInit()));
102135
}
@@ -105,22 +138,10 @@ private boolean isPositive(Number value) {
105138
return value.longValue() > 0;
106139
}
107140

108-
private void customizeConnectionTimeout(ConfigurableUndertowWebServerFactory factory,
109-
Duration connectionTimeout) {
110-
factory.addBuilderCustomizers((builder) -> builder.setServerOption(
111-
UndertowOptions.NO_REQUEST_TIMEOUT, (int) connectionTimeout.toMillis()));
112-
}
113-
114-
private void customizeMaxHttpHeaderSize(ConfigurableUndertowWebServerFactory factory,
115-
int maxHttpHeaderSize) {
116-
factory.addBuilderCustomizers((builder) -> builder
117-
.setServerOption(UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
118-
}
119-
120-
private void customizeMaxHttpPostSize(ConfigurableUndertowWebServerFactory factory,
121-
long maxHttpPostSize) {
122-
factory.addBuilderCustomizers((builder) -> builder
123-
.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize));
141+
private <T> void customizeProperties(ConfigurableUndertowWebServerFactory factory,
142+
Option<T> propType, T prop) {
143+
factory.addBuilderCustomizers(
144+
(builder) -> builder.setServerOption(propType, prop));
124145
}
125146

126147
private boolean getOrDeduceUseForwardHeaders() {

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
* @author Brian Clozel
4949
* @author Phillip Webb
5050
* @author Artsiom Yudovin
51+
* @author Rafiullah Hamedy
52+
*
5153
*/
5254
public class UndertowWebServerFactoryCustomizerTests {
5355

@@ -85,6 +87,40 @@ public void customizeUndertowAccessLog() {
8587
verify(factory).setAccessLogRotate(false);
8688
}
8789

90+
@Test
91+
public void customizeUndertowConnectionCommonSettings() {
92+
bind("server.undertow.maxParameters=50", "server.undertow.maxHeaders=60",
93+
"server.undertow.maxCookies=70", "server.undertow.allowEncodedSlash=true",
94+
"server.undertow.decodeUrl=true", "server.undertow.urlCharset=UTF-8",
95+
"server.undertow.alwaysSetKeepAlive=true");
96+
Builder builder = Undertow.builder();
97+
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
98+
this.customizer.customize(factory);
99+
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
100+
"serverOptions")).getMap();
101+
assertThat(map.get(UndertowOptions.MAX_PARAMETERS)).isEqualTo(50);
102+
assertThat(map.get(UndertowOptions.MAX_HEADERS)).isEqualTo(60);
103+
assertThat(map.get(UndertowOptions.MAX_COOKIES)).isEqualTo(70);
104+
assertThat(map.get(UndertowOptions.ALLOW_ENCODED_SLASH)).isTrue();
105+
assertThat(map.get(UndertowOptions.DECODE_URL)).isTrue();
106+
assertThat(map.get(UndertowOptions.URL_CHARSET)).isEqualTo("UTF-8");
107+
assertThat(map.get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isTrue();
108+
}
109+
110+
@Test
111+
public void customizeUndertowCommonConnectionCommonBoolSettings() {
112+
bind("server.undertow.allowEncodedSlash=false", "server.undertow.decodeUrl=false",
113+
"server.undertow.alwaysSetKeepAlive=false");
114+
Builder builder = Undertow.builder();
115+
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
116+
this.customizer.customize(factory);
117+
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
118+
"serverOptions")).getMap();
119+
assertThat(map.get(UndertowOptions.ALLOW_ENCODED_SLASH)).isFalse();
120+
assertThat(map.get(UndertowOptions.DECODE_URL)).isFalse();
121+
assertThat(map.get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
122+
}
123+
88124
@Test
89125
public void deduceUseForwardHeaders() {
90126
this.environment.setProperty("DYNO", "-");

0 commit comments

Comments
 (0)