check if folder/file name is valid

This commit is contained in:
fyears 2024-09-01 16:15:42 +08:00
parent fccebb0594
commit e9b9cd97ac
3 changed files with 200 additions and 0 deletions

View File

@ -28,6 +28,7 @@ import {
} from "../../src/metadataOnRemote"; } from "../../src/metadataOnRemote";
import { import {
atWhichLevel, atWhichLevel,
checkValidName,
getParentFolder, getParentFolder,
isHiddenPath, isHiddenPath,
isSpecialFolderNameToSkip, isSpecialFolderNameToSkip,
@ -211,6 +212,13 @@ const ensembleMixedEnties = async (
continue; continue;
} }
const checkValidNameResult = checkValidName(key);
if (!checkValidNameResult.result) {
throw Error(
`your remote folder/file name is invalid: ${checkValidNameResult.reason}`
);
}
finalMappings[key] = { finalMappings[key] = {
key: key, key: key,
remote: remoteCopied, remote: remoteCopied,
@ -280,6 +288,13 @@ const ensembleMixedEnties = async (
continue; continue;
} }
const checkValidNameResult = checkValidName(key);
if (!checkValidNameResult.result) {
throw Error(
`your local folder/file name is invalid: ${checkValidNameResult.reason}`
);
}
// TODO: abstraction leaking? // TODO: abstraction leaking?
const localCopied = await fsEncrypt.encryptEntity( const localCopied = await fsEncrypt.encryptEntity(
copyEntityAndFixTimeFormat(local, serviceType) copyEntityAndFixTimeFormat(local, serviceType)
@ -920,6 +935,8 @@ const getSyncPlanInplace = async (
keptFolder.delete("/"); keptFolder.delete("/");
keptFolder.delete(""); keptFolder.delete("");
if (keptFolder.size > 0) { if (keptFolder.size > 0) {
console.error(sortedKeys);
console.error(mixedEntityMappings);
throw Error(`unexpectedly keptFolder no decisions: ${[...keptFolder]}`); throw Error(`unexpectedly keptFolder no decisions: ${[...keptFolder]}`);
} }

View File

@ -742,3 +742,110 @@ export const getSha1 = async (x: ArrayBuffer, stringify: "base64" | "hex") => {
} }
throw Error(`not supported stringify option = ${stringify}`); throw Error(`not supported stringify option = ${stringify}`);
}; };
/**
* https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
* https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa#invalidcharacters
*/
export const checkValidName = (x: string) => {
if (x === undefined || x === "") {
// what??
return {
reason: "empty",
result: false,
};
}
// The following reserved characters:
const invalidChars = '*"<>:|?'.split("");
for (const c of invalidChars) {
if (x.includes(c)) {
return {
reason: `reserved character: ${c}`,
result: false,
};
}
}
// directory component
for (const c of [".", ".."]) {
if (
x === c ||
x.endsWith(`/${c}`) ||
x.startsWith(`${c}/`) ||
x.includes(`/${c}/`)
) {
return {
reason: `directory being ${c}`,
result: false,
};
}
}
// reserved file names
const reservedNames = [
"CON",
"PRN",
"AUX",
"NUL",
"COM0",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"COM¹",
"COM²",
"COM³",
"LPT0",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
"LPT¹",
"LPT²",
"LPT³",
];
for (const f of reservedNames) {
if (
x === f ||
x.startsWith(`${f}.`) ||
x.startsWith(`${f}/`) ||
x.includes(`/${f}/`) ||
x.endsWith(`/${f}`) ||
x.includes(`/${f}.`)
) {
return {
reason: `reserved folder/file name: ${f}`,
result: false,
};
}
}
// Do not end a file or directory name with a space or a period.
if (
x.endsWith(" ") ||
x.endsWith(".") ||
x.includes(" /") ||
x.includes("./")
) {
return {
reason: `folder/file name ending with a space or a period`,
result: false,
};
}
return {
reason: "ok",
result: true,
};
};

View File

@ -363,6 +363,82 @@ describe("Misc: split chunk ranges", () => {
}); });
}); });
describe("Misc: check valid file names", () => {
it("should be ok for normal file nmes", () => {
let item = "what/.hidden/what/what/what";
assert.ok(misc.checkValidName(item).result);
item = "ssss/%%%^^^$xxxx.md";
assert.ok(misc.checkValidName(item).result);
});
it("should be not ok for reserved characters", () => {
let item = "a**";
assert.ok(!misc.checkValidName(item).result);
item = "a?*";
assert.ok(!misc.checkValidName(item).result);
item = "<>:";
assert.ok(!misc.checkValidName(item).result);
item = "<>:/ssss";
assert.ok(!misc.checkValidName(item).result);
});
it("should be not ok for reserved names", () => {
let item = "CON";
assert.ok(!misc.checkValidName(item).result);
item = "CON.txt";
assert.ok(!misc.checkValidName(item).result);
item = "CON.md";
assert.ok(!misc.checkValidName(item).result);
item = "con"; // lower case is ok
assert.ok(misc.checkValidName(item).result);
item = "CON/";
assert.ok(!misc.checkValidName(item).result);
item = "CON.dir/";
assert.ok(!misc.checkValidName(item).result);
item = "CON.dir.folder/";
assert.ok(!misc.checkValidName(item).result);
item = "xxx/CON";
assert.ok(!misc.checkValidName(item).result);
item = "xxx/CON.txt";
assert.ok(!misc.checkValidName(item).result);
item = "xxx/CON.txt.md";
assert.ok(!misc.checkValidName(item).result);
});
it("should be not ok for invalid endings", () => {
let item = "xxx ";
assert.ok(!misc.checkValidName(item).result);
item = "/xxx ";
assert.ok(!misc.checkValidName(item).result);
item = "xxx.yyy.";
assert.ok(!misc.checkValidName(item).result);
item = "xxx.yyy.";
assert.ok(!misc.checkValidName(item).result);
item = "xxx /";
assert.ok(!misc.checkValidName(item).result);
item = "xxx.yyy./";
assert.ok(!misc.checkValidName(item).result);
});
});
describe("Misc: Dropbox: should fix the folder name cases", () => { describe("Misc: Dropbox: should fix the folder name cases", () => {
it("should do nothing on empty folders", () => { it("should do nothing on empty folders", () => {
const input: any[] = []; const input: any[] = [];