diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java index da3da11319..970337fb62 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ManagementErrorEndpoint.java @@ -36,6 +36,7 @@ import org.springframework.web.context.request.ServletWebRequest; * * @author Dave Syer * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 */ @Controller @@ -72,6 +73,7 @@ public class ManagementErrorEndpoint { if (includeBindingErrors(request)) { options = options.including(Include.BINDING_ERRORS); } + options = includePath(request) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -79,7 +81,7 @@ public class ManagementErrorEndpoint { return switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "trace"); - default -> false; + case NEVER -> false; }; } @@ -87,7 +89,7 @@ public class ManagementErrorEndpoint { return switch (this.errorProperties.getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "message"); - default -> false; + case NEVER -> false; }; } @@ -95,7 +97,15 @@ public class ManagementErrorEndpoint { return switch (this.errorProperties.getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> getBooleanParameter(request, "errors"); - default -> false; + case NEVER -> false; + }; + } + + private boolean includePath(ServletWebRequest request) { + return switch (this.errorProperties.getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> getBooleanParameter(request, "path"); + case NEVER -> false; }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java index 9900a86157..03db7cc402 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorProperties.java @@ -55,6 +55,11 @@ public class ErrorProperties { */ private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER; + /** + * When to include "path" attribute. + */ + private IncludeAttribute includePath = IncludeAttribute.ALWAYS; + private final Whitelabel whitelabel = new Whitelabel(); public String getPath() { @@ -97,6 +102,14 @@ public class ErrorProperties { this.includeBindingErrors = includeBindingErrors; } + public IncludeAttribute getIncludePath() { + return this.includePath; + } + + public void setIncludePath(IncludeAttribute includePath) { + this.includePath = includePath; + } + public Whitelabel getWhitelabel() { return this.whitelabel; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java index 8afe4a624f..e8ec35bb49 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java @@ -54,6 +54,7 @@ import org.springframework.web.util.HtmlUtils; * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 * @see ErrorAttributes */ @@ -168,6 +169,17 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept return getBooleanParameter(request, "errors"); } + /** + * Check whether the path attribute has been set on the given request. + * @param request the source request + * @return {@code true} if the path attribute has been requested, {@code false} + * otherwise + * @since 3.3.0 + */ + protected boolean isPathEnabled(ServerRequest request) { + return getBooleanParameter(request, "path"); + } + private boolean getBooleanParameter(ServerRequest request, String parameterName) { String parameter = request.queryParam(parameterName).orElse("false"); return !"false".equalsIgnoreCase(parameter); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java index 5bc52de7c7..82f2fd240a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java @@ -75,6 +75,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 */ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { @@ -164,6 +165,7 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa if (isIncludeBindingErrors(request, mediaType)) { options = options.including(Include.BINDING_ERRORS); } + options = isIncludePath(request, mediaType) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -177,7 +179,7 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa return switch (this.errorProperties.getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> isTraceEnabled(request); - default -> false; + case NEVER -> false; }; } @@ -191,7 +193,7 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa return switch (this.errorProperties.getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> isMessageEnabled(request); - default -> false; + case NEVER -> false; }; } @@ -205,7 +207,22 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa return switch (this.errorProperties.getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> isBindingErrorsEnabled(request); - default -> false; + case NEVER -> false; + }; + } + + /** + * Determine if the path attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the path attribute should be included + * @since 3.3.0 + */ + protected boolean isIncludePath(ServerRequest request, MediaType produces) { + return switch (this.errorProperties.getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> isPathEnabled(request); + case NEVER -> false; }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java index 4777a492d6..474b5c5519 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/AbstractErrorController.java @@ -41,6 +41,7 @@ import org.springframework.web.servlet.ModelAndView; * @author Dave Syer * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter * @since 1.3.0 * @see ErrorAttributes */ @@ -74,18 +75,43 @@ public abstract class AbstractErrorController implements ErrorController { return this.errorAttributes.getErrorAttributes(webRequest, options); } + /** + * Returns whether the trace parameter is set. + * @param request the request + * @return whether the trace parameter is set + */ protected boolean getTraceParameter(HttpServletRequest request) { return getBooleanParameter(request, "trace"); } + /** + * Returns whether the message parameter is set. + * @param request the request + * @return whether the message parameter is set + */ protected boolean getMessageParameter(HttpServletRequest request) { return getBooleanParameter(request, "message"); } + /** + * Returns whether the errors parameter is set. + * @param request the request + * @return whether the errors parameter is set + */ protected boolean getErrorsParameter(HttpServletRequest request) { return getBooleanParameter(request, "errors"); } + /** + * Returns whether the path parameter is set. + * @param request the request + * @return whether the path parameter is set + * @since 3.3.0 + */ + protected boolean getPathParameter(HttpServletRequest request) { + return getBooleanParameter(request, "path"); + } + protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) { String parameter = request.getParameter(parameterName); if (parameter == null) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java index 610f24517f..3b2e473127 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java @@ -49,6 +49,7 @@ import org.springframework.web.servlet.ModelAndView; * @author Michael Stummvoll * @author Stephane Nicoll * @author Scott Frederick + * @author Moritz Halbritter * @since 1.0.0 * @see ErrorAttributes * @see ErrorProperties @@ -121,6 +122,7 @@ public class BasicErrorController extends AbstractErrorController { if (isIncludeBindingErrors(request, mediaType)) { options = options.including(Include.BINDING_ERRORS); } + options = isIncludePath(request, mediaType) ? options.including(Include.PATH) : options.excluding(Include.PATH); return options; } @@ -134,7 +136,7 @@ public class BasicErrorController extends AbstractErrorController { return switch (getErrorProperties().getIncludeStacktrace()) { case ALWAYS -> true; case ON_PARAM -> getTraceParameter(request); - default -> false; + case NEVER -> false; }; } @@ -148,7 +150,7 @@ public class BasicErrorController extends AbstractErrorController { return switch (getErrorProperties().getIncludeMessage()) { case ALWAYS -> true; case ON_PARAM -> getMessageParameter(request); - default -> false; + case NEVER -> false; }; } @@ -162,7 +164,22 @@ public class BasicErrorController extends AbstractErrorController { return switch (getErrorProperties().getIncludeBindingErrors()) { case ALWAYS -> true; case ON_PARAM -> getErrorsParameter(request); - default -> false; + case NEVER -> false; + }; + } + + /** + * Determine if the path attribute should be included. + * @param request the source request + * @param produces the media type produced (or {@code MediaType.ALL}) + * @return if the path attribute should be included + * @since 3.3.0 + */ + protected boolean isIncludePath(HttpServletRequest request, MediaType produces) { + return switch (getErrorProperties().getIncludePath()) { + case ALWAYS -> true; + case ON_PARAM -> getPathParameter(request); + case NEVER -> false; }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java index e4d211f546..a0099198d6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java @@ -112,8 +112,7 @@ class ErrorMvcAutoConfigurationTests { } private ErrorAttributeOptions withAllOptions() { - return ErrorAttributeOptions.of(Include.EXCEPTION, Include.STACK_TRACE, Include.MESSAGE, - Include.BINDING_ERRORS); + return ErrorAttributeOptions.of(Include.values()); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java index c32f683276..04622711e8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/error/ErrorAttributeOptions.java @@ -88,7 +88,7 @@ public final class ErrorAttributeOptions { * @return an {@code ErrorAttributeOptions} */ public static ErrorAttributeOptions defaults() { - return of(); + return of(Include.PATH); } /** @@ -135,7 +135,13 @@ public final class ErrorAttributeOptions { /** * Include the binding errors attribute. */ - BINDING_ERRORS + BINDING_ERRORS, + + /** + * Include the request path. + * @since 3.3.0 + */ + PATH } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java index 354b46988a..3fb955851f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java @@ -57,6 +57,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Stephane Nicoll * @author Michele Mancioppi * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 * @see ErrorAttributes */ @@ -79,6 +80,9 @@ public class DefaultErrorAttributes implements ErrorAttributes { if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } + if (!options.isIncluded(Include.PATH)) { + errorAttributes.remove("path"); + } return errorAttributes; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java index ef02be9670..bfe3f0b71e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.java @@ -61,6 +61,7 @@ import org.springframework.web.servlet.ModelAndView; * @author Stephane Nicoll * @author Vedran Pavic * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 * @see ErrorAttributes */ @@ -100,6 +101,9 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } + if (!options.isIncluded(Include.PATH)) { + errorAttributes.remove("path"); + } return errorAttributes; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java index 9b3d63375c..00de9d00bb 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/error/DefaultErrorAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * @author Brian Clozel * @author Stephane Nicoll * @author Scott Frederick + * @author Moritz Halbritter */ class DefaultErrorAttributesTests { @@ -212,6 +213,14 @@ class DefaultErrorAttributesTests { assertThat(attributes.get("trace").toString()).startsWith("java.lang"); } + @Test + void includePathByDefault() { + MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); + Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), + ErrorAttributeOptions.defaults()); + assertThat(attributes).containsEntry("path", "/test"); + } + @Test void includePath() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); @@ -220,6 +229,14 @@ class DefaultErrorAttributesTests { assertThat(attributes).containsEntry("path", "/test"); } + @Test + void excludePath() { + MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); + Map attributes = this.errorAttributes.getErrorAttributes(buildServerRequest(request, NOT_FOUND), + ErrorAttributeOptions.of()); + assertThat(attributes).doesNotContainEntry("path", "/test"); + } + @Test void includeLogPrefix() { MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java index fe6848d0cb..53fa510427 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/error/DefaultErrorAttributesTests.java @@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Vedran Pavic * @author Scott Frederick + * @author Moritz Halbritter */ class DefaultErrorAttributesTests { @@ -249,13 +250,29 @@ class DefaultErrorAttributesTests { } @Test - void path() { + void shouldIncludePathByDefault() { this.request.setAttribute("jakarta.servlet.error.request_uri", "path"); Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, ErrorAttributeOptions.defaults()); assertThat(attributes).containsEntry("path", "path"); } + @Test + void shouldIncludePath() { + this.request.setAttribute("jakarta.servlet.error.request_uri", "path"); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, + ErrorAttributeOptions.of(Include.PATH)); + assertThat(attributes).containsEntry("path", "path"); + } + + @Test + void shouldExcludePath() { + this.request.setAttribute("jakarta.servlet.error.request_uri", "path"); + Map attributes = this.errorAttributes.getErrorAttributes(this.webRequest, + ErrorAttributeOptions.of()); + assertThat(attributes).doesNotContainEntry("path", "path"); + } + @Test void whenGetMessageIsOverriddenThenMessageAttributeContainsValueReturnedFromIt() { Map attributes = new DefaultErrorAttributes() {