提交 ae034e9a 编写于 作者: R Rossen Stoyanchev

Polish content disposition

上级 eabd8a29
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -23,6 +23,9 @@ import java.nio.charset.StandardCharsets; ...@@ -23,6 +23,9 @@ import java.nio.charset.StandardCharsets;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; 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. * Represent the content disposition type and parameters as defined in RFC 2183.
* *
...@@ -42,10 +45,9 @@ public class ContentDisposition { ...@@ -42,10 +45,9 @@ public class ContentDisposition {
private final Long size; private final Long size;
/** /**
* Create a {@code ContentDisposition} instance with the specified disposition type * Private constructor. See static factory methods in this class.
* and {@litteral name}, {@litteral filename} (encoded with the specified {@link Charset}
* if any) and {@litteral size} parameter values.
*/ */
private ContentDisposition(String type, String name, String filename, Charset charset, Long size) { private ContentDisposition(String type, String name, String filename, Charset charset, Long size) {
this.type = type; this.type = type;
...@@ -55,22 +57,6 @@ public class ContentDisposition { ...@@ -55,22 +57,6 @@ public class ContentDisposition {
this.size = size; 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}, * Return the disposition type, like for example {@literal inline}, {@literal attachment},
...@@ -109,6 +95,24 @@ public class ContentDisposition { ...@@ -109,6 +95,24 @@ public class ContentDisposition {
return this.size; 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. * Parse a {@literal Content-Disposition} header value as defined in RFC 2183.
* @param contentDisposition the {@literal Content-Disposition} header value * @param contentDisposition the {@literal Content-Disposition} header value
...@@ -124,20 +128,20 @@ public class ContentDisposition { ...@@ -124,20 +128,20 @@ public class ContentDisposition {
Charset charset = null; Charset charset = null;
Long size = null; Long size = null;
for (int i = 1; i < parts.length; i++) { for (int i = 1; i < parts.length; i++) {
String parameter = parts[i]; String part = parts[i];
int eqIndex = parameter.indexOf('='); int eqIndex = part.indexOf('=');
if (eqIndex != -1) { if (eqIndex != -1) {
String attribute = parameter.substring(0, eqIndex); String attribute = part.substring(0, eqIndex);
String value = (parameter.startsWith("\"", eqIndex + 1) && parameter.endsWith("\"") ? String value = (part.startsWith("\"", eqIndex + 1) && part.endsWith("\"") ?
parameter.substring(eqIndex + 2, parameter.length() - 1) : part.substring(eqIndex + 2, part.length() - 1) :
parameter.substring(eqIndex + 1, parameter.length())); part.substring(eqIndex + 1, part.length()));
if (attribute.equals("name") ) { if (attribute.equals("name") ) {
name = value; name = value;
} }
else if (attribute.equals("filename*") ) { else if (attribute.equals("filename*") ) {
filename = decodeHeaderFieldParam(value); filename = decodeHeaderFieldParam(value);
charset = Charset.forName(value.substring(0, value.indexOf("'"))); 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"); "Charset should be UTF-8 or ISO-8859-1");
} }
else if (attribute.equals("filename") && (filename == null)) { else if (attribute.equals("filename") && (filename == null)) {
...@@ -154,42 +158,6 @@ public class ContentDisposition { ...@@ -154,42 +158,6 @@ public class ContentDisposition {
return new ContentDisposition(type, name, filename, charset, size); 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. * 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. * <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported.
...@@ -206,7 +174,7 @@ public class ContentDisposition { ...@@ -206,7 +174,7 @@ public class ContentDisposition {
return input; return input;
} }
Charset charset = Charset.forName(input.substring(0, firstQuoteIndex)); 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"); "Charset should be UTF-8 or ISO-8859-1");
byte[] value = input.substring(secondQuoteIndex + 1, input.length()).getBytes(charset); byte[] value = input.substring(secondQuoteIndex + 1, input.length()).getBytes(charset);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
...@@ -297,6 +265,42 @@ public class ContentDisposition { ...@@ -297,6 +265,42 @@ public class ContentDisposition {
return builder.toString(); 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}. * A mutable builder for {@code ContentDisposition}.
...@@ -377,7 +381,6 @@ public class ContentDisposition { ...@@ -377,7 +381,6 @@ public class ContentDisposition {
public ContentDisposition build() { public ContentDisposition build() {
return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size); return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size);
} }
} }
} }
...@@ -36,7 +36,8 @@ public class ContentDispositionTests { ...@@ -36,7 +36,8 @@ public class ContentDispositionTests {
public void parse() { public void parse() {
ContentDisposition disposition = ContentDisposition ContentDisposition disposition = ContentDisposition
.parse("form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"); .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 @Test
...@@ -86,7 +87,8 @@ public class ContentDispositionTests { ...@@ -86,7 +87,8 @@ public class ContentDispositionTests {
public void headerValueWithEncodedFilename() { public void headerValueWithEncodedFilename() {
ContentDisposition disposition = ContentDisposition.builder("form-data") ContentDisposition disposition = ContentDisposition.builder("form-data")
.name("name").filename("中文.txt", StandardCharsets.UTF_8).build(); .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 @Test // SPR-14547
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册