|
21 | 21 | import java.nio.charset.StandardCharsets; |
22 | 22 | import java.time.ZonedDateTime; |
23 | 23 | import java.time.format.DateTimeParseException; |
| 24 | +import java.util.ArrayList; |
| 25 | +import java.util.List; |
24 | 26 |
|
25 | 27 | import org.springframework.lang.Nullable; |
26 | 28 | import org.springframework.util.Assert; |
27 | 29 | import org.springframework.util.ObjectUtils; |
28 | | -import org.springframework.util.StringUtils; |
29 | 30 |
|
30 | 31 | import static java.nio.charset.StandardCharsets.*; |
31 | 32 | import static java.time.format.DateTimeFormatter.*; |
@@ -253,18 +254,17 @@ public static ContentDisposition empty() { |
253 | 254 | * @see #toString() |
254 | 255 | */ |
255 | 256 | public static ContentDisposition parse(String contentDisposition) { |
256 | | - String[] parts = StringUtils.tokenizeToStringArray(contentDisposition, ";"); |
257 | | - Assert.isTrue(parts.length >= 1, "Content-Disposition header must not be empty"); |
258 | | - String type = parts[0]; |
| 257 | + List<String> parts = tokenize(contentDisposition); |
| 258 | + String type = parts.get(0); |
259 | 259 | String name = null; |
260 | 260 | String filename = null; |
261 | 261 | Charset charset = null; |
262 | 262 | Long size = null; |
263 | 263 | ZonedDateTime creationDate = null; |
264 | 264 | ZonedDateTime modificationDate = null; |
265 | 265 | ZonedDateTime readDate = null; |
266 | | - for (int i = 1; i < parts.length; i++) { |
267 | | - String part = parts[i]; |
| 266 | + for (int i = 1; i < parts.size(); i++) { |
| 267 | + String part = parts.get(i); |
268 | 268 | int eqIndex = part.indexOf('='); |
269 | 269 | if (eqIndex != -1) { |
270 | 270 | String attribute = part.substring(0, eqIndex); |
@@ -318,6 +318,41 @@ else if (attribute.equals("read-date")) { |
318 | 318 | return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate); |
319 | 319 | } |
320 | 320 |
|
| 321 | + private static List<String> tokenize(String headerValue) { |
| 322 | + int index = headerValue.indexOf(';'); |
| 323 | + String type = (index >= 0 ? headerValue.substring(0, index) : headerValue).trim(); |
| 324 | + if (type.isEmpty()) { |
| 325 | + throw new IllegalArgumentException("Content-Disposition header must not be empty"); |
| 326 | + } |
| 327 | + List<String> parts = new ArrayList<>(); |
| 328 | + parts.add(type); |
| 329 | + if (index >= 0) { |
| 330 | + do { |
| 331 | + int nextIndex = index + 1; |
| 332 | + boolean quoted = false; |
| 333 | + while (nextIndex < headerValue.length()) { |
| 334 | + char ch = headerValue.charAt(nextIndex); |
| 335 | + if (ch == ';') { |
| 336 | + if (!quoted) { |
| 337 | + break; |
| 338 | + } |
| 339 | + } |
| 340 | + else if (ch == '"') { |
| 341 | + quoted = !quoted; |
| 342 | + } |
| 343 | + nextIndex++; |
| 344 | + } |
| 345 | + String part = headerValue.substring(index + 1, nextIndex).trim(); |
| 346 | + if (!part.isEmpty()) { |
| 347 | + parts.add(part); |
| 348 | + } |
| 349 | + index = nextIndex; |
| 350 | + } |
| 351 | + while (index < headerValue.length()); |
| 352 | + } |
| 353 | + return parts; |
| 354 | + } |
| 355 | + |
321 | 356 | /** |
322 | 357 | * Decode the given header field param as describe in RFC 5987. |
323 | 358 | * <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported. |
|
0 commit comments