From 59f8dca1a44d550488f4040ddb8502c459460c82 Mon Sep 17 00:00:00 2001 From: husseinvr97 Date: Thu, 26 Mar 2026 00:18:21 +0200 Subject: [PATCH 1/2] Add configuration for HTML escaping configuration with WebFlux See gh-49791 Signed-off-by: husseinvr97 --- .../WebFluxAutoConfiguration.java | 5 +++ .../autoconfigure/WebFluxProperties.java | 13 +++++++ .../WebFluxAutoConfigurationTests.java | 34 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java index d19f3a49b061..eddb93aca312 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java @@ -304,6 +304,11 @@ private void configureApiVersioningUse(ApiVersionConfigurer configurer, Use use) .forEach((mediaType, parameterName) -> configurer.useMediaTypeParameter(mediaType, parameterName)); } + @Bean + WebHttpHandlerBuilderCustomizer defaultHtmlEscapeCustomizer() { + return (builder) -> builder.defaultHtmlEscape(this.webFluxProperties.getDefaultHtmlEscape()); + } + } /** diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxProperties.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxProperties.java index 7a5d19329247..aadd36b79ac4 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxProperties.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxProperties.java @@ -58,6 +58,11 @@ public class WebFluxProperties { */ private String webjarsPathPattern = "/webjars/**"; + /** + * Whether default HTML escaping is enabled for the web application. + */ + private @Nullable Boolean defaultHtmlEscape; + public @Nullable String getBasePath() { return this.basePath; } @@ -110,6 +115,14 @@ public void setWebjarsPathPattern(String webjarsPathPattern) { this.webjarsPathPattern = webjarsPathPattern; } + public @Nullable Boolean getDefaultHtmlEscape() { + return this.defaultHtmlEscape; + } + + public void setDefaultHtmlEscape(@Nullable Boolean defaultHtmlEscape) { + this.defaultHtmlEscape = defaultHtmlEscape; + } + public static class Format { /** diff --git a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java index 43084d0f1c48..e6a7fc1e52db 100644 --- a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java +++ b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java @@ -120,6 +120,7 @@ import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebHandler; import org.springframework.web.server.WebSession; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; @@ -461,6 +462,39 @@ void hiddenHttpMethodFilterCanBeEnabled() { .run((context) -> assertThat(context).hasSingleBean(OrderedHiddenHttpMethodFilter.class)); } + @Test + void defaultHtmlEscapeIsNotConfiguredByDefault() { + this.contextRunner.run((context) -> { + WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", + WebHttpHandlerBuilderCustomizer.class); + WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); + customizer.customize(builder); + assertThat(builder.getDefaultHtmlEscape()).isNull(); + }); + } + + @Test + void defaultHtmlEscapeCanBeEnabled() { + this.contextRunner.withPropertyValues("spring.webflux.default-html-escape=true").run((context) -> { + WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", + WebHttpHandlerBuilderCustomizer.class); + WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); + customizer.customize(builder); + assertThat(builder.getDefaultHtmlEscape()).isTrue(); + }); + } + + @Test + void defaultHtmlEscapeCanBeDisabled() { + this.contextRunner.withPropertyValues("spring.webflux.default-html-escape=false").run((context) -> { + WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", + WebHttpHandlerBuilderCustomizer.class); + WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); + customizer.customize(builder); + assertThat(builder.getDefaultHtmlEscape()).isFalse(); + }); + } + @Test void customRequestMappingHandlerMapping() { this.contextRunner.withUserConfiguration(CustomRequestMappingHandlerMapping.class).run((context) -> { From c747c9c53dc9d4a4a1a45832c2fec6f88a3e7328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 26 Mar 2026 15:11:36 +0100 Subject: [PATCH 2/2] Polish "Add configuration for HTML escaping configuration with WebFlux" See gh-49791 --- .../HttpHandlerAutoConfiguration.java | 5 ++- .../WebFluxAutoConfiguration.java | 5 --- .../HttpHandlerAutoConfigurationTests.java | 26 ++++++++++++++ .../WebFluxAutoConfigurationTests.java | 34 ------------------- 4 files changed, 30 insertions(+), 40 deletions(-) diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfiguration.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfiguration.java index 18361fceb75f..81f5930d3bf2 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfiguration.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfiguration.java @@ -64,10 +64,13 @@ static class AnnotationConfig { @Bean HttpHandler httpHandler(ObjectProvider propsProvider, ObjectProvider handlerBuilderCustomizers) { + WebFluxProperties properties = propsProvider.getIfAvailable(); WebHttpHandlerBuilder handlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext); + if (properties != null) { + handlerBuilder.defaultHtmlEscape(properties.getDefaultHtmlEscape()); + } handlerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(handlerBuilder)); HttpHandler httpHandler = handlerBuilder.build(); - WebFluxProperties properties = propsProvider.getIfAvailable(); if (properties != null && StringUtils.hasText(properties.getBasePath())) { Map handlersMap = Collections.singletonMap(properties.getBasePath(), httpHandler); return new ContextPathCompositeHandler(handlersMap); diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java index eddb93aca312..d19f3a49b061 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java @@ -304,11 +304,6 @@ private void configureApiVersioningUse(ApiVersionConfigurer configurer, Use use) .forEach((mediaType, parameterName) -> configurer.useMediaTypeParameter(mediaType, parameterName)); } - @Bean - WebHttpHandlerBuilderCustomizer defaultHtmlEscapeCustomizer() { - return (builder) -> builder.defaultHtmlEscape(this.webFluxProperties.getDefaultHtmlEscape()); - } - } /** diff --git a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfigurationTests.java b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfigurationTests.java index e2e4cb7d70ee..bcb6439fcb42 100644 --- a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfigurationTests.java +++ b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/HttpHandlerAutoConfigurationTests.java @@ -18,6 +18,8 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -35,6 +37,7 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.WebHandler; +import org.springframework.web.server.adapter.HttpWebHandlerAdapter; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; @@ -100,6 +103,29 @@ void shouldConfigureBasePathCompositeHandler() { }); } + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldConfigureDefaultHtmlEscape(boolean enabled) { + this.contextRunner.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class)) + .withPropertyValues("spring.webflux.default-html-escape=" + enabled) + .run((context) -> { + assertThat(context).hasSingleBean(HttpHandler.class); + assertThat(context.getBean(HttpHandler.class)).isInstanceOfSatisfying(HttpWebHandlerAdapter.class, + (adapter) -> assertThat(adapter.getDefaultHtmlEscape()).isEqualTo(enabled)); + }); + } + + @Test + void shouldNotConfigureDefaultHtmlEscaperWithoutWebFluxAutoConfiguration() { + this.contextRunner.withUserConfiguration(CustomWebHandler.class) + .withPropertyValues("spring.webflux.default-html-escape=true") + .run((context) -> { + assertThat(context).hasSingleBean(HttpHandler.class); + assertThat(context.getBean(HttpHandler.class)).isInstanceOfSatisfying(HttpWebHandlerAdapter.class, + (adapter) -> assertThat(adapter.getDefaultHtmlEscape()).isNull()); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomHttpHandler { diff --git a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java index e6a7fc1e52db..43084d0f1c48 100644 --- a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java +++ b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java @@ -120,7 +120,6 @@ import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebHandler; import org.springframework.web.server.WebSession; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; @@ -462,39 +461,6 @@ void hiddenHttpMethodFilterCanBeEnabled() { .run((context) -> assertThat(context).hasSingleBean(OrderedHiddenHttpMethodFilter.class)); } - @Test - void defaultHtmlEscapeIsNotConfiguredByDefault() { - this.contextRunner.run((context) -> { - WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", - WebHttpHandlerBuilderCustomizer.class); - WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); - customizer.customize(builder); - assertThat(builder.getDefaultHtmlEscape()).isNull(); - }); - } - - @Test - void defaultHtmlEscapeCanBeEnabled() { - this.contextRunner.withPropertyValues("spring.webflux.default-html-escape=true").run((context) -> { - WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", - WebHttpHandlerBuilderCustomizer.class); - WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); - customizer.customize(builder); - assertThat(builder.getDefaultHtmlEscape()).isTrue(); - }); - } - - @Test - void defaultHtmlEscapeCanBeDisabled() { - this.contextRunner.withPropertyValues("spring.webflux.default-html-escape=false").run((context) -> { - WebHttpHandlerBuilderCustomizer customizer = context.getBean("defaultHtmlEscapeCustomizer", - WebHttpHandlerBuilderCustomizer.class); - WebHttpHandlerBuilder builder = WebHttpHandlerBuilder.webHandler(mock(WebHandler.class)); - customizer.customize(builder); - assertThat(builder.getDefaultHtmlEscape()).isFalse(); - }); - } - @Test void customRequestMappingHandlerMapping() { this.contextRunner.withUserConfiguration(CustomRequestMappingHandlerMapping.class).run((context) -> {