security(upload): cap upload size to prevent memody/disk consumption
add OXICLOUD_CHUNK_MAX_BYTES which correspond to the max upload chunk allowed
(differs from OXICLOUD_MAX_UPLOAD_SIZE which is the max total size of a file)
hurl test validate the change
Streams the request body straight to the chunk file with peak heap of
~one HTTP frame, regardless of chunk size or the configured cap. The
`storage.chunk_max_bytes` config (env `OXICLOUD_CHUNK_MAX_BYTES`,
default 100 MB) bounds a single PUT — separate from `max_upload_size`
which governs whole-file uploads. Without this separation, a client
could submit a chunk up to the whole-file cap (10 GB default) and
monopolise server memory.
This commit is contained in:
@@ -52,7 +52,30 @@ impl NextcloudChunkedUploadService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store a chunk in the session directory.
|
||||
/// Resolve and validate the filesystem path for a chunk file.
|
||||
///
|
||||
/// Public so the interface layer can stream an HTTP body straight into
|
||||
/// the chunk file without copying through the service. The service
|
||||
/// retains responsibility for path-component validation; the caller
|
||||
/// owns the I/O (open, write, fsync, size enforcement, cleanup on
|
||||
/// failure). All three `validate_path_component` calls run before the
|
||||
/// path is constructed, so a returned `PathBuf` is always inside
|
||||
/// `base_dir/{user}/{upload_id}`.
|
||||
pub fn safe_chunk_path(
|
||||
&self,
|
||||
user: &str,
|
||||
upload_id: &str,
|
||||
chunk_name: &str,
|
||||
) -> Result<PathBuf> {
|
||||
Self::validate_path_component(chunk_name, "chunk_name")?;
|
||||
Ok(self.safe_session_dir(user, upload_id)?.join(chunk_name))
|
||||
}
|
||||
|
||||
/// Store a chunk in the session directory. Buffers `data` in memory —
|
||||
/// use [`safe_chunk_path`](Self::safe_chunk_path) + the
|
||||
/// `interfaces/upload_spool::stream_body_to_path` helper to stream the
|
||||
/// HTTP body directly to disk and avoid materialising the whole chunk
|
||||
/// in RAM.
|
||||
pub async fn store_chunk(
|
||||
&self,
|
||||
user: &str,
|
||||
@@ -60,8 +83,7 @@ impl NextcloudChunkedUploadService {
|
||||
chunk_name: &str,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
Self::validate_path_component(chunk_name, "chunk_name")?;
|
||||
let chunk_path = self.safe_session_dir(user, upload_id)?.join(chunk_name);
|
||||
let chunk_path = self.safe_chunk_path(user, upload_id, chunk_name)?;
|
||||
let mut file = fs::File::create(&chunk_path)
|
||||
.await
|
||||
.map_err(|e| DomainError::internal_error("ChunkedUpload", e.to_string()))?;
|
||||
|
||||
Reference in New Issue
Block a user