Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
爱吃血肠
spring-framework
提交
32a585d1
S
spring-framework
项目概览
爱吃血肠
/
spring-framework
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-framework
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
32a585d1
编写于
9月 03, 2019
作者:
R
Rossen Stoyanchev
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #23485
上级
670cbb9a
d927d31e
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
155 addition
and
136 deletion
+155
-136
spring-web/src/main/java/org/springframework/http/ContentDisposition.java
...ain/java/org/springframework/http/ContentDisposition.java
+34
-25
spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
...ava/org/springframework/http/ContentDispositionTests.java
+121
-111
未找到文件。
spring-web/src/main/java/org/springframework/http/ContentDisposition.java
浏览文件 @
32a585d1
...
...
@@ -42,6 +42,9 @@ import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
*/
public
final
class
ContentDisposition
{
private
static
final
String
INVALID_HEADER_FIELD_PARAMETER_FORMAT
=
"Invalid header field parameter format (as defined in RFC 5987)"
;
@Nullable
private
final
String
type
;
...
...
@@ -205,7 +208,7 @@ public final class ContentDisposition {
}
else
{
sb
.
append
(
"; filename*="
);
sb
.
append
(
encode
HeaderFieldParam
(
this
.
filename
,
this
.
charset
));
sb
.
append
(
encode
Filename
(
this
.
filename
,
this
.
charset
));
}
}
if
(
this
.
size
!=
null
)
{
...
...
@@ -271,15 +274,23 @@ public final class ContentDisposition {
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
()
));
part
.
substring
(
eqIndex
+
1
));
if
(
attribute
.
equals
(
"name"
)
)
{
name
=
value
;
}
else
if
(
attribute
.
equals
(
"filename*"
)
)
{
filename
=
decodeHeaderFieldParam
(
value
);
charset
=
Charset
.
forName
(
value
.
substring
(
0
,
value
.
indexOf
(
'\''
)));
Assert
.
isTrue
(
UTF_8
.
equals
(
charset
)
||
ISO_8859_1
.
equals
(
charset
),
"Charset should be UTF-8 or ISO-8859-1"
);
int
idx1
=
value
.
indexOf
(
'\''
);
int
idx2
=
value
.
indexOf
(
'\''
,
idx1
+
1
);
if
(
idx1
!=
-
1
&&
idx2
!=
-
1
)
{
charset
=
Charset
.
forName
(
value
.
substring
(
0
,
idx1
));
Assert
.
isTrue
(
UTF_8
.
equals
(
charset
)
||
ISO_8859_1
.
equals
(
charset
),
"Charset should be UTF-8 or ISO-8859-1"
);
filename
=
decodeFilename
(
value
.
substring
(
idx2
+
1
),
charset
);
}
else
{
// US ASCII
filename
=
decodeFilename
(
value
,
StandardCharsets
.
US_ASCII
);
}
}
else
if
(
attribute
.
equals
(
"filename"
)
&&
(
filename
==
null
))
{
filename
=
value
;
...
...
@@ -357,24 +368,17 @@ public final class ContentDisposition {
}
/**
* Decode the given header field param as describe in RFC 5987.
* Decode the given header field param as describe
d
in RFC 5987.
* <p>Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported.
* @param input the header field param
* @param filename the filename
* @param charset the charset for the filename
* @return the encoded header field param
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
*/
private
static
String
decodeHeaderFieldParam
(
String
input
)
{
Assert
.
notNull
(
input
,
"Input String should not be null"
);
int
firstQuoteIndex
=
input
.
indexOf
(
'\''
);
int
secondQuoteIndex
=
input
.
indexOf
(
'\''
,
firstQuoteIndex
+
1
);
// US_ASCII
if
(
firstQuoteIndex
==
-
1
||
secondQuoteIndex
==
-
1
)
{
return
input
;
}
Charset
charset
=
Charset
.
forName
(
input
.
substring
(
0
,
firstQuoteIndex
));
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
);
private
static
String
decodeFilename
(
String
filename
,
Charset
charset
)
{
Assert
.
notNull
(
filename
,
"'input' String` should not be null"
);
Assert
.
notNull
(
charset
,
"'charset' should not be null"
);
byte
[]
value
=
filename
.
getBytes
(
charset
);
ByteArrayOutputStream
bos
=
new
ByteArrayOutputStream
();
int
index
=
0
;
while
(
index
<
value
.
length
)
{
...
...
@@ -383,13 +387,18 @@ public final class ContentDisposition {
bos
.
write
((
char
)
b
);
index
++;
}
else
if
(
b
==
'%'
)
{
char
[]
array
=
{
(
char
)
value
[
index
+
1
],
(
char
)
value
[
index
+
2
]};
bos
.
write
(
Integer
.
parseInt
(
String
.
valueOf
(
array
),
16
));
else
if
(
b
==
'%'
&&
index
<
value
.
length
-
2
)
{
char
[]
array
=
new
char
[]{(
char
)
value
[
index
+
1
],
(
char
)
value
[
index
+
2
]};
try
{
bos
.
write
(
Integer
.
parseInt
(
String
.
valueOf
(
array
),
16
));
}
catch
(
NumberFormatException
ex
)
{
throw
new
IllegalArgumentException
(
INVALID_HEADER_FIELD_PARAMETER_FORMAT
,
ex
);
}
index
+=
3
;
}
else
{
throw
new
IllegalArgumentException
(
"Invalid header field parameter format (as defined in RFC 5987)"
);
throw
new
IllegalArgumentException
(
INVALID_HEADER_FIELD_PARAMETER_FORMAT
);
}
}
return
new
String
(
bos
.
toByteArray
(),
charset
);
...
...
@@ -409,7 +418,7 @@ public final class ContentDisposition {
* @return the encoded header field param
* @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
*/
private
static
String
encode
HeaderFieldParam
(
String
input
,
Charset
charset
)
{
private
static
String
encode
Filename
(
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
))
{
...
...
spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
浏览文件 @
32a585d1
...
...
@@ -16,186 +16,196 @@
package
org.springframework.http
;
import
java.lang.reflect.Method
;
import
java.nio.charset.Charset
;
import
java.nio.charset.StandardCharsets
;
import
java.time.ZonedDateTime
;
import
java.time.format.DateTimeFormatter
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.util.ReflectionUtils
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThatIllegalArgumentException
;
/**
* Unit tests for {@link ContentDisposition}
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public
class
ContentDispositionTests
{
private
static
DateTimeFormatter
formatter
=
DateTimeFormatter
.
RFC_1123_DATE_TIME
;
@Test
public
void
parseTest
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
).
filename
(
"foo.txt"
).
size
(
123L
).
build
());
}
@Test
public
void
parse
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
).
filename
(
"foo.txt"
).
size
(
123L
).
build
());
assertThat
(
parse
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
)
.
filename
(
"foo.txt"
)
.
size
(
123L
)
.
build
());
}
@Test
public
void
parseType
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
).
build
());
public
void
parseFilenameUnquoted
()
{
assertThat
(
parse
(
"form-data; filename=unquoted"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
filename
(
"unquoted"
)
.
build
());
}
@Test
// SPR-16091
public
void
parseFilenameWithSemicolon
()
{
assertThat
(
parse
(
"attachment; filename=\"filename with ; semicolon.txt\""
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
filename
(
"filename with ; semicolon.txt"
)
.
build
());
}
@Test
public
void
parseUnquotedFilename
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; filename=unquoted"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
).
filename
(
"unquoted"
).
build
());
public
void
parseEncodedFilename
()
{
assertThat
(
parse
(
"form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
)
.
filename
(
"中文.txt"
,
StandardCharsets
.
UTF_8
)
.
build
());
}
@Test
// SPR-16091
public
void
parseFilenameWithSemicolon
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"attachment; filename=\"filename with ; semicolon.txt\""
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
filename
(
"filename with ; semicolon.txt"
).
build
());
@Test
public
void
parseEncodedFilenameWithoutCharset
()
{
assertThat
(
parse
(
"form-data; name=\"name\"; filename*=test.txt"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
)
.
filename
(
"test.txt"
)
.
build
());
}
@Test
public
void
parseAndIgnoreEmptyParts
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"foo\";; ; filename=\"foo.txt\"; size=123"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
).
filename
(
"foo.txt"
).
size
(
123L
).
build
());
public
void
parseEncodedFilenameWithInvalidCharset
()
{
assertThatIllegalArgumentException
()
.
isThrownBy
(()
->
parse
(
"form-data; name=\"name\"; filename*=UTF-16''test.txt"
));
}
@Test
public
void
parseEncodedFilename
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
).
name
(
"name"
)
.
filename
(
"中文.txt"
,
StandardCharsets
.
UTF_8
).
build
());
public
void
parseEncodedFilenameWithInvalidName
()
{
assertThatIllegalArgumentException
()
.
isThrownBy
(()
->
parse
(
"form-data; name=\"name\"; filename*=UTF-8''%A"
));
assertThatIllegalArgumentException
()
.
isThrownBy
(()
->
parse
(
"form-data; name=\"name\"; filename*=UTF-8''%A.txt"
));
}
@Test
// gh-23077
public
void
parseWithEscapedQuote
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"file\"; filename=\"\\\"The Twilight Zone\\\".txt\"; size=123"
);
assertThat
(
ContentDisposition
.
builder
(
"form-data"
).
name
(
"file"
)
.
filename
(
"\\\"The Twilight Zone\\\".txt"
).
size
(
123L
).
build
()).
isEqualTo
(
disposition
);
assertThat
(
parse
(
"form-data; name=\"file\"; filename=\"\\\"The Twilight Zone\\\".txt\"; size=123"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"file"
)
.
filename
(
"\\\"The Twilight Zone\\\".txt"
)
.
size
(
123L
)
.
build
());
}
@Test
public
void
parseEmpty
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
ContentDisposition
.
parse
(
""
));
public
void
parseWithExtraSemicolons
()
{
assertThat
(
parse
(
"form-data; name=\"foo\";; ; filename=\"foo.txt\"; size=123"
))
.
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
)
.
filename
(
"foo.txt"
)
.
size
(
123L
)
.
build
());
}
@Test
public
void
parseNoType
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
ContentDisposition
.
parse
(
";"
));
public
void
parseDates
()
{
ZonedDateTime
creationTime
=
ZonedDateTime
.
parse
(
"Mon, 12 Feb 2007 10:15:30 -0500"
,
formatter
);
ZonedDateTime
modificationTime
=
ZonedDateTime
.
parse
(
"Tue, 13 Feb 2007 10:15:30 -0500"
,
formatter
);
ZonedDateTime
readTime
=
ZonedDateTime
.
parse
(
"Wed, 14 Feb 2007 10:15:30 -0500"
,
formatter
);
assertThat
(
parse
(
"attachment; "
+
"creation-date=\""
+
creationTime
.
format
(
formatter
)
+
"\"; "
+
"modification-date=\""
+
modificationTime
.
format
(
formatter
)
+
"\"; "
+
"read-date=\""
+
readTime
.
format
(
formatter
)
+
"\""
)).
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
creationDate
(
creationTime
)
.
modificationDate
(
modificationTime
)
.
readDate
(
readTime
)
.
build
());
}
@Test
public
void
parseInvalidParameter
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
ContentDisposition
.
parse
(
"foo;bar"
));
}
public
void
parseIgnoresInvalidDates
()
{
ZonedDateTime
readTime
=
ZonedDateTime
.
parse
(
"Wed, 14 Feb 2007 10:15:30 -0500"
,
formatter
);
@Test
public
void
parseDates
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"attachment; creation-date=\"Mon, 12 Feb 2007 10:15:30 -0500\"; "
+
"modification-date=\"Tue, 13 Feb 2007 10:15:30 -0500\"; "
+
"read-date=\"Wed, 14 Feb 2007 10:15:30 -0500\""
);
DateTimeFormatter
formatter
=
DateTimeFormatter
.
RFC_1123_DATE_TIME
;
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
creationDate
(
ZonedDateTime
.
parse
(
"Mon, 12 Feb 2007 10:15:30 -0500"
,
formatter
))
.
modificationDate
(
ZonedDateTime
.
parse
(
"Tue, 13 Feb 2007 10:15:30 -0500"
,
formatter
))
.
readDate
(
ZonedDateTime
.
parse
(
"Wed, 14 Feb 2007 10:15:30 -0500"
,
formatter
)).
build
());
assertThat
(
parse
(
"attachment; "
+
"creation-date=\"-1\"; "
+
"modification-date=\"-1\"; "
+
"read-date=\""
+
readTime
.
format
(
formatter
)
+
"\""
)).
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
readDate
(
readTime
)
.
build
());
}
@Test
public
void
parseInvalidDates
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"attachment; creation-date=\"-1\"; modification-date=\"-1\"; "
+
"read-date=\"Wed, 14 Feb 2007 10:15:30 -0500\""
);
DateTimeFormatter
formatter
=
DateTimeFormatter
.
RFC_1123_DATE_TIME
;
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"attachment"
)
.
readDate
(
ZonedDateTime
.
parse
(
"Wed, 14 Feb 2007 10:15:30 -0500"
,
formatter
)).
build
());
public
void
parseEmpty
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
parse
(
""
));
}
@Test
public
void
headerValue
()
{
ContentDisposition
disposition
=
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
).
filename
(
"foo.txt"
).
size
(
123L
).
build
();
assertThat
(
disposition
.
toString
()).
isEqualTo
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
);
public
void
parseNoType
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
parse
(
";"
));
}
@Test
public
void
headerValueWithEncodedFilename
()
{
ContentDisposition
disposition
=
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
).
filename
(
"中文.txt"
,
StandardCharsets
.
UTF_8
).
build
();
assertThat
(
disposition
.
toString
()).
isEqualTo
(
"form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt"
);
public
void
parseInvalidParameter
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
parse
(
"foo;bar"
));
}
@Test
// SPR-14547
public
void
encodeHeaderFieldParam
()
{
Method
encode
=
ReflectionUtils
.
findMethod
(
ContentDisposition
.
class
,
"encodeHeaderFieldParam"
,
String
.
class
,
Charset
.
class
);
ReflectionUtils
.
makeAccessible
(
encode
);
private
static
ContentDisposition
parse
(
String
input
)
{
return
ContentDisposition
.
parse
(
input
);
}
String
result
=
(
String
)
ReflectionUtils
.
invokeMethod
(
encode
,
null
,
"test.txt"
,
StandardCharsets
.
US_ASCII
);
assertThat
(
result
).
isEqualTo
(
"test.txt"
);
result
=
(
String
)
ReflectionUtils
.
invokeMethod
(
encode
,
null
,
"中文.txt"
,
StandardCharsets
.
UTF_8
);
assertThat
(
result
).
isEqualTo
(
"UTF-8''%E4%B8%AD%E6%96%87.txt"
);
@Test
public
void
format
()
{
assertThat
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
)
.
filename
(
"foo.txt"
)
.
size
(
123L
)
.
build
().
toString
())
.
isEqualTo
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
);
}
@Test
public
void
encodeHeaderFieldParamInvalidCharset
()
{
Method
encode
=
ReflectionUtils
.
findMethod
(
ContentDisposition
.
class
,
"encodeHeaderFieldParam"
,
String
.
class
,
Charset
.
class
);
ReflectionUtils
.
makeAccessible
(
encode
);
assertThatIllegalArgumentException
().
isThrownBy
(()
->
ReflectionUtils
.
invokeMethod
(
encode
,
null
,
"test"
,
StandardCharsets
.
UTF_16
));
public
void
formatWithEncodedFilename
()
{
assertThat
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
)
.
filename
(
"中文.txt"
,
StandardCharsets
.
UTF_8
)
.
build
().
toString
())
.
isEqualTo
(
"form-data; name=\"name\"; filename*=UTF-8''%E4%B8%AD%E6%96%87.txt"
);
}
@Test
// SPR-14408
public
void
decodeHeaderFieldParam
()
{
Method
decode
=
ReflectionUtils
.
findMethod
(
ContentDisposition
.
class
,
"decodeHeaderFieldParam"
,
String
.
class
);
ReflectionUtils
.
makeAccessible
(
decode
);
String
result
=
(
String
)
ReflectionUtils
.
invokeMethod
(
decode
,
null
,
"test.txt"
);
assertThat
(
result
).
isEqualTo
(
"test.txt"
);
result
=
(
String
)
ReflectionUtils
.
invokeMethod
(
decode
,
null
,
"UTF-8''%E4%B8%AD%E6%96%87.txt"
);
assertThat
(
result
).
isEqualTo
(
"中文.txt"
);
@Test
public
void
formatWithEncodedFilenameUsingUsAscii
()
{
assertThat
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
)
.
filename
(
"test.txt"
,
StandardCharsets
.
US_ASCII
)
.
build
()
.
toString
())
.
isEqualTo
(
"form-data; name=\"name\"; filename=\"test.txt\""
);
}
@Test
public
void
decodeHeaderFieldParamInvalidCharset
()
{
Method
decode
=
ReflectionUtils
.
findMethod
(
ContentDisposition
.
class
,
"decodeHeaderFieldParam"
,
String
.
class
);
ReflectionUtils
.
makeAccessible
(
decode
);
public
void
formatWithEncodedFilenameUsingInvalidCharset
()
{
assertThatIllegalArgumentException
().
isThrownBy
(()
->
ReflectionUtils
.
invokeMethod
(
decode
,
null
,
"UTF-16''test"
));
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"name"
)
.
filename
(
"test.txt"
,
StandardCharsets
.
UTF_16
)
.
build
()
.
toString
());
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录