use futures_lite::{AsyncRead, AsyncReadExt};
use std::{
io::Result,
pin::Pin,
task::{Context, Poll},
};
use trillium_http::{Body, BodySource, Headers};
pub(crate) async fn buffer_request_body(body: Body, limit: usize) -> Result<(Body, bool)> {
if body.len().is_some() {
return Ok((body, true));
}
let Some(mut source) = body.into_body_source() else {
return Ok((Body::default(), true));
};
let mut buf = vec![0u8; limit + 1];
let mut filled = 0;
let fully_buffered = loop {
if filled == buf.len() {
break false;
}
let bytes = source.read(&mut buf[filled..]).await?;
if bytes == 0 {
break true;
}
filled += bytes;
};
buf.truncate(filled);
let body = if !fully_buffered {
Body::new_with_trailers(
PrefixedBody {
prefix: buf,
cursor: 0,
inner: source,
},
None,
)
} else if let Some(trailers) = source.as_mut().trailers() {
Body::new_with_trailers(
BufferedTrailers {
content: buf,
cursor: 0,
trailers: Some(trailers),
},
None,
)
} else {
Body::new_static(buf)
};
Ok((body, fully_buffered))
}
struct PrefixedBody {
prefix: Vec<u8>,
cursor: usize,
inner: Pin<Box<dyn BodySource>>,
}
impl AsyncRead for PrefixedBody {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<Result<usize>> {
let this = self.get_mut();
if this.cursor < this.prefix.len() {
let n = buf.len().min(this.prefix.len() - this.cursor);
buf[..n].copy_from_slice(&this.prefix[this.cursor..this.cursor + n]);
this.cursor += n;
return Poll::Ready(Ok(n));
}
this.inner.as_mut().poll_read(cx, buf)
}
}
impl BodySource for PrefixedBody {
fn trailers(self: Pin<&mut Self>) -> Option<Headers> {
self.get_mut().inner.as_mut().trailers()
}
}
struct BufferedTrailers {
content: Vec<u8>,
cursor: usize,
trailers: Option<Headers>,
}
impl AsyncRead for BufferedTrailers {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<Result<usize>> {
let this = self.get_mut();
let n = buf.len().min(this.content.len() - this.cursor);
buf[..n].copy_from_slice(&this.content[this.cursor..this.cursor + n]);
this.cursor += n;
Poll::Ready(Ok(n))
}
}
impl BodySource for BufferedTrailers {
fn trailers(self: Pin<&mut Self>) -> Option<Headers> {
self.get_mut().trailers.take()
}
}