fix: return NotFound when a storage key resolves to a directory
`get` (fs::read → EISDIR) and `get_stream` (File::open succeeds on a Unix directory, then streams garbage that fails mid-read) both mishandled a key resolving to a directory. Mirror `size`'s existing guard: `get` maps IsADirectory to NotFound; `get_stream` checks metadata.is_file() after open. Test: get_on_directory_is_not_found. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.128.6"
|
version = "0.128.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.128.6"
|
version = "0.128.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ impl Storage for LocalStorage {
|
|||||||
match fs::read(&path).await {
|
match fs::read(&path).await {
|
||||||
Ok(b) => Ok(b),
|
Ok(b) => Ok(b),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(StorageError::NotFound),
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(StorageError::NotFound),
|
||||||
|
// A key resolving to a directory isn't a stored blob; `fs::read`
|
||||||
|
// fails with EISDIR. Treat it as absent, mirroring `size`.
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::IsADirectory => {
|
||||||
|
Err(StorageError::NotFound)
|
||||||
|
}
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +113,14 @@ impl Storage for LocalStorage {
|
|||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
let size_bytes = file.metadata().await?.len();
|
let meta = file.metadata().await?;
|
||||||
|
// Opening a directory succeeds on Unix, but it isn't a stored blob:
|
||||||
|
// its inode "size" is meaningless and ReaderStream would fail mid-read.
|
||||||
|
// Treat it as absent, mirroring `size` and `get`.
|
||||||
|
if !meta.is_file() {
|
||||||
|
return Err(StorageError::NotFound);
|
||||||
|
}
|
||||||
|
let size_bytes = meta.len();
|
||||||
// 64 KiB chunks: small enough that a few-MB page emits many frames
|
// 64 KiB chunks: small enough that a few-MB page emits many frames
|
||||||
// (so streaming is observable), large enough to keep syscalls cheap.
|
// (so streaming is observable), large enough to keep syscalls cheap.
|
||||||
let stream = ReaderStream::with_capacity(file, 64 * 1024);
|
let stream = ReaderStream::with_capacity(file, 64 * 1024);
|
||||||
@@ -244,6 +256,21 @@ mod tests {
|
|||||||
assert!(matches!(s.size("adir").await, Err(StorageError::NotFound)));
|
assert!(matches!(s.size("adir").await, Err(StorageError::NotFound)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_on_directory_is_not_found() {
|
||||||
|
// A key resolving to a directory isn't a stored blob. `fs::read` on a
|
||||||
|
// dir errors with EISDIR (not NotFound), and `File::open` on a dir
|
||||||
|
// succeeds on Unix then streams garbage — both must surface NotFound.
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let s = LocalStorage::new(dir.path());
|
||||||
|
std::fs::create_dir(dir.path().join("adir")).unwrap();
|
||||||
|
assert!(matches!(s.get("adir").await, Err(StorageError::NotFound)));
|
||||||
|
assert!(matches!(
|
||||||
|
s.get_stream("adir").await.err(),
|
||||||
|
Some(StorageError::NotFound)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn put_stream_writes_full_body_and_removes_temp_on_error() {
|
async fn put_stream_writes_full_body_and_removes_temp_on_error() {
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.128.6",
|
"version": "0.128.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user