MCPcopy Create free account
hub / github.com/chhoumann/quickadd / normalizeTemplateFilePath

Function normalizeTemplateFilePath

src/engine/TemplateEngine.ts:529–574  ·  view source on GitHub ↗
(
		folderPath: string,
		fileName: string,
		templatePath: string
	)

Source from the content-addressed store, hash-verified

527 }
528
529 protected normalizeTemplateFilePath(
530 folderPath: string,
531 fileName: string,
532 templatePath: string
533 ): string {
534 const safeFolderPath = this.stripLeadingSlash(folderPath);
535 const actualFolderPath: string = safeFolderPath ? `${safeFolderPath}/` : "";
536 const extension = this.getTemplateExtension(templatePath);
537 const normalizedFileName = normalizeGeneratedFilePath(
538 this.stripLeadingSlash(fileName),
539 "File name",
540 );
541 const formattedFileName: string = normalizeGeneratedFilePath(
542 normalizedFileName
543 .replace(MARKDOWN_FILE_EXTENSION_REGEX, "")
544 .replace(CANVAS_FILE_EXTENSION_REGEX, "")
545 .replace(BASE_FILE_EXTENSION_REGEX, ""),
546 "File name",
547 );
548 // Validate the final path segment, not just the whole string — a
549 // trailing-slash name like "Projects/" (optional leaf token left
550 // empty) would otherwise still produce "Projects/.md".
551 const baseName = formattedFileName.slice(
552 formattedFileName.lastIndexOf("/") + 1
553 );
554 if (!baseName.trim()) {
555 throw new Error(
556 "File name is empty after formatting. Make sure the tokens in the file name format produce a value (an optional token left empty can cause this)."
557 );
558 }
559 const assembledPath = `${actualFolderPath}${formattedFileName}${extension}`;
560 // Contain the assembled target at this shared chokepoint. The file NAME is run
561 // through normalizeGeneratedFilePath above, but the FOLDER portion is only
562 // stripLeadingSlash'd — so a folder like "../../../evil" (from an untrusted,
563 // synced Template choice resolved via formatFolderPath) would assemble an
564 // out-of-vault path. Both callers act on it without a sink guard: the relocation
565 // flow (computeChoiceTargetPath -> createFolder + fileManager.renameFile) would
566 // otherwise move the active note OUTSIDE the vault. Reject any escape here so
567 // every caller of this assembler is contained.
568 if (escapesVaultBoundary(assembledPath)) {
569 throw new Error(
570 `Refusing to build a file path outside the vault: "${assembledPath}".`,
571 );
572 }
573 return assembledPath;
574 }
575
576 protected async createFileWithTemplate(
577 filePath: string,

Callers

nothing calls this directly

Calls 2

escapesVaultBoundaryFunction · 0.90

Tested by

no test coverage detected