diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java index 799479313a5..ffd6a5a672c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java @@ -49,7 +49,7 @@ import java.beans.PropertyDescriptor; public interface BeanWrapper extends ConfigurablePropertyAccessor { /** - * Specify a limit for array and collection auto-growing. + * Specify a limit for array and collection/set/list auto-growing. *

Default is unlimited on a plain BeanWrapper. * @since 4.1 */ diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index 8455682b949..db804b4a031 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -295,8 +295,13 @@ class TypeConverterDelegate { ClassLoader cl = this.targetObject.getClass().getClassLoader(); try { Class enumValueType = ClassUtils.forName(enumType, cl); - Field enumField = enumValueType.getField(fieldName); - convertedValue = enumField.get(null); + if (enumValueType.isEnum()) { + Field enumField = enumValueType.getField(fieldName); + convertedValue = enumField.get(null); + } + else if (logger.isTraceEnabled()) { + logger.trace("Specified enum class [" + enumType + "] is not a Java enum"); + } } catch (ClassNotFoundException ex) { if (logger.isTraceEnabled()) { @@ -313,8 +318,7 @@ class TypeConverterDelegate { if (convertedValue == currentConvertedValue) { // Try field lookup as fallback: for Java enum or custom enum - // with values defined as static fields. Resulting value still needs - // to be checked, hence we don't return it right away. + // with values defined as static fields. try { Field enumField = requiredType.getField(trimmedValue); ReflectionUtils.makeAccessible(enumField); diff --git a/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java b/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java index 93ca113e077..64a59bd4780 100644 --- a/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java +++ b/spring-context/src/main/java/org/springframework/context/support/MessageSourceSupport.java @@ -17,15 +17,19 @@ package org.springframework.context.support; import java.text.MessageFormat; +import java.util.Arrays; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Base class for message source implementations, providing support infrastructure @@ -41,6 +45,9 @@ import org.springframework.util.ObjectUtils; */ public abstract class MessageSourceSupport { + static final Set JVM_LOCALES = Arrays.stream(Locale.getAvailableLocales()). + filter(l -> StringUtils.hasLength(l.getLanguage())).collect(Collectors.toSet()); + private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat(""); /** Logger available to subclasses. */ @@ -116,22 +123,17 @@ public abstract class MessageSourceSupport { if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) { return msg; } - Map messageFormatsPerLocale = this.messageFormatsPerMessage - .computeIfAbsent(msg, key -> new ConcurrentHashMap<>()); - MessageFormat messageFormat = messageFormatsPerLocale.computeIfAbsent(locale, key -> { - try { - return createMessageFormat(msg, locale); - } - catch (IllegalArgumentException ex) { - // Invalid message format - probably not intended for formatting, - // rather using a message structure with no arguments involved... - if (isAlwaysUseMessageFormat()) { - throw ex; - } - // Silently proceed with raw message if format not enforced... - return INVALID_MESSAGE_FORMAT; - } - }); + + MessageFormat messageFormat; + if (locale != null && JVM_LOCALES.contains(locale)) { + Map messageFormatsPerLocale = this.messageFormatsPerMessage + .computeIfAbsent(msg, key -> new ConcurrentHashMap<>()); + messageFormat = messageFormatsPerLocale.computeIfAbsent(locale, key -> resolveMessageFormat(msg, key)); + } + else { + messageFormat = resolveMessageFormat(msg, locale); + } + if (messageFormat == INVALID_MESSAGE_FORMAT) { return msg; } @@ -140,6 +142,29 @@ public abstract class MessageSourceSupport { } } + /** + * Resolve a {@code MessageFormat} for the given message and Locale. + * @param msg the message to create a {@code MessageFormat} for + * @param locale the Locale to create a {@code MessageFormat} for + * @return the {@code MessageFormat} instance, or otherwise + * {@link #INVALID_MESSAGE_FORMAT} if not resolvable + * @see #createMessageFormat + */ + private MessageFormat resolveMessageFormat(String msg, @Nullable Locale locale) { + try { + return createMessageFormat(msg, locale); + } + catch (IllegalArgumentException ex) { + // Invalid message format - probably not intended for formatting, + // rather using a message structure with no arguments involved... + if (isAlwaysUseMessageFormat()) { + throw ex; + } + // Silently proceed with raw message if format not enforced... + return INVALID_MESSAGE_FORMAT; + } + } + /** * Create a {@code MessageFormat} for the given message and Locale. * @param msg the message to create a {@code MessageFormat} for diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java index 305852097cc..3c2f7bf57f6 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java @@ -40,6 +40,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.ConcurrentLruCache; import org.springframework.util.DefaultPropertiesPersister; import org.springframework.util.PropertiesPersister; import org.springframework.util.StringUtils; @@ -114,15 +115,19 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased private ResourceLoader resourceLoader = new DefaultResourceLoader(); - // Cache to hold filename lists per Locale + // Cache to hold filename lists per Locale. private final ConcurrentMap>> cachedFilenames = new ConcurrentHashMap<>(); - // Cache to hold already loaded properties per filename + // Cache to hold already loaded properties per filename. private final ConcurrentMap cachedProperties = new ConcurrentHashMap<>(); - // Cache to hold already loaded properties per filename + // Cache to hold already merged properties per Locale. private final ConcurrentMap cachedMergedProperties = new ConcurrentHashMap<>(); + // Cache to hold merged properties per non-JVM Locale. + private final ConcurrentLruCache customLocaleProperties = + new ConcurrentLruCache<>(64, locale -> mergeProperties(collectPropertiesToMerge(locale))); + /** * Set the list of supported file extensions. @@ -201,20 +206,16 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased protected @Nullable String resolveCodeWithoutArguments(String code, Locale locale) { if (getCacheMillis() < 0) { PropertiesHolder propHolder = getMergedProperties(locale); - String result = propHolder.getProperty(code); - if (result != null) { - return result; - } + return propHolder.getProperty(code); } - else { - for (String basename : getBasenameSet()) { - List filenames = calculateAllFilenames(basename, locale); - for (String filename : filenames) { - PropertiesHolder propHolder = getProperties(filename); - String result = propHolder.getProperty(code); - if (result != null) { - return result; - } + + for (String basename : getBasenameSet()) { + List filenames = calculateAllFilenames(basename, locale); + for (String filename : filenames) { + PropertiesHolder propHolder = getProperties(filename, locale); + String result = propHolder.getProperty(code); + if (result != null) { + return result; } } } @@ -229,20 +230,16 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased protected @Nullable MessageFormat resolveCode(String code, Locale locale) { if (getCacheMillis() < 0) { PropertiesHolder propHolder = getMergedProperties(locale); - MessageFormat result = propHolder.getMessageFormat(code, locale); - if (result != null) { - return result; - } + return propHolder.getMessageFormat(code, locale); } - else { - for (String basename : getBasenameSet()) { - List filenames = calculateAllFilenames(basename, locale); - for (String filename : filenames) { - PropertiesHolder propHolder = getProperties(filename); - MessageFormat result = propHolder.getMessageFormat(code, locale); - if (result != null) { - return result; - } + + for (String basename : getBasenameSet()) { + List filenames = calculateAllFilenames(basename, locale); + for (String filename : filenames) { + PropertiesHolder propHolder = getProperties(filename, locale); + MessageFormat result = propHolder.getMessageFormat(code, locale); + if (result != null) { + return result; } } } @@ -265,12 +262,18 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased if (mergedHolder != null) { return mergedHolder; } - mergedHolder = mergeProperties(collectPropertiesToMerge(locale)); - PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder); - if (existing != null) { - mergedHolder = existing; + + if (JVM_LOCALES.contains(locale)) { + mergedHolder = mergeProperties(collectPropertiesToMerge(locale)); + PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder); + if (existing != null) { + mergedHolder = existing; + } + return mergedHolder; + } + else { + return this.customLocaleProperties.get(locale); } - return mergedHolder; } /** @@ -289,7 +292,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased List filenames = calculateAllFilenames(basenames[i], locale); for (int j = filenames.size() - 1; j >= 0; j--) { String filename = filenames.get(j); - PropertiesHolder propHolder = getProperties(filename); + PropertiesHolder propHolder = getProperties(filename, locale); if (propHolder.getProperties() != null) { holders.add(propHolder); } @@ -338,11 +341,11 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased } } - // Filenames for given Locale + // Filenames for given Locale. List filenames = new ArrayList<>(7); filenames.addAll(calculateFilenamesForLocale(basename, locale)); - // Filenames for default Locale, if any + // Filenames for default Locale, if any. Locale defaultLocale = getDefaultLocale(); if (defaultLocale != null && !defaultLocale.equals(locale)) { List fallbackFilenames = calculateFilenamesForLocale(basename, defaultLocale); @@ -354,24 +357,27 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased } } - // Filename for default bundle file + // Filename for default bundle file. filenames.add(basename); - if (localeMap == null) { - localeMap = new ConcurrentHashMap<>(); - Map> existing = this.cachedFilenames.putIfAbsent(basename, localeMap); - if (existing != null) { - localeMap = existing; + if (JVM_LOCALES.contains(locale)) { + if (localeMap == null) { + localeMap = new ConcurrentHashMap<>(); + Map> existing = this.cachedFilenames.putIfAbsent(basename, localeMap); + if (existing != null) { + localeMap = existing; + } } + localeMap.put(locale, filenames); } - localeMap.put(locale, filenames); + return filenames; } /** * Calculate the filenames for the given bundle basename and Locale, * appending language code, country code, and variant code. - *

For example, basename "messages", Locale "de_AT_oo" → "messages_de_AT_OO", + *

For example, basename "messages", Locale "de_AT_OO" → "messages_de_AT_OO", * "messages_de_AT", "messages_de". *

Follows the rules defined by {@link java.util.Locale#toString()}. * @param basename the basename of the bundle @@ -406,6 +412,22 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased } + /** + * Get a PropertiesHolder for the given filename, either from the + * cache or freshly loaded. + * @param filename the bundle filename (basename + Locale) + * @param locale the requested locale (for cache filtering) + * @return the current PropertiesHolder for the bundle + * @see #getProperties(String) + */ + private PropertiesHolder getProperties(String filename, Locale locale) { + PropertiesHolder propHolder = getProperties(filename); + if (propHolder.getProperties() == null && !JVM_LOCALES.contains(locale)) { + this.cachedProperties.remove(filename); + } + return propHolder; + } + /** * Get a PropertiesHolder for the given filename, either from the * cache or freshly loaded. diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java index 954d6edb780..2b02ec479e9 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java @@ -188,36 +188,35 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou * found for the given basename and Locale */ protected @Nullable ResourceBundle getResourceBundle(String basename, Locale locale) { - if (getCacheMillis() >= 0) { + if (getCacheMillis() >= 0 || !JVM_LOCALES.contains(locale)) { // Fresh ResourceBundle.getBundle call in order to let ResourceBundle // do its native caching, at the expense of more extensive lookup steps. return doGetBundle(basename, locale); } - else { - // Cache forever: prefer locale cache over repeated getBundle calls. - Map localeMap = this.cachedResourceBundles.get(basename); - if (localeMap != null) { - ResourceBundle bundle = localeMap.get(locale); - if (bundle != null) { - return bundle; - } - } - try { - ResourceBundle bundle = doGetBundle(basename, locale); - if (localeMap == null) { - localeMap = this.cachedResourceBundles.computeIfAbsent(basename, bn -> new ConcurrentHashMap<>()); - } - localeMap.put(locale, bundle); + + // Cache forever: prefer local cache over repeated getBundle calls. + Map localeMap = this.cachedResourceBundles.get(basename); + if (localeMap != null) { + ResourceBundle bundle = localeMap.get(locale); + if (bundle != null) { return bundle; } - catch (MissingResourceException ex) { - if (logger.isWarnEnabled()) { - logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage()); - } - // Assume bundle not found - // -> do NOT throw the exception to allow for checking parent message source. - return null; + } + try { + ResourceBundle bundle = doGetBundle(basename, locale); + if (localeMap == null) { + localeMap = this.cachedResourceBundles.computeIfAbsent(basename, bn -> new ConcurrentHashMap<>()); } + localeMap.put(locale, bundle); + return bundle; + } + catch (MissingResourceException ex) { + if (logger.isWarnEnabled()) { + logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage()); + } + // Assume bundle not found + // -> do NOT throw the exception to allow for checking parent message source. + return null; } } @@ -311,6 +310,11 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou protected @Nullable MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale) throws MissingResourceException { + if (!JVM_LOCALES.contains(locale)) { + String msg = getStringOrNull(bundle, code); + return (msg != null ? createMessageFormat(msg, locale) : null); + } + Map> codeMap = this.cachedBundleMessageFormats.get(bundle); Map localeMap = null; if (codeMap != null) { diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 2174afd37e9..61321a0fe53 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -270,12 +270,12 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { } /** - * Specify the limit for array and collection auto-growing. + * Specify the limit for array and collection/set/list auto-growing. *

Default is 256, preventing OutOfMemoryErrors in case of large indexes. * Raise this limit if your auto-growing needs are unusually high. *

Used for setter injection - and as of 7.1 also for field injection - - * via {@link #bind(PropertyValues)}; not applicable to constructor binding - * via {@link #construct}. + * via {@link #bind(PropertyValues)}; not applicable to map properties and + * not to constructor binding via {@link #construct} either. * @see #initBeanPropertyAccess() * @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit */ @@ -326,6 +326,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { /** * Initialize direct field access for this DataBinder, * as alternative to the default bean property access. + *

NOTE: This is an advanced option for trusted scenarios. + * Do not use direct field access for data binding from untrusted sources. * @see #initBeanPropertyAccess() * @see #createDirectFieldBindingResult() */ diff --git a/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java index f2ee2398c1c..fe343e51474 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java @@ -100,20 +100,20 @@ public class LocalizedResourceHelper { String variant = locale.getVariant(); // Check for file with language, country and variant localization. - if (variant.length() > 0) { + if (!variant.isEmpty()) { String location = name + this.separator + lang + this.separator + country + this.separator + variant + extension; resource = this.resourceLoader.getResource(location); } // Check for file with language and country localization. - if ((resource == null || !resource.exists()) && country.length() > 0) { + if ((resource == null || !resource.exists()) && !country.isEmpty()) { String location = name + this.separator + lang + this.separator + country + extension; resource = this.resourceLoader.getResource(location); } // Check for document with language localization. - if ((resource == null || !resource.exists()) && lang.length() > 0) { + if ((resource == null || !resource.exists()) && !lang.isEmpty()) { String location = name + this.separator + lang + extension; resource = this.resourceLoader.getResource(location); } diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java index 5a3d760edd1..9a66902ce4c 100644 --- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java +++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java @@ -18,6 +18,7 @@ package org.springframework.core.serializer; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import org.jspecify.annotations.Nullable; @@ -38,6 +39,8 @@ public class DefaultDeserializer implements Deserializer { private final @Nullable ClassLoader classLoader; + private final @Nullable ObjectInputFilter objectInputFilter; + /** * Create a {@code DefaultDeserializer} with default {@link ObjectInputStream} @@ -45,6 +48,7 @@ public class DefaultDeserializer implements Deserializer { */ public DefaultDeserializer() { this.classLoader = null; + this.objectInputFilter = null; } /** @@ -56,6 +60,21 @@ public class DefaultDeserializer implements Deserializer { */ public DefaultDeserializer(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; + this.objectInputFilter = null; + } + + /** + * Create a {@code DefaultDeserializer} for using an {@link ObjectInputStream} + * with the given {@code ClassLoader}. + * @param classLoader the ClassLoader to use + * @param objectInputFilter a custom ObjectInputFilter to apply + * @since 7.0.9 + * @see ConfigurableObjectInputStream#ConfigurableObjectInputStream(InputStream, ClassLoader) + * @see ObjectInputStream#setObjectInputFilter + */ + public DefaultDeserializer(@Nullable ClassLoader classLoader, @Nullable ObjectInputFilter objectInputFilter) { + this.classLoader = classLoader; + this.objectInputFilter = objectInputFilter; } @@ -65,10 +84,20 @@ public class DefaultDeserializer implements Deserializer { * @since 6.2.19 * @see ConfigurableObjectInputStream#ConfigurableObjectInputStream(InputStream, ClassLoader) */ - public @Nullable ClassLoader getClassLoader() { + public final @Nullable ClassLoader getClassLoader() { return this.classLoader; } + /** + * Return the {@link ObjectInputFilter} to apply to the {@link ObjectInputStream}, + * if any. + * @since 7.0.9 + * @see ObjectInputStream#setObjectInputFilter + */ + public final @Nullable ObjectInputFilter getObjectInputFilter() { + return this.objectInputFilter; + } + /** * Read from the supplied {@code InputStream} and deserialize the contents @@ -78,6 +107,9 @@ public class DefaultDeserializer implements Deserializer { @Override public Object deserialize(InputStream inputStream) throws IOException { ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader); + if (this.objectInputFilter != null) { + objectInputStream.setObjectInputFilter(this.objectInputFilter); + } try { return objectInputStream.readObject(); } diff --git a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java index 0f5f8d6b16f..d0f7f293853 100644 --- a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java +++ b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java @@ -118,7 +118,7 @@ public abstract class FileSystemUtils { /** * Recursively copy the contents of the {@code src} file/directory - * to the {@code dest} file/directory. + * to the {@code dest} file/directory, including symbolic links. * @param src the source directory * @param dest the destination directory * @throws IOException in the case of I/O errors diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 84d455a61eb..ea787d7cdd0 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -956,6 +956,7 @@ public abstract class StringUtils { String country = tokens[1]; validateLocalePart(country); String variant = Arrays.stream(tokens).skip(2).collect(Collectors.joining(delimiter)); + validateLocalePart(variant); return new Locale(language, country, variant); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqliteMaxValueIncrementer.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqliteMaxValueIncrementer.java index 7a9549b486e..2a2cc0cc3f4 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqliteMaxValueIncrementer.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqliteMaxValueIncrementer.java @@ -58,7 +58,7 @@ public class SqliteMaxValueIncrementer extends AbstractColumnMaxValueIncrementer @Override - protected long getNextKey() { + protected synchronized long getNextKey() { Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java b/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java index 05346ec2be3..e7f8788c165 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java @@ -30,6 +30,9 @@ import org.springframework.util.Assert; * if the destination resolving process is expensive (for example, the destination has to be * resolved through an external system) and the resolution results are stable anyway. * + *

Note: This cache is not designed for setups with varying dynamic destination names. + * Prefer direct destination resolution in such scenarios. + * * @author Agim Emruli * @author Juergen Hoeller * @since 4.1 diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java index ea6f7521698..d9d833a49cd 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java @@ -83,6 +83,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer * Set whether to use direct field access instead of bean property access. *

Default is {@code false}, using bean property access. * Switch this to {@code true} in order to enforce direct field access. + *

NOTE: This is an advanced option for trusted scenarios. + * Do not use direct field access for data binding from untrusted sources. * @see org.springframework.validation.DataBinder#initDirectFieldAccess() * @see org.springframework.validation.DataBinder#initBeanPropertyAccess() */ diff --git a/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java b/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java index 615ff14c571..ba0051246cc 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java +++ b/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java @@ -124,7 +124,7 @@ class HtmlCharacterEntityDecoder { int value = (!isHexNumberedReference ? Integer.parseInt(getReferenceSubstring(2)) : Integer.parseInt(getReferenceSubstring(3), 16)); - if (value > Character.MAX_CODE_POINT) { + if (value < 0 || value > Character.MAX_CODE_POINT) { return false; } this.decodedMessage.appendCodePoint(value); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java index 1212e7eb1a4..d124c7276f1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java @@ -51,6 +51,7 @@ public class ParameterizableViewController extends AbstractController { setSupportedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name()); } + /** * Set a view name for the ModelAndView to return, to be resolved by the * DispatcherServlet via a ViewResolver. Will override any pre-existing @@ -117,12 +118,11 @@ public class ParameterizableViewController extends AbstractController { return this.statusCode; } - /** * The property can be used to indicate the request is considered fully * handled within the controller and that no view should be used for rendering. * Useful in combination with {@link #setStatusCode}. - *

By default this is set to {@code false}. + *

By default, this is set to {@code false}. * @since 4.1 */ public void setStatusOnly(boolean statusOnly) { @@ -187,10 +187,11 @@ public class ParameterizableViewController extends AbstractController { sb.append("status=").append(this.statusCode); } if (this.view != null) { - sb.append(sb.length() != 0 ? ", " : ""); + sb.append(!sb.isEmpty() ? ", " : ""); String viewName = getViewName(); sb.append("view=").append(viewName != null ? "\"" + viewName + "\"" : this.view); } return sb.toString(); } + }