@cloudflare/pages-shared 0.11.36 → 0.11.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/asset-server/handler.ts +61 -33
- package/asset-server/rulesEngine.ts +4 -2
- package/metadata-generator/createMetadataObject.ts +9 -3
- package/metadata-generator/parseHeaders.ts +3 -1
- package/metadata-generator/parseRedirects.ts +3 -1
- package/metadata-generator/validateURL.ts +8 -3
- package/package.json +2 -1
package/asset-server/handler.ts
CHANGED
|
@@ -133,7 +133,7 @@ export async function generateHandler<
|
|
|
133
133
|
Asset extends { body: ReadableStream | null; contentType: string } = {
|
|
134
134
|
body: ReadableStream | null;
|
|
135
135
|
contentType: string;
|
|
136
|
-
}
|
|
136
|
+
},
|
|
137
137
|
>({
|
|
138
138
|
request,
|
|
139
139
|
metadata,
|
|
@@ -226,10 +226,10 @@ export async function generateHandler<
|
|
|
226
226
|
destination.origin === new URL(request.url).origin
|
|
227
227
|
? `${destination.pathname}${destination.search || search}${
|
|
228
228
|
destination.hash
|
|
229
|
-
|
|
229
|
+
}`
|
|
230
230
|
: `${destination.href}${destination.search ? "" : search}${
|
|
231
231
|
destination.hash
|
|
232
|
-
|
|
232
|
+
}`;
|
|
233
233
|
switch (status) {
|
|
234
234
|
case 301:
|
|
235
235
|
return new MovedPermanentlyResponse(location, undefined, {
|
|
@@ -350,19 +350,24 @@ export async function generateHandler<
|
|
|
350
350
|
// "Early Hints cache entries are keyed by request URI and ignore query strings."
|
|
351
351
|
// https://developers.cloudflare.com/cache/about/early-hints/
|
|
352
352
|
const earlyHintsCacheKey = `${protocol}//${host}${pathname}`;
|
|
353
|
-
const earlyHintsResponse =
|
|
354
|
-
earlyHintsCacheKey
|
|
355
|
-
);
|
|
353
|
+
const earlyHintsResponse =
|
|
354
|
+
await earlyHintsCache.match(earlyHintsCacheKey);
|
|
356
355
|
if (earlyHintsResponse) {
|
|
357
356
|
const earlyHintsLinkHeader = earlyHintsResponse.headers.get("Link");
|
|
358
357
|
if (earlyHintsLinkHeader) {
|
|
359
358
|
headers.set("Link", earlyHintsLinkHeader);
|
|
360
|
-
if (setMetrics)
|
|
359
|
+
if (setMetrics) {
|
|
360
|
+
setMetrics({ earlyHintsResult: "used-hit" });
|
|
361
|
+
}
|
|
361
362
|
} else {
|
|
362
|
-
if (setMetrics)
|
|
363
|
+
if (setMetrics) {
|
|
364
|
+
setMetrics({ earlyHintsResult: "notused-hit" });
|
|
365
|
+
}
|
|
363
366
|
}
|
|
364
367
|
} else {
|
|
365
|
-
if (setMetrics)
|
|
368
|
+
if (setMetrics) {
|
|
369
|
+
setMetrics({ earlyHintsResult: "notused-miss" });
|
|
370
|
+
}
|
|
366
371
|
|
|
367
372
|
const clonedResponse = response.clone();
|
|
368
373
|
|
|
@@ -373,26 +378,29 @@ export async function generateHandler<
|
|
|
373
378
|
const links: { href: string; rel: string; as?: string }[] = [];
|
|
374
379
|
|
|
375
380
|
const transformedResponse = new HTMLRewriter()
|
|
376
|
-
.on(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
381
|
+
.on(
|
|
382
|
+
"link[rel~=preconnect],link[rel~=preload],link[rel~=modulepreload]",
|
|
383
|
+
{
|
|
384
|
+
element(element) {
|
|
385
|
+
for (const [attributeName] of element.attributes) {
|
|
386
|
+
if (
|
|
387
|
+
!ALLOWED_EARLY_HINT_LINK_ATTRIBUTES.includes(
|
|
388
|
+
attributeName.toLowerCase()
|
|
389
|
+
)
|
|
390
|
+
) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const href = element.getAttribute("href") || undefined;
|
|
396
|
+
const rel = element.getAttribute("rel") || undefined;
|
|
397
|
+
const as = element.getAttribute("as") || undefined;
|
|
398
|
+
if (href && !href.startsWith("data:") && rel) {
|
|
399
|
+
links.push({ href, rel, as });
|
|
385
400
|
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const rel = element.getAttribute("rel") || undefined;
|
|
390
|
-
const as = element.getAttribute("as") || undefined;
|
|
391
|
-
if (href && !href.startsWith("data:") && rel) {
|
|
392
|
-
links.push({ href, rel, as });
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
})
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
)
|
|
396
404
|
.transform(clonedResponse);
|
|
397
405
|
|
|
398
406
|
// Needed to actually execute the HTMLRewriter handlers
|
|
@@ -429,7 +437,9 @@ export async function generateHandler<
|
|
|
429
437
|
}
|
|
430
438
|
}
|
|
431
439
|
} else {
|
|
432
|
-
if (setMetrics)
|
|
440
|
+
if (setMetrics) {
|
|
441
|
+
setMetrics({ earlyHintsResult: "disabled" });
|
|
442
|
+
}
|
|
433
443
|
}
|
|
434
444
|
|
|
435
445
|
// Iterate through rules and find rules that match the path
|
|
@@ -482,7 +492,19 @@ export async function generateHandler<
|
|
|
482
492
|
if (responseWithoutHeaders.status >= 500) {
|
|
483
493
|
return responseWithoutHeaders;
|
|
484
494
|
}
|
|
485
|
-
|
|
495
|
+
|
|
496
|
+
const responseWithHeaders = await attachHeaders(responseWithoutHeaders);
|
|
497
|
+
if (responseWithHeaders.status === 404) {
|
|
498
|
+
// Remove any user-controlled cache-control headers
|
|
499
|
+
// This is to prevent the footgun of potentionally caching this 404 for a long time
|
|
500
|
+
if (responseWithHeaders.headers.has("cache-control")) {
|
|
501
|
+
responseWithHeaders.headers.delete("cache-control");
|
|
502
|
+
}
|
|
503
|
+
// Add cache-control: no-store to prevent this from being cached on the responding zones.
|
|
504
|
+
responseWithHeaders.headers.append("cache-control", "no-store");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return responseWithHeaders;
|
|
486
508
|
|
|
487
509
|
async function serveAsset(
|
|
488
510
|
servingAssetEntry: AssetEntry,
|
|
@@ -675,7 +697,9 @@ export async function generateHandler<
|
|
|
675
697
|
logError(err as Error);
|
|
676
698
|
}
|
|
677
699
|
} else {
|
|
678
|
-
if (setMetrics)
|
|
700
|
+
if (setMetrics) {
|
|
701
|
+
setMetrics({ preservationCacheResult: "disabled" });
|
|
702
|
+
}
|
|
679
703
|
}
|
|
680
704
|
|
|
681
705
|
// Traverse upwards from the current path looking for a custom 404 page
|
|
@@ -748,13 +772,17 @@ export function isPreservationCacheResponseExpiring(
|
|
|
748
772
|
response: Response
|
|
749
773
|
): boolean {
|
|
750
774
|
const ageHeader = response.headers.get("age");
|
|
751
|
-
if (!ageHeader)
|
|
775
|
+
if (!ageHeader) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
752
778
|
try {
|
|
753
779
|
const age = parseInt(ageHeader);
|
|
754
780
|
// Add up to 12 hours of jitter to help prevent a
|
|
755
781
|
// thundering heard when a lot of assets expire at once.
|
|
756
782
|
const jitter = Math.floor(Math.random() * 43_200);
|
|
757
|
-
if (age > CACHE_PRESERVATION_WRITE_FREQUENCY + jitter)
|
|
783
|
+
if (age > CACHE_PRESERVATION_WRITE_FREQUENCY + jitter) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
758
786
|
} catch {
|
|
759
787
|
return false;
|
|
760
788
|
}
|
|
@@ -28,7 +28,9 @@ export const generateRulesMatcher = <T>(
|
|
|
28
28
|
rules?: Record<string, T>,
|
|
29
29
|
replacerFn: (match: T, replacements: Replacements) => T = (match) => match
|
|
30
30
|
) => {
|
|
31
|
-
if (!rules)
|
|
31
|
+
if (!rules) {
|
|
32
|
+
return () => [];
|
|
33
|
+
}
|
|
32
34
|
|
|
33
35
|
const compiledRules = Object.entries(rules)
|
|
34
36
|
.map(([rule, match]) => {
|
|
@@ -64,7 +66,7 @@ export const generateRulesMatcher = <T>(
|
|
|
64
66
|
})
|
|
65
67
|
.filter((value) => value !== undefined) as [
|
|
66
68
|
{ crossHost: boolean; regExp: RegExp },
|
|
67
|
-
T
|
|
69
|
+
T,
|
|
68
70
|
][];
|
|
69
71
|
|
|
70
72
|
return ({ request }: { request: Request }) => {
|
|
@@ -54,7 +54,9 @@ function constructRedirects({
|
|
|
54
54
|
redirects?: ParsedRedirects;
|
|
55
55
|
logger: Logger;
|
|
56
56
|
}): Metadata {
|
|
57
|
-
if (!redirects)
|
|
57
|
+
if (!redirects) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
58
60
|
|
|
59
61
|
const num_valid = redirects.rules.length;
|
|
60
62
|
const num_invalid = redirects.invalid.length;
|
|
@@ -117,7 +119,9 @@ function constructHeaders({
|
|
|
117
119
|
headers?: ParsedHeaders;
|
|
118
120
|
logger: Logger;
|
|
119
121
|
}): Metadata {
|
|
120
|
-
if (!headers)
|
|
122
|
+
if (!headers) {
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
121
125
|
|
|
122
126
|
const num_valid = headers.rules.length;
|
|
123
127
|
const num_invalid = headers.invalid.length;
|
|
@@ -167,7 +171,9 @@ function constructWebAnalytics({
|
|
|
167
171
|
webAnalyticsToken?: string;
|
|
168
172
|
logger: Logger;
|
|
169
173
|
}) {
|
|
170
|
-
if (!webAnalyticsToken)
|
|
174
|
+
if (!webAnalyticsToken) {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
171
177
|
|
|
172
178
|
return {
|
|
173
179
|
analytics: {
|
|
@@ -21,7 +21,9 @@ export function parseHeaders(input: string): ParsedHeaders {
|
|
|
21
21
|
|
|
22
22
|
for (let i = 0; i < lines.length; i++) {
|
|
23
23
|
const line = lines[i].trim();
|
|
24
|
-
if (line.length === 0 || line.startsWith("#"))
|
|
24
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
25
27
|
|
|
26
28
|
if (line.length > MAX_LINE_LENGTH) {
|
|
27
29
|
invalid.push({
|
|
@@ -26,7 +26,9 @@ export function parseRedirects(input: string): ParsedRedirects {
|
|
|
26
26
|
|
|
27
27
|
for (let i = 0; i < lines.length; i++) {
|
|
28
28
|
const line = lines[i].trim();
|
|
29
|
-
if (line.length === 0 || line.startsWith("#"))
|
|
29
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
30
32
|
|
|
31
33
|
if (line.length > MAX_LINE_LENGTH) {
|
|
32
34
|
invalid.push({
|
|
@@ -3,7 +3,9 @@ export const extractPathname = (
|
|
|
3
3
|
includeSearch: boolean,
|
|
4
4
|
includeHash: boolean
|
|
5
5
|
): string => {
|
|
6
|
-
if (!path.startsWith("/"))
|
|
6
|
+
if (!path.startsWith("/")) {
|
|
7
|
+
path = `/${path}`;
|
|
8
|
+
}
|
|
7
9
|
const url = new URL(`//${path}`, "relative://");
|
|
8
10
|
return `${url.pathname}${includeSearch ? url.search : ""}${
|
|
9
11
|
includeHash ? url.hash : ""
|
|
@@ -23,11 +25,12 @@ export const validateUrl = (
|
|
|
23
25
|
): [undefined, string] | [string, undefined] => {
|
|
24
26
|
const host = URL_REGEX.exec(token);
|
|
25
27
|
if (host && host.groups && host.groups.host) {
|
|
26
|
-
if (onlyRelative)
|
|
28
|
+
if (onlyRelative) {
|
|
27
29
|
return [
|
|
28
30
|
undefined,
|
|
29
31
|
`Only relative URLs are allowed. Skipping absolute URL ${token}.`,
|
|
30
32
|
];
|
|
33
|
+
}
|
|
31
34
|
|
|
32
35
|
if (disallowPorts && host.groups.host.match(HOST_WITH_PORT_REGEX)) {
|
|
33
36
|
return [
|
|
@@ -45,7 +48,9 @@ export const validateUrl = (
|
|
|
45
48
|
undefined,
|
|
46
49
|
];
|
|
47
50
|
} else {
|
|
48
|
-
if (!token.startsWith("/") && onlyRelative)
|
|
51
|
+
if (!token.startsWith("/") && onlyRelative) {
|
|
52
|
+
token = `/${token}`;
|
|
53
|
+
}
|
|
49
54
|
|
|
50
55
|
const path = PATH_REGEX.exec(token);
|
|
51
56
|
if (path) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/pages-shared",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.38",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cloudflare/workers-sdk.git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"check:type": "tsc",
|
|
33
|
+
"check:lint": "eslint .",
|
|
33
34
|
"test": "vitest run",
|
|
34
35
|
"test:ci": "vitest run"
|
|
35
36
|
}
|