Skip to content

Commit 79bcf5c

Browse files
committed
Added remaining Undertow server configurations
Added support for Undertow's server configurations namely max-parameters, max-headers, max-cookies, allow-encoded-slash, decode-url, url-charset, and always-set-keep-alive. Fixes gh-16077
1 parent 28bd5f5 commit 79bcf5c

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
@@ -57,6 +57,8 @@
5757
* @author Chentao Qu
5858
* @author Artsiom Yudovin
5959
* @author Andrew McGhie
60+
* @author Rafiullah Hamedy
61+
*
6062
*/
6163
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
6264
public class ServerProperties {
@@ -1066,6 +1068,49 @@ public static class Undertow {
10661068
*/
10671069
private boolean eagerFilterInit = true;
10681070

1071+
/**
1072+
* The maximum number of query or path parameters that are allowed. This limit
1073+
* exists to prevent hash collision based DOS attacks.
1074+
*/
1075+
private Integer maxParameters;
1076+
1077+
/**
1078+
* The maximum number of headers that are allowed. This limit exists to prevent
1079+
* hash collision based DOS attacks.
1080+
*/
1081+
private Integer maxHeaders;
1082+
1083+
/**
1084+
* The maximum number of cookies that are allowed. This limit exists to prevent
1085+
* hash collision based DOS attacks.
1086+
*/
1087+
private Integer maxCookies;
1088+
1089+
/**
1090+
* Set this to true if you want the server to decode percent encoded slash
1091+
* characters. This is probably a bad idea, as it can have security implications,
1092+
* due to different servers interpreting the slash differently. Only enable this
1093+
* if you have a legacy application that requires it.
1094+
*/
1095+
private Boolean allowEncodedSlash;
1096+
1097+
/**
1098+
* If the URL should be decoded. If this is not set to true then percent encoded
1099+
* characters in the URL will be left as is.
1100+
*/
1101+
private Boolean decodeUrl;
1102+
1103+
/**
1104+
* The charset to decode the URL to.
1105+
*/
1106+
private String urlCharset;
1107+
1108+
/**
1109+
* If the 'Connection: keep-alive' header should be added to all responses, even
1110+
* if not required by spec.
1111+
*/
1112+
private Boolean alwaysSetKeepAlive;
1113+
10691114
private final Accesslog accesslog = new Accesslog();
10701115

10711116
public DataSize getMaxHttpPostSize() {
@@ -1116,6 +1161,62 @@ public void setEagerFilterInit(boolean eagerFilterInit) {
11161161
this.eagerFilterInit = eagerFilterInit;
11171162
}
11181163

1164+
public Integer getMaxParameters() {
1165+
return this.maxParameters;
1166+
}
1167+
1168+
public void setMaxParameters(Integer maxParameters) {
1169+
this.maxParameters = maxParameters;
1170+
}
1171+
1172+
public Integer getMaxHeaders() {
1173+
return this.maxHeaders;
1174+
}
1175+
1176+
public void setMaxHeaders(Integer maxHeaders) {
1177+
this.maxHeaders = maxHeaders;
1178+
}
1179+
1180+
public Integer getMaxCookies() {
1181+
return this.maxCookies;
1182+
}
1183+
1184+
public void setMaxCookies(Integer maxCookies) {
1185+
this.maxCookies = maxCookies;
1186+
}
1187+
1188+
public Boolean isAllowEncodedSlash() {
1189+
return this.allowEncodedSlash;
1190+
}
1191+
1192+
public void setAllowEncodedSlash(Boolean allowEncodedSlash) {
1193+
this.allowEncodedSlash = allowEncodedSlash;
1194+
}
1195+
1196+
public Boolean isDecodeUrl() {
1197+
return this.decodeUrl;
1198+
}
1199+
1200+
public void setDecodeUrl(Boolean decodeUrl) {
1201+
this.decodeUrl = decodeUrl;
1202+
}
1203+
1204+
public String getUrlCharset() {
1205+
return this.urlCharset;
1206+
}
1207+
1208+
public void setUrlCharset(String urlCharset) {
1209+
this.urlCharset = urlCharset;
1210+
}
1211+
1212+
public Boolean isAlwaysSetKeepAlive() {
1213+
return this.alwaysSetKeepAlive;
1214+
}
1215+
1216+
public void setAlwaysSetKeepAlive(Boolean alwaysSetKeepAlive) {
1217+
this.alwaysSetKeepAlive = alwaysSetKeepAlive;
1218+
}
1219+
11191220
public Accesslog getAccesslog() {
11201221
return this.accesslog;
11211222
}

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)