From 1601be41da156ff19922ce5622989d8ed674550d Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:26:54 +0530 Subject: [PATCH] fix: prevent folder paste loops and improve SAF handling - Add validation to prevent pasting folders into themselves or subdirectories - Implement special Termux SAF handling for cut operations with recursive file/folder moving - Fix createFileStructure to handle nested paths (foo/bar) for file:// URIs --- src/lib/openFolder.js | 64 +++++++++++++++++++++++++++++++++++++++++-- src/utils/helpers.js | 43 +++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/lib/openFolder.js b/src/lib/openFolder.js index bbbcb99db..dd769d25a 100644 --- a/src/lib/openFolder.js +++ b/src/lib/openFolder.js @@ -532,6 +532,27 @@ function execOperation(type, action, url, $target, name) { return; } + // Prevent pasting a folder into itself or its subdirectories + if (helpers.isDir(clipBoard.$el.dataset.type)) { + const sourceUrl = Url.parse(clipBoard.url).url; + const targetUrl = Url.parse(url).url; + + // Check if trying to paste folder into itself + if (sourceUrl === targetUrl) { + alert(strings.warning, "Cannot paste a folder into itself"); + return; + } + + // Check if trying to paste folder into one of its subdirectories + if ( + targetUrl.startsWith(sourceUrl + "/") || + targetUrl.startsWith(sourceUrl + "\\") + ) { + alert(strings.warning, "Cannot paste a folder into its subdirectory"); + return; + } + } + let CASE = ""; const $src = clipBoard.$el; const srcType = $src.dataset.type; @@ -559,8 +580,47 @@ function execOperation(type, action, url, $target, name) { if (!confirmation) return; } let newUrl; - if (clipBoard.action === "cut") newUrl = await fs.moveTo(url); - else newUrl = await fs.copyTo(url); + if (clipBoard.action === "cut") { + // Special handling for Termux SAF folders - move manually due to SAF limitations + if ( + clipBoard.url.startsWith("content://com.termux.documents/tree/") && + IS_DIR + ) { + const moveRecursively = async (sourceUrl, targetParentUrl) => { + const sourceFs = fsOperation(sourceUrl); + const sourceName = Url.basename(sourceUrl); + const targetUrl = Url.join(targetParentUrl, sourceName); + + // Create target folder + await fsOperation(targetParentUrl).createDirectory(sourceName); + + // Get all entries in source folder + const entries = await sourceFs.lsDir(); + + // Move all files and folders recursively + for (const entry of entries) { + if (entry.isDirectory) { + await moveRecursively(entry.url, targetUrl); + } else { + const fileContent = await fsOperation(entry.url).readFile(); + const fileName = entry.name || Url.basename(entry.url); + await fsOperation(targetUrl).createFile(fileName, fileContent); + await fsOperation(entry.url).delete(); + } + } + + // Delete the now-empty source folder + await sourceFs.delete(); + return targetUrl; + }; + + newUrl = await moveRecursively(clipBoard.url, url); + } else { + newUrl = await fs.moveTo(url); + } + } else { + newUrl = await fs.copyTo(url); + } const { name: newName } = await fsOperation(newUrl).stat(); stopLoading(); /** diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 9eed0489a..9a2edcab1 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -396,12 +396,45 @@ export default { currentUri.includes("com.termux.documents") ) ) { - if (isFile) { - uri = await fsOperation(uri).createFile(pathString); - } else { - uri = await fsOperation(uri).createDirectory(pathString); + // Handle nested paths for regular file:// URIs + const pathParts = pathString.split("/").filter(Boolean); + let currentPath = uri; + let firstCreatedPath = null; + let firstCreatedType = null; + + for (let i = 0; i < pathParts.length; i++) { + const isLastPart = i === pathParts.length - 1; + const partName = pathParts[i]; + const newPath = Url.join(currentPath, partName); + + if (isLastPart && isFile) { + // Create file if it's the last part and we're creating a file + if (!(await fsOperation(newPath).exists())) { + await fsOperation(currentPath).createFile(partName); + if (firstCreatedPath === null) { + firstCreatedPath = newPath; + firstCreatedType = "file"; + } + } + } else { + // Create directory for intermediate parts or when creating a folder + if (!(await fsOperation(newPath).exists())) { + await fsOperation(currentPath).createDirectory(partName); + if (firstCreatedPath === null) { + firstCreatedPath = newPath; + firstCreatedType = "folder"; + } + } + } + currentPath = newPath; } - return { uri: uri, type: isFile ? "file" : "folder" }; + + return { + uri: firstCreatedPath || Url.join(uri, pathParts[0]), + type: + firstCreatedType || + (isFile && pathParts.length === 1 ? "file" : "folder"), + }; } for (let i = 0; i < parts.length; i++) {