use crate::{Error, HeaderValues, Headers, KnownHeaderName, Result};
use encoding_rs::Encoding;
use futures_lite::{AsyncRead, AsyncReadExt};
use memchr::memmem::Finder;
use mime::Mime;
use std::{io, str::FromStr, sync::LazyLock};
pub(crate) static CRLF: LazyLock<Finder<'static>> = LazyLock::new(|| Finder::new(b"\r\n"));
pub(crate) static CRLF_CRLF: LazyLock<Finder<'static>> = LazyLock::new(|| Finder::new(b"\r\n\r\n"));
pub fn encoding(headers: &Headers) -> &'static Encoding {
headers
.get_str(KnownHeaderName::ContentType)
.and_then(|c| Mime::from_str(c).ok())
.and_then(|m| {
m.params()
.find(|(name, _)| name.as_str() == "charset")
.and_then(|(_, v)| Encoding::for_label(v.as_str().as_bytes()))
})
.unwrap_or(encoding_rs::UTF_8)
}
pub fn validate_content_length(values: Option<&HeaderValues>) -> Result<Option<u64>> {
match values {
None => Ok(None),
Some(values) => parse_content_length(values)
.map(Some)
.ok_or_else(|| Error::InvalidHeaderValue(KnownHeaderName::ContentLength.into())),
}
}
pub(crate) fn parse_content_length(values: &HeaderValues) -> Option<u64> {
values
.as_str()
.filter(|cl| !cl.is_empty() && cl.bytes().all(|b| b.is_ascii_digit()))
.and_then(|cl| cl.parse::<u64>().ok())
}
pub(crate) fn is_tchar(b: u8) -> bool {
b.is_ascii_alphanumeric()
|| matches!(
b,
b'!' | b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~'
)
}
const ZEROED_WINDOW_FLOOR: usize = 4096;
pub(crate) async fn read_to_end<R>(reader: &mut R, buf: &mut Vec<u8>) -> io::Result<()>
where
R: AsyncRead + Unpin + ?Sized,
{
struct Filled<'a> {
buf: &'a mut Vec<u8>,
len: usize,
}
impl Drop for Filled<'_> {
fn drop(&mut self) {
self.buf.truncate(self.len);
}
}
let len = buf.len();
let mut filled = Filled { buf, len };
loop {
if filled.len == filled.buf.len() {
if filled.buf.len() == filled.buf.capacity() {
let mut probe = [0; 32];
let bytes = reader.read(&mut probe).await?;
if bytes == 0 {
return Ok(());
}
filled.buf.extend_from_slice(&probe[..bytes]);
filled.len = filled.buf.len();
continue;
}
let window = (filled.len * 2)
.max(ZEROED_WINDOW_FLOOR)
.min(filled.buf.capacity());
filled.buf.resize(window, 0);
}
let bytes = reader.read(&mut filled.buf[filled.len..]).await?;
if bytes == 0 {
return Ok(());
}
filled.len += bytes;
}
}
#[cfg(test)]
mod tests {
use super::read_to_end;
use futures_lite::{AsyncRead, future::block_on};
use std::{
io,
pin::Pin,
task::{Context, Poll},
};
struct FailsAfter(&'static [u8]);
impl AsyncRead for FailsAfter {
fn poll_read(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
if self.0.is_empty() {
return Poll::Ready(Err(io::ErrorKind::ConnectionReset.into()));
}
let len = buf.len().min(self.0.len());
buf[..len].copy_from_slice(&self.0[..len]);
self.0 = &self.0[len..];
Poll::Ready(Ok(len))
}
}
struct RecordsWindows(&'static [u8], Vec<usize>);
impl AsyncRead for RecordsWindows {
fn poll_read(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.1.push(buf.len());
let len = buf.len().min(self.0.len());
buf[..len].copy_from_slice(&self.0[..len]);
self.0 = &self.0[len..];
Poll::Ready(Ok(len))
}
}
#[test]
fn a_small_body_zeroes_little_of_a_large_preallocation() {
let mut buf = Vec::with_capacity(64 * 1024);
let mut reader = RecordsWindows(b"hi", Vec::new());
block_on(read_to_end(&mut reader, &mut buf)).unwrap();
assert_eq!(buf, b"hi");
assert!(
reader
.1
.iter()
.all(|&window| window <= super::ZEROED_WINDOW_FLOOR),
"read windows: {:?}",
reader.1
);
}
#[test]
fn a_failed_read_leaves_only_the_bytes_read() {
let mut buf = Vec::with_capacity(64);
let result = block_on(read_to_end(&mut FailsAfter(b"abc"), &mut buf));
assert!(result.is_err());
assert_eq!(buf, b"abc");
}
}