Merge branch '3.4.x'

Closes gh-45872
This commit is contained in:
Andy Wilkinson 2025-06-18 09:56:06 +01:00
commit fa81ecddc1
4 changed files with 121 additions and 1 deletions

View File

@ -412,6 +412,20 @@ public class ServerProperties {
*/ */
private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2); private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);
/**
* Maximum per-part header size permitted in a multipart/form-data request.
* Requests that exceed this limit will be rejected. A value of less than 0 means
* no limit.
*/
private DataSize maxPartHeaderSize = DataSize.ofBytes(512);
/**
* Maximum total number of parts permitted in a multipart/form-data request.
* Requests that exceed this limit will be rejected. A value of less than 0 means
* no limit.
*/
private int maxPartCount = 10;
/** /**
* Maximum amount of request body to swallow. * Maximum amount of request body to swallow.
*/ */
@ -524,6 +538,22 @@ public class ServerProperties {
*/ */
private UseApr useApr = UseApr.NEVER; private UseApr useApr = UseApr.NEVER;
public DataSize getMaxPartHeaderSize() {
return this.maxPartHeaderSize;
}
public void setMaxPartHeaderSize(DataSize maxPartHeaderSize) {
this.maxPartHeaderSize = maxPartHeaderSize;
}
public int getMaxPartCount() {
return this.maxPartCount;
}
public void setMaxPartCount(int maxPartCount) {
this.maxPartCount = maxPartCount;
}
public Accesslog getAccesslog() { public Accesslog getAccesslog() {
return this.accesslog; return this.accesslog;
} }

View File

@ -121,6 +121,10 @@ public class TomcatWebServerFactoryCustomizer
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize)); .to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
map.from(properties::getMaxParameterCount) map.from(properties::getMaxParameterCount)
.to((maxParameterCount) -> customizeMaxParameterCount(factory, maxParameterCount)); .to((maxParameterCount) -> customizeMaxParameterCount(factory, maxParameterCount));
map.from(properties::getMaxPartHeaderSize)
.asInt(DataSize::toBytes)
.to((maxPartHeaderSize) -> customizeMaxPartHeaderSize(factory, maxPartHeaderSize));
map.from(properties::getMaxPartCount).to((maxPartCount) -> customizeMaxPartCount(factory, maxPartCount));
map.from(properties::getAccesslog) map.from(properties::getAccesslog)
.when(ServerProperties.Tomcat.Accesslog::isEnabled) .when(ServerProperties.Tomcat.Accesslog::isEnabled)
.to((enabled) -> customizeAccessLog(factory)); .to((enabled) -> customizeAccessLog(factory));
@ -298,6 +302,28 @@ public class TomcatWebServerFactoryCustomizer
factory.addConnectorCustomizers((connector) -> connector.setMaxParameterCount(maxParameterCount)); factory.addConnectorCustomizers((connector) -> connector.setMaxParameterCount(maxParameterCount));
} }
private void customizeMaxPartCount(ConfigurableTomcatWebServerFactory factory, int maxPartCount) {
factory.addConnectorCustomizers((connector) -> {
try {
connector.setMaxPartCount(maxPartCount);
}
catch (NoSuchMethodError ex) {
// Tomcat < 10.1.42
}
});
}
private void customizeMaxPartHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxPartHeaderSize) {
factory.addConnectorCustomizers((connector) -> {
try {
connector.setMaxPartHeaderSize(maxPartHeaderSize);
}
catch (NoSuchMethodError ex) {
// Tomcat < 10.1.42
}
});
}
private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) { private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat(); ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
AccessLogValve valve = new AccessLogValve(); AccessLogValve valve = new AccessLogValve();

View File

@ -260,6 +260,18 @@ class ServerPropertiesTests {
assertThat(this.properties.getTomcat().getThreads().getMinSpare()).isEqualTo(10); assertThat(this.properties.getTomcat().getThreads().getMinSpare()).isEqualTo(10);
} }
@Test
void customizeTomcatMaxPartCount() {
bind("server.tomcat.max-part-count", "5");
assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(5);
}
@Test
void customizeTomcatMaxPartHeaderSize() {
bind("server.tomcat.max-part-header-size", "128");
assertThat(this.properties.getTomcat().getMaxPartHeaderSize()).isEqualTo(DataSize.ofBytes(128));
}
@Test @Test
void testCustomizeJettyAcceptors() { void testCustomizeJettyAcceptors() {
bind("server.jetty.threads.acceptors", "10"); bind("server.jetty.threads.acceptors", "10");
@ -405,6 +417,17 @@ class ServerPropertiesTests {
.isEqualTo(getDefaultConnector().getMaxPostSize()); .isEqualTo(getDefaultConnector().getMaxPostSize());
} }
@Test
void tomcatMaxPartCountMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(getDefaultConnector().getMaxPartCount());
}
@Test
void tomcatMaxPartHeaderSizeMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getMaxPartHeaderSize().toBytes())
.isEqualTo(getDefaultConnector().getMaxPartHeaderSize());
}
@Test @Test
void tomcatUriEncodingMatchesConnectorDefault() { void tomcatUriEncodingMatchesConnectorDefault() {
assertThat(this.properties.getTomcat().getUriEncoding().name()) assertThat(this.properties.getTomcat().getUriEncoding().name())

View File

@ -37,6 +37,8 @@ import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeader
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
@ -45,6 +47,7 @@ import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.unit.DataSize; import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
/** /**
* Tests for {@link TomcatWebServerFactoryCustomizer} * Tests for {@link TomcatWebServerFactoryCustomizer}
@ -60,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Parviz Rozikov * @author Parviz Rozikov
* @author Moritz Halbritter * @author Moritz Halbritter
*/ */
@DirtiesUrlFactories
class TomcatWebServerFactoryCustomizerTests { class TomcatWebServerFactoryCustomizerTests {
private MockEnvironment environment; private MockEnvironment environment;
@ -177,6 +181,37 @@ class TomcatWebServerFactoryCustomizerTests {
(server) -> assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(10000)); (server) -> assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(10000));
} }
@Test
void defaultMaxPartCount() {
customizeAndRunServer(
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartCount()).isEqualTo(10));
}
@Test
void customMaxPartCount() {
bind("server.tomcat.max-part-count=5");
customizeAndRunServer((server) -> assertThat(server.getTomcat().getConnector().getMaxPartCount()).isEqualTo(5));
}
@Test
void defaultMaxPartHeaderSize() {
customizeAndRunServer(
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartHeaderSize()).isEqualTo(512));
}
@Test
void customMaxPartHeaderSize() {
bind("server.tomcat.max-part-header-size=4KB");
customizeAndRunServer(
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartHeaderSize()).isEqualTo(4096));
}
@Test
@ClassPathOverrides("org.apache.tomcat.embed:tomcat-embed-core:10.1.41")
void customizerIsCompatibleWithTomcatVersionsWithoutMaxPartCountAndMaxPartHeaderSize() {
assertThatNoException().isThrownBy(this::customizeAndRunServer);
}
@Test @Test
void defaultMaxHttpRequestHeaderSize() { void defaultMaxHttpRequestHeaderSize() {
customizeAndRunServer((server) -> assertThat( customizeAndRunServer((server) -> assertThat(
@ -593,11 +628,17 @@ class TomcatWebServerFactoryCustomizerTests {
Bindable.ofInstance(this.serverProperties)); Bindable.ofInstance(this.serverProperties));
} }
private void customizeAndRunServer() {
customizeAndRunServer(null);
}
private void customizeAndRunServer(Consumer<TomcatWebServer> consumer) { private void customizeAndRunServer(Consumer<TomcatWebServer> consumer) {
TomcatWebServer server = customizeAndGetServer(); TomcatWebServer server = customizeAndGetServer();
server.start(); server.start();
try { try {
consumer.accept(server); if (consumer != null) {
consumer.accept(server);
}
} }
finally { finally {
server.stop(); server.stop();