Skip to content

Commit 57725ee

Browse files
Don't parse CachePut payloads as chunked encoding
The PUT request body itself may be chunked, but normally clients send the body that ought to be cached without chunked encoding applied.
1 parent 256323b commit 57725ee

File tree

2 files changed

+15
-145
lines changed

2 files changed

+15
-145
lines changed

.bleep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6638da6bb3588e4924e2952b14d72f3b2deb8ab3
1+
ab4b4c023bc3b26dfe253cf43ce410ccd76a6b27

pingora-cache/src/put.rs

Lines changed: 14 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -351,20 +351,17 @@ mod parse_response {
351351
ErrorType::{self, *},
352352
};
353353

354-
pub const INVALID_CHUNK: ErrorType = ErrorType::new("InvalidChunk");
355354
pub const INCOMPLETE_BODY: ErrorType = ErrorType::new("IncompleteHttpBody");
356355

357356
const MAX_HEADERS: usize = 256;
358357
const INIT_HEADER_BUF_SIZE: usize = 4096;
359-
const CHUNK_DELIMITER_SIZE: usize = 2; // \r\n
360358

361359
#[derive(Debug, Clone, Copy, PartialEq)]
362360
enum ParseState {
363361
Init,
364362
PartialHeader,
365363
PartialBodyContentLength(usize, usize),
366-
PartialChunkedBody(usize),
367-
PartialHttp10Body(usize),
364+
PartialBody(usize),
368365
Done(usize),
369366
Invalid(httparse::Error),
370367
}
@@ -379,9 +376,7 @@ mod parse_response {
379376
fn read_body(&self) -> bool {
380377
matches!(
381378
self,
382-
Self::PartialBodyContentLength(..)
383-
| Self::PartialChunkedBody(_)
384-
| Self::PartialHttp10Body(_)
379+
Self::PartialBodyContentLength(..) | Self::PartialBody(_)
385380
)
386381
}
387382
}
@@ -509,49 +504,15 @@ mod parse_response {
509504
}
510505
Ok(Some(self.buf.split_to(end).freeze()))
511506
}
512-
PartialChunkedBody(seen) => {
513-
let parsed = httparse::parse_chunk_size(&self.buf).map_err(|e| {
514-
self.state = Done(seen);
515-
Error::explain(INVALID_CHUNK, format!("Invalid chunked encoding: {e:?}"))
516-
})?;
517-
match parsed {
518-
httparse::Status::Complete((header_len, body_len)) => {
519-
// 4\r\nRust\r\n: header: "4\r\n", body: "Rust", "\r\n"
520-
let total_chunk_size =
521-
header_len + body_len as usize + CHUNK_DELIMITER_SIZE;
522-
if self.buf.len() < total_chunk_size {
523-
// wait for the full chunk to be read
524-
// Note that we have to buffer the entire chunk in this design
525-
Ok(None)
526-
} else {
527-
if body_len == 0 {
528-
self.state = Done(seen);
529-
} else {
530-
self.state = PartialChunkedBody(seen + body_len as usize);
531-
}
532-
let mut chunk_bytes = self.buf.split_to(total_chunk_size);
533-
let mut chunk_body = chunk_bytes.split_off(header_len);
534-
chunk_body.truncate(body_len as usize);
535-
// Note that the final 0 sized chunk will return an empty Bytes
536-
// instead of not None
537-
Ok(Some(chunk_body.freeze()))
538-
}
539-
}
540-
httparse::Status::Partial => {
541-
// not even a full chunk, continue waiting for more data
542-
Ok(None)
543-
}
544-
}
545-
}
546-
PartialHttp10Body(seen) => {
547-
self.state = PartialHttp10Body(seen + self.buf.len());
507+
PartialBody(seen) => {
508+
self.state = PartialBody(seen + self.buf.len());
548509
Ok(Some(self.buf.split().freeze()))
549510
}
550511
}
551512
}
552513

553514
pub fn finish(&mut self) -> Result<()> {
554-
if let ParseState::PartialHttp10Body(seen) = self.state {
515+
if let ParseState::PartialBody(seen) = self.state {
555516
self.state = ParseState::Done(seen);
556517
}
557518
if !self.state.is_done() {
@@ -572,12 +533,6 @@ mod parse_response {
572533
// these status codes cannot have body by definition
573534
return ParseState::Done(0);
574535
}
575-
if let Some(encoding) = resp.headers.get(http::header::TRANSFER_ENCODING) {
576-
// TODO: case sensitive?
577-
if encoding.as_bytes() == b"chunked" {
578-
return ParseState::PartialChunkedBody(0);
579-
}
580-
}
581536
if let Some(cl) = resp.headers.get(http::header::CONTENT_LENGTH) {
582537
// ignore invalid header value
583538
if let Some(cl) = std::str::from_utf8(cl.as_bytes())
@@ -591,7 +546,10 @@ mod parse_response {
591546
};
592547
}
593548
}
594-
ParseState::PartialHttp10Body(0)
549+
// HTTP/1.0 and chunked encoding are both treated as PartialBody
550+
// The response body payload should _not_ be chunked encoded
551+
// even if the Transfer-Encoding: chunked header is added
552+
ParseState::PartialBody(0)
595553
}
596554

597555
#[cfg(test)]
@@ -684,7 +642,7 @@ mod parse_response {
684642

685643
#[test]
686644
fn test_body_chunked() {
687-
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nrust\r\n";
645+
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nrust";
688646
let mut parser = ResponseParse::new();
689647
let output = parser.inject_data(input).unwrap();
690648

@@ -700,14 +658,6 @@ mod parse_response {
700658
assert_eq!(data.as_ref().unwrap(), "rust");
701659
assert!(!eos);
702660

703-
let output = parser.inject_data(b"0\r\n\r\n").unwrap();
704-
assert_eq!(output.len(), 1);
705-
let HttpTask::Body(data, eos) = &output[0] else {
706-
panic!("{:?}", output);
707-
};
708-
assert_eq!(data.as_ref().unwrap(), "");
709-
assert!(eos);
710-
711661
parser.finish().unwrap();
712662
}
713663

@@ -755,8 +705,8 @@ mod parse_response {
755705
}
756706

757707
#[test]
758-
fn test_body_chunked_early() {
759-
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nrust\r\n";
708+
fn test_body_chunked_partial_chunk() {
709+
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nru";
760710
let mut parser = ResponseParse::new();
761711
let output = parser.inject_data(input).unwrap();
762712

@@ -769,75 +719,15 @@ mod parse_response {
769719
let HttpTask::Body(data, eos) = &output[1] else {
770720
panic!("{:?}", output);
771721
};
772-
assert_eq!(data.as_ref().unwrap(), "rust");
722+
assert_eq!(data.as_ref().unwrap(), "ru");
773723
assert!(!eos);
774724

775-
parser.finish().unwrap_err();
776-
}
777-
778-
#[test]
779-
fn test_body_chunked_partial_chunk() {
780-
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nru";
781-
let mut parser = ResponseParse::new();
782-
let output = parser.inject_data(input).unwrap();
783-
784-
assert_eq!(output.len(), 1);
785-
let HttpTask::Header(header, _eos) = &output[0] else {
786-
panic!("{:?}", output);
787-
};
788-
assert_eq!(header.status, 200);
789-
790725
let output = parser.inject_data(b"st\r\n").unwrap();
791726
assert_eq!(output.len(), 1);
792727
let HttpTask::Body(data, eos) = &output[0] else {
793728
panic!("{:?}", output);
794729
};
795-
assert_eq!(data.as_ref().unwrap(), "rust");
796-
assert!(!eos);
797-
}
798-
799-
#[test]
800-
fn test_body_chunked_partial_chunk_head() {
801-
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r";
802-
let mut parser = ResponseParse::new();
803-
let output = parser.inject_data(input).unwrap();
804-
805-
assert_eq!(output.len(), 1);
806-
let HttpTask::Header(header, _eos) = &output[0] else {
807-
panic!("{:?}", output);
808-
};
809-
assert_eq!(header.status, 200);
810-
811-
let output = parser.inject_data(b"\nrust\r\n").unwrap();
812-
assert_eq!(output.len(), 1);
813-
let HttpTask::Body(data, eos) = &output[0] else {
814-
panic!("{:?}", output);
815-
};
816-
assert_eq!(data.as_ref().unwrap(), "rust");
817-
assert!(!eos);
818-
}
819-
820-
#[test]
821-
fn test_body_chunked_many_chunks() {
822-
let input =
823-
b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nrust\r\n1\r\ny\r\n";
824-
let mut parser = ResponseParse::new();
825-
let output = parser.inject_data(input).unwrap();
826-
827-
assert_eq!(output.len(), 3);
828-
let HttpTask::Header(header, _eos) = &output[0] else {
829-
panic!("{:?}", output);
830-
};
831-
assert_eq!(header.status, 200);
832-
let HttpTask::Body(data, eos) = &output[1] else {
833-
panic!("{:?}", output);
834-
};
835-
assert!(!eos);
836-
assert_eq!(data.as_ref().unwrap(), "rust");
837-
let HttpTask::Body(data, eos) = &output[2] else {
838-
panic!("{:?}", output);
839-
};
840-
assert_eq!(data.as_ref().unwrap(), "y");
730+
assert_eq!(data.as_ref().unwrap(), "st\r\n");
841731
assert!(!eos);
842732
}
843733

@@ -928,25 +818,5 @@ mod parse_response {
928818
assert!(output.is_empty());
929819
parser.finish().unwrap();
930820
}
931-
932-
#[test]
933-
fn test_no_body_chunked() {
934-
let input = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n";
935-
let mut parser = ResponseParse::new();
936-
let output = parser.inject_data(input).unwrap();
937-
938-
assert_eq!(output.len(), 2);
939-
let HttpTask::Header(header, _eos) = &output[0] else {
940-
panic!("{:?}", output);
941-
};
942-
assert_eq!(header.status, 200);
943-
944-
let HttpTask::Body(data, eos) = &output[1] else {
945-
panic!("{:?}", output);
946-
};
947-
assert_eq!(data.as_ref().unwrap(), "");
948-
assert!(eos);
949-
parser.finish().unwrap();
950-
}
951821
}
952822
}

0 commit comments

Comments
 (0)