mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Move extended web data binders to common support packages
This resolves cyclic dependencies between function and annotation-based packages. See gh-35800
This commit is contained in:
+1
-1
@@ -61,7 +61,7 @@ import org.springframework.web.reactive.accept.ApiVersionStrategy;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
|
||||
import org.springframework.web.reactive.result.method.annotation.ExtendedWebExchangeDataBinder;
|
||||
import org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Extended variant of {@link WebExchangeDataBinder} that adds URI path variables
|
||||
* and request headers to the bind values map.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0.2
|
||||
* @see WebExchangeDataBinder
|
||||
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
|
||||
*/
|
||||
public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
|
||||
|
||||
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
|
||||
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
|
||||
|
||||
|
||||
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance, with default object name.
|
||||
* @param target the target object to bind onto (or {@code null} if the
|
||||
* binder is just used to convert a plain parameter value)
|
||||
* @see #DEFAULT_OBJECT_NAME
|
||||
*/
|
||||
public ExtendedWebExchangeDataBinder(@Nullable Object target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
|
||||
super(target, objectName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a Predicate that filters the header names to use for data binding.
|
||||
* Multiple predicates are combined with {@code AND}.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void addHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = this.headerPredicate.and(headerPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Predicate that filters the header names to use for data binding.
|
||||
* <p>Note that this method resets any previous predicates that may have been
|
||||
* set, including headers excluded by default such as the RFC 9218 defined
|
||||
* "Priority" header.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void setHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = headerPredicate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
|
||||
return super.getValuesToBind(exchange).doOnNext(map -> {
|
||||
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (!CollectionUtils.isEmpty(vars)) {
|
||||
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
|
||||
}
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
for (Map.Entry<String, List<String>> entry : headers.headerSet()) {
|
||||
String name = entry.getKey();
|
||||
if (!this.headerPredicate.test(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
List<String> values = entry.getValue();
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
// For constructor args with @BindParam mapped to the actual header name
|
||||
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
|
||||
// Also adapt to Java conventions for setters
|
||||
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
|
||||
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addValueIfNotPresent(
|
||||
Map<String, Object> map, String label, String name, @Nullable Object value) {
|
||||
|
||||
if (value != null) {
|
||||
if (map.containsKey(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
map.put(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+4
-95
@@ -16,21 +16,9 @@
|
||||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Extended variant of {@link WebExchangeDataBinder} that adds URI path variables
|
||||
@@ -41,93 +29,14 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.2.1
|
||||
* @deprecated in favor of the relocated
|
||||
* {@link org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder}
|
||||
*/
|
||||
public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
|
||||
|
||||
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
|
||||
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
|
||||
|
||||
|
||||
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance, with default object name.
|
||||
* @param target the target object to bind onto (or {@code null} if the
|
||||
* binder is just used to convert a plain parameter value)
|
||||
* @since 7.0.2
|
||||
* @see #DEFAULT_OBJECT_NAME
|
||||
*/
|
||||
public ExtendedWebExchangeDataBinder(@Nullable Object target) {
|
||||
super(target);
|
||||
}
|
||||
@Deprecated(since = "7.0.2", forRemoval = true)
|
||||
public class ExtendedWebExchangeDataBinder extends org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder {
|
||||
|
||||
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
|
||||
super(target, objectName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a Predicate that filters the header names to use for data binding.
|
||||
* Multiple predicates are combined with {@code AND}.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void addHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = this.headerPredicate.and(headerPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Predicate that filters the header names to use for data binding.
|
||||
* <p>Note that this method resets any previous predicates that may have been
|
||||
* set, including headers excluded by default such as the RFC 9218 defined
|
||||
* "Priority" header.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void setHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = headerPredicate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
|
||||
return super.getValuesToBind(exchange).doOnNext(map -> {
|
||||
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (!CollectionUtils.isEmpty(vars)) {
|
||||
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
|
||||
}
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
for (Map.Entry<String, List<String>> entry : headers.headerSet()) {
|
||||
String name = entry.getKey();
|
||||
if (!this.headerPredicate.test(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
List<String> values = entry.getValue();
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
// For constructor args with @BindParam mapped to the actual header name
|
||||
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
|
||||
// Also adapt to Java conventions for setters
|
||||
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
|
||||
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addValueIfNotPresent(
|
||||
Map<String, Object> map, String label, String name, @Nullable Object value) {
|
||||
|
||||
if (value != null) {
|
||||
if (map.containsKey(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
map.put(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -72,12 +72,12 @@ class InitBinderBindingContext extends BindingContext {
|
||||
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link ExtendedWebExchangeDataBinder}.
|
||||
* Returns an instance of {@link org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder}.
|
||||
* @since 6.2.1
|
||||
*/
|
||||
@Override
|
||||
protected WebExchangeDataBinder createBinderInstance(@Nullable Object target, String name) {
|
||||
return new ExtendedWebExchangeDataBinder(target, name);
|
||||
return new org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder(target, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-2
@@ -215,7 +215,9 @@ class InitBinderBindingContextTests {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
|
||||
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
|
||||
ExtendedWebExchangeDataBinder binder = (ExtendedWebExchangeDataBinder) context.createDataBinder(exchange, null, "", null);
|
||||
org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder binder =
|
||||
(org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder)
|
||||
context.createDataBinder(exchange, null, "", null);
|
||||
binder.addHeaderPredicate(name -> !name.equalsIgnoreCase("Another-Int-Array"));
|
||||
|
||||
Map<String, Object> map = binder.getValuesToBind(exchange).block();
|
||||
@@ -233,7 +235,9 @@ class InitBinderBindingContextTests {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
|
||||
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
|
||||
ExtendedWebExchangeDataBinder binder = (ExtendedWebExchangeDataBinder) context.createDataBinder(exchange, null, "", null);
|
||||
org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder binder =
|
||||
(org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder)
|
||||
context.createDataBinder(exchange, null, "", null);
|
||||
|
||||
Map<String, Object> map = binder.getValuesToBind(exchange).block();
|
||||
assertThat(map).isEmpty();
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
|
||||
import org.springframework.web.servlet.support.ExtendedServletRequestDataBinder;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
||||
+4
-160
@@ -16,22 +16,9 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
@@ -51,15 +38,11 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||
* @since 3.1
|
||||
* @see ServletRequestDataBinder
|
||||
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
|
||||
* @deprecated in favor of the relocated
|
||||
* {@link org.springframework.web.servlet.support.ExtendedServletRequestDataBinder}
|
||||
*/
|
||||
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||
|
||||
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
|
||||
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
|
||||
|
||||
|
||||
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
|
||||
|
||||
@Deprecated(since = "7.0.2", forRemoval = true)
|
||||
public class ExtendedServletRequestDataBinder extends org.springframework.web.servlet.support.ExtendedServletRequestDataBinder {
|
||||
|
||||
/**
|
||||
* Create a new instance, with default object name.
|
||||
@@ -82,143 +65,4 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||
super(target, objectName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a Predicate that filters the header names to use for data binding.
|
||||
* Multiple predicates are combined with {@code AND}.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void addHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = this.headerPredicate.and(headerPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Predicate that filters the header names to use for data binding.
|
||||
* <p>Note that this method resets any previous predicates that may have been
|
||||
* set, including headers excluded by default such as the RFC 9218 defined
|
||||
* "Priority" header.
|
||||
* @param headerPredicate the predicate to add
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public void setHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = headerPredicate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ServletRequestValueResolver createValueResolver(ServletRequest request) {
|
||||
return new ExtendedServletRequestValueResolver(request, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge URI variables into the property values to use for data binding.
|
||||
*/
|
||||
@Override
|
||||
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
||||
Map<String, String> uriVars = getUriVars(request);
|
||||
if (uriVars != null) {
|
||||
uriVars.forEach((name, value) -> addValueIfNotPresent(mpvs, "URI variable", name, value));
|
||||
}
|
||||
if (request instanceof HttpServletRequest httpRequest) {
|
||||
Enumeration<String> names = httpRequest.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Object value = getHeaderValue(httpRequest, name);
|
||||
if (value != null) {
|
||||
name = normalizeHeaderName(name);
|
||||
addValueIfNotPresent(mpvs, "Header", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static @Nullable Map<String, String> getUriVars(ServletRequest request) {
|
||||
return (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private static void addValueIfNotPresent(MutablePropertyValues mpvs, String label, String name, Object value) {
|
||||
if (mpvs.contains(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.addPropertyValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Object getHeaderValue(HttpServletRequest request, String name) {
|
||||
if (!this.headerPredicate.test(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Enumeration<String> valuesEnum = request.getHeaders(name);
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = valuesEnum.nextElement();
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
while (valuesEnum.hasMoreElements()) {
|
||||
values.add(valuesEnum.nextElement());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static String normalizeHeaderName(String name) {
|
||||
return StringUtils.uncapitalize(name.replace("-", ""));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolver of values that looks up URI path variables.
|
||||
*/
|
||||
private class ExtendedServletRequestValueResolver extends ServletRequestValueResolver {
|
||||
|
||||
ExtendedServletRequestValueResolver(ServletRequest request, WebDataBinder dataBinder) {
|
||||
super(request, dataBinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getRequestParameter(String name, Class<?> type) {
|
||||
Object value = super.getRequestParameter(name, type);
|
||||
if (value == null) {
|
||||
Map<String, String> uriVars = getUriVars(getRequest());
|
||||
if (uriVars != null) {
|
||||
value = uriVars.get(name);
|
||||
}
|
||||
if (value == null && getRequest() instanceof HttpServletRequest httpServletRequest) {
|
||||
value = getHeaderValue(httpServletRequest, name);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> initParameterNames(ServletRequest request) {
|
||||
Set<String> set = super.initParameterNames(request);
|
||||
Map<String, String> uriVars = getUriVars(getRequest());
|
||||
if (uriVars != null) {
|
||||
set.addAll(uriVars.keySet());
|
||||
}
|
||||
if (request instanceof HttpServletRequest httpServletRequest) {
|
||||
Enumeration<String> enumeration = httpServletRequest.getHeaderNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String name = enumeration.nextElement();
|
||||
if (headerPredicate.test(name)) {
|
||||
set.add(normalizeHeaderName(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -46,13 +46,13 @@ public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link ExtendedServletRequestDataBinder}.
|
||||
* Returns an instance of {@link org.springframework.web.servlet.support.ExtendedServletRequestDataBinder}.
|
||||
*/
|
||||
@Override
|
||||
protected ServletRequestDataBinder createBinderInstance(
|
||||
@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {
|
||||
|
||||
return new ExtendedServletRequestDataBinder(target, objectName);
|
||||
return new org.springframework.web.servlet.support.ExtendedServletRequestDataBinder(target, objectName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* Subclass of {@link ServletRequestDataBinder} that adds URI template variables
|
||||
* and request headers to the values used for data binding.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore, the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0.2
|
||||
* @see ServletRequestDataBinder
|
||||
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
|
||||
*/
|
||||
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||
|
||||
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
|
||||
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
|
||||
|
||||
|
||||
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance, with default object name.
|
||||
* @param target the target object to bind onto (or {@code null}
|
||||
* if the binder is just used to convert a plain parameter value)
|
||||
* @see #DEFAULT_OBJECT_NAME
|
||||
*/
|
||||
public ExtendedServletRequestDataBinder(@Nullable Object target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param target the target object to bind onto (or {@code null}
|
||||
* if the binder is just used to convert a plain parameter value)
|
||||
* @param objectName the name of the target object
|
||||
* @see #DEFAULT_OBJECT_NAME
|
||||
*/
|
||||
public ExtendedServletRequestDataBinder(@Nullable Object target, String objectName) {
|
||||
super(target, objectName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a Predicate that filters the header names to use for data binding.
|
||||
* Multiple predicates are combined with {@code AND}.
|
||||
* @param headerPredicate the predicate to add
|
||||
*/
|
||||
public void addHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = this.headerPredicate.and(headerPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Predicate that filters the header names to use for data binding.
|
||||
* <p>Note that this method resets any previous predicates that may have been
|
||||
* set, including headers excluded by default such as the RFC 9218 defined
|
||||
* "Priority" header.
|
||||
* @param headerPredicate the predicate to add
|
||||
*/
|
||||
public void setHeaderPredicate(Predicate<String> headerPredicate) {
|
||||
this.headerPredicate = headerPredicate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ServletRequestValueResolver createValueResolver(ServletRequest request) {
|
||||
return new ExtendedServletRequestValueResolver(request, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge URI variables into the property values to use for data binding.
|
||||
*/
|
||||
@Override
|
||||
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
||||
Map<String, String> uriVars = getUriVars(request);
|
||||
if (uriVars != null) {
|
||||
uriVars.forEach((name, value) -> addValueIfNotPresent(mpvs, "URI variable", name, value));
|
||||
}
|
||||
if (request instanceof HttpServletRequest httpRequest) {
|
||||
Enumeration<String> names = httpRequest.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Object value = getHeaderValue(httpRequest, name);
|
||||
if (value != null) {
|
||||
name = normalizeHeaderName(name);
|
||||
addValueIfNotPresent(mpvs, "Header", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static @Nullable Map<String, String> getUriVars(ServletRequest request) {
|
||||
return (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private static void addValueIfNotPresent(MutablePropertyValues mpvs, String label, String name, Object value) {
|
||||
if (mpvs.contains(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.addPropertyValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Object getHeaderValue(HttpServletRequest request, String name) {
|
||||
if (!this.headerPredicate.test(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Enumeration<String> valuesEnum = request.getHeaders(name);
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = valuesEnum.nextElement();
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
while (valuesEnum.hasMoreElements()) {
|
||||
values.add(valuesEnum.nextElement());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static String normalizeHeaderName(String name) {
|
||||
return StringUtils.uncapitalize(name.replace("-", ""));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolver of values that looks up URI path variables.
|
||||
*/
|
||||
private class ExtendedServletRequestValueResolver extends ServletRequestValueResolver {
|
||||
|
||||
ExtendedServletRequestValueResolver(ServletRequest request, WebDataBinder dataBinder) {
|
||||
super(request, dataBinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getRequestParameter(String name, Class<?> type) {
|
||||
Object value = super.getRequestParameter(name, type);
|
||||
if (value == null) {
|
||||
Map<String, String> uriVars = getUriVars(getRequest());
|
||||
if (uriVars != null) {
|
||||
value = uriVars.get(name);
|
||||
}
|
||||
if (value == null && getRequest() instanceof HttpServletRequest httpServletRequest) {
|
||||
value = getHeaderValue(httpServletRequest, name);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> initParameterNames(ServletRequest request) {
|
||||
Set<String> set = super.initParameterNames(request);
|
||||
Map<String, String> uriVars = getUriVars(getRequest());
|
||||
if (uriVars != null) {
|
||||
set.addAll(uriVars.keySet());
|
||||
}
|
||||
if (request instanceof HttpServletRequest httpServletRequest) {
|
||||
Enumeration<String> enumeration = httpServletRequest.getHeaderNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String name = enumeration.nextElement();
|
||||
if (headerPredicate.test(name)) {
|
||||
set.add(normalizeHeaderName(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
package org.springframework.web.servlet.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
Reference in New Issue
Block a user