From ae034e9afeebef5264a42ea3cca29435f0cf0bb7 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 25 Jan 2017 17:19:30 -0500 Subject: [PATCH] Polish content disposition --- .../http/ContentDisposition.java | 133 +++++++++--------- .../http/ContentDispositionTests.java | 6 +- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java index 67db20e1e1a..a7ab9da5639 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -23,6 +23,9 @@ import java.nio.charset.StandardCharsets; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Represent the content disposition type and parameters as defined in RFC 2183. * @@ -42,10 +45,9 @@ public class ContentDisposition { private final Long size; + /** - * Create a {@code ContentDisposition} instance with the specified disposition type - * and {@litteral name}, {@litteral filename} (encoded with the specified {@link Charset} - * if any) and {@litteral size} parameter values. + * Private constructor. See static factory methods in this class. */ private ContentDisposition(String type, String name, String filename, Charset charset, Long size) { this.type = type; @@ -55,22 +57,6 @@ public class ContentDisposition { this.size = size; } - /** - * Return a builder for a {@code ContentDisposition}. - * @param type the disposition type like for example {@literal inline}, {@literal attachment}, - * or {@literal form-data} - * @return a content disposition builder - */ - public static Builder builder(String type) { - return new BuilderImpl(type); - } - - /** - * @return an empty content disposition - */ - public static ContentDisposition empty() { - return new ContentDisposition(null, null, null, null, null); - } /** * Return the disposition type, like for example {@literal inline}, {@literal attachment}, @@ -109,6 +95,24 @@ public class ContentDisposition { return this.size; } + + /** + * Return a builder for a {@code ContentDisposition}. + * @param type the disposition type like for example {@literal inline}, + * {@literal attachment}, or {@literal form-data} + * @return the builder + */ + public static Builder builder(String type) { + return new BuilderImpl(type); + } + + /** + * Return an empty content disposition. + */ + public static ContentDisposition empty() { + return new ContentDisposition(null, null, null, null, null); + } + /** * Parse a {@literal Content-Disposition} header value as defined in RFC 2183. * @param contentDisposition the {@literal Content-Disposition} header value @@ -124,20 +128,20 @@ public class ContentDisposition { Charset charset = null; Long size = null; for (int i = 1; i < parts.length; i++) { - String parameter = parts[i]; - int eqIndex = parameter.indexOf('='); + String part = parts[i]; + int eqIndex = part.indexOf('='); if (eqIndex != -1) { - String attribute = parameter.substring(0, eqIndex); - String value = (parameter.startsWith("\"", eqIndex + 1) && parameter.endsWith("\"") ? - parameter.substring(eqIndex + 2, parameter.length() - 1) : - parameter.substring(eqIndex + 1, parameter.length())); + String attribute = part.substring(0, eqIndex); + String value = (part.startsWith("\"", eqIndex + 1) && part.endsWith("\"") ? + part.substring(eqIndex + 2, part.length() - 1) : + part.substring(eqIndex + 1, part.length())); if (attribute.equals("name") ) { name = value; } else if (attribute.equals("filename*") ) { filename = decodeHeaderFieldParam(value); charset = Charset.forName(value.substring(0, value.indexOf("'"))); - Assert.isTrue(StandardCharsets.UTF_8.equals(charset) || StandardCharsets.ISO_8859_1.equals(charset), + Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Charset should be UTF-8 or ISO-8859-1"); } else if (attribute.equals("filename") && (filename == null)) { @@ -154,42 +158,6 @@ public class ContentDisposition { return new ContentDisposition(type, name, filename, charset, size); } - /** - * Encode the given header field param as describe in RFC 5987. - * @param input the header field param - * @param charset the charset of the header field param string, - * only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported - * @return the encoded header field param - * @see RFC 5987 - */ - private static String encodeHeaderFieldParam(String input, Charset charset) { - Assert.notNull(input, "Input String should not be null"); - Assert.notNull(charset, "Charset should not be null"); - if (StandardCharsets.US_ASCII.equals(charset)) { - return input; - } - Assert.isTrue(StandardCharsets.UTF_8.equals(charset) || StandardCharsets.ISO_8859_1.equals(charset), - "Charset should be UTF-8 or ISO-8859-1"); - byte[] source = input.getBytes(charset); - int len = source.length; - StringBuilder sb = new StringBuilder(len << 1); - sb.append(charset.name()); - sb.append("''"); - for (byte b : source) { - if (isRFC5987AttrChar(b)) { - sb.append((char) b); - } - else { - sb.append('%'); - char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); - char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); - sb.append(hex1); - sb.append(hex2); - } - } - return sb.toString(); - } - /** * Decode the given header field param as describe in RFC 5987. *

Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported. @@ -206,7 +174,7 @@ public class ContentDisposition { return input; } Charset charset = Charset.forName(input.substring(0, firstQuoteIndex)); - Assert.isTrue(StandardCharsets.UTF_8.equals(charset) || StandardCharsets.ISO_8859_1.equals(charset), + Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Charset should be UTF-8 or ISO-8859-1"); byte[] value = input.substring(secondQuoteIndex + 1, input.length()).getBytes(charset); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -297,6 +265,42 @@ public class ContentDisposition { return builder.toString(); } + /** + * Encode the given header field param as describe in RFC 5987. + * @param input the header field param + * @param charset the charset of the header field param string, + * only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported + * @return the encoded header field param + * @see RFC 5987 + */ + private static String encodeHeaderFieldParam(String input, Charset charset) { + Assert.notNull(input, "Input String should not be null"); + Assert.notNull(charset, "Charset should not be null"); + if (StandardCharsets.US_ASCII.equals(charset)) { + return input; + } + Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), + "Charset should be UTF-8 or ISO-8859-1"); + byte[] source = input.getBytes(charset); + int len = source.length; + StringBuilder sb = new StringBuilder(len << 1); + sb.append(charset.name()); + sb.append("''"); + for (byte b : source) { + if (isRFC5987AttrChar(b)) { + sb.append((char) b); + } + else { + sb.append('%'); + char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); + char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); + sb.append(hex1); + sb.append(hex2); + } + } + return sb.toString(); + } + /** * A mutable builder for {@code ContentDisposition}. @@ -377,7 +381,6 @@ public class ContentDisposition { public ContentDisposition build() { return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size); } - } } diff --git a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java index b704f3ce211..0d24061c050 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -36,7 +36,8 @@ public class ContentDispositionTests { public void parse() { ContentDisposition disposition = ContentDisposition .parse("form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"); - assertEquals(ContentDisposition.builder("form-data").name("foo").filename("foo.txt").size(123L).build(), disposition); + assertEquals(ContentDisposition.builder("form-data") + .name("foo").filename("foo.txt").size(123L).build(), disposition); } @Test @@ -86,7 +87,8 @@ public class ContentDispositionTests { public void headerValueWithEncodedFilename() { ContentDisposition disposition = ContentDisposition.builder("form-data") .name("name").filename("中文.txt", StandardCharsets.UTF_8).build(); - assertEquals("form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt", disposition.toString()); + assertEquals("form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt", + disposition.toString()); } @Test // SPR-14547