Skip to content

Commit 9bf324f

Browse files
authored
fix(core): validate disposition tokens (#16)
1 parent ae3b120 commit 9bf324f

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

crates/fastmulp_core/src/content_disposition/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,38 @@ fn parse_parameter_value<'a>(
126126
offset: offset + start,
127127
});
128128
}
129+
if !token.iter().copied().all(is_tchar) {
130+
return Err(Error::InvalidContentDisposition {
131+
offset: offset + start,
132+
});
133+
}
129134

130135
Ok((TextValue::Borrowed(token), cursor))
131136
}
132137

138+
fn is_tchar(byte: u8) -> bool {
139+
matches!(
140+
byte,
141+
b'!' | b'#'
142+
| b'$'
143+
| b'%'
144+
| b'&'
145+
| b'\''
146+
| b'*'
147+
| b'+'
148+
| b'-'
149+
| b'.'
150+
| b'^'
151+
| b'_'
152+
| b'`'
153+
| b'|'
154+
| b'~'
155+
| b'0'..=b'9'
156+
| b'A'..=b'Z'
157+
| b'a'..=b'z'
158+
)
159+
}
160+
133161
fn parse_quoted_value<'a>(
134162
value: &'a [u8],
135163
start: usize,

crates/fastmulp_core/tests/spec_invalid.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
use fastmulp_core::{Boundary, Error, parse};
22

3+
fn assert_invalid_content_disposition(disposition: &str) {
4+
let body = format!(
5+
"--abc123\r\nContent-Disposition: {disposition}\r\n\r\npayload\r\n--abc123--\r\n"
6+
);
7+
8+
assert!(matches!(
9+
parse(body.as_bytes(), b"abc123"),
10+
Err(Error::InvalidContentDisposition { .. })
11+
));
12+
}
13+
314
#[test]
415
fn rejects_missing_content_disposition() {
516
let body = concat!(
@@ -32,6 +43,33 @@ fn rejects_missing_name_parameter() {
3243
));
3344
}
3445

46+
#[test]
47+
fn rejects_unquoted_content_disposition_parameter_values_with_whitespace() {
48+
for disposition in [
49+
"form-data; name=field value",
50+
"form-data; name=field\tvalue",
51+
"form-data; name=field value; filename=blob.txt",
52+
] {
53+
assert_invalid_content_disposition(disposition);
54+
}
55+
}
56+
57+
#[test]
58+
fn rejects_unquoted_content_disposition_parameter_values_with_separators() {
59+
for disposition in [
60+
"form-data; name=field\"value",
61+
"form-data; name=field,value",
62+
"form-data; name=field:value",
63+
"form-data; name=field/value",
64+
"form-data; name=field(value)",
65+
"form-data; name=field[value]",
66+
"form-data; name=field{value}",
67+
"form-data; name=field;value",
68+
] {
69+
assert_invalid_content_disposition(disposition);
70+
}
71+
}
72+
3573
#[test]
3674
fn rejects_header_continuation() {
3775
let body = concat!(
@@ -143,4 +181,3 @@ fn rejects_non_form_data_disposition() {
143181
Err(Error::InvalidContentDisposition { .. })
144182
));
145183
}
146-

0 commit comments

Comments
 (0)