Repository navigation
Expand file tree
/
Copy pathencoding.test.ts
More file actions
111 lines (99 loc) · 4.05 KB
/
Copy pathencoding.test.ts
File metadata and controls
111 lines (99 loc) · 4.05 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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* Request targets: path style only, a key's "/" sent as a literal "/", every other byte
* outside the unreserved set as upper-case %XX once, "." and ".." key segments byte-exact, and a bucket that fails
* the bucket rule refused before any request whatever the caller.
*/
import { describe, expect, test } from "bun:test";
import { DatabaseConfigError } from "@/lib/db/errors";
import { compareEncodedPairs, objectPath, s3Query } from "@/lib/db/providers/objectstore/s3/encoding";
import { sourceAddressSentence } from "@/lib/db/providers/objectstore/s3/names";
describe("objectPath", () => {
test.each([
["keys/plus+sign.txt", "/b/keys/plus%2Bsign.txt"],
["keys/lit%2Fname.txt", "/b/keys/lit%252Fname.txt"],
["with space.txt", "/b/with%20space.txt"],
["ü.txt", "/b/%C3%BC.txt"],
["a/./b", "/b/a/./b"],
["x/../y", "/b/x/../y"],
["a//c", "/b/a//c"],
["tilde~-._", "/b/tilde~-._"],
])("key %p is sent as %p", (key, path) => {
expect(objectPath("b", key)).toBe(path);
});
test("a bucket alone is /<bucket>", () => {
expect(objectPath("sales")).toBe("/sales");
});
test.each([[".."], ["a b"], ["a".repeat(256)]])("bucket %p is refused before any request", (bucket) => {
let caught: unknown;
try {
objectPath(bucket, "k");
} catch (error) {
caught = error;
}
expect(caught).toBeInstanceOf(DatabaseConfigError);
expect((caught as DatabaseConfigError).provider as string).toBe("s3");
expect((caught as Error).message).toBe(sourceAddressSentence("bucket-pattern", { bucket, key: "k" }));
});
test("a key whose dot segments leave its bucket is refused before any request, whoever the caller", () => {
let caught: unknown;
try {
objectPath("sales", "../other/x");
} catch (error) {
caught = error;
}
expect(caught).toBeInstanceOf(DatabaseConfigError);
expect((caught as DatabaseConfigError).provider as string).toBe("s3");
expect((caught as Error).message).toBe(
sourceAddressSentence("key-dot-segments", { bucket: "sales", key: "../other/x" }),
);
expect(() => objectPath("sales", "")).toThrow(
sourceAddressSentence("key-dot-segments", { bucket: "sales", key: "" }),
);
});
test("a key whose dot segments stay inside its bucket is sent as given", () => {
expect(objectPath("sp", "x/../dotdot.txt")).toBe("/sp/x/../dotdot.txt");
});
test("a backslash counts as a separator: backslash dot segments that leave the bucket are refused before any request", () => {
for (const key of ["..\\other\\x", "a\\..\\..\\b", "\\.."]) {
expect(() => objectPath("sales", key), key).toThrow(
sourceAddressSentence("key-dot-segments", { bucket: "sales", key }),
);
}
});
test("a key with backslashes whose dot segments stay inside its bucket is sent, each backslash encoded", () => {
expect(objectPath("sales", "dir\\file.txt")).toBe("/sales/dir%5Cfile.txt");
expect(objectPath("sales", "a\\b\\..\\c")).toBe("/sales/a%5Cb%5C..%5Cc");
});
});
describe("s3Query", () => {
test("encodes each side and sorts by encoded name, then encoded value", () => {
expect(
s3Query([
["prefix", "a b/"],
["list-type", "2"],
["encoding-type", "url"],
["delimiter", "/"],
]),
).toBe("delimiter=%2F&encoding-type=url&list-type=2&prefix=a%20b%2F");
});
test("a subresource is written name=", () => {
expect(s3Query([["versions", ""]])).toBe("versions=");
});
test("two values of one name are ordered by encoded value", () => {
expect(
s3Query([
["a", "z"],
["a", "b"],
]),
).toBe("a=b&a=z");
});
test("no pairs give the empty query", () => {
expect(s3Query([])).toBe("");
});
});
test("compareEncodedPairs is plain code-unit order on the name, then the value", () => {
expect(compareEncodedPairs(["a", "1"], ["b", "0"])).toBeLessThan(0);
expect(compareEncodedPairs(["B", "1"], ["a", "0"])).toBeLessThan(0);
expect(compareEncodedPairs(["a", "2"], ["a", "1"])).toBeGreaterThan(0);
expect(compareEncodedPairs(["a", "1"], ["a", "1"])).toBe(0);
});