From 9a1c7b2ecdb6f2eb8ef055ccfb74557662a55236 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 23 Aug 2022 18:35:41 +0200 Subject: [PATCH] Bad Windows names also apply with any extension A file name con.zip is also treated in the same way as the special CON redirection and may not be used as a normal filesystem path in Windows. This includes any extension, even with multiple dots. Fix checkFilename and cleaneFilename not handling these cases. --- packages/lib/file_ops.js | 50 ++++++++++++++++++++-------------------- test/lib/file_ops.js | 12 ++++++++-- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/lib/file_ops.js b/packages/lib/file_ops.js index cbed8df5..be007912 100644 --- a/packages/lib/file_ops.js +++ b/packages/lib/file_ops.js @@ -108,6 +108,18 @@ async function safeOutputFile(file, data, options={}) { await fs.rename(temporary, file); } + +// Reserved names by allmost all filesystems +const badNames = [".", ".."]; + +// Reserved namespaces in Windows +const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9]; +const badWinNamespaces = [ + "CON", "PRN", "AUX", "NUL", + ...oneToNine.map(n => `COM${n}`), + ...oneToNine.map(n => `LPT${n}`), +]; + /** * Check if a string is a valid file name * @@ -120,17 +132,6 @@ function checkFilename(name) { const badChars = /[<>:"\/\\|?*\x00-\x1f]/g; const badEnd = /[. ]$/; - const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - const badNames = [ - // Relative path components - ".", "..", - - // Reserved filenames in Windows - "CON", "PRN", "AUX", "NUL", - ...oneToNine.map(n => `COM${n}`), - ...oneToNine.map(n => `LPT${n}`), - ]; - if (typeof name !== "string") { throw new Error("must be a string"); } @@ -143,9 +144,15 @@ function checkFilename(name) { throw new Error('cannot contain <>:"\\/|=* or control characters'); } - if (badNames.includes(name.toUpperCase())) { + if (badNames.includes(name)) { throw new Error( - "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9" + `cannot be named ${name}` + ); + } + + if (badWinNamespaces.includes(name.toUpperCase().split(".")[0])) { + throw new Error( + "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9" ); } @@ -161,21 +168,10 @@ function checkFilename(name) { * @returns {string} Filename suitable to use in the filesystem */ function cleanFilename(name) { - // copied from checkFilename + // copied from checkFilename due to RegExp with global flag containing state. const badChars = /[<>:"\/\\|?*\x00-\x1f]/g; const badEnd = /[. ]$/; - const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - const badNames = [ - // Relative path components - ".", "..", - - // Reserved filenames in Windows - "CON", "PRN", "AUX", "NUL", - ...oneToNine.map(n => `COM${n}`), - ...oneToNine.map(n => `LPT${n}`), - ]; - if (typeof name !== "string") { throw new Error("name must be a string"); } @@ -184,6 +180,10 @@ function cleanFilename(name) { name += "_"; } + if (badWinNamespaces.includes(name.toUpperCase().split(".")[0])) { + name = [`${name.split(".")[0]}_`, ...name.split(".").slice(1)].join("."); + } + name = name.replace(badChars, "_"); name = name.replace(badEnd, "_"); diff --git a/test/lib/file_ops.js b/test/lib/file_ops.js index 416c90a0..da256995 100644 --- a/test/lib/file_ops.js +++ b/test/lib/file_ops.js @@ -115,13 +115,17 @@ describe("lib/file_ops", function() { it("should throw on CON, PRN, AUX, NUL, COM1, LPT1", function() { for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) { - check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"); + check(bad, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9"); + check(`${bad}.zip`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9"); + check(`${bad}.anything.txt`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9"); + check(`${bad}....a`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9"); + check(`${bad}.`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9"); } }); it("should throw on . and ..", function() { for (let bad of [".", ".."]) { - check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"); + check(bad, `cannot be named ${bad}`); } }); @@ -166,6 +170,10 @@ describe("lib/file_ops", function() { it("should clean CON, PRN, AUX, NUL, COM1, LPT1", function() { for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) { clean(bad, `${bad}_`); + clean(`${bad}.zip`, `${bad}_.zip`); + clean(`${bad}.anything.txt`, `${bad}_.anything.txt`); + clean(`${bad}....a`, `${bad}_....a`); + clean(`${bad}.`, `${bad}__`); } });