From b3f4074d67d7052c66f93c45875a63167373e25f Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 25 Jun 2026 00:41:43 +0330 Subject: [PATCH] fix: resolve low severity vulnerabilities from deepseek audit --- src-tauri/src/download.rs | 10 +++-- src-tauri/src/lib.rs | 19 +++++++-- src/components/AddDownloadsModal.tsx | 4 +- src/components/PropertiesModal.tsx | 9 ++-- src/store/downloadStore.ts | 3 +- src/store/useDownloadStore.ts | 63 +++++++++++++++++++++------- 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src-tauri/src/download.rs b/src-tauri/src/download.rs index 11ef788..6155568 100644 --- a/src-tauri/src/download.rs +++ b/src-tauri/src/download.rs @@ -480,7 +480,9 @@ async fn download_file( return DownloadOutcome::Paused; } Err(AttemptError::Controlled(DownloadControl::Cancel)) => { - let _ = fs::remove_file(&payload.output_path).await; + if let Err(e) = fs::remove_file(&payload.output_path).await { + log::warn!("Failed to remove cancelled file '{}': {}", payload.output_path.display(), e); + } return DownloadOutcome::Cancelled; } Err(AttemptError::Controlled(DownloadControl::Replace)) => { @@ -508,7 +510,9 @@ async fn download_file( return match control.unwrap_or(DownloadControl::Cancel) { DownloadControl::Pause => DownloadOutcome::Paused, DownloadControl::Cancel => { - let _ = fs::remove_file(&payload.output_path).await; + if let Err(e) = fs::remove_file(&payload.output_path).await { + log::warn!("Failed to remove cancelled file '{}': {}", payload.output_path.display(), e); + } DownloadOutcome::Cancelled } DownloadControl::Replace => DownloadOutcome::Cancelled, @@ -717,7 +721,7 @@ fn build_client(payload: &DownloadPayload) -> Result<(Client, HeaderMap), String if proxy == "none" { builder = builder.no_proxy(); } else { - builder = builder.proxy(reqwest::Proxy::all(proxy).map_err(|error| error.to_string())?); + builder = builder.proxy(reqwest::Proxy::all(proxy).map_err(|_| "Invalid proxy URL configured".to_string())?); } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index de8f00b..e886ddb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1374,12 +1374,16 @@ pub(crate) fn resolve_path(path: &str, app_handle: &tauri::AppHandle) -> std::pa .strip_prefix("~/") .or_else(|| path.strip_prefix("~\\")) { - if let Ok(home) = app_handle.path().home_dir() { + if let Some(home) = app_handle.path().home_dir().ok().or_else(|| std::env::var("USERPROFILE").ok().map(std::path::PathBuf::from)) { resolved = home.join(stripped); + } else { + log::warn!("Failed to resolve home directory for ~ expansion"); } } else if path == "~" { - if let Ok(home) = app_handle.path().home_dir() { + if let Some(home) = app_handle.path().home_dir().ok().or_else(|| std::env::var("USERPROFILE").ok().map(std::path::PathBuf::from)) { resolved = home; + } else { + log::warn!("Failed to resolve home directory for ~ expansion"); } } resolved @@ -1655,13 +1659,18 @@ fn version_check_cache( } fn version_cache_key(binary_path: &std::path::Path, args: &[&str]) -> String { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); let modified = std::fs::metadata(binary_path) .and_then(|metadata| metadata.modified()) .ok() .and_then(|modified| modified.duration_since(std::time::UNIX_EPOCH).ok()) .map(|duration| duration.as_nanos()) .unwrap_or_default(); - format!("{}:{modified}:{}", binary_path.display(), args.join("\u{1f}")) + binary_path.hash(&mut hasher); + modified.hash(&mut hasher); + args.hash(&mut hasher); + hasher.finish().to_string() } fn validate_bundled_binary(binary_path: &std::path::Path) -> Result<(), String> { @@ -2598,7 +2607,9 @@ async fn remove_download_assets( } for suffix in [".aria2", ".part", ".ytdl"] { - let candidate = std::path::PathBuf::from(format!("{}{}", primary.display(), suffix)); + let mut candidate_os = primary.as_os_str().to_os_string(); + candidate_os.push(suffix); + let candidate = std::path::PathBuf::from(candidate_os); if candidate.exists() && is_safe_path(&candidate, app_handle) { tokio::fs::remove_file(&candidate) .await diff --git a/src/components/AddDownloadsModal.tsx b/src/components/AddDownloadsModal.tsx index a15de75..8066b72 100644 --- a/src/components/AddDownloadsModal.tsx +++ b/src/components/AddDownloadsModal.tsx @@ -422,7 +422,9 @@ export const AddDownloadsModal = () => { fileExistsOnDisk = await invoke('check_file_exists', { path: await resolveDownloadFilePath(itemLocation, finalFile) }); - } catch (e) {} + } catch (e) { + console.error("Failed to check if file exists on disk:", e); + } if (fileExistsInStore || fileExistsOnDisk) { newConflicts.push({ diff --git a/src/components/PropertiesModal.tsx b/src/components/PropertiesModal.tsx index 686211e..67a4506 100644 --- a/src/components/PropertiesModal.tsx +++ b/src/components/PropertiesModal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { useDownloadStore, DownloadItem } from '../store/useDownloadStore'; import { useDownloadProgressStore } from '../store/downloadStore'; +import { useShallow } from 'zustand/react/shallow'; import { useSettingsStore } from '../store/useSettingsStore'; import { ChevronDown, ChevronRight, FolderPlus, Info, CheckCircle, AlertCircle, Play, Pause } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; @@ -15,16 +16,16 @@ type LoginMode = 'matching' | 'custom' | 'none'; export const PropertiesModal = () => { const selectedPropertiesDownloadId = useDownloadStore(state => state.selectedPropertiesDownloadId); const setSelectedPropertiesDownloadId = useDownloadStore(state => state.setSelectedPropertiesDownloadId); - const item = useDownloadStore(state => + const item = useDownloadStore(useShallow(state => selectedPropertiesDownloadId ? state.downloads.find(d => d.id === selectedPropertiesDownloadId) ?? null : null - ); - const liveProgress = useDownloadProgressStore(state => + )); + const liveProgress = useDownloadProgressStore(useShallow(state => selectedPropertiesDownloadId ? state.progressMap[selectedPropertiesDownloadId] : undefined - ); + )); const { baseDownloadFolder, perServerConnections } = useSettingsStore(); diff --git a/src/store/downloadStore.ts b/src/store/downloadStore.ts index 150f82f..da9e5ca 100644 --- a/src/store/downloadStore.ts +++ b/src/store/downloadStore.ts @@ -3,6 +3,7 @@ import type { UnlistenFn } from '@tauri-apps/api/event'; import type { DownloadProgressEvent } from '../bindings/DownloadProgressEvent'; import type { DownloadStatus } from '../bindings/DownloadStatus'; import { listenEvent as listen } from '../ipc'; +import type { DownloadItem } from '../bindings/DownloadItem'; interface DownloadProgressState { progressMap: Record; @@ -58,7 +59,7 @@ export async function initDownloadListener() { if (current) { const status = payload.status as DownloadStatus; const progress = useDownloadProgressStore.getState().progressMap[payload.id]; - const updates: Partial = { + const updates: Partial = { status, ...(progress ? { fraction: progress.fraction } : {}) }; diff --git a/src/store/useDownloadStore.ts b/src/store/useDownloadStore.ts index 7172ba2..fa8fdf2 100644 --- a/src/store/useDownloadStore.ts +++ b/src/store/useDownloadStore.ts @@ -35,7 +35,9 @@ export async function dispatchItem(id: string): Promise { if (login) { try { keychainPassword = await invoke('get_keychain_password', { id: login.id }); - } catch (e) {} + } catch (e) { + console.warn("Failed to retrieve keychain password for dispatch:", e); + } } const enqueueItem = { @@ -813,18 +815,51 @@ export const useDownloadStore = create((set, get) => ({ })); let lastSavedDownloads = ''; -let downloadsSave = Promise.resolve(); -let queuesSave = Promise.resolve(); +let isSavingDownloads = false; +let nextDownloadsData: string | null = null; -useDownloadStore.subscribe(async (state, prevState) => { +async function processDownloadsSave() { + if (isSavingDownloads || !nextDownloadsData) return; + isSavingDownloads = true; + while (nextDownloadsData) { + const data = nextDownloadsData; + nextDownloadsData = null; + try { + await invoke('db_replace_downloads', { data }); + } catch (error) { + console.error('Failed to persist downloads:', error); + } + } + isSavingDownloads = false; +} + +let lastSavedQueues = ''; +let isSavingQueues = false; +let nextQueuesData: string | null = null; + +async function processQueuesSave() { + if (isSavingQueues || !nextQueuesData) return; + isSavingQueues = true; + while (nextQueuesData) { + const data = nextQueuesData; + nextQueuesData = null; + try { + await invoke('db_replace_queues', { data }); + } catch (error) { + console.error('Failed to persist queues:', error); + } + } + isSavingQueues = false; +} + +useDownloadStore.subscribe((state, prevState) => { if (state.queues !== prevState.queues) { const data = JSON.stringify(state.queues); - queuesSave = queuesSave - .then(() => invoke('db_replace_queues', { data })) - .catch(error => { - console.error('Failed to persist queues:', error); - }); - await queuesSave; + if (data !== lastSavedQueues) { + lastSavedQueues = data; + nextQueuesData = data; + processQueuesSave(); + } } if (state.downloads !== prevState.downloads) { @@ -836,12 +871,8 @@ useDownloadStore.subscribe(async (state, prevState) => { const currentSerialized = JSON.stringify(staticDownloads); if (currentSerialized !== lastSavedDownloads) { lastSavedDownloads = currentSerialized; - downloadsSave = downloadsSave - .then(() => invoke('db_replace_downloads', { data: currentSerialized })) - .catch(error => { - console.error('Failed to persist downloads:', error); - }); - await downloadsSave; + nextDownloadsData = currentSerialized; + processDownloadsSave(); } } });