diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle
index 92fd5833714..7c3ea772331 100644
--- a/spring-web/spring-web.gradle
+++ b/spring-web/spring-web.gradle
@@ -15,7 +15,6 @@ dependencies {
optional("jakarta.el:jakarta.el-api")
optional("jakarta.faces:jakarta.faces-api")
optional("jakarta.json.bind:jakarta.json.bind-api")
- optional("jakarta.mail:jakarta.mail-api")
optional("jakarta.validation:jakarta.validation-api")
optional("jakarta.xml.bind:jakarta.xml.bind-api")
optional("io.reactivex.rxjava3:rxjava")
diff --git a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
index 63149ae9e01..4793657852f 100644
--- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
+++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -33,6 +33,7 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
@@ -51,6 +52,9 @@ public final class ContentDisposition {
private final static Pattern BASE64_ENCODED_PATTERN =
Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?=");
+ private final static Pattern QUOTED_PRINTABLE_ENCODED_PATTERN =
+ Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?Q\\?(\\p{Print}+)\\?=");
+
private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT =
"Invalid header field parameter format (as defined in RFC 5987)";
@@ -371,12 +375,20 @@ public final class ContentDisposition {
if (value.startsWith("=?") ) {
Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value);
if (matcher.find()) {
- String match1 = matcher.group(1);
- String match2 = matcher.group(2);
- filename = new String(Base64.getDecoder().decode(match2), Charset.forName(match1));
+ charset = Charset.forName(matcher.group(1));
+ String encodedValue = matcher.group(2);
+ filename = new String(Base64.getDecoder().decode(encodedValue), charset);
}
else {
- filename = value;
+ matcher = QUOTED_PRINTABLE_ENCODED_PATTERN.matcher(value);
+ if (matcher.find()) {
+ charset = Charset.forName(matcher.group(1));
+ String encodedValue = matcher.group(2);
+ filename = decodeQuotedPrintableFilename(encodedValue, charset);
+ }
+ else {
+ filename = value;
+ }
}
}
else {
@@ -498,6 +510,43 @@ public final class ContentDisposition {
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
}
+ /**
+ * Decode the given header field param as described in RFC 2047.
+ * @param filename the filename
+ * @param charset the charset for the filename
+ * @return the encoded header field param
+ * @see RFC 2047
+ */
+ private static String decodeQuotedPrintableFilename(String filename, Charset charset) {
+ Assert.notNull(filename, "'input' String` should not be null");
+ Assert.notNull(charset, "'charset' should not be null");
+
+ byte[] value = filename.getBytes(US_ASCII);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int index = 0;
+ while (index < value.length) {
+ byte b = value[index];
+ if (b == '_') {
+ baos.write(' ');
+ index++;
+ }
+ else if (b == '=' && index < value.length - 2) {
+ int i1 = Character.digit((char) value[index + 1], 16);
+ int i2 = Character.digit((char) value[index + 2], 16);
+ if (i1 == -1 || i2 == -1) {
+ throw new IllegalArgumentException("Not a valid hex sequence: " + filename.substring(index));
+ }
+ baos.write((i1 << 4) | i2);
+ index += 3;
+ }
+ else {
+ baos.write(b);
+ index++;
+ }
+ }
+ return StreamUtils.copyToString(baos, charset);
+ }
+
private static String escapeQuotationsInFilename(String filename) {
if (filename.indexOf('"') == -1 && filename.indexOf('\\') == -1) {
return filename;
diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
index 73254647415..ed430a56232 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -18,7 +18,6 @@ package org.springframework.http.converter;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
@@ -29,9 +28,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import jakarta.mail.internet.MimeUtility;
-
import org.springframework.core.io.Resource;
+import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
@@ -526,7 +524,13 @@ public class FormHttpMessageConverter implements HttpMessageConverter