From 2ae22f942ec342b3b83d10c8bc8f32bacd94fc09 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Mon, 2 Mar 2026 20:24:55 +0100 Subject: [PATCH] Fix extraction failure on long paths (>260 chars) with \?\ prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add longPathForWindows() helper that prefixes extract target directories with \?\ on Windows, bypassing the 260-char MAX_PATH limit. Applied to both WinRAR/UnRAR and 7z arguments. Fixes "Die Syntax für den Dateinamen, Verzeichnisnamen" errors when archive internal directories create deeply nested output paths. Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/extractor.ts | 20 ++++++++++++++++++-- tests/extractor.test.ts | 8 +++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 05cd038..cdfec94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.15", + "version": "1.5.16", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 1ef5f7f..dc12963 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -8,6 +8,21 @@ import { logger } from "./logger"; import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; const DEFAULT_ARCHIVE_PASSWORDS = ["", "serienfans.org", "serienjunkies.org"]; + +/** + * On Windows, prefix an absolute path with \\?\ to bypass the 260-char MAX_PATH limit. + * WinRAR 5.x+ and 7-Zip support this prefix for long output paths. + */ +function longPathForWindows(p: string): string { + if (process.platform !== "win32") return p; + const resolved = path.resolve(p); + if (resolved.startsWith("\\\\?\\")) return resolved; + if (resolved.startsWith("\\\\")) { + // UNC path \\server\share -> \\?\UNC\server\share + return "\\\\?\\UNC\\" + resolved.slice(2); + } + return "\\\\?\\" + resolved; +} const NO_EXTRACTOR_MESSAGE = "WinRAR/UnRAR nicht gefunden. Bitte WinRAR installieren."; let resolvedExtractorCommand: string | null = null; @@ -606,13 +621,14 @@ export function buildExternalExtractArgs( const perfArgs = usePerformanceFlags && shouldUseExtractorPerformanceFlags() ? ["-idc", extractorThreadSwitch()] : []; - return ["x", overwrite, pass, "-y", ...perfArgs, archivePath, `${targetDir}${path.sep}`]; + const longTarget = longPathForWindows(targetDir); + return ["x", overwrite, pass, "-y", ...perfArgs, archivePath, `${longTarget}${path.sep}`]; } const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos"; // NOTE: Same password-in-args limitation as above applies to 7z as well. const pass = password ? `-p${password}` : "-p"; - return ["x", "-y", overwrite, pass, archivePath, `-o${targetDir}`]; + return ["x", "-y", overwrite, pass, archivePath, `-o${longPathForWindows(targetDir)}`]; } async function resolveExtractorCommandInternal(): Promise { diff --git a/tests/extractor.test.ts b/tests/extractor.test.ts index 662d6fd..498c23f 100644 --- a/tests/extractor.test.ts +++ b/tests/extractor.test.ts @@ -20,14 +20,15 @@ describe("extractor", () => { expect(overwriteArgs).toContain("-idc"); expect(overwriteArgs.some((value) => /^-mt\d+$/i.test(value))).toBe(true); expect(overwriteArgs[overwriteArgs.length - 2]).toBe("archive.rar"); - expect(overwriteArgs[overwriteArgs.length - 1]).toBe("C:\\target\\"); + const winrarTarget = process.platform === "win32" ? "\\\\?\\C:\\target\\" : "C:\\target\\"; + expect(overwriteArgs[overwriteArgs.length - 1]).toBe(winrarTarget); const askArgs = buildExternalExtractArgs("WinRAR.exe", "archive.rar", "C:\\target", "ask", "serienfans.org"); expect(askArgs.slice(0, 4)).toEqual(["x", "-o-", "-pserienfans.org", "-y"]); expect(askArgs).toContain("-idc"); expect(askArgs.some((value) => /^-mt\d+$/i.test(value))).toBe(true); expect(askArgs[askArgs.length - 2]).toBe("archive.rar"); - expect(askArgs[askArgs.length - 1]).toBe("C:\\target\\"); + expect(askArgs[askArgs.length - 1]).toBe(winrarTarget); const compatibilityArgs = buildExternalExtractArgs("WinRAR.exe", "archive.rar", "C:\\target", "overwrite", "", false); expect(compatibilityArgs).not.toContain("-idc"); @@ -455,7 +456,8 @@ describe("extractor", () => { expect(args7z).toContain("-aoa"); expect(args7z).toContain("-p"); expect(args7z).toContain("archive.7z"); - expect(args7z).toContain("-oC:\\target"); + const sevenZTarget = process.platform === "win32" ? "-o\\\\?\\C:\\target" : "-oC:\\target"; + expect(args7z).toContain(sevenZTarget); }); it("builds 7z args with skip conflict mode", () => {