Merge pull request #353 from EdouardVanbelle/fix/sanity_check_files_and_folder_names

fix(storage names): enure folder and file names are not using reserved chars
This commit is contained in:
Dionisio Pozo
2026-05-08 01:21:05 +02:00
committed by GitHub
4 changed files with 49 additions and 34 deletions
+11 -13
View File
@@ -4,7 +4,7 @@ use crate::application::dtos::folder_dto::{
use crate::application::ports::inbound::FolderUseCase;
use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::repositories::folder_repository::FolderRepository;
use crate::domain::services::path_service::StoragePath;
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
use std::sync::Arc;
use uuid::Uuid;
@@ -136,12 +136,11 @@ impl FolderUseCase for FolderService {
/// Creates a new folder
async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
// Input validation
if dto.name.is_empty() {
return Err(DomainError::new(
ErrorKind::InvalidInput,
"Folder",
"Folder name cannot be empty",
));
if let Err(reason) = validate_storage_name(&dto.name) {
return Err(DomainError::validation_error(format!(
"Invalid folder name '{}': {reason}",
dto.name
)));
}
// If a parent_id is provided, verify it exists
@@ -397,12 +396,11 @@ impl FolderUseCase for FolderService {
caller_id: Uuid,
) -> Result<FolderDto, DomainError> {
// Input validation
if dto.name.is_empty() {
return Err(DomainError::new(
ErrorKind::InvalidInput,
"Folder",
"New folder name cannot be empty",
));
if let Err(reason) = validate_storage_name(&dto.name) {
return Err(DomainError::validation_error(format!(
"Invalid folder name '{}': {reason}",
dto.name
)));
}
// Verify the folder exists and belongs to the caller
+9 -13
View File
@@ -1,6 +1,6 @@
use uuid::Uuid;
use crate::domain::services::path_service::StoragePath;
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
// Re-export entity errors from the centralized module
pub use super::entity_errors::{FileError, FileResult};
@@ -100,9 +100,8 @@ impl File {
mime_type: String,
folder_id: Option<String>,
) -> FileResult<Self> {
// Validate file name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
let now = std::time::SystemTime::now()
@@ -137,9 +136,8 @@ impl File {
created_at: u64,
modified_at: u64,
) -> FileResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -199,9 +197,8 @@ impl File {
owner_id: Option<Uuid>,
etag: String,
) -> FileResult<Self> {
// Validate file name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -321,9 +318,8 @@ impl File {
/// Creates a new version of the file with updated name
pub fn with_name(&self, new_name: String) -> FileResult<Self> {
// Validate file name
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
return Err(FileError::InvalidFileName(new_name));
if let Err(reason) = validate_storage_name(&new_name) {
return Err(FileError::InvalidFileName(format!("{new_name}: {reason}")));
}
// Update path based on name
+9 -8
View File
@@ -1,6 +1,6 @@
use uuid::Uuid;
use crate::domain::services::path_service::StoragePath;
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
// Re-export entity errors from the centralized module
pub use super::entity_errors::{FolderError, FolderResult};
@@ -71,8 +71,8 @@ impl Folder {
owner_id: Option<Uuid>,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FolderError::InvalidFolderName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FolderError::InvalidFolderName(format!("{name}: {reason}")));
}
let now = std::time::SystemTime::now()
@@ -126,8 +126,8 @@ impl Folder {
modified_at: u64,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FolderError::InvalidFolderName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FolderError::InvalidFolderName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -208,9 +208,10 @@ impl Folder {
/// Creates a new version of the folder with updated name
pub fn with_name(&self, new_name: String) -> FolderResult<Self> {
// Validate folder name
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
return Err(FolderError::InvalidFolderName(new_name));
if let Err(reason) = validate_storage_name(&new_name) {
return Err(FolderError::InvalidFolderName(format!(
"{new_name}: {reason}"
)));
}
// Update path based on the name
+20
View File
@@ -6,6 +6,26 @@
use std::path::PathBuf;
/// Validates a single file or folder name component.
///
/// Returns `Err` with a human-readable reason if the name is rejected.
/// Callers should wrap the reason into their own error type.
pub fn validate_storage_name(name: &str) -> Result<(), &'static str> {
if name.is_empty() {
return Err("name cannot be empty");
}
if name.contains('/') || name.contains('\\') {
return Err("name must not contain '/' or '\\'");
}
if name.contains('\0') {
return Err("name must not contain null bytes");
}
if name == "." || name == ".." {
return Err("'.' and '..' are not valid names");
}
Ok(())
}
/// Represents a storage path in the domain (Value Object)
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct StoragePath {