use std::cmp;
use std::io::{self, Read};
use std::str;
use std::u64;
use crate::parsing::{error, ExpandingBufReader};
pub struct ChunkedReader<R>
where
R: Read,
{
inner: ExpandingBufReader<R>,
is_waiting: bool, read: u64, length: u64, }
impl<R> ChunkedReader<R>
where
R: Read,
{
pub fn new(reader: ExpandingBufReader<R>) -> ChunkedReader<R> {
ChunkedReader {
inner: reader,
is_waiting: true,
read: 0,
length: 0,
}
}
}
fn parse_chunk_size(line: &[u8]) -> io::Result<u64> {
line.iter()
.position(|&b| b == b';')
.map_or_else(|| str::from_utf8(line), |idx| str::from_utf8(&line[..idx]))
.map_err(|_| error("cannot decode chunk size as utf-8"))
.and_then(|line| u64::from_str_radix(line, 16).map_err(|_| error("cannot decode chunk size as hex")))
}
impl<R> Read for ChunkedReader<R>
where
R: Read,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.is_waiting {
self.read = 0;
self.length = parse_chunk_size(self.inner.read_line()?)?;
if self.length == 0 {
let buf = self.inner.read_line()?;
debug_assert!(buf.len() == 0);
}
self.is_waiting = false;
}
if self.length == 0 {
return Ok(0);
}
let remaining = self.length - self.read;
let count = cmp::min(remaining, buf.len() as u64) as usize;
let n = self.inner.read(&mut buf[..count])?;
self.read += n as u64;
if n == 0 {
self.length = 0;
return Err(io::ErrorKind::UnexpectedEof.into());
}
if self.read == self.length {
let buf = self.inner.read_line()?;
debug_assert!(buf.len() == 0);
self.is_waiting = true;
}
Ok(n)
}
}
#[test]
fn test_read_works() {
let msg = b"4\r\nwiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
reader.read_to_string(&mut s).unwrap();
assert_eq!(s, "wikipedia in\r\n\r\nchunks.");
}
#[test]
fn test_read_empty() {
let msg = b"0\r\n\r\n";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
reader.read_to_string(&mut s).unwrap();
assert_eq!(s, "");
}
#[test]
fn test_read_invalid_empty() {
let msg = b"";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
assert!(reader.read_to_string(&mut s).is_err());
}
#[test]
fn test_read_invalid_chunk() {
let msg = b"4\r\nwik";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
assert_eq!(
reader.read_to_string(&mut s).err().unwrap().kind(),
io::ErrorKind::UnexpectedEof
);
}
#[test]
fn test_read_invalid_no_terminating_chunk() {
let msg = b"4\r\nwiki";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
assert_eq!(
reader.read_to_string(&mut s).err().unwrap().kind(),
io::ErrorKind::UnexpectedEof
);
}
#[test]
fn test_read_invalid_bad_terminating_chunk() {
let msg = b"4\r\nwiki\r\n0\r\n";
let mut reader = ChunkedReader::new(ExpandingBufReader::new(&msg[..]));
let mut s = String::new();
assert_eq!(
reader.read_to_string(&mut s).err().unwrap().kind(),
io::ErrorKind::UnexpectedEof
);
}