Apply all CODE-AUDIT.md findings (S1-S5, P1-P3, M1-M3)
Security: - S1 (backup.ts): require explicit confirmation before enabling custom-command templates from a backup file; import templates in a disabled state if declined - S2 (cookies.ts): deny non-http/https popups in cookie login window, matching the main window's setWindowOpenHandler defence - S3 (download.ts): main-process concurrency cap — startDownload now rejects spawns when active.size >= maxConcurrent (defence-in-depth; renderer already enforces this but should not be the only gate) - S4 (settings.ts, validation.ts): reject filenameTemplate values containing path traversal (.. segments or absolute paths); validate outputDir is absolute - S5 (history.ts, errorlog.ts, templates.ts, validation.ts): per-field validation on JSON reads — invalid entries dropped rather than blindly trusted Performance: - P1 (settings.ts): getSettings() now only writes to disk when sanitization actually changed a value; removes synchronous file I/O on every hot-path read - P2 (ipc.ts, download.ts, downloads.ts): renderer forwards its pre-probed metadata (title/channel/duration) via StartDownloadOptions.meta; main uses it directly instead of spawning a redundant second yt-dlp probe Maintainability: - M1 (log.ts, download.ts, probe.ts): extract duplicated cleanError() to src/main/log.ts; both callers import from the shared module - M2 (buildArgs.ts): document parseExtraArgs escape-sequence limitations - M3 (electron-builder.yml): clarify icon TODO as a pre-release action - P3 (downloads.ts): document that lowering maxConcurrent mid-flight does not pause active downloads (intended behaviour, now written down) Tests: - Add test/validation.test.ts covering isSafeFilenameTemplate, isSafeOutputDir, isValidHistoryEntry, isValidErrorLogEntry, isTemplateLike (76 tests pass) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand'
|
||||
import type { DownloadEvent, DownloadOptions } from '@shared/ipc'
|
||||
import type { DownloadEvent, DownloadMeta, DownloadOptions } from '@shared/ipc'
|
||||
import { useSettings } from './settings'
|
||||
import { useHistory } from './history'
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface DownloadItem {
|
||||
extraArgs?: string
|
||||
/** private download — completion is never recorded to history */
|
||||
incognito?: boolean
|
||||
/** metadata already fetched at probe time; forwarded to main so it skips a redundant probe */
|
||||
probedMeta?: DownloadMeta
|
||||
}
|
||||
|
||||
export const QUALITY_OPTIONS: Record<MediaKind, string[]> = {
|
||||
@@ -237,7 +239,8 @@ export const useDownloads = create<DownloadState>((set, get) => {
|
||||
formatId: item.formatId,
|
||||
formatHasAudio: item.formatHasAudio,
|
||||
options: item.options,
|
||||
extraArgs: item.extraArgs
|
||||
extraArgs: item.extraArgs,
|
||||
meta: item.probedMeta
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) markError(item.id, res.error)
|
||||
@@ -246,6 +249,11 @@ export const useDownloads = create<DownloadState>((set, get) => {
|
||||
}
|
||||
|
||||
// Promote queued items (oldest first) into free slots, then launch them.
|
||||
//
|
||||
// Note: this only gates *future* promotions. Lowering maxConcurrent while N
|
||||
// downloads are already active does NOT pause the overflow — the running
|
||||
// processes finish on their own, and the lower cap takes effect only as slots
|
||||
// free up. (audit P3 — documented behaviour, not a bug.)
|
||||
function pump(): void {
|
||||
const items = get().items
|
||||
const running = items.filter((i) => i.status === 'downloading').length
|
||||
@@ -270,6 +278,13 @@ export const useDownloads = create<DownloadState>((set, get) => {
|
||||
|
||||
addFromUrl: (url, kind, quality, opts) => {
|
||||
const id = newId()
|
||||
// If the caller already probed the URL, carry that metadata so main can skip
|
||||
// its own redundant probe (audit P2). Only set it when something real was
|
||||
// provided — a bare paste with no probe leaves it undefined.
|
||||
const probedMeta: DownloadMeta | undefined =
|
||||
opts?.title || opts?.channel || opts?.durationLabel
|
||||
? { title: opts?.title, channel: opts?.channel, durationLabel: opts?.durationLabel }
|
||||
: undefined
|
||||
const item: DownloadItem = {
|
||||
id,
|
||||
url,
|
||||
@@ -285,7 +300,8 @@ export const useDownloads = create<DownloadState>((set, get) => {
|
||||
formatHasAudio: opts?.format?.hasAudio,
|
||||
options: opts?.options,
|
||||
extraArgs: opts?.extraArgs,
|
||||
incognito: opts?.incognito
|
||||
incognito: opts?.incognito,
|
||||
probedMeta
|
||||
}
|
||||
set((s) => ({ items: [item, ...s.items] }))
|
||||
pump()
|
||||
|
||||
Reference in New Issue
Block a user