-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpaths.ts
More file actions
77 lines (70 loc) · 2.47 KB
/
paths.ts
File metadata and controls
77 lines (70 loc) · 2.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/** Normalize root prefix: no leading slash issues, trailing slash optional (we normalize to trailing slash or empty). */
export function getRootPrefix(): string {
const raw = process.env.S3_ROOT_PREFIX ?? "";
const trimmed = raw.replace(/^\/+/, "").trim();
if (!trimmed) return "";
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
}
export function getBucket(): string {
const b = process.env.S3_BUCKET_NAME?.trim();
if (!b) throw new Error("S3_BUCKET_NAME is not set");
return b;
}
/** Collection folder must be a single path segment (no slashes, no ..). */
export function assertValidCollectionSlug(slug: string) {
if (
!slug ||
slug.includes("/") ||
slug.includes("..") ||
slug === "." ||
slug === ".."
) {
throw new Error("Invalid collection");
}
}
/** Relative path within collection: no absolute, no `..` segments. */
export function assertSafeRelativePath(relativePath: string) {
if (!relativePath || relativePath.startsWith("/")) {
throw new Error("Invalid object path");
}
const parts = relativePath.split("/");
for (const p of parts) {
if (p === ".." || p === ".") throw new Error("Invalid object path");
}
}
export function collectionPrefix(collectionSlug: string): string {
assertValidCollectionSlug(collectionSlug);
return `${getRootPrefix()}${collectionSlug}/`;
}
export function fullObjectKey(
collectionSlug: string,
relativePath: string,
): string {
assertValidCollectionSlug(collectionSlug);
assertSafeRelativePath(relativePath);
return `${getRootPrefix()}${collectionSlug}/${relativePath}`;
}
/** First path segment after root = collection slug; remainder = path within collection. */
export function splitObjectKeyAfterRoot(objectKey: string): {
slug: string;
relativePath: string;
} {
assertKeyUnderRoot(objectKey);
const root = getRootPrefix();
const rest = root ? objectKey.slice(root.length) : objectKey;
const slash = rest.indexOf("/");
if (slash === -1) {
return { slug: rest, relativePath: "" };
}
return { slug: rest.slice(0, slash), relativePath: rest.slice(slash + 1) };
}
/** Ensure an arbitrary S3 key is under the configured root prefix (for get by full key). */
export function assertKeyUnderRoot(objectKey: string) {
if (!objectKey || objectKey.startsWith("/") || objectKey.includes("..")) {
throw new Error("Invalid object key");
}
const root = getRootPrefix();
if (root && !objectKey.startsWith(root)) {
throw new Error("Object key outside configured root");
}
}