diff --git a/crates/alexandrie/src/api/crates/publish.rs b/crates/alexandrie/src/api/crates/publish.rs index d5e4df05..c4607fc1 100644 --- a/crates/alexandrie/src/api/crates/publish.rs +++ b/crates/alexandrie/src/api/crates/publish.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::io::Read; use std::path::PathBuf; +use std::pin::pin; use async_std::io::prelude::*; @@ -171,6 +172,21 @@ fn link_badges( Ok(()) } +/// Checks whether the passed-in reader has ended (meaning it has reached EOF). +/// +/// This function tests for this by attempting to read one more byte from the passed-in reader. +/// Therefore, the reader should not be used after having called this function, because that one byte +/// will be missing from the output. +async fn has_reader_ended(reader: R) -> std::io::Result +where + R: async_std::io::Read, +{ + pin!(reader) + .read(&mut [0]) + .await + .map(|bytes_read| bytes_read == 0) +} + /// Route to publish a new crate (used by `cargo publish`). pub(crate) async fn put(mut req: Request) -> tide::Result { let state = req.state().clone(); @@ -191,6 +207,10 @@ pub(crate) async fn put(mut req: Request) -> tide::Result { .take(max_crate_size) .read_to_end(&mut bytes) .await?; + + if !has_reader_ended(&mut req).await? { + return Err(Error::from(AlexError::CrateTooLarge { max_crate_size }).into()); + } } else { (&mut req).read_to_end(&mut bytes).await?; } diff --git a/crates/alexandrie/src/error.rs b/crates/alexandrie/src/error.rs index 02fc30ec..f4aa52f4 100644 --- a/crates/alexandrie/src/error.rs +++ b/crates/alexandrie/src/error.rs @@ -87,6 +87,14 @@ pub enum AlexError { /// The list of missing query parameters. missing_params: &'static [&'static str], }, + /// The uploaded crate is larger than the maximum allowed crate size. + #[error( + "uploaded crate is larger than the maximum allowed crate size of {max_crate_size} bytes" + )] + CrateTooLarge { + /// The maximum allowed crate size (in bytes). + max_crate_size: u64, + }, } // impl IntoResponse for Error {