cloudflare/cloudflare-typescript

Public

mirrored fromhttps://github.com/cloudflare/cloudflare-typescriptAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v7

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tests/path.test.ts

462lines · modecode

1import { createPathTagFunction, encodeURIPath } from 'cloudflare/internal/utils/path';
2import { inspect } from 'node:util';
3import { runInNewContext } from 'node:vm';
4
5describe('path template tag function', () => {
6 test('validates input', () => {
7 const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E'];
8 const testCases = [
9 ['/path_params/', '/a'],
10 ['/path_params/', '/'],
11 ['/path_params/', ''],
12 ['', '/a'],
13 ['', '/'],
14 ['', ''],
15 ['a'],
16 [''],
17 ['/path_params/', ':initiate'],
18 ['/path_params/', '.json'],
19 ['/path_params/', '?beta=true'],
20 ['/path_params/', '.?beta=true'],
21 ['/path_params/', '/', '/download'],
22 ['/path_params/', '-', '/download'],
23 ['/path_params/', '', '/download'],
24 ['/path_params/', '.', '/download'],
25 ['/path_params/', '..', '/download'],
26 ['/plain/path'],
27 ];
28
29 function paramPermutations(len: number): string[][] {
30 if (len === 0) return [];
31 if (len === 1) return testParams.map((e) => [e]);
32 const rest = paramPermutations(len - 1);
33 return testParams.flatMap((e) => rest.map((r) => [e, ...r]));
34 }
35
36 // We need to test how %2E is handled, so we use a custom encoder that does no escaping.
37 const rawPath = createPathTagFunction((s) => s);
38
39 const emptyObject = {};
40 const mathObject = Math;
41 const numberObject = new Number();
42 const stringObject = new String();
43 const basicClass = new (class {})();
44 const classWithToString = new (class {
45 toString() {
46 return 'ok';
47 }
48 })();
49
50 // Invalid values
51 expect(() => rawPath`/a/${null}/b`).toThrow(
52 'Path parameters result in path with invalid segments:\n' +
53 'Value of type Null is not a valid path parameter\n' +
54 '/a/null/b\n' +
55 ' ^^^^',
56 );
57 expect(() => rawPath`/a/${undefined}/b`).toThrow(
58 'Path parameters result in path with invalid segments:\n' +
59 'Value of type Undefined is not a valid path parameter\n' +
60 '/a/undefined/b\n' +
61 ' ^^^^^^^^^',
62 );
63 expect(() => rawPath`/a/${emptyObject}/b`).toThrow(
64 'Path parameters result in path with invalid segments:\n' +
65 'Value of type Object is not a valid path parameter\n' +
66 '/a/[object Object]/b\n' +
67 ' ^^^^^^^^^^^^^^^',
68 );
69 expect(() => rawPath`?${mathObject}`).toThrow(
70 'Path parameters result in path with invalid segments:\n' +
71 'Value of type Math is not a valid path parameter\n' +
72 '?[object Math]\n' +
73 ' ^^^^^^^^^^^^^',
74 );
75 expect(() => rawPath`/${basicClass}`).toThrow(
76 'Path parameters result in path with invalid segments:\n' +
77 'Value of type Object is not a valid path parameter\n' +
78 '/[object Object]\n' +
79 ' ^^^^^^^^^^^^^^',
80 );
81 expect(() => rawPath`/../${''}`).toThrow(
82 'Path parameters result in path with invalid segments:\n' +
83 'Value ".." can\'t be safely passed as a path parameter\n' +
84 '/../\n' +
85 ' ^^',
86 );
87 expect(() => rawPath`/../${{}}`).toThrow(
88 'Path parameters result in path with invalid segments:\n' +
89 'Value ".." can\'t be safely passed as a path parameter\n' +
90 'Value of type Object is not a valid path parameter\n' +
91 '/../[object Object]\n' +
92 ' ^^ ^^^^^^^^^^^^^^',
93 );
94
95 // Valid values
96 expect(rawPath`/${0}`).toBe('/0');
97 expect(rawPath`/${''}`).toBe('/');
98 expect(rawPath`/${numberObject}`).toBe('/0');
99 expect(rawPath`${stringObject}/`).toBe('/');
100 expect(rawPath`/${classWithToString}`).toBe('/ok');
101
102 // We need to check what happens with cross-realm values, which we might get from
103 // Jest or other frames in a browser.
104
105 const newRealm = runInNewContext('globalThis');
106 expect(newRealm.Object).not.toBe(Object);
107
108 const crossRealmObject = newRealm.Object();
109 const crossRealmMathObject = newRealm.Math;
110 const crossRealmNumber = new newRealm.Number();
111 const crossRealmString = new newRealm.String();
112 const crossRealmClass = new (class extends newRealm.Object {})();
113 const crossRealmClassWithToString = new (class extends newRealm.Object {
114 toString() {
115 return 'ok';
116 }
117 })();
118
119 // Invalid cross-realm values
120 expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow(
121 'Path parameters result in path with invalid segments:\n' +
122 'Value of type Object is not a valid path parameter\n' +
123 '/a/[object Object]/b\n' +
124 ' ^^^^^^^^^^^^^^^',
125 );
126 expect(() => rawPath`?${crossRealmMathObject}`).toThrow(
127 'Path parameters result in path with invalid segments:\n' +
128 'Value of type Math is not a valid path parameter\n' +
129 '?[object Math]\n' +
130 ' ^^^^^^^^^^^^^',
131 );
132 expect(() => rawPath`/${crossRealmClass}`).toThrow(
133 'Path parameters result in path with invalid segments:\n' +
134 'Value of type Object is not a valid path parameter\n' +
135 '/[object Object]\n' +
136 ' ^^^^^^^^^^^^^^^',
137 );
138
139 // Valid cross-realm values
140 expect(rawPath`/${crossRealmNumber}`).toBe('/0');
141 expect(rawPath`${crossRealmString}/`).toBe('/');
142 expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok');
143
144 const results: {
145 [pathParts: string]: {
146 [params: string]: { valid: boolean; result?: string; error?: string };
147 };
148 } = {};
149
150 for (const pathParts of testCases) {
151 const pathResults: Record<string, { valid: boolean; result?: string; error?: string }> = {};
152 results[JSON.stringify(pathParts)] = pathResults;
153 for (const params of paramPermutations(pathParts.length - 1)) {
154 const stringRaw = String.raw({ raw: pathParts }, ...params);
155 const plainString = String.raw(
156 { raw: pathParts.map((e) => e.replace(/\./g, 'x')) },
157 ...params.map((e) => 'X'.repeat(e.length)),
158 );
159 const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href;
160 const normalizedPlainString = new URL(plainString, 'https://example.com').href;
161 const pathResultsKey = JSON.stringify(params);
162 try {
163 const result = rawPath(pathParts, ...params);
164 expect(result).toBe(stringRaw);
165 // there are no special segments, so the length of the normalized path is
166 // equal to the length of the normalized plain path.
167 expect(normalizedStringRaw.length).toBe(normalizedPlainString.length);
168 pathResults[pathResultsKey] = {
169 valid: true,
170 result,
171 };
172 } catch (e) {
173 const error = String(e);
174 expect(error).toMatch(/Path parameters result in path with invalid segment/);
175 // there are special segments, so the length of the normalized path is
176 // different than the length of the normalized plain path.
177 expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length);
178 pathResults[pathResultsKey] = {
179 valid: false,
180 error,
181 };
182 }
183 }
184 }
185
186 expect(results).toMatchObject({
187 '["/path_params/","/a"]': {
188 '["x"]': { valid: true, result: '/path_params/x/a' },
189 '[""]': { valid: true, result: '/path_params//a' },
190 '["%2E%2e"]': {
191 valid: false,
192 error:
193 'Error: Path parameters result in path with invalid segments:\n' +
194 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' +
195 '/path_params/%2E%2e/a\n' +
196 ' ^^^^^^',
197 },
198 '["%2E"]': {
199 valid: false,
200 error:
201 'Error: Path parameters result in path with invalid segments:\n' +
202 'Value "%2E" can\'t be safely passed as a path parameter\n' +
203 '/path_params/%2E/a\n' +
204 ' ^^^',
205 },
206 },
207 '["/path_params/","/"]': {
208 '["x"]': { valid: true, result: '/path_params/x/' },
209 '[""]': { valid: true, result: '/path_params//' },
210 '["%2e%2E"]': {
211 valid: false,
212 error:
213 'Error: Path parameters result in path with invalid segments:\n' +
214 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' +
215 '/path_params/%2e%2E/\n' +
216 ' ^^^^^^',
217 },
218 '["%2e"]': {
219 valid: false,
220 error:
221 'Error: Path parameters result in path with invalid segments:\n' +
222 'Value "%2e" can\'t be safely passed as a path parameter\n' +
223 '/path_params/%2e/\n' +
224 ' ^^^',
225 },
226 },
227 '["/path_params/",""]': {
228 '[""]': { valid: true, result: '/path_params/' },
229 '["x"]': { valid: true, result: '/path_params/x' },
230 '["%2E"]': {
231 valid: false,
232 error:
233 'Error: Path parameters result in path with invalid segments:\n' +
234 'Value "%2E" can\'t be safely passed as a path parameter\n' +
235 '/path_params/%2E\n' +
236 ' ^^^',
237 },
238 '["%2E%2e"]': {
239 valid: false,
240 error:
241 'Error: Path parameters result in path with invalid segments:\n' +
242 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' +
243 '/path_params/%2E%2e\n' +
244 ' ^^^^^^',
245 },
246 },
247 '["","/a"]': {
248 '[""]': { valid: true, result: '/a' },
249 '["x"]': { valid: true, result: 'x/a' },
250 '["%2E"]': {
251 valid: false,
252 error:
253 'Error: Path parameters result in path with invalid segments:\n' +
254 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^',
255 },
256 '["%2e%2E"]': {
257 valid: false,
258 error:
259 'Error: Path parameters result in path with invalid segments:\n' +
260 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' +
261 '%2e%2E/a\n' +
262 '^^^^^^',
263 },
264 },
265 '["","/"]': {
266 '["x"]': { valid: true, result: 'x/' },
267 '[""]': { valid: true, result: '/' },
268 '["%2E%2e"]': {
269 valid: false,
270 error:
271 'Error: Path parameters result in path with invalid segments:\n' +
272 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' +
273 '%2E%2e/\n' +
274 '^^^^^^',
275 },
276 '["."]': {
277 valid: false,
278 error:
279 'Error: Path parameters result in path with invalid segments:\n' +
280 'Value "." can\'t be safely passed as a path parameter\n' +
281 './\n^',
282 },
283 },
284 '["",""]': {
285 '[""]': { valid: true, result: '' },
286 '["x"]': { valid: true, result: 'x' },
287 '[".."]': {
288 valid: false,
289 error:
290 'Error: Path parameters result in path with invalid segments:\n' +
291 'Value ".." can\'t be safely passed as a path parameter\n' +
292 '..\n^^',
293 },
294 '["."]': {
295 valid: false,
296 error:
297 'Error: Path parameters result in path with invalid segments:\n' +
298 'Value "." can\'t be safely passed as a path parameter\n' +
299 '.\n^',
300 },
301 },
302 '["a"]': {},
303 '[""]': {},
304 '["/path_params/",":initiate"]': {
305 '[""]': { valid: true, result: '/path_params/:initiate' },
306 '["."]': { valid: true, result: '/path_params/.:initiate' },
307 },
308 '["/path_params/",".json"]': {
309 '["x"]': { valid: true, result: '/path_params/x.json' },
310 '["."]': { valid: true, result: '/path_params/..json' },
311 },
312 '["/path_params/","?beta=true"]': {
313 '["x"]': { valid: true, result: '/path_params/x?beta=true' },
314 '[""]': { valid: true, result: '/path_params/?beta=true' },
315 '["%2E%2E"]': {
316 valid: false,
317 error:
318 'Error: Path parameters result in path with invalid segments:\n' +
319 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' +
320 '/path_params/%2E%2E?beta=true\n' +
321 ' ^^^^^^',
322 },
323 '["%2e%2E"]': {
324 valid: false,
325 error:
326 'Error: Path parameters result in path with invalid segments:\n' +
327 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' +
328 '/path_params/%2e%2E?beta=true\n' +
329 ' ^^^^^^',
330 },
331 },
332 '["/path_params/",".?beta=true"]': {
333 '[".."]': { valid: true, result: '/path_params/...?beta=true' },
334 '["x"]': { valid: true, result: '/path_params/x.?beta=true' },
335 '[""]': {
336 valid: false,
337 error:
338 'Error: Path parameters result in path with invalid segments:\n' +
339 'Value "." can\'t be safely passed as a path parameter\n' +
340 '/path_params/.?beta=true\n' +
341 ' ^',
342 },
343 '["%2e"]': {
344 valid: false,
345 error:
346 'Error: Path parameters result in path with invalid segments:\n' +
347 'Value "%2e." can\'t be safely passed as a path parameter\n' +
348 '/path_params/%2e.?beta=true\n' +
349 ' ^^^^',
350 },
351 },
352 '["/path_params/","/","/download"]': {
353 '["",""]': { valid: true, result: '/path_params///download' },
354 '["","x"]': { valid: true, result: '/path_params//x/download' },
355 '[".","%2e"]': {
356 valid: false,
357 error:
358 'Error: Path parameters result in path with invalid segments:\n' +
359 'Value "." can\'t be safely passed as a path parameter\n' +
360 'Value "%2e" can\'t be safely passed as a path parameter\n' +
361 '/path_params/./%2e/download\n' +
362 ' ^ ^^^',
363 },
364 '["%2E%2e","%2e"]': {
365 valid: false,
366 error:
367 'Error: Path parameters result in path with invalid segments:\n' +
368 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' +
369 'Value "%2e" can\'t be safely passed as a path parameter\n' +
370 '/path_params/%2E%2e/%2e/download\n' +
371 ' ^^^^^^ ^^^',
372 },
373 },
374 '["/path_params/","-","/download"]': {
375 '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' },
376 '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' },
377 },
378 '["/path_params/","","/download"]': {
379 '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' },
380 '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' },
381 '["","%2E"]': {
382 valid: false,
383 error:
384 'Error: Path parameters result in path with invalid segments:\n' +
385 'Value "%2E" can\'t be safely passed as a path parameter\n' +
386 '/path_params/%2E/download\n' +
387 ' ^^^',
388 },
389 '["%2E","."]': {
390 valid: false,
391 error:
392 'Error: Path parameters result in path with invalid segments:\n' +
393 'Value "%2E." can\'t be safely passed as a path parameter\n' +
394 '/path_params/%2E./download\n' +
395 ' ^^^^',
396 },
397 },
398 '["/path_params/",".","/download"]': {
399 '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' },
400 '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' },
401 '["",""]': {
402 valid: false,
403 error:
404 'Error: Path parameters result in path with invalid segments:\n' +
405 'Value "." can\'t be safely passed as a path parameter\n' +
406 '/path_params/./download\n' +
407 ' ^',
408 },
409 '["","."]': {
410 valid: false,
411 error:
412 'Error: Path parameters result in path with invalid segments:\n' +
413 'Value ".." can\'t be safely passed as a path parameter\n' +
414 '/path_params/../download\n' +
415 ' ^^',
416 },
417 },
418 '["/path_params/","..","/download"]': {
419 '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' },
420 '["","x"]': { valid: true, result: '/path_params/..x/download' },
421 '["",""]': {
422 valid: false,
423 error:
424 'Error: Path parameters result in path with invalid segments:\n' +
425 'Value ".." can\'t be safely passed as a path parameter\n' +
426 '/path_params/../download\n' +
427 ' ^^',
428 },
429 },
430 });
431 });
432});
433
434describe('encodeURIPath', () => {
435 const testCases: string[] = [
436 '',
437 // Every ASCII character
438 ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)),
439 // Unicode BMP codepoint
440 'å',
441 // Unicode supplementary codepoint
442 '😃',
443 ];
444
445 for (const param of testCases) {
446 test('properly encodes ' + inspect(param), () => {
447 const encoded = encodeURIPath(param);
448 const naiveEncoded = encodeURIComponent(param);
449 // we should never encode more characters than encodeURIComponent
450 expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length);
451 expect(decodeURIComponent(encoded)).toBe(param);
452 });
453 }
454
455 test("leaves ':' intact", () => {
456 expect(encodeURIPath(':')).toBe(':');
457 });
458
459 test("leaves '@' intact", () => {
460 expect(encodeURIPath('@')).toBe('@');
461 });
462});
463