mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-04 15:05:22 +00:00
fix: resolve low severity vulnerabilities from deepseek audit
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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 } : {})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user