This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 3894961 Allow creation of a new directory as a file movement
destination
3894961 is described below
commit 3894961cf1d707d8ecbb2972ae6d7a361f191c22
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue May 27 19:48:23 2025 +0100
Allow creation of a new directory as a file movement destination
---
atr/routes/finish.py | 26 +++++++++++++++++++++-----
atr/static/js/finish-selected-move.js | 23 ++++++++++++++++++++++-
atr/static/js/finish-selected-move.js.map | 2 +-
atr/static/ts/finish-selected-move.ts | 25 ++++++++++++++++++++++++-
4 files changed, 68 insertions(+), 8 deletions(-)
diff --git a/atr/routes/finish.py b/atr/routes/finish.py
index 8dda9c5..6475ff8 100644
--- a/atr/routes/finish.py
+++ b/atr/routes/finish.py
@@ -66,7 +66,7 @@ class MoveFileForm(util.QuartFormTyped):
validators=[wtforms.validators.DataRequired(message="Please select at
least one file to move.")],
)
target_directory = wtforms.SelectField(
- "Target directory", choices=[],
validators=[wtforms.validators.DataRequired()]
+ "Target directory", choices=[],
validators=[wtforms.validators.DataRequired()], validate_choice=False
)
submit = wtforms.SubmitField("Move file")
@@ -415,6 +415,24 @@ async def _setup_revision(
moved_files_names: list[str],
skipped_files_names: list[str],
) -> None:
+ target_path = creating.interim_path / target_dir_rel
+ try:
+ target_path.resolve().relative_to(creating.interim_path.resolve())
+ except ValueError:
+ # Path traversal detected
+ creating.failed = True
+ return
+
+ if not await aiofiles.os.path.exists(target_path):
+ try:
+ await aiofiles.os.makedirs(target_path)
+ except OSError:
+ creating.failed = True
+ return
+ elif not await aiofiles.os.path.isdir(target_path):
+ creating.failed = True
+ return
+
for source_file_rel in source_files_rel:
if source_file_rel.parent == target_dir_rel:
skipped_files_names.append(source_file_rel.name)
@@ -422,15 +440,13 @@ async def _setup_revision(
related_files = _related_files(source_file_rel)
bundle = [f for f in related_files if await
aiofiles.os.path.exists(creating.interim_path / f)]
- collisions = [
- f.name for f in bundle if await
aiofiles.os.path.exists(creating.interim_path / target_dir_rel / f.name)
- ]
+ collisions = [f.name for f in bundle if await
aiofiles.os.path.exists(target_path / f.name)]
if collisions:
creating.failed = True
return
for f in bundle:
- await aiofiles.os.rename(creating.interim_path / f,
creating.interim_path / target_dir_rel / f.name)
+ await aiofiles.os.rename(creating.interim_path / f, target_path /
f.name)
if f == source_file_rel:
moved_files_names.append(f.name)
diff --git a/atr/static/js/finish-selected-move.js
b/atr/static/js/finish-selected-move.js
index c35a6ad..8ccf4c1 100644
--- a/atr/static/js/finish-selected-move.js
+++ b/atr/static/js/finish-selected-move.js
@@ -55,6 +55,9 @@ function includesCaseInsensitive(haystack, needle) {
return false;
return toLower(haystack).includes(toLower(needle));
}
+function isValidNewDirName(d) {
+ return d.length > 0 && !d.includes("..") && !d.startsWith("/") &&
!d.endsWith("/");
+}
function getParentPath(filePathString) {
if (!filePathString || typeof filePathString !== "string")
return ".";
@@ -110,6 +113,12 @@ function updateMoveSelectionInfo() {
const strongDest = document.createElement("strong");
strongDest.textContent = destinationDir;
currentMoveSelectionInfoElement.appendChild(strongDest);
+ if (destinationDir && uiState.allTargetDirs.indexOf(destinationDir)
=== -1 && isValidNewDirName(destinationDir)) {
+ const newDirSpan = document.createElement("span");
+ newDirSpan.textContent = " (will be created)";
+ newDirSpan.className = "text-muted small";
+ currentMoveSelectionInfoElement.appendChild(newDirSpan);
+ }
message = "";
disableConfirmButton = false;
}
@@ -164,6 +173,12 @@ function renderListItems(tbodyElement, items, config) {
else {
row.setAttribute("aria-selected", "false");
}
+ if (itemPathString === uiState.filters.dir.trim() &&
uiState.allTargetDirs.indexOf(itemPathString) === -1 &&
isValidNewDirName(itemPathString)) {
+ const newDirSpan = document.createElement("span");
+ newDirSpan.textContent = " (new directory)";
+ newDirSpan.className = "text-muted small";
+ span.appendChild(newDirSpan);
+ }
controlCell.appendChild(radio);
break;
}
@@ -192,7 +207,13 @@ function renderAllLists() {
moreInfoId: ID.fileListMoreInfo
};
renderListItems(fileListTableBody, filteredFilePaths, filesConfig);
- const filteredDirs = uiState.allTargetDirs.filter(dirP =>
includesCaseInsensitive(dirP, uiState.filters.dir));
+ const displayDirs = [...uiState.allTargetDirs];
+ const trimmedDirFilter = uiState.filters.dir.trim();
+ if (isValidNewDirName(trimmedDirFilter) &&
uiState.allTargetDirs.indexOf(trimmedDirFilter) === -1) {
+ displayDirs.push(trimmedDirFilter);
+ displayDirs.sort();
+ }
+ const filteredDirs = displayDirs.filter(dirP =>
includesCaseInsensitive(dirP, uiState.filters.dir));
const dirsConfig = {
itemType: ItemType.Dir,
selectedItem: uiState.currentlyChosenDirectoryPath,
diff --git a/atr/static/js/finish-selected-move.js.map
b/atr/static/js/finish-selected-move.js.map
index 2887f6f..8f6d5e9 100644
--- a/atr/static/js/finish-selected-move.js.map
+++ b/atr/static/js/finish-selected-move.js.map
@@ -1 +1 @@
-{"version":3,"file":"finish-selected-move.js","sourceRoot":"","sources":["../ts/finish-selected-move.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;AAEb,IAAK,QAGJ;AAHD,WAAK,QAAQ;IACT,yBAAa,CAAA;IACb,uBAAW,CAAA;AACf,CAAC,EAHI,QAAQ,KAAR,QAAQ,QAGZ;AAED,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACrB,iBAAiB,EAAE,qBAAqB;IACxC,wBAAwB,EAAE,6BAA6B;IACvD,OAAO,EAAE,UAAU;IACnB,cAAc,EAAE,kBAAkB;IAClC,eAAe,EAAE,oBAAoB;IACrC,gBAAgB,EAAE,qBAAqB;IACvC,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,WAAW;IACrB,UAAU,EAAE,
[...]
+{"version":3,"file":"finish-selected-move.js","sourceRoot":"","sources":["../ts/finish-selected-move.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;AAEb,IAAK,QAGJ;AAHD,WAAK,QAAQ;IACT,yBAAa,CAAA;IACb,uBAAW,CAAA;AACf,CAAC,EAHI,QAAQ,KAAR,QAAQ,QAGZ;AAED,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACrB,iBAAiB,EAAE,qBAAqB;IACxC,wBAAwB,EAAE,6BAA6B;IACvD,OAAO,EAAE,UAAU;IACnB,cAAc,EAAE,kBAAkB;IAClC,eAAe,EAAE,oBAAoB;IACrC,gBAAgB,EAAE,qBAAqB;IACvC,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,WAAW;IACrB,UAAU,EAAE,
[...]
diff --git a/atr/static/ts/finish-selected-move.ts
b/atr/static/ts/finish-selected-move.ts
index c5089d7..5d81587 100644
--- a/atr/static/ts/finish-selected-move.ts
+++ b/atr/static/ts/finish-selected-move.ts
@@ -70,6 +70,10 @@ function includesCaseInsensitive(haystack: string | null |
undefined, needle: st
return toLower(haystack).includes(toLower(needle));
}
+function isValidNewDirName(d: string): boolean {
+ return d.length > 0 && !d.includes("..") && !d.startsWith("/") &&
!d.endsWith("/");
+}
+
function getParentPath(filePathString: string | null | undefined): string {
if (!filePathString || typeof filePathString !== "string") return ".";
const lastSlash = filePathString.lastIndexOf("/");
@@ -126,6 +130,12 @@ function updateMoveSelectionInfo(): void {
const strongDest = document.createElement("strong");
strongDest.textContent = destinationDir;
currentMoveSelectionInfoElement.appendChild(strongDest);
+ if (destinationDir && uiState.allTargetDirs.indexOf(destinationDir)
=== -1 && isValidNewDirName(destinationDir)) {
+ const newDirSpan = document.createElement("span");
+ newDirSpan.textContent = " (will be created)";
+ newDirSpan.className = "text-muted small";
+ currentMoveSelectionInfoElement.appendChild(newDirSpan);
+ }
message = "";
disableConfirmButton = false;
}
@@ -191,6 +201,12 @@ function renderListItems(
} else {
row.setAttribute("aria-selected", "false");
}
+ if (itemPathString === uiState.filters.dir.trim() &&
uiState.allTargetDirs.indexOf(itemPathString) === -1 &&
isValidNewDirName(itemPathString)){
+ const newDirSpan = document.createElement("span");
+ newDirSpan.textContent = " (new directory)";
+ newDirSpan.className = "text-muted small";
+ span.appendChild(newDirSpan);
+ }
controlCell.appendChild(radio);
break;
}
@@ -225,7 +241,14 @@ function renderAllLists(): void {
};
renderListItems(fileListTableBody, filteredFilePaths, filesConfig);
- const filteredDirs = uiState.allTargetDirs.filter(dirP =>
+ const displayDirs = [...uiState.allTargetDirs];
+ const trimmedDirFilter = uiState.filters.dir.trim();
+ if (isValidNewDirName(trimmedDirFilter) &&
uiState.allTargetDirs.indexOf(trimmedDirFilter) === -1) {
+ displayDirs.push(trimmedDirFilter);
+ displayDirs.sort();
+ }
+
+ const filteredDirs = displayDirs.filter(dirP =>
includesCaseInsensitive(dirP, uiState.filters.dir)
);
const dirsConfig: RenderListDisplayConfig = {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]