mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Merge branch '7.0.x'
This commit is contained in:
@@ -17,11 +17,14 @@
|
||||
package org.springframework.http;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.BitSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -59,23 +62,9 @@ public final class ContentDisposition {
|
||||
private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT =
|
||||
"Invalid header field parameter format (as defined in RFC 5987)";
|
||||
|
||||
private static final BitSet PRINTABLE = new BitSet(256);
|
||||
|
||||
private static final HexFormat HEX_FORMAT = HexFormat.of().withUpperCase();
|
||||
|
||||
|
||||
static {
|
||||
// RFC 2045, Section 6.7, and RFC 2047, Section 4.2
|
||||
for (int i=33; i<= 126; i++) {
|
||||
PRINTABLE.set(i);
|
||||
}
|
||||
PRINTABLE.set(34, false); // "
|
||||
PRINTABLE.set(61, false); // =
|
||||
PRINTABLE.set(63, false); // ?
|
||||
PRINTABLE.set(95, false); // _
|
||||
}
|
||||
|
||||
|
||||
private final @Nullable String type;
|
||||
|
||||
private final @Nullable String name;
|
||||
@@ -195,7 +184,7 @@ public final class ContentDisposition {
|
||||
}
|
||||
else {
|
||||
sb.append("; filename=\"");
|
||||
sb.append(encodeQuotedPrintableFilename(this.filename, this.charset)).append('\"');
|
||||
sb.append(toIso88591(encodeQuotedPairs(this.filename))).append('\"');
|
||||
sb.append("; filename*=");
|
||||
sb.append(encodeRfc5987Filename(this.filename, this.charset));
|
||||
}
|
||||
@@ -446,44 +435,16 @@ public final class ContentDisposition {
|
||||
return StreamUtils.copyToString(baos, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode 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 <a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a>
|
||||
*/
|
||||
private static String encodeQuotedPrintableFilename(String filename, Charset charset) {
|
||||
Assert.notNull(filename, "'filename' must not be null");
|
||||
Assert.notNull(charset, "'charset' must not be null");
|
||||
|
||||
byte[] source = filename.getBytes(charset);
|
||||
StringBuilder sb = new StringBuilder(source.length << 1);
|
||||
sb.append("=?");
|
||||
sb.append(charset.name());
|
||||
sb.append("?Q?");
|
||||
for (byte b : source) {
|
||||
if (b == 32) { // RFC 2047, section 4.2, rule (2)
|
||||
sb.append('_');
|
||||
}
|
||||
else if (isPrintable(b)) {
|
||||
sb.append((char) b);
|
||||
}
|
||||
else {
|
||||
sb.append('=');
|
||||
HEX_FORMAT.toHexDigits(sb, b);
|
||||
}
|
||||
private static String toIso88591(String input) {
|
||||
CharsetEncoder encoder = ISO_8859_1.newEncoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPLACE)
|
||||
.replaceWith(new byte[] { (byte) '_' });
|
||||
try {
|
||||
return ISO_8859_1.decode(encoder.encode(CharBuffer.wrap(input))).toString();
|
||||
}
|
||||
sb.append("?=");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isPrintable(byte c) {
|
||||
int b = c;
|
||||
if (b < 0) {
|
||||
b = 256 + b;
|
||||
catch (CharacterCodingException exc) {
|
||||
throw new IllegalArgumentException("Failed to convert to ISO 8859-1", exc);
|
||||
}
|
||||
return PRINTABLE.get(b);
|
||||
}
|
||||
|
||||
private static String encodeQuotedPairs(String filename) {
|
||||
|
||||
@@ -221,7 +221,7 @@ class ContentDispositionTests {
|
||||
.filename("中文.txt", StandardCharsets.UTF_8)
|
||||
.build().toString())
|
||||
.isEqualTo("form-data; name=\"name\"; " +
|
||||
"filename=\"=?UTF-8?Q?=E4=B8=AD=E6=96=87.txt?=\"; " +
|
||||
"filename=\"__.txt\"; " +
|
||||
"filename*=UTF-8''%E4%B8%AD%E6%96%87.txt");
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ class ContentDispositionTests {
|
||||
void formatWithUtf8FilenameWithQuotes() {
|
||||
String filename = "\"中文.txt";
|
||||
assertThat(ContentDisposition.formData().filename(filename, StandardCharsets.UTF_8).build().toString())
|
||||
.isEqualTo("form-data; filename=\"=?UTF-8?Q?=22=E4=B8=AD=E6=96=87.txt?=\"; filename*=UTF-8''%22%E4%B8%AD%E6%96%87.txt");
|
||||
.isEqualTo("form-data; filename=\"\\\"__.txt\"; filename*=UTF-8''%22%E4%B8%AD%E6%96%87.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -303,14 +303,14 @@ class ContentDispositionTests {
|
||||
.build();
|
||||
String result = cd.toString();
|
||||
assertThat(result).isEqualTo("attachment; " +
|
||||
"filename=\"=?UTF-8?Q?filename_with_=3F=E9=97=AE=E5=8F=B7.txt?=\"; " +
|
||||
"filename=\"filename with ?__.txt\"; " +
|
||||
"filename*=UTF-8''filename%20with%20%3F%E9%97%AE%E5%8F%B7.txt");
|
||||
|
||||
String[] parts = result.split("; ");
|
||||
|
||||
String quotedPrintableFilename = parts[0] + "; " + parts[1];
|
||||
assertThat(ContentDisposition.parse(quotedPrintableFilename).getFilename())
|
||||
.isEqualTo(filename);
|
||||
.isEqualTo("filename with ?__.txt");
|
||||
|
||||
String rfc5987Filename = parts[0] + "; " + parts[2];
|
||||
assertThat(ContentDisposition.parse(rfc5987Filename).getFilename())
|
||||
|
||||
Reference in New Issue
Block a user