Include quoted printable filename in ContentDisposition::toString
This commit ensures the ContentDisposition class prints the filename in both in the regular filename parameter and the extended filename* parameter (RFC 5987). Quoted printable (RFC 2047) is used to encode any non-ASCII characters in the regular filename parameter. Closes gh-29861
This commit is contained in:
parent
ebca9f726f
commit
58fbf60d2d
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
|
|
@ -262,8 +262,10 @@ public final class ContentDisposition {
|
|||
sb.append(encodeQuotedPairs(this.filename)).append('\"');
|
||||
}
|
||||
else {
|
||||
sb.append("; filename=\"");
|
||||
sb.append(encodeQuotedPrintableFilename(this.filename, this.charset)).append('\"');
|
||||
sb.append("; filename*=");
|
||||
sb.append(encodeFilename(this.filename, this.charset));
|
||||
sb.append(encodeRfc5987Filename(this.filename, this.charset));
|
||||
}
|
||||
}
|
||||
if (this.size != null) {
|
||||
|
|
@ -364,11 +366,11 @@ public final class ContentDisposition {
|
|||
charset = Charset.forName(value.substring(0, idx1).trim());
|
||||
Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
|
||||
"Charset must be UTF-8 or ISO-8859-1");
|
||||
filename = decodeFilename(value.substring(idx2 + 1), charset);
|
||||
filename = decodeRfc5987Filename(value.substring(idx2 + 1), charset);
|
||||
}
|
||||
else {
|
||||
// US ASCII
|
||||
filename = decodeFilename(value, StandardCharsets.US_ASCII);
|
||||
filename = decodeRfc5987Filename(value, StandardCharsets.US_ASCII);
|
||||
}
|
||||
}
|
||||
else if (attribute.equals("filename") && (filename == null)) {
|
||||
|
|
@ -491,7 +493,7 @@ public final class ContentDisposition {
|
|||
* @return the encoded header field param
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
|
||||
*/
|
||||
private static String decodeFilename(String filename, Charset charset) {
|
||||
private static String decodeRfc5987Filename(String filename, Charset charset) {
|
||||
Assert.notNull(filename, "'filename' must not be null");
|
||||
Assert.notNull(charset, "'charset' must not be null");
|
||||
|
||||
|
|
@ -531,7 +533,7 @@ public final class ContentDisposition {
|
|||
* 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
|
||||
* @return the decoded header field param
|
||||
* @see <a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a>
|
||||
*/
|
||||
private static String decodeQuotedPrintableFilename(String filename, Charset charset) {
|
||||
|
|
@ -564,6 +566,42 @@ 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 (isPrintable(b)) {
|
||||
sb.append((char) b);
|
||||
}
|
||||
else {
|
||||
sb.append('=');
|
||||
char ch1 = hexDigit(b >> 4);
|
||||
char ch2 = hexDigit(b);
|
||||
sb.append(ch1);
|
||||
sb.append(ch2);
|
||||
}
|
||||
}
|
||||
sb.append("?=");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isPrintable(byte c) {
|
||||
return (c >= '!' && c <= '<') || (c >= '>' && c <= '~');
|
||||
}
|
||||
|
||||
private static String encodeQuotedPairs(String filename) {
|
||||
if (filename.indexOf('"') == -1 && filename.indexOf('\\') == -1) {
|
||||
return filename;
|
||||
|
|
@ -603,15 +641,14 @@ public final class ContentDisposition {
|
|||
* @return the encoded header field param
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
|
||||
*/
|
||||
private static String encodeFilename(String input, Charset charset) {
|
||||
private static String encodeRfc5987Filename(String input, Charset charset) {
|
||||
Assert.notNull(input, "'input' must not be null");
|
||||
Assert.notNull(charset, "'charset' must not be null");
|
||||
Assert.isTrue(!StandardCharsets.US_ASCII.equals(charset), "ASCII does not require encoding");
|
||||
Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Only UTF-8 and ISO-8859-1 are supported");
|
||||
|
||||
byte[] source = input.getBytes(charset);
|
||||
int len = source.length;
|
||||
StringBuilder sb = new StringBuilder(len << 1);
|
||||
StringBuilder sb = new StringBuilder(source.length << 1);
|
||||
sb.append(charset.name());
|
||||
sb.append("''");
|
||||
for (byte b : source) {
|
||||
|
|
@ -620,8 +657,8 @@ public final class ContentDisposition {
|
|||
}
|
||||
else {
|
||||
sb.append('%');
|
||||
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
|
||||
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
|
||||
char hex1 = hexDigit(b >> 4);
|
||||
char hex2 = hexDigit(b);
|
||||
sb.append(hex1);
|
||||
sb.append(hex2);
|
||||
}
|
||||
|
|
@ -629,6 +666,10 @@ public final class ContentDisposition {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
private static char hexDigit(int b) {
|
||||
return Character.toUpperCase(Character.forDigit(b & 0xF, 16));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A mutable builder for {@code ContentDisposition}.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
|
|
@ -249,7 +249,9 @@ class ContentDispositionTests {
|
|||
.name("name")
|
||||
.filename("中文.txt", StandardCharsets.UTF_8)
|
||||
.build().toString())
|
||||
.isEqualTo("form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt");
|
||||
.isEqualTo("form-data; name=\"name\"; " +
|
||||
"filename=\"=?UTF-8?Q?=E4=B8=AD=E6=96=87.txt?=\"; " +
|
||||
"filename*=UTF-8''%E4%B8%AD%E6%96%87.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue