diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 50877ba82503..9f2202760fd5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -73,6 +73,7 @@ * @author Florian Storz * @author Michael Weidmann * @author Lasse Wulff + * @author Daeho Kwon * @since 1.0.0 */ @ConfigurationProperties("server") @@ -519,6 +520,21 @@ public static class Tomcat { */ private int maxParameterCount = 10000; + /** + * The maximum total number of parts permitted in a request where the content type + * is multipart/form-data. This limit is in addition to maxParameterCount. + * Requests that exceed this limit will be rejected. A value of less than 0 means + * no limit. + */ + private int maxPartCount = 10; + + /** + * The maximum number of header bytes permitted per part in a request where the + * content type is multipart/form-data. Requests that exceed this limit will be + * rejected. A value of less than 0 means no limit. + */ + private DataSize maxPartHeaderSize = DataSize.ofBytes(512); + /** * Whether to use APR. */ @@ -688,6 +704,22 @@ public void setMaxParameterCount(int maxParameterCount) { this.maxParameterCount = maxParameterCount; } + public int getMaxPartCount() { + return this.maxPartCount; + } + + public void setMaxPartCount(int maxPartCount) { + this.maxPartCount = maxPartCount; + } + + public DataSize getMaxPartHeaderSize() { + return this.maxPartHeaderSize; + } + + public void setMaxPartHeaderSize(DataSize maxPartHeaderSize) { + this.maxPartHeaderSize = maxPartHeaderSize; + } + public UseApr getUseApr() { return this.useApr; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index c60ce2cffe02..d9f9b2192bd1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -62,6 +62,7 @@ * @author Parviz Rozikov * @author Florian Storz * @author Michael Weidmann + * @author Daeho Kwon * @since 2.0.0 */ public class TomcatWebServerFactoryCustomizer @@ -121,6 +122,10 @@ public void customize(ConfigurableTomcatWebServerFactory factory) { .to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize)); map.from(properties::getMaxParameterCount) .to((maxParameterCount) -> customizeMaxParameterCount(factory, maxParameterCount)); + map.from(properties::getMaxPartCount).to((maxPartCount) -> customizeMaxPartCount(factory, maxPartCount)); + map.from(properties::getMaxPartHeaderSize) + .asInt(DataSize::toBytes) + .to((maxPartHeaderSize) -> customizeMaxPartHeaderSize(factory, maxPartHeaderSize)); map.from(properties::getAccesslog) .when(ServerProperties.Tomcat.Accesslog::isEnabled) .to((enabled) -> customizeAccessLog(factory)); @@ -298,6 +303,14 @@ private void customizeMaxParameterCount(ConfigurableTomcatWebServerFactory facto factory.addConnectorCustomizers((connector) -> connector.setMaxParameterCount(maxParameterCount)); } + private void customizeMaxPartCount(ConfigurableTomcatWebServerFactory factory, int maxPartCount) { + factory.addConnectorCustomizers((connector) -> connector.setMaxPartCount(maxPartCount)); + } + + private void customizeMaxPartHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxPartHeaderSize) { + factory.addConnectorCustomizers((connector) -> connector.setMaxPartHeaderSize(maxPartHeaderSize)); + } + private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) { ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat(); AccessLogValve valve = new AccessLogValve(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 55c41832809f..a67b33202cda 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -71,6 +71,7 @@ * @author Parviz Rozikov * @author Lasse Wulff * @author Moritz Halbritter + * @author Daeho Kwon */ @DirtiesUrlFactories class ServerPropertiesTests { @@ -254,6 +255,18 @@ void testCustomizeTomcatMaxParameterCount() { assertThat(this.properties.getTomcat().getMaxParameterCount()).isEqualTo(100); } + @Test + void testCustomizeTomcatMaxPartCount() { + bind("server.tomcat.max-part-count", "20"); + assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(20); + } + + @Test + void testCustomizeTomcatMaxPartHeaderSize() { + bind("server.tomcat.max-part-header-size", "1024"); + assertThat(this.properties.getTomcat().getMaxPartHeaderSize()).isEqualTo(DataSize.ofKilobytes(1)); + } + @Test void testCustomizeTomcatMinSpareThreads() { bind("server.tomcat.threads.min-spare", "10"); @@ -393,6 +406,17 @@ void tomcatMaxParameterCountMatchesConnectorDefault() { .isEqualTo(getDefaultConnector().getMaxParameterCount()); } + @Test + void tomcatMaxPartCountMatchesConnectorDefault() { + assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(getDefaultConnector().getMaxPartCount()); + } + + @Test + void tomcatMaxPartHeaderSizeMatchesConnectorDefault() { + assertThat(this.properties.getTomcat().getMaxPartHeaderSize().toBytes()) + .isEqualTo(getDefaultConnector().getMaxPartHeaderSize()); + } + @Test void tomcatBackgroundProcessorDelayMatchesEngineDefault() { assertThat(this.properties.getTomcat().getBackgroundProcessorDelay()) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java index cc34e5359cea..0fea71d0be08 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java @@ -59,6 +59,7 @@ * @author Victor Mandujano * @author Parviz Rozikov * @author Moritz Halbritter + * @author Daeho Kwon */ class TomcatWebServerFactoryCustomizerTests { @@ -201,6 +202,20 @@ void customMaxParameterCount() { (server) -> assertThat(server.getTomcat().getConnector().getMaxParameterCount()).isEqualTo(100)); } + @Test + void customMaxPartCount() { + bind("server.tomcat.max-part-count=20"); + customizeAndRunServer( + (server) -> assertThat(server.getTomcat().getConnector().getMaxPartCount()).isEqualTo(20)); + } + + @Test + void customMaxPartHeaderSize() { + bind("server.tomcat.max-part-header-size=1024"); + customizeAndRunServer( + (server) -> assertThat(server.getTomcat().getConnector().getMaxPartHeaderSize()).isEqualTo(1024)); + } + @Test void customMaxRequestHttpHeaderSizeIgnoredIfNegative() { bind("server.max-http-request-header-size=-1");