mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15:04:52 +09:00
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.
193 lines
6.4 KiB
JavaScript
193 lines
6.4 KiB
JavaScript
"use strict";
|
|
const assert = require("assert").strict;
|
|
const fs = require("fs-extra");
|
|
const path = require("path");
|
|
|
|
const libFileOps = require("@clusterio/lib/file_ops");
|
|
|
|
describe("lib/file_ops", function() {
|
|
let baseDir = path.join("temp", "test", "file_ops");
|
|
async function setupTestingEnv() {
|
|
await fs.ensureDir(path.join(baseDir, "test", "folder"));
|
|
await fs.ensureDir(path.join(baseDir, "test", "another folder"));
|
|
await fs.remove(path.join(baseDir, "safe"));
|
|
await fs.ensureDir(path.join(baseDir, "safe"));
|
|
|
|
await fs.outputFile(path.join(baseDir, "test", "file.txt"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "test", "another file.txt"), "more contents");
|
|
|
|
await fs.outputFile(path.join(baseDir, "find", "file"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "find", "file.txt"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "find", "foo-1"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "find", "foo-2"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "find", "bar-1.txt"), "contents");
|
|
await fs.outputFile(path.join(baseDir, "find", "bar-2.txt"), "contents");
|
|
}
|
|
|
|
before(setupTestingEnv);
|
|
|
|
describe("getNewestFile()", function() {
|
|
it("returns a string in a directory with files", async function() {
|
|
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test"));
|
|
assert.equal(typeof newest, "string");
|
|
});
|
|
it("returns null if all entries were filtered out", async function() {
|
|
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test"), (name) => !name.endsWith(".txt"));
|
|
assert.equal(newest, null);
|
|
});
|
|
it("returns null if directory is empty", async function() {
|
|
let newest = await libFileOps.getNewestFile(path.join(baseDir, "test", "folder"));
|
|
assert.equal(newest, null);
|
|
});
|
|
});
|
|
|
|
describe("findUnusedName()", function() {
|
|
it("should return named unchanged if it does not exist", async function() {
|
|
let cases = [
|
|
[["file"], "file"],
|
|
[["file", ".txt"], "file.txt"],
|
|
[["file.txt", ".txt"], "file.txt"],
|
|
];
|
|
for (let [args, expected] of cases) {
|
|
let actual = await libFileOps.findUnusedName(path.join(baseDir, "test", "folder"), ...args);
|
|
assert.equal(actual, expected);
|
|
}
|
|
});
|
|
it("should return changed name if it does exist", async function() {
|
|
let cases = [
|
|
[["file"], "file-2"],
|
|
[["file", ".txt"], "file-2.txt"],
|
|
[["file.txt", ".txt"], "file-2.txt"],
|
|
[["foo-1"], "foo-3"],
|
|
[["bar-1", ".txt"], "bar-3.txt"],
|
|
[["bar-1.txt", ".txt"], "bar-3.txt"],
|
|
];
|
|
for (let [args, expected] of cases) {
|
|
let actual = await libFileOps.findUnusedName(path.join(baseDir, "find"), ...args);
|
|
assert.equal(actual, expected);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("safeOutputFile()", function() {
|
|
it("should write new target file", async function() {
|
|
let target = path.join(baseDir, "safe", "simple.txt");
|
|
await libFileOps.safeOutputFile(target, "a text file", "utf8");
|
|
assert(!await fs.pathExists(`${target}.tmp`), "temporary was left behind");
|
|
assert.equal(await fs.readFile(target, "utf8"), "a text file");
|
|
});
|
|
it("should overwrite existing target file", async function() {
|
|
let target = path.join(baseDir, "safe", "exists.txt");
|
|
await fs.outputFile(target, "previous", "utf8");
|
|
await libFileOps.safeOutputFile(target, "current", "utf8");
|
|
assert(!await fs.pathExists(`${target}.tmp`), "temporary was left behind");
|
|
assert.equal(await fs.readFile(target, "utf8"), "current");
|
|
});
|
|
});
|
|
|
|
describe("checkFilename()", function() {
|
|
it("should allow a basic name", function() {
|
|
libFileOps.checkFilename("file");
|
|
});
|
|
|
|
function check(item, msg) {
|
|
assert.throws(() => libFileOps.checkFilename(item), new Error(msg));
|
|
}
|
|
|
|
it("should throw on non-string", function() {
|
|
check(undefined, "must be a string");
|
|
check(null, "must be a string");
|
|
check({}, "must be a string");
|
|
check([], "must be a string");
|
|
check(0, "must be a string");
|
|
check(false, "must be a string");
|
|
});
|
|
|
|
it("should throw on empty name", function() {
|
|
check("", "cannot be empty");
|
|
});
|
|
|
|
it("should throw on <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
|
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
|
check(char, 'cannot contain <>:"\\/|=* or control characters');
|
|
}
|
|
});
|
|
|
|
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}.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 ${bad}`);
|
|
}
|
|
});
|
|
|
|
it("should throw on names ending with . or space", function() {
|
|
check("a ", "cannot end with . or space");
|
|
check("a.", "cannot end with . or space");
|
|
});
|
|
});
|
|
|
|
describe("cleanFilename()", function() {
|
|
function clean(item, expected) {
|
|
assert.equal(libFileOps.cleanFilename(item), expected);
|
|
}
|
|
|
|
it("should allow a basic name", function() {
|
|
clean("file", "file");
|
|
});
|
|
|
|
function check(item, msg) {
|
|
assert.throws(() => libFileOps.cleanFilename(item), new Error(msg));
|
|
}
|
|
|
|
it("should throw on non-string", function() {
|
|
check(undefined, "name must be a string");
|
|
check(null, "name must be a string");
|
|
check({}, "name must be a string");
|
|
check([], "name must be a string");
|
|
check(0, "name must be a string");
|
|
check(false, "name must be a string");
|
|
});
|
|
|
|
it("should clean empty name", function() {
|
|
clean("", "_");
|
|
});
|
|
|
|
it("should clean <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
|
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
|
clean(char, "_");
|
|
}
|
|
});
|
|
|
|
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}__`);
|
|
}
|
|
});
|
|
|
|
it("should clean . and ..", function() {
|
|
for (let bad of [".", ".."]) {
|
|
clean(bad, `${bad}_`);
|
|
}
|
|
});
|
|
|
|
it("should clean names ending with . or space", function() {
|
|
clean("a ", "a_");
|
|
clean("a.", "a_");
|
|
});
|
|
});
|
|
|
|
});
|