Skip to content

Commit 68338aa

Browse files
committed
Use ASCII chars in Content-Disposition filename parameter
Prior to this commit, gh-36328 avoided using RFC 2047 encoding for the "filename" parameter and use ISO-8859-1 only. This change unfortunately caused issues because some implementations might try and detect the encoding automatically. This commit restricts the filename parameter to ASCII encoding only by: * transliterating characters to the closes ASCII character ("é"->"e", "ä"->"ae"...) * falling back to "_" for other chacacters with non latin alphabet or emojis Fixes gh-36805
1 parent fbbc0f4 commit 68338aa

2 files changed

Lines changed: 382 additions & 305 deletions

File tree

spring-web/src/main/java/org/springframework/http/ContentDisposition.java

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@
1717
package org.springframework.http;
1818

1919
import java.io.ByteArrayOutputStream;
20-
import java.nio.CharBuffer;
21-
import java.nio.charset.CharacterCodingException;
2220
import java.nio.charset.Charset;
23-
import java.nio.charset.CharsetEncoder;
24-
import java.nio.charset.CodingErrorAction;
2521
import java.nio.charset.StandardCharsets;
22+
import java.text.Normalizer;
2623
import java.util.ArrayList;
2724
import java.util.Base64;
2825
import java.util.HexFormat;
@@ -47,6 +44,7 @@
4744
* @author Sebastien Deleuze
4845
* @author Juergen Hoeller
4946
* @author Rossen Stoyanchev
47+
* @author Brian Clozel
5048
* @author Sergey Tsypanov
5149
* @since 5.0
5250
* @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a>
@@ -174,19 +172,19 @@ public String toString() {
174172
sb.append(this.type);
175173
}
176174
if (this.name != null) {
177-
sb.append("; name=\"");
178-
sb.append(this.name).append('\"');
175+
sb.append("; name=\"").append(this.name).append('\"');
179176
}
180177
if (this.filename != null) {
181178
if (this.charset == null || StandardCharsets.US_ASCII.equals(this.charset)) {
182-
sb.append("; filename=\"");
183-
sb.append(encodeQuotedPairs(this.filename)).append('\"');
179+
sb.append("; filename=\"")
180+
.append(encodeQuotedPairs(this.filename))
181+
.append('\"');
184182
}
185183
else {
186-
sb.append("; filename=\"");
187-
sb.append(toIso88591(encodeQuotedPairs(this.filename))).append('\"');
188-
sb.append("; filename*=");
189-
sb.append(encodeRfc5987Filename(this.filename, this.charset));
184+
sb.append("; filename=\"")
185+
.append(transliterateToAscii(encodeQuotedPairs(this.filename)))
186+
.append("\"; filename*=")
187+
.append(encodeRfc5987Filename(this.filename, this.charset));
190188
}
191189
}
192190
return sb.toString();
@@ -435,16 +433,74 @@ else if (b == '=' && index < value.length - 2) {
435433
return StreamUtils.copyToString(baos, charset);
436434
}
437435

438-
private static String toIso88591(String input) {
439-
CharsetEncoder encoder = ISO_8859_1.newEncoder()
440-
.onUnmappableCharacter(CodingErrorAction.REPLACE)
441-
.replaceWith(new byte[] { (byte) '_' });
442-
try {
443-
return ISO_8859_1.decode(encoder.encode(CharBuffer.wrap(input))).toString();
444-
}
445-
catch (CharacterCodingException exc) {
446-
throw new IllegalArgumentException("Failed to convert to ISO 8859-1", exc);
436+
private static String transliterateToAscii(String input) {
437+
StringBuilder sb = new StringBuilder(input.length() + 16);
438+
for (int i = 0; i < input.length();) {
439+
int codePoint = input.codePointAt(i);
440+
i += Character.charCount(codePoint);
441+
if (codePoint <= 127) {
442+
sb.append((char) codePoint);
443+
}
444+
else {
445+
switch (codePoint) {
446+
case 'ä':
447+
sb.append("ae");
448+
break;
449+
case 'ö':
450+
sb.append("oe");
451+
break;
452+
case 'ü':
453+
sb.append("ue");
454+
break;
455+
case 'Ä':
456+
sb.append("Ae");
457+
break;
458+
case 'Ö':
459+
sb.append("Oe");
460+
break;
461+
case 'Ü':
462+
sb.append("Ue");
463+
break;
464+
case 'ß':
465+
sb.append("ss");
466+
break;
467+
case 'æ':
468+
sb.append("ae");
469+
break;
470+
case 'Æ':
471+
sb.append("AE");
472+
break;
473+
case 'œ':
474+
sb.append("oe");
475+
break;
476+
case 'Œ':
477+
sb.append("OE");
478+
break;
479+
default:
480+
String cpStr = new String(Character.toChars(codePoint));
481+
// decompose accented characters into two separate parts
482+
String normalized = Normalizer.normalize(cpStr, Normalizer.Form.NFD);
483+
for (int j = 0; j < normalized.length(); ) {
484+
int ncp = normalized.codePointAt(j);
485+
j += Character.charCount(ncp);
486+
if (ncp <= 127) {
487+
sb.append((char) ncp);
488+
}
489+
else {
490+
int type = Character.getType(ncp);
491+
// do not write fallback character for accents
492+
if (type != Character.NON_SPACING_MARK &&
493+
type != Character.COMBINING_SPACING_MARK &&
494+
type != Character.ENCLOSING_MARK) {
495+
sb.append('_');
496+
}
497+
}
498+
}
499+
break;
500+
}
501+
}
447502
}
503+
return sb.toString();
448504
}
449505

450506
private static String encodeQuotedPairs(String filename) {

0 commit comments

Comments
 (0)