diff --git a/src/api/assets.rs b/src/api/assets.rs index c8406ff..eb395f2 100644 --- a/src/api/assets.rs +++ b/src/api/assets.rs @@ -112,6 +112,22 @@ fn parse_path(path: &str) -> Result<(Option, String), ApiError> { } } +/// Parse a path for serving only – preserves original case, only validates for path traversal. +fn parse_serve_path(path: &str) -> Result<(Option, String), ApiError> { + let path = path.trim_start_matches('/'); + if path.contains("..") || path.contains('\\') { + return Err(ApiError::BadRequest("Invalid asset path".into())); + } + let mut parts = path.splitn(2, '/'); + match (parts.next(), parts.next()) { + (Some(a), None) if !a.is_empty() => Ok((None, a.to_string())), + (Some(folder), Some(filename)) if !folder.is_empty() && !filename.is_empty() => { + Ok((Some(folder.to_string()), filename.to_string())) + } + _ => Err(ApiError::BadRequest("Invalid asset path".into())), + } +} + /// Read all image files from a directory, tagging them with the folder name. async fn read_images( dir: &std::path::Path, @@ -521,7 +537,7 @@ pub async fn get_asset( ) -> Result { let env = state.effective_environment_from_param(env_param.environment.as_deref().map(|s| s.trim()).filter(|s| !s.is_empty())); let assets_dir = state.assets_dir_for(&env); - let (folder, filename) = parse_path(&path)?; + let (folder, filename) = parse_serve_path(&path)?; let file_path = match &folder { Some(f) => assets_dir.join(f).join(&filename), None => assets_dir.join(&filename),