fix: OOM protection, lock-free thumbnail cache, OIDC JWKS TTL

- Streaming WebDAV PUT: body spooled to tempfile with incremental SHA-256,
  peak RAM ~64KB regardless of file size (Solution 2)
- RequestBodyLimitLayer (1MB) on CalDAV/CardDAV routers (Solution 3)
- All body::to_bytes(body, usize::MAX) replaced with explicit limits:
  PROPFIND/PROPPATCH/LOCK → 1MB, MKCOL → 4KB
- Added AppError::payload_too_large (HTTP 413)
- Added max_upload_size to StorageConfig (default 10GB, env override)
- New streaming update chain: FileWritePort::update_file_content_from_temp
  → FileUploadUseCase::update_file_streaming
- ThumbnailService: migrated from RwLock<LruCache> to moka::future::Cache
  with weight-based eviction — eliminates lock contention on read hot-path
- OIDC: discovery + JWKS caches now expire after 1 hour (Cached<T> wrapper)
  so IdP key rotation no longer requires server restart
This commit is contained in:
Diocrafts
2026-02-22 23:28:03 +01:00
parent b48f2867ac
commit 92e0364a60
12 changed files with 395 additions and 172 deletions
+12
View File
@@ -200,6 +200,9 @@ pub struct StorageConfig {
pub parallel_threshold: usize,
/// Retention days for files in the trash
pub trash_retention_days: u32,
/// Maximum upload file size in bytes (default: 10 GB).
/// Applied as a hard limit to WebDAV PUT and streaming uploads.
pub max_upload_size: usize,
}
impl Default for StorageConfig {
@@ -209,6 +212,7 @@ impl Default for StorageConfig {
chunk_size: 1024 * 1024, // 1 MB
parallel_threshold: 100 * 1024 * 1024, // 100 MB
trash_retention_days: 30, // 30 days
max_upload_size: 10 * 1024 * 1024 * 1024, // 10 GB
}
}
}
@@ -566,6 +570,14 @@ impl AppConfig {
config.features.enable_search = val;
}
// Storage limits
if let Ok(max_upload) = env::var("OXICLOUD_MAX_UPLOAD_SIZE")
.map(|v| v.parse::<usize>())
&& let Ok(val) = max_upload
{
config.storage.max_upload_size = val;
}
// OIDC configuration
if let Ok(v) = env::var("OXICLOUD_OIDC_ENABLED") {
config.oidc.enabled = v.parse::<bool>().unwrap_or(false);
+22
View File
@@ -193,6 +193,17 @@ impl FileWritePort for StubFileWritePort {
Ok(())
}
async fn update_file_content_from_temp(
&self,
_file_id: &str,
_temp_path: &Path,
_size: u64,
_content_type: Option<String>,
_pre_computed_hash: Option<String>,
) -> Result<(), DomainError> {
Ok(())
}
async fn register_file_deferred(
&self,
_name: String,
@@ -485,6 +496,17 @@ impl FileUploadUseCase for StubFileUploadUseCase {
async fn update_file(&self, _path: &str, _content: &[u8]) -> Result<(), DomainError> {
Ok(())
}
async fn update_file_streaming(
&self,
_path: &str,
_temp_path: &Path,
_size: u64,
_content_type: &str,
_pre_computed_hash: Option<String>,
) -> Result<(), DomainError> {
Ok(())
}
}
// ---------------------------------------------------------------------------