1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

actix-multipart: Correctly parse multipart body which does not end in CRLF (#1042)

* Correctly parse multipart body which does not end in CRLF

* Add in an eof guard for extra safety
This commit is contained in:
Jeffrey Shen 2019-09-09 17:58:00 +10:00 committed by Nikolay Kim
parent 8d61fe0925
commit 1d96ae9bc3
2 changed files with 73 additions and 48 deletions

View File

@ -1,4 +1,7 @@
# Changes # Changes
## [0.1.4] - 2019-xx-xx
* Multipart handling now parses requests which do not end in CRLF #1038
## [0.1.3] - 2019-08-18 ## [0.1.3] - 2019-08-18

View File

@ -167,7 +167,7 @@ impl InnerMultipart {
boundary: &str, boundary: &str,
) -> Result<Option<bool>, MultipartError> { ) -> Result<Option<bool>, MultipartError> {
// TODO: need to read epilogue // TODO: need to read epilogue
match payload.readline()? { match payload.readline_or_eof()? {
None => { None => {
if payload.eof { if payload.eof {
Ok(Some(true)) Ok(Some(true))
@ -176,16 +176,15 @@ impl InnerMultipart {
} }
} }
Some(chunk) => { Some(chunk) => {
if chunk.len() == boundary.len() + 4 if chunk.len() < boundary.len() + 4
&& &chunk[..2] == b"--" || &chunk[..2] != b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() || &chunk[2..boundary.len() + 2] != boundary.as_bytes() {
{ Err(MultipartError::Boundary)
} else if &chunk[boundary.len() + 2..] == b"\r\n" {
Ok(Some(false)) Ok(Some(false))
} else if chunk.len() == boundary.len() + 6 } else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
&& &chunk[..2] == b"--" && (chunk.len() == boundary.len() + 4
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() || &chunk[boundary.len() + 4..] == b"\r\n") {
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{
Ok(Some(true)) Ok(Some(true))
} else { } else {
Err(MultipartError::Boundary) Err(MultipartError::Boundary)
@ -779,6 +778,14 @@ impl PayloadBuffer {
self.read_until(b"\n") self.read_until(b"\n")
} }
/// Read bytes until new line delimiter or eof
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
match self.readline() {
Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.take().freeze())),
line => line
}
}
/// Put unprocessed data back to the buffer /// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) { pub fn unprocessed(&mut self, data: Bytes) {
let buf = BytesMut::from(data); let buf = BytesMut::from(data);
@ -849,32 +856,65 @@ mod tests {
(tx, rx.map_err(|_| panic!()).and_then(|res| res)) (tx, rx.map_err(|_| panic!()).and_then(|res| res))
} }
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
(bytes, headers)
}
#[test]
fn test_multipart_no_end_crlf() {
run_on(|| {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf
sender.unbounded_send(Ok(bytes_stripped)).unwrap();
drop(sender); // eof
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() {
Async::Ready(Some(_)) => (),
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(Some(_)) => (),
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
})
}
#[test] #[test]
fn test_multipart() { fn test_multipart() {
run_on(|| { run_on(|| {
let (sender, payload) = create_stream(); let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
sender.unbounded_send(Ok(bytes)).unwrap(); sender.unbounded_send(Ok(bytes)).unwrap();
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let mut multipart = Multipart::new(&headers, payload); let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => { Async::Ready(Some(mut field)) => {
@ -925,28 +965,10 @@ mod tests {
fn test_stream() { fn test_stream() {
run_on(|| { run_on(|| {
let (sender, payload) = create_stream(); let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
sender.unbounded_send(Ok(bytes)).unwrap(); sender.unbounded_send(Ok(bytes)).unwrap();
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let mut multipart = Multipart::new(&headers, payload); let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => { Async::Ready(Some(mut field)) => {