Skip to content

Commit ecb75c4

Browse files
committed
fix: validate folder paths before creation
1 parent dd5e966 commit ecb75c4

File tree

1 file changed

+41
-32
lines changed

1 file changed

+41
-32
lines changed

src/engine/TemplateEngine.ts

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ type FolderSelection = {
4747
isEmpty: boolean;
4848
};
4949

50+
class InvalidFolderPathError extends Error {
51+
constructor(message: string) {
52+
super(message);
53+
this.name = "InvalidFolderPathError";
54+
}
55+
}
56+
5057
function isMacroAbortError(error: unknown): error is MacroAbortError {
5158
return (
5259
error instanceof MacroAbortError ||
@@ -217,15 +224,17 @@ export abstract class TemplateEngine extends QuickAddEngine {
217224
}
218225

219226
try {
220-
await this.ensureFolderExists(selection);
227+
this.validateFolderPath(selection.resolved);
221228
} catch (error) {
222-
if (this.isInvalidPathError(error)) {
223-
this.showInvalidFolderNotice(error);
229+
if (error instanceof InvalidFolderPathError) {
230+
new Notice(error.message);
224231
continue;
225232
}
226233
throw error;
227234
}
228235

236+
await this.ensureFolderExists(selection);
237+
229238
return selection;
230239
}
231240
}
@@ -247,48 +256,48 @@ export abstract class TemplateEngine extends QuickAddEngine {
247256
return "";
248257
}
249258

250-
try {
251-
await this.ensureFolderExists(selection);
252-
} catch (error) {
253-
if (this.isInvalidPathError(error)) {
254-
this.showInvalidFolderNotice(error);
255-
return "";
259+
if (selection.resolved) {
260+
try {
261+
this.validateFolderPath(selection.resolved);
262+
} catch (error) {
263+
if (error instanceof InvalidFolderPathError) {
264+
new Notice(error.message);
265+
return "";
266+
}
267+
throw error;
256268
}
257-
throw error;
258269
}
270+
271+
await this.ensureFolderExists(selection);
259272
return selection.resolved;
260273
}
261274

262275
private normalizeFolderPath(path: string): string {
263276
return path.trim().replace(/^\/+/, "").replace(/\/+$/, "");
264277
}
265278

266-
private isInvalidPathError(error: unknown): error is Error {
267-
if (!(error instanceof Error)) return false;
268-
return (
269-
error.message.includes("File name cannot contain") ||
270-
error.message.includes("File name cannot be empty") ||
271-
error.message.includes("File name cannot start with")
272-
);
273-
}
279+
private validateFolderPath(path: string): void {
280+
const trimmed = path.trim();
281+
if (!trimmed) return;
274282

275-
private showInvalidFolderNotice(error: Error): void {
276-
new Notice(this.formatInvalidFolderMessage(error.message));
277-
}
283+
const segments = trimmed.split("/");
284+
for (const segment of segments) {
285+
if (!segment) {
286+
throw new InvalidFolderPathError("Folder name cannot be empty.");
287+
}
278288

279-
private formatInvalidFolderMessage(message: string): string {
280-
const invalidCharsMatch = message.match(
281-
/^File name cannot contain any of the following characters:\s*(.+)$/u,
282-
);
283-
if (invalidCharsMatch?.[1]) {
284-
return `Folder name cannot contain any of the following characters: ${invalidCharsMatch[1]}`;
285-
}
289+
if (segment === "." || segment === "..") {
290+
throw new InvalidFolderPathError(
291+
"Folder name cannot be '.' or '..'.",
292+
);
293+
}
286294

287-
if (message.startsWith("File name ")) {
288-
return `Folder name ${message.slice("File name ".length)}`;
295+
if (/[\\:]/u.test(segment)) {
296+
throw new InvalidFolderPathError(
297+
"Folder name cannot contain any of the following characters: \\ / :",
298+
);
299+
}
289300
}
290-
291-
return "Invalid folder name.";
292301
}
293302

294303
private isPathAllowed(path: string, roots: string[]): boolean {

0 commit comments

Comments
 (0)