fix: resolve low severity vulnerabilities from deepseek audit

This commit is contained in:
NimBold
2026-06-25 00:41:43 +03:30
parent 68607d6909
commit b3f4074d67
6 changed files with 79 additions and 29 deletions
+7 -3
View File
@@ -480,7 +480,9 @@ async fn download_file(
return DownloadOutcome::Paused; return DownloadOutcome::Paused;
} }
Err(AttemptError::Controlled(DownloadControl::Cancel)) => { 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; return DownloadOutcome::Cancelled;
} }
Err(AttemptError::Controlled(DownloadControl::Replace)) => { Err(AttemptError::Controlled(DownloadControl::Replace)) => {
@@ -508,7 +510,9 @@ async fn download_file(
return match control.unwrap_or(DownloadControl::Cancel) { return match control.unwrap_or(DownloadControl::Cancel) {
DownloadControl::Pause => DownloadOutcome::Paused, DownloadControl::Pause => DownloadOutcome::Paused,
DownloadControl::Cancel => { 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 DownloadOutcome::Cancelled
} }
DownloadControl::Replace => DownloadOutcome::Cancelled, DownloadControl::Replace => DownloadOutcome::Cancelled,
@@ -717,7 +721,7 @@ fn build_client(payload: &DownloadPayload) -> Result<(Client, HeaderMap), String
if proxy == "none" { if proxy == "none" {
builder = builder.no_proxy(); builder = builder.no_proxy();
} else { } 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())?);
} }
} }
+15 -4
View File
@@ -1374,12 +1374,16 @@ pub(crate) fn resolve_path(path: &str, app_handle: &tauri::AppHandle) -> std::pa
.strip_prefix("~/") .strip_prefix("~/")
.or_else(|| path.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); resolved = home.join(stripped);
} else {
log::warn!("Failed to resolve home directory for ~ expansion");
} }
} else if path == "~" { } 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; resolved = home;
} else {
log::warn!("Failed to resolve home directory for ~ expansion");
} }
} }
resolved resolved
@@ -1655,13 +1659,18 @@ fn version_check_cache(
} }
fn version_cache_key(binary_path: &std::path::Path, args: &[&str]) -> String { 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) let modified = std::fs::metadata(binary_path)
.and_then(|metadata| metadata.modified()) .and_then(|metadata| metadata.modified())
.ok() .ok()
.and_then(|modified| modified.duration_since(std::time::UNIX_EPOCH).ok()) .and_then(|modified| modified.duration_since(std::time::UNIX_EPOCH).ok())
.map(|duration| duration.as_nanos()) .map(|duration| duration.as_nanos())
.unwrap_or_default(); .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> { 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"] { 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) { if candidate.exists() && is_safe_path(&candidate, app_handle) {
tokio::fs::remove_file(&candidate) tokio::fs::remove_file(&candidate)
.await .await
+3 -1
View File
@@ -422,7 +422,9 @@ export const AddDownloadsModal = () => {
fileExistsOnDisk = await invoke('check_file_exists', { fileExistsOnDisk = await invoke('check_file_exists', {
path: await resolveDownloadFilePath(itemLocation, finalFile) path: await resolveDownloadFilePath(itemLocation, finalFile)
}); });
} catch (e) {} } catch (e) {
console.error("Failed to check if file exists on disk:", e);
}
if (fileExistsInStore || fileExistsOnDisk) { if (fileExistsInStore || fileExistsOnDisk) {
newConflicts.push({ newConflicts.push({
+5 -4
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useDownloadStore, DownloadItem } from '../store/useDownloadStore'; import { useDownloadStore, DownloadItem } from '../store/useDownloadStore';
import { useDownloadProgressStore } from '../store/downloadStore'; import { useDownloadProgressStore } from '../store/downloadStore';
import { useShallow } from 'zustand/react/shallow';
import { useSettingsStore } from '../store/useSettingsStore'; import { useSettingsStore } from '../store/useSettingsStore';
import { ChevronDown, ChevronRight, FolderPlus, Info, CheckCircle, AlertCircle, Play, Pause } from 'lucide-react'; import { ChevronDown, ChevronRight, FolderPlus, Info, CheckCircle, AlertCircle, Play, Pause } from 'lucide-react';
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
@@ -15,16 +16,16 @@ type LoginMode = 'matching' | 'custom' | 'none';
export const PropertiesModal = () => { export const PropertiesModal = () => {
const selectedPropertiesDownloadId = useDownloadStore(state => state.selectedPropertiesDownloadId); const selectedPropertiesDownloadId = useDownloadStore(state => state.selectedPropertiesDownloadId);
const setSelectedPropertiesDownloadId = useDownloadStore(state => state.setSelectedPropertiesDownloadId); const setSelectedPropertiesDownloadId = useDownloadStore(state => state.setSelectedPropertiesDownloadId);
const item = useDownloadStore(state => const item = useDownloadStore(useShallow(state =>
selectedPropertiesDownloadId selectedPropertiesDownloadId
? state.downloads.find(d => d.id === selectedPropertiesDownloadId) ?? null ? state.downloads.find(d => d.id === selectedPropertiesDownloadId) ?? null
: null : null
); ));
const liveProgress = useDownloadProgressStore(state => const liveProgress = useDownloadProgressStore(useShallow(state =>
selectedPropertiesDownloadId selectedPropertiesDownloadId
? state.progressMap[selectedPropertiesDownloadId] ? state.progressMap[selectedPropertiesDownloadId]
: undefined : undefined
); ));
const { baseDownloadFolder, perServerConnections } = useSettingsStore(); const { baseDownloadFolder, perServerConnections } = useSettingsStore();
+2 -1
View File
@@ -3,6 +3,7 @@ import type { UnlistenFn } from '@tauri-apps/api/event';
import type { DownloadProgressEvent } from '../bindings/DownloadProgressEvent'; import type { DownloadProgressEvent } from '../bindings/DownloadProgressEvent';
import type { DownloadStatus } from '../bindings/DownloadStatus'; import type { DownloadStatus } from '../bindings/DownloadStatus';
import { listenEvent as listen } from '../ipc'; import { listenEvent as listen } from '../ipc';
import type { DownloadItem } from '../bindings/DownloadItem';
interface DownloadProgressState { interface DownloadProgressState {
progressMap: Record<string, DownloadProgressEvent>; progressMap: Record<string, DownloadProgressEvent>;
@@ -58,7 +59,7 @@ export async function initDownloadListener() {
if (current) { if (current) {
const status = payload.status as DownloadStatus; const status = payload.status as DownloadStatus;
const progress = useDownloadProgressStore.getState().progressMap[payload.id]; const progress = useDownloadProgressStore.getState().progressMap[payload.id];
const updates: Partial<any> = { const updates: Partial<DownloadItem> = {
status, status,
...(progress ? { fraction: progress.fraction } : {}) ...(progress ? { fraction: progress.fraction } : {})
}; };
+47 -16
View File
@@ -35,7 +35,9 @@ export async function dispatchItem(id: string): Promise<boolean> {
if (login) { if (login) {
try { try {
keychainPassword = await invoke('get_keychain_password', { id: login.id }); 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 = { const enqueueItem = {
@@ -813,18 +815,51 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
})); }));
let lastSavedDownloads = ''; let lastSavedDownloads = '';
let downloadsSave = Promise.resolve(); let isSavingDownloads = false;
let queuesSave = Promise.resolve(); 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) { if (state.queues !== prevState.queues) {
const data = JSON.stringify(state.queues); const data = JSON.stringify(state.queues);
queuesSave = queuesSave if (data !== lastSavedQueues) {
.then(() => invoke('db_replace_queues', { data })) lastSavedQueues = data;
.catch(error => { nextQueuesData = data;
console.error('Failed to persist queues:', error); processQueuesSave();
}); }
await queuesSave;
} }
if (state.downloads !== prevState.downloads) { if (state.downloads !== prevState.downloads) {
@@ -836,12 +871,8 @@ useDownloadStore.subscribe(async (state, prevState) => {
const currentSerialized = JSON.stringify(staticDownloads); const currentSerialized = JSON.stringify(staticDownloads);
if (currentSerialized !== lastSavedDownloads) { if (currentSerialized !== lastSavedDownloads) {
lastSavedDownloads = currentSerialized; lastSavedDownloads = currentSerialized;
downloadsSave = downloadsSave nextDownloadsData = currentSerialized;
.then(() => invoke('db_replace_downloads', { data: currentSerialized })) processDownloadsSave();
.catch(error => {
console.error('Failed to persist downloads:', error);
});
await downloadsSave;
} }
} }
}); });