| 1 | // @effect-diagnostics nodeBuiltinImport:off |
| 2 | import * as NodePath from "node:path"; |
| 3 | |
| 4 | export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null { |
| 5 | const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, ""); |
| 6 | if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) { |
| 7 | return null; |
| 8 | } |
| 9 | return normalized.replace(/\\/g, "/"); |
| 10 | } |
| 11 | |
| 12 | export function resolveAttachmentRelativePath(input: { |
| 13 | readonly attachmentsDir: string; |
| 14 | readonly relativePath: string; |
| 15 | }): string | null { |
| 16 | const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath); |
| 17 | if (!normalizedRelativePath) { |
| 18 | return null; |
| 19 | } |
| 20 | |
| 21 | const attachmentsRoot = NodePath.resolve(input.attachmentsDir); |
| 22 | const filePath = NodePath.resolve(NodePath.join(attachmentsRoot, normalizedRelativePath)); |
| 23 | if (!filePath.startsWith(`${attachmentsRoot}${NodePath.sep}`)) { |
| 24 | return null; |
| 25 | } |
| 26 | return filePath; |
| 27 | } |