fix: normalize recursive listing paths to forward slashes (#6003)

This commit is contained in:
Henrique Dias 2026-06-27 08:01:26 +02:00 committed by GitHub
parent 43a404ca69
commit 2472fbcd30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 4 deletions

View file

@ -220,4 +220,52 @@ describe("checkConflict", () => {
expect(conflicts).toHaveLength(0);
});
// Regression for #5980: a FileBrowser server running on Windows returns
// backslash-separated paths from the recursive listing. Without normalizing
// them, the prefix strip and key lookup never match, so the conflict modal is
// skipped and the backend returns a bare 409.
it("detects a conflict for backslash-separated server paths (Windows)", async () => {
vi.mocked(api.fetchAll).mockResolvedValue([
{
path: "\\target\\file.txt",
name: "file.txt",
size: 10,
modified: "2026-06-04T00:00:00Z",
isDir: false,
},
]);
const conflicts = await checkConflict(
[moveItem("file.txt", "/files/target/")],
"/files/target/"
);
expect(conflicts).toHaveLength(1);
});
it("detects nested conflicts for backslash-separated server paths (Windows)", async () => {
vi.mocked(api.fetchAll).mockResolvedValue([
{
path: "\\target\\folder\\nested file.txt",
name: "nested file.txt",
size: 10,
modified: "2026-06-04T00:00:00Z",
isDir: false,
},
]);
const files = [
{
name: "nested file.txt",
size: 12,
isDir: false,
fullPath: "folder/nested file.txt",
},
];
const conflicts = await checkConflict(files, "/files/target/");
expect(conflicts).toHaveLength(1);
});
});

View file

@ -62,9 +62,10 @@ export async function checkConflict(
const normBase = removePrefix(basePath).replace(/\/+$/, "");
const serverMap = new Map<string, RecursiveEntry>();
for (const entry of serverEntries) {
const rel = entry.path.startsWith(normBase)
? entry.path.slice(normBase.length)
: entry.path;
// A Windows server may return OS-native backslash separators; normalize to
// forward slashes so the prefix strip and key lookup line up.
const path = entry.path.replace(/\\/g, "/");
const rel = path.startsWith(normBase) ? path.slice(normBase.length) : path;
serverMap.set(rel.replace(/^\/+/, ""), entry);
}

View file

@ -424,7 +424,10 @@ var resourceGetRecursiveHandler = withUser(func(w http.ResponseWriter, r *http.R
}
entries = append(entries, RecursiveEntry{
Path: fPath,
// afero.Walk joins paths with the OS separator, so on Windows fPath
// uses backslashes. The web API contract is forward slashes, so
// normalize it here (mirrors search/search.go).
Path: filepath.ToSlash(fPath),
Name: info.Name(),
Size: info.Size(),
ModTime: info.ModTime(),