Polish content disposition
This commit is contained in:
parent
eabd8a2964
commit
ae034e9afe
|
|
@ -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 <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
|
||||
*/
|
||||
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.
|
||||
* <p>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 <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue