|
17 | 17 | package org.springframework.http; |
18 | 18 |
|
19 | 19 | import java.io.ByteArrayOutputStream; |
20 | | -import java.nio.CharBuffer; |
21 | | -import java.nio.charset.CharacterCodingException; |
22 | 20 | import java.nio.charset.Charset; |
23 | | -import java.nio.charset.CharsetEncoder; |
24 | | -import java.nio.charset.CodingErrorAction; |
25 | 21 | import java.nio.charset.StandardCharsets; |
| 22 | +import java.text.Normalizer; |
26 | 23 | import java.util.ArrayList; |
27 | 24 | import java.util.Base64; |
28 | 25 | import java.util.HexFormat; |
|
47 | 44 | * @author Sebastien Deleuze |
48 | 45 | * @author Juergen Hoeller |
49 | 46 | * @author Rossen Stoyanchev |
| 47 | + * @author Brian Clozel |
50 | 48 | * @author Sergey Tsypanov |
51 | 49 | * @since 5.0 |
52 | 50 | * @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a> |
@@ -174,19 +172,19 @@ public String toString() { |
174 | 172 | sb.append(this.type); |
175 | 173 | } |
176 | 174 | if (this.name != null) { |
177 | | - sb.append("; name=\""); |
178 | | - sb.append(this.name).append('\"'); |
| 175 | + sb.append("; name=\"").append(this.name).append('\"'); |
179 | 176 | } |
180 | 177 | if (this.filename != null) { |
181 | 178 | 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('\"'); |
184 | 182 | } |
185 | 183 | 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)); |
190 | 188 | } |
191 | 189 | } |
192 | 190 | return sb.toString(); |
@@ -435,16 +433,74 @@ else if (b == '=' && index < value.length - 2) { |
435 | 433 | return StreamUtils.copyToString(baos, charset); |
436 | 434 | } |
437 | 435 |
|
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 | + } |
447 | 502 | } |
| 503 | + return sb.toString(); |
448 | 504 | } |
449 | 505 |
|
450 | 506 | private static String encodeQuotedPairs(String filename) { |
|
0 commit comments