This is an automated email from the ASF dual-hosted git repository. matrei pushed a commit to branch fix/issue-16145 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 41730acd35f44fc088b5cca4701e5dec4b7d1d8a Author: Mattias Reichel <[email protected]> AuthorDate: Thu Aug 13 13:36:32 2026 +0200 fix: issue 16145 Closes gh-16145 --- .../web/controllers/ControllersAutoConfiguration.java | 15 +++++++++++++++ .../org/grails/web/filters/HiddenHttpMethodFilter.java | 10 +++++++++- .../grails/web/servlet/mvc/GrailsDispatcherServlet.groovy | 12 ++++++++++++ .../grails/web/servlet/mvc/GrailsWebRequestFilter.java | 4 ++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/grails-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersAutoConfiguration.java b/grails-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersAutoConfiguration.java index e9742d7471..6d1207daa7 100644 --- a/grails-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersAutoConfiguration.java +++ b/grails-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.util.ClassUtils; import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.multipart.support.MultipartFilter; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -101,6 +102,20 @@ public class ControllersAutoConfiguration { return characterEncodingFilter; } + @Bean + @ConditionalOnMissingBean(MultipartFilter.class) + public FilterRegistrationBean<Filter> multipartFilter() { + var registrationBean = new FilterRegistrationBean<>(); + var multipartFilter = new MultipartFilter(); + multipartFilter.setMultipartResolverBeanName(GrailsApplication.MULTIPART_RESOLVER_BEAN); + registrationBean.setFilter(multipartFilter); + registrationBean.addUrlPatterns(Settings.DEFAULT_WEB_SERVLET_PATH); + // Resolve multipart requests before HiddenHttpMethodFilter and security filters access parameters. + // Otherwise, the servlet container parses the parts first and oversized uploads bypass the multipart resolver. + registrationBean.setOrder(GrailsFilters.FIRST.getOrder()); + return registrationBean; + } + @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public FilterRegistrationBean<Filter> hiddenHttpMethodFilter() { diff --git a/grails-web-mvc/src/main/groovy/org/grails/web/filters/HiddenHttpMethodFilter.java b/grails-web-mvc/src/main/groovy/org/grails/web/filters/HiddenHttpMethodFilter.java index 59c8fc3322..7282353607 100644 --- a/grails-web-mvc/src/main/groovy/org/grails/web/filters/HiddenHttpMethodFilter.java +++ b/grails-web-mvc/src/main/groovy/org/grails/web/filters/HiddenHttpMethodFilter.java @@ -71,7 +71,10 @@ public class HiddenHttpMethodFilter extends OncePerRequestFilter { } protected String getHttpMethodOverride(HttpServletRequest request) { - String httpMethod = request.getParameter(methodParam); + String httpMethod = null; + if (!isMultipart(request)) { + httpMethod = request.getParameter(methodParam); + } if (httpMethod == null) { httpMethod = request.getHeader(HEADER_X_HTTP_METHOD_OVERRIDE); @@ -79,6 +82,11 @@ public class HiddenHttpMethodFilter extends OncePerRequestFilter { return httpMethod == null ? null : httpMethod.toUpperCase(); } + private boolean isMultipart(HttpServletRequest request) { + var contentType = request.getContentType(); + return contentType != null && contentType.regionMatches(true, 0, "multipart/", 0, "multipart/".length()); + } + /** * Simple {@link HttpServletRequest} wrapper that returns the supplied method for * {@link HttpServletRequest#getMethod()}. diff --git a/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsDispatcherServlet.groovy b/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsDispatcherServlet.groovy index 5c5331e1bf..79cf359a3b 100644 --- a/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsDispatcherServlet.groovy +++ b/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsDispatcherServlet.groovy @@ -33,6 +33,7 @@ import org.springframework.web.context.WebApplicationContext import org.springframework.web.context.request.RequestAttributes import org.springframework.web.context.request.ServletRequestAttributes import org.springframework.web.multipart.MultipartException +import org.springframework.web.multipart.MultipartHttpServletRequest import org.springframework.web.servlet.DispatcherServlet import grails.util.Holders @@ -65,6 +66,7 @@ class GrailsDispatcherServlet extends DispatcherServlet implements ServletContex else { GrailsWebRequest webRequest = (GrailsWebRequest) previousAttributes if (webRequest.isActive()) { + propagateMultipartRequest(webRequest, request) return webRequest } else { @@ -75,10 +77,19 @@ class GrailsDispatcherServlet extends DispatcherServlet implements ServletContex protected GrailsWebRequest buildGrailsWebRequest(HttpServletRequest request, HttpServletResponse response) { def webRequest = new GrailsWebRequest(request, response, request.getServletContext()) + propagateMultipartRequest(webRequest, request) webRequest.informParameterCreationListeners() return webRequest } + private void propagateMultipartRequest(GrailsWebRequest webRequest, HttpServletRequest request) { + def multipartRequest = + org.springframework.web.util.WebUtils.getNativeRequest(request, MultipartHttpServletRequest) + if (multipartRequest != null) { + webRequest.multipartRequest = multipartRequest + } + } + @Override protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { boolean shouldProcessMultiPart = !WebUtils.isError(request) && !WebUtils.isForwardOrInclude(request) @@ -89,6 +100,7 @@ class GrailsDispatcherServlet extends DispatcherServlet implements ServletContex if (webRequest != null) { webRequest.multipartRequest = processedRequest } + return processedRequest } } return request diff --git a/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsWebRequestFilter.java b/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsWebRequestFilter.java index 820ee1a00b..e86d07d954 100644 --- a/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsWebRequestFilter.java +++ b/grails-web-mvc/src/main/groovy/org/grails/web/servlet/mvc/GrailsWebRequestFilter.java @@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.web.filter.RequestContextFilter; +import org.springframework.web.multipart.MultipartHttpServletRequest; import grails.web.mvc.FlashScope; import org.grails.web.util.WebUtils; @@ -58,6 +59,9 @@ public class GrailsWebRequestFilter extends RequestContextFilter implements Appl boolean isIncludeOrForward = WebUtils.isForwardOrInclude(request); GrailsWebRequest previous = isIncludeOrForward ? GrailsWebRequest.lookup(request) : null; GrailsWebRequest webRequest = new GrailsWebRequest(request, response, getServletContext()); + if (request instanceof MultipartHttpServletRequest) { + webRequest.setMultipartRequest(request); + } configureParameterCreationListeners(webRequest); if (logger.isDebugEnabled()) {
