From e1c0f3b067016b1940003a63220eb0fa4a9cd1e4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 12 Jul 2021 23:19:08 +0200 Subject: [PATCH] CommonsMultipartResolver supports configurable HTTP methods Closes gh-27161 --- .../commons/CommonsMultipartResolver.java | 39 +++++++++++- .../CommonsMultipartResolverTests.java | 62 ++++++++++++++++++- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java index eed600aff08..b6911fe9813 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -16,7 +16,10 @@ package org.springframework.web.multipart.commons; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -27,7 +30,9 @@ import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload.servlet.ServletRequestContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.context.ServletContextAware; import org.springframework.web.multipart.MaxUploadSizeExceededException; @@ -41,7 +46,11 @@ import org.springframework.web.util.WebUtils; /** * Servlet-based {@link MultipartResolver} implementation for * Apache Commons FileUpload - * 1.2 or above. + * 1.2 or above. This resolver variant delegates to a local FileUpload library + * within the application, providing maximum portability across Servlet containers. + * + *

Commons FileUpload traditionally parses POST requests with any "multipart/" type. + * Supported HTTP methods may be customized through {@link #setSupportedMethods}. * *

Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as * bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding @@ -52,19 +61,29 @@ import org.springframework.web.util.WebUtils; * Needs to be initialized either by an application context or * via the constructor that takes a ServletContext (for standalone usage). * + *

Note: The common alternative is + * {@link org.springframework.web.multipart.support.StandardServletMultipartResolver}, + * delegating to the Servlet container's own multipart parser, with configuration to + * happen at the container level and potentially with container-specific limitations. + * * @author Trevor D. Cook * @author Juergen Hoeller * @since 29.09.2003 * @see #CommonsMultipartResolver(ServletContext) * @see #setResolveLazily + * @see #setSupportedMethods * @see org.apache.commons.fileupload.servlet.ServletFileUpload * @see org.apache.commons.fileupload.disk.DiskFileItemFactory + * @see org.springframework.web.multipart.support.StandardServletMultipartResolver */ public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { private boolean resolveLazily = false; + @Nullable + private Set supportedMethods; + /** * Constructor for use as bean. Determines the servlet container's @@ -101,6 +120,17 @@ public class CommonsMultipartResolver extends CommonsFileUploadSupport this.resolveLazily = resolveLazily; } + /** + * Specify supported methods as an array of HTTP method names. + * The traditional Commons FileUpload default is "POST" only. + *

When configured as a Spring property value, + * this can be a comma-separated String: e.g. "POST,PUT". + * @since 5.3.9 + */ + public void setSupportedMethods(String... supportedMethods) { + this.supportedMethods = new HashSet<>(Arrays.asList(supportedMethods)); + } + /** * Initialize the underlying {@code org.apache.commons.fileupload.servlet.ServletFileUpload} * instance. Can be overridden to use a custom subclass, e.g. for testing purposes. @@ -122,7 +152,10 @@ public class CommonsMultipartResolver extends CommonsFileUploadSupport @Override public boolean isMultipart(HttpServletRequest request) { - return ServletFileUpload.isMultipartContent(request); + return (this.supportedMethods != null ? + this.supportedMethods.contains(request.getMethod()) && + FileUploadBase.isMultipartContent(new ServletRequestContext(request)) : + ServletFileUpload.isMultipartContent(request)); } @Override diff --git a/spring-web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java b/spring-web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java index e4aa779da5b..e37bb54c025 100644 --- a/spring-web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -46,6 +46,7 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.junit.jupiter.api.Test; import org.springframework.beans.MutablePropertyValues; +import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.context.WebApplicationContext; @@ -71,6 +72,65 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class CommonsMultipartResolverTests { + @Test + public void isMultipartWithDefaultSetting() { + CommonsMultipartResolver resolver = new CommonsMultipartResolver(); + + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/"); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_MIXED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_RELATED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request = new MockHttpServletRequest("PUT", "/"); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_MIXED_VALUE); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_RELATED_VALUE); + assertThat(resolver.isMultipart(request)).isFalse(); + } + + @Test + public void isMultipartWithSupportedMethods() { + CommonsMultipartResolver resolver = new CommonsMultipartResolver(); + resolver.setSupportedMethods("POST", "PUT"); + + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/"); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_MIXED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_RELATED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request = new MockHttpServletRequest("PUT", "/"); + assertThat(resolver.isMultipart(request)).isFalse(); + + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_MIXED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + + request.setContentType(MediaType.MULTIPART_RELATED_VALUE); + assertThat(resolver.isMultipart(request)).isTrue(); + } + @Test public void withApplicationContext() throws Exception { doTestWithApplicationContext(false);