From c2209dec85603d2c206098d54299657a5e38cac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=B1=BC=E5=BC=80=E5=8F=91?= Date: Tue, 9 Jun 2026 11:22:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E8=B7=AF=E5=BE=84=E7=9A=84=20validate=5Fsafe=5Fpath?= =?UTF-8?q?=20=E9=AA=8C=E8=AF=81=EF=BC=8C=E6=94=BE=E5=AE=BD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=AE=BF=E9=97=AE=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tauri-app/src-tauri/src/ffmpeg_cmd.rs | 83 ++++++--------------------- 1 file changed, 16 insertions(+), 67 deletions(-) diff --git a/tauri-app/src-tauri/src/ffmpeg_cmd.rs b/tauri-app/src-tauri/src/ffmpeg_cmd.rs index f7fb587..144f816 100644 --- a/tauri-app/src-tauri/src/ffmpeg_cmd.rs +++ b/tauri-app/src-tauri/src/ffmpeg_cmd.rs @@ -60,50 +60,10 @@ fn normalize_path(path: &std::path::Path) -> String { } } -/// 验证路径在允许的目录内,防止路径遍历攻击 -/// 允许的目录:应用数据目录(app_local_data_dir) -fn validate_safe_path(path: &str) -> Result { - let path = std::path::Path::new(path); - - // 获取绝对路径 - let abs_path = if path.is_absolute() { - path.to_path_buf() - } else { - std::env::current_dir() - .map_err(|e| format!("无法获取当前目录: {}", e))? - .join(path) - }; - - // 检查是否在允许的目录内 - let allowed_dir = crate::storage::paths::get_app_data_dir() - .map_err(|e| format!("无法获取应用数据目录: {}", e))?; - - // 规范化路径 - let canonical = abs_path.canonicalize() - .unwrap_or(abs_path.clone()); - - // 同时规范化允许目录(Windows 上 canonicalize 会添加 \\?\ 前缀, - // 必须与输入路径保持一致的规范化格式才能正确比较) - let allowed_canonical = allowed_dir.canonicalize() - .unwrap_or(allowed_dir.clone()); - - // 检查是否在允许目录下 - if !canonical.starts_with(&allowed_canonical) { - return Err(format!("路径不在允许目录内: {}", path.display())); - } - - Ok(canonical.to_string_lossy().to_string()) -} - -/// 清理并验证输出路径 +/// 清理输出路径(转绝对路径) pub fn sanitize_output_path(path: &str) -> Result { let path = std::path::Path::new(path); - // 获取父目录并验证 - if let Some(parent) = path.parent() { - validate_safe_path(&parent.to_string_lossy())?; - } - // 确保是绝对路径 if path.is_absolute() { Ok(path.to_string_lossy().to_string()) @@ -215,8 +175,7 @@ pub async fn standardize_video( target_width: u32, target_height: u32, ) -> Result<(), String> { - // 验证路径安全 - let safe_input = validate_safe_path(input_path)?; + let safe_input = input_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let vf = format!( @@ -244,8 +203,7 @@ pub async fn standardize_video( * 拼接视频 - 快速模式 (要求编码/分辨率一致) */ pub async fn concat_videos_copy(app: &AppHandle, list_path: &str, output_path: &str) -> Result<(), String> { - // 验证路径安全 - let safe_list = validate_safe_path(list_path)?; + let safe_list = list_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let args = vec![ @@ -321,9 +279,8 @@ pub async fn concat_videos_robust( * 音画合并 - 优化音质 */ pub async fn add_audio_to_video(app: &AppHandle, video_path: &str, audio_path: &str, output_path: &str) -> Result<(), String> { - // 验证路径安全 - let safe_video = validate_safe_path(video_path)?; - let safe_audio = validate_safe_path(audio_path)?; + let safe_video = video_path.to_string(); + let safe_audio = audio_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let args = vec![ @@ -353,8 +310,7 @@ pub async fn create_cover_video( target_width: u32, target_height: u32, ) -> Result<(), String> { - // 验证路径安全 - let safe_input = validate_safe_path(input_path)?; + let safe_input = input_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let vf = format!( @@ -491,13 +447,13 @@ pub async fn burn_ass_subtitle( output_path: &str, overlay_image: Option<&str>, ) -> Result<(), String> { - // 输入路径验证:HTTP URL 直接传递,本地文件需要安全检查 + // 输入路径:HTTP URL 直接传递,本地文件直接传递 let safe_video = if video_path.starts_with("http://") || video_path.starts_with("https://") { video_path.to_string() } else { - validate_safe_path(video_path)? + video_path.to_string() }; - let safe_ass = validate_safe_path(ass_path)?; + let safe_ass = ass_path.to_string(); let safe_output = sanitize_output_path(output_path)?; // 准备临时工作目录(ASS + 字体),使用相对路径避开 Windows C: 盘符问题 @@ -505,7 +461,7 @@ pub async fn burn_ass_subtitle( // 如果有 overlay 图片,先 overlay 再 burn 字幕(两步避免 filter_complex 音频映射问题) if let Some(img_path) = overlay_image { - let safe_img = validate_safe_path(img_path)?; + let safe_img = img_path.to_string(); let temp_output = format!("{}.tmp_overlay.mp4", safe_output); // Step 1: overlay PNG 到视频 @@ -588,14 +544,8 @@ pub async fn mix_bgm_to_video( video_volume: f64, bgm_volume: f64, ) -> Result<(), String> { - let safe_video = validate_safe_path(video_path)?; - // BGM 可以是用户通过系统文件选择器选取的任意本地文件, - // 验证文件存在且不含路径遍历字符 - let safe_bgm = if !bgm_path.contains("..") && std::path::Path::new(bgm_path).exists() { - bgm_path.to_string() - } else { - return Err(format!("BGM 文件不存在或路径非法: {}", bgm_path)); - }; + let safe_video = video_path.to_string(); + let safe_bgm = bgm_path.to_string(); let safe_output = sanitize_output_path(output_path)?; // 构建 filter_complex: @@ -635,9 +585,8 @@ pub async fn replace_audio_track( audio_path: &str, output_path: &str, ) -> Result<(), String> { - // 验证路径安全 - let safe_video = validate_safe_path(video_path)?; - let safe_audio = validate_safe_path(audio_path)?; + let safe_video = video_path.to_string(); + let safe_audio = audio_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let args = vec![ @@ -839,7 +788,7 @@ pub async fn extract_audio_segment( duration: f64, output_path: &str, ) -> Result<(), String> { - let safe_input = validate_safe_path(input_path)?; + let safe_input = input_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let start_str = format!("{:.3}", start); @@ -873,7 +822,7 @@ pub async fn extract_audio_from_video( input_path: &str, output_path: &str, ) -> Result<(), String> { - let safe_input = validate_safe_path(input_path)?; + let safe_input = input_path.to_string(); let safe_output = sanitize_output_path(output_path)?; let args = vec![