(
filePath: string,
fileContent: string,
opts: { suppressTemplaterOnCreate?: boolean } = {},
)
| 63 | } |
| 64 | |
| 65 | protected async createFileWithInput( |
| 66 | filePath: string, |
| 67 | fileContent: string, |
| 68 | opts: { suppressTemplaterOnCreate?: boolean } = {}, |
| 69 | ): Promise<TFile> { |
| 70 | // Authoritative vault-containment boundary for every new-file write (Template |
| 71 | // and Capture both funnel through here). `filePath` is assembled from formatted |
| 72 | // names that can be seeded by untrusted input — an obsidian:// URI, the CLI, or |
| 73 | // `{{VALUE}}` resolved from synced note content. A traversal/absolute/drive/UNC |
| 74 | // path (e.g. "..\\..\\..\\evil.md" or "C:/evil.md") would make `vault.create` |
| 75 | // AND the folder pre-creation below resolve OUTSIDE the vault root. Reject it |
| 76 | // here, before any filesystem touch, so the sink can never escape even if an |
| 77 | // upstream normalizer is bypassed. Mirrors the package-import write guard (#1434). |
| 78 | if (escapesVaultBoundary(filePath)) { |
| 79 | throw new Error( |
| 80 | `Refusing to create a file outside the vault: "${filePath}".`, |
| 81 | ); |
| 82 | } |
| 83 | |
| 84 | const dirMatch = filePath.match(/(.*)[/\\]/); |
| 85 | let dirName = ""; |
| 86 | if (dirMatch) dirName = dirMatch[1]; |
| 87 | |
| 88 | const dir = this.app.vault.getAbstractFileByPath(dirName); |
| 89 | |
| 90 | if (!dir || !(dir instanceof TFolder)) { |
| 91 | await this.createFolder(dirName); |
| 92 | |
| 93 | } |
| 94 | |
| 95 | const createFile = () => this.app.vault.create(filePath, fileContent); |
| 96 | const shouldSuppress = |
| 97 | opts.suppressTemplaterOnCreate && |
| 98 | filePath.toLowerCase().endsWith(".md"); |
| 99 | |
| 100 | return shouldSuppress |
| 101 | ? await withTemplaterFileCreationSuppressed(this.app, filePath, createFile) |
| 102 | : await createFile(); |
| 103 | } |
| 104 | } |
nothing calls this directly
no test coverage detected