use crate::error::{Error, Result};
use bytes::Bytes;
use http_body_util::BodyExt;
use std::path::Path;
use tokio::io::{AsyncWrite, AsyncWriteExt};
enum Source {
Hyper(hyper::body::Incoming),
Memory(Option<Bytes>),
}
pub struct BodyStream {
source: Source,
limit: Option<u64>,
read: u64,
declared: Option<u64>,
}
fn too_large(limit: u64) -> Error {
Error::custom(413, format!("Request body too large (max {limit} bytes)"))
}
impl BodyStream {
pub(crate) fn from_incoming(body: hyper::body::Incoming, limit: Option<u64>) -> Self {
use hyper::body::Body;
let hint = body.size_hint();
let declared = hint.exact().or_else(|| {
(hint.lower() > 0 && hint.upper() == Some(hint.lower())).then(|| hint.lower())
});
Self {
source: Source::Hyper(body),
limit,
read: 0,
declared,
}
}
pub(crate) fn from_bytes(body: Bytes, limit: Option<u64>) -> Self {
let declared = Some(body.len() as u64);
Self {
source: Source::Memory(Some(body)),
limit,
read: 0,
declared,
}
}
pub fn content_length(&self) -> Option<u64> {
self.declared
}
pub fn bytes_read(&self) -> u64 {
self.read
}
pub async fn chunk(&mut self) -> Result<Option<Bytes>> {
if let (Some(limit), Some(declared)) = (self.limit, self.declared) {
if declared > limit {
return Err(self.refuse(limit).await);
}
}
let next = match &mut self.source {
Source::Memory(slot) => slot.take().filter(|b| !b.is_empty()),
Source::Hyper(body) => loop {
match body.frame().await {
None => break None,
Some(Err(_)) => return Err(Error::bad_request("Request body was interrupted")),
Some(Ok(frame)) => {
if let Ok(data) = frame.into_data() {
if !data.is_empty() {
break Some(data);
}
}
}
}
},
};
if let Some(data) = &next {
self.read += data.len() as u64;
if let Some(limit) = self.limit {
if self.read > limit {
return Err(self.refuse(limit).await);
}
}
}
Ok(next)
}
async fn refuse(&mut self, limit: u64) -> Error {
if let Source::Hyper(body) = &mut self.source {
crate::app::drain(body, self.read).await;
}
too_large(limit)
}
pub async fn copy_to<W: AsyncWrite + Unpin>(&mut self, writer: &mut W) -> Result<u64> {
let mut total = 0;
while let Some(chunk) = self.chunk().await? {
writer
.write_all(&chunk)
.await
.map_err(|e| Error::internal(format!("Writing the upload failed: {e}")))?;
total += chunk.len() as u64;
}
writer
.flush()
.await
.map_err(|e| Error::internal(format!("Writing the upload failed: {e}")))?;
Ok(total)
}
pub async fn save_to(&mut self, path: impl AsRef<Path>) -> Result<u64> {
let path = path.as_ref();
let mut file = tokio::fs::File::create(path)
.await
.map_err(|e| Error::internal(format!("Cannot create the upload file: {e}")))?;
match self.copy_to(&mut file).await {
Ok(n) => Ok(n),
Err(e) => {
drop(file);
let _ = tokio::fs::remove_file(path).await;
Err(e)
}
}
}
pub async fn collect(&mut self) -> Result<Bytes> {
let mut out = bytes::BytesMut::new();
while let Some(chunk) = self.chunk().await? {
out.extend_from_slice(&chunk);
}
Ok(out.freeze())
}
}