@cloudflare/pages-shared 0.0.5 → 0.0.7
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/README.md +5 -0
- package/{src/asset-server → asset-server}/handler.ts +50 -35
- package/{src/asset-server → asset-server}/metadata.ts +0 -0
- package/{src/asset-server → asset-server}/patchUrl.ts +0 -0
- package/{src/asset-server → asset-server}/responses.ts +0 -0
- package/{src/asset-server → asset-server}/rulesEngine.ts +0 -0
- package/{src/environment-polyfills → environment-polyfills}/index.ts +0 -0
- package/{src/environment-polyfills → environment-polyfills}/miniflare.ts +0 -0
- package/{src/environment-polyfills → environment-polyfills}/types.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/constants.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/createMetadataObject.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/parseHeaders.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/parseRedirects.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/types.ts +0 -0
- package/{src/metadata-generator → metadata-generator}/validateURL.ts +0 -0
- package/package.json +7 -7
- package/dist/asset-server/handler.js +0 -395
- package/dist/asset-server/metadata.js +0 -0
- package/dist/asset-server/patchUrl.js +0 -15
- package/dist/asset-server/responses.js +0 -123
- package/dist/asset-server/rulesEngine.js +0 -44
- package/dist/environment-polyfills/index.js +0 -10
- package/dist/environment-polyfills/miniflare.js +0 -13
- package/dist/environment-polyfills/types.js +0 -1
- package/dist/index.js +0 -1
- package/dist/metadata-generator/constants.js +0 -13
- package/dist/metadata-generator/createMetadataObject.js +0 -118
- package/dist/metadata-generator/parseHeaders.js +0 -140
- package/dist/metadata-generator/parseRedirects.js +0 -100
- package/dist/metadata-generator/types.js +0 -0
- package/dist/metadata-generator/validateURL.js +0 -41
- package/src/index.ts +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# `@cloudflare/pages-shared`
|
|
2
|
+
|
|
3
|
+
This is a package that is used internally to power Wrangler and Cloudflare Pages. It contains all the code that is shared between these clients (and possibly any other in the future e.g. the dashboard).
|
|
4
|
+
|
|
5
|
+
Code should be written so that it can be run in almost any environment (e.g. Deno, Node, browser, Workers), but note that there's currently a bit of weirdness here for `asset-server`, hence the `environment-polyfills` piece.
|
|
@@ -34,6 +34,11 @@ export const HEADERS_VERSION = 2;
|
|
|
34
34
|
export const HEADERS_VERSION_V1 = 1;
|
|
35
35
|
export const ANALYTICS_VERSION = 1;
|
|
36
36
|
|
|
37
|
+
// In rolling this out, we're taking a conservative approach to only generate these Link headers from <link> elements that have these attributes.
|
|
38
|
+
// We'll ignore any <link> elements that contain other attributes (e.g. `fetchpriority`, `crossorigin` or `data-please-dont-generate-a-header`).
|
|
39
|
+
// We're not confident in browser support for all of these additional attributes, so we'll wait until we have that information before proceeding further.
|
|
40
|
+
const ALLOWED_EARLY_HINT_LINK_ATTRIBUTES = ["rel", "as", "href"];
|
|
41
|
+
|
|
37
42
|
// Takes metadata headers and "normalise" them
|
|
38
43
|
// to the latest version
|
|
39
44
|
export function normaliseHeaders(
|
|
@@ -295,41 +300,6 @@ export async function generateHandler<
|
|
|
295
300
|
...Object.fromEntries(extraHeaders.entries()),
|
|
296
301
|
});
|
|
297
302
|
|
|
298
|
-
// Iterate through rules and find rules that match the path
|
|
299
|
-
const headersMatcher = generateRulesMatcher(
|
|
300
|
-
headerRules,
|
|
301
|
-
({ set = {}, unset = [] }, replacements) => {
|
|
302
|
-
const replacedSet: Record<string, string> = {};
|
|
303
|
-
Object.keys(set).forEach((key) => {
|
|
304
|
-
replacedSet[key] = replacer(set[key], replacements);
|
|
305
|
-
});
|
|
306
|
-
return {
|
|
307
|
-
set: replacedSet,
|
|
308
|
-
unset,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
);
|
|
312
|
-
const matches = headersMatcher({ request });
|
|
313
|
-
|
|
314
|
-
// This keeps track of every header that we've set from _headers
|
|
315
|
-
// because we want to combine user declared headers but overwrite
|
|
316
|
-
// existing and extra ones
|
|
317
|
-
const setMap = new Set();
|
|
318
|
-
// Apply every matched rule in order
|
|
319
|
-
matches.forEach(({ set = {}, unset = [] }) => {
|
|
320
|
-
Object.keys(set).forEach((key) => {
|
|
321
|
-
if (setMap.has(key.toLowerCase())) {
|
|
322
|
-
headers.append(key, set[key]);
|
|
323
|
-
} else {
|
|
324
|
-
headers.set(key, set[key]);
|
|
325
|
-
setMap.add(key.toLowerCase());
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
unset.forEach((key) => {
|
|
329
|
-
headers.delete(key);
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
303
|
if (earlyHintsCache) {
|
|
334
304
|
const preEarlyHintsHeaders = new Headers(headers);
|
|
335
305
|
|
|
@@ -357,6 +327,16 @@ export async function generateHandler<
|
|
|
357
327
|
const transformedResponse = new HTMLRewriter()
|
|
358
328
|
.on("link[rel=preconnect],link[rel=preload]", {
|
|
359
329
|
element(element) {
|
|
330
|
+
for (const [attributeName] of element.attributes) {
|
|
331
|
+
if (
|
|
332
|
+
!ALLOWED_EARLY_HINT_LINK_ATTRIBUTES.includes(
|
|
333
|
+
attributeName.toLowerCase()
|
|
334
|
+
)
|
|
335
|
+
) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
360
340
|
const href = element.getAttribute("href") || undefined;
|
|
361
341
|
const rel = element.getAttribute("rel") || undefined;
|
|
362
342
|
const as = element.getAttribute("as") || undefined;
|
|
@@ -396,6 +376,41 @@ export async function generateHandler<
|
|
|
396
376
|
}
|
|
397
377
|
}
|
|
398
378
|
|
|
379
|
+
// Iterate through rules and find rules that match the path
|
|
380
|
+
const headersMatcher = generateRulesMatcher(
|
|
381
|
+
headerRules,
|
|
382
|
+
({ set = {}, unset = [] }, replacements) => {
|
|
383
|
+
const replacedSet: Record<string, string> = {};
|
|
384
|
+
Object.keys(set).forEach((key) => {
|
|
385
|
+
replacedSet[key] = replacer(set[key], replacements);
|
|
386
|
+
});
|
|
387
|
+
return {
|
|
388
|
+
set: replacedSet,
|
|
389
|
+
unset,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
const matches = headersMatcher({ request });
|
|
394
|
+
|
|
395
|
+
// This keeps track of every header that we've set from _headers
|
|
396
|
+
// because we want to combine user declared headers but overwrite
|
|
397
|
+
// existing and extra ones
|
|
398
|
+
const setMap = new Set();
|
|
399
|
+
// Apply every matched rule in order
|
|
400
|
+
matches.forEach(({ set = {}, unset = [] }) => {
|
|
401
|
+
Object.keys(set).forEach((key) => {
|
|
402
|
+
if (setMap.has(key.toLowerCase())) {
|
|
403
|
+
headers.append(key, set[key]);
|
|
404
|
+
} else {
|
|
405
|
+
headers.set(key, set[key]);
|
|
406
|
+
setMap.add(key.toLowerCase());
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
unset.forEach((key) => {
|
|
410
|
+
headers.delete(key);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
399
414
|
// https://fetch.spec.whatwg.org/#null-body-status
|
|
400
415
|
return new Response(
|
|
401
416
|
[101, 204, 205, 304].includes(response.status) ? null : response.body,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/pages-shared",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"files": [
|
|
5
5
|
"tsconfig.json",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"asset-server/**/*",
|
|
7
|
+
"environment-polyfills/**/*",
|
|
8
|
+
"metadata-generator/**/*"
|
|
8
9
|
],
|
|
9
10
|
"scripts": {
|
|
10
|
-
"build": "node scripts/build.js",
|
|
11
11
|
"check:type": "tsc"
|
|
12
12
|
},
|
|
13
13
|
"jest": {
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@miniflare/core": "2.
|
|
39
|
+
"@miniflare/core": "2.9.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@miniflare/cache": "2.
|
|
43
|
-
"@miniflare/html-rewriter": "2.
|
|
42
|
+
"@miniflare/cache": "2.9.0",
|
|
43
|
+
"@miniflare/html-rewriter": "2.9.0",
|
|
44
44
|
"@types/service-worker-mock": "^2.0.1",
|
|
45
45
|
"concurrently": "^7.3.0",
|
|
46
46
|
"glob": "^8.0.3",
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FoundResponse,
|
|
3
|
-
InternalServerErrorResponse,
|
|
4
|
-
MethodNotAllowedResponse,
|
|
5
|
-
MovedPermanentlyResponse,
|
|
6
|
-
NotAcceptableResponse,
|
|
7
|
-
NotFoundResponse,
|
|
8
|
-
NotModifiedResponse,
|
|
9
|
-
OkResponse,
|
|
10
|
-
PermanentRedirectResponse,
|
|
11
|
-
SeeOtherResponse,
|
|
12
|
-
TemporaryRedirectResponse
|
|
13
|
-
} from "./responses";
|
|
14
|
-
import { generateRulesMatcher, replacer } from "./rulesEngine";
|
|
15
|
-
export const ASSET_PRESERVATION_CACHE = "assetPreservationCache";
|
|
16
|
-
const CACHE_CONTROL_PRESERVATION = "public, s-maxage=604800";
|
|
17
|
-
export const CACHE_CONTROL_BROWSER = "public, max-age=0, must-revalidate";
|
|
18
|
-
export const REDIRECTS_VERSION = 1;
|
|
19
|
-
export const HEADERS_VERSION = 2;
|
|
20
|
-
export const HEADERS_VERSION_V1 = 1;
|
|
21
|
-
export const ANALYTICS_VERSION = 1;
|
|
22
|
-
export function normaliseHeaders(headers) {
|
|
23
|
-
if (headers.version === HEADERS_VERSION) {
|
|
24
|
-
return headers.rules;
|
|
25
|
-
} else if (headers.version === HEADERS_VERSION_V1) {
|
|
26
|
-
return Object.keys(headers.rules).reduce(
|
|
27
|
-
(acc, key) => {
|
|
28
|
-
acc[key] = {
|
|
29
|
-
set: headers.rules[key]
|
|
30
|
-
};
|
|
31
|
-
return acc;
|
|
32
|
-
},
|
|
33
|
-
{}
|
|
34
|
-
);
|
|
35
|
-
} else {
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export async function generateHandler({
|
|
40
|
-
request,
|
|
41
|
-
metadata,
|
|
42
|
-
xServerEnvHeader,
|
|
43
|
-
logError,
|
|
44
|
-
findAssetEntryForPath,
|
|
45
|
-
getAssetKey,
|
|
46
|
-
negotiateContent,
|
|
47
|
-
fetchAsset,
|
|
48
|
-
generateNotFoundResponse = async (notFoundRequest, notFoundFindAssetEntryForPath, notFoundServeAsset) => {
|
|
49
|
-
let assetEntry;
|
|
50
|
-
if (assetEntry = await notFoundFindAssetEntryForPath("/index.html")) {
|
|
51
|
-
return notFoundServeAsset(assetEntry, { preserve: false });
|
|
52
|
-
}
|
|
53
|
-
return new NotFoundResponse();
|
|
54
|
-
},
|
|
55
|
-
attachAdditionalHeaders = () => {
|
|
56
|
-
},
|
|
57
|
-
caches,
|
|
58
|
-
waitUntil
|
|
59
|
-
}) {
|
|
60
|
-
const url = new URL(request.url);
|
|
61
|
-
const { protocol, host, search } = url;
|
|
62
|
-
let { pathname } = url;
|
|
63
|
-
const earlyHintsCache = metadata.deploymentId ? await caches?.open(`eh:${metadata.deploymentId}`) : void 0;
|
|
64
|
-
const headerRules = metadata.headers ? normaliseHeaders(metadata.headers) : {};
|
|
65
|
-
const staticRules = metadata.redirects?.version === REDIRECTS_VERSION ? metadata.redirects.staticRules || {} : {};
|
|
66
|
-
const staticRedirectsMatcher = () => {
|
|
67
|
-
const withHostMatch = staticRules[`https://${host}${pathname}`];
|
|
68
|
-
const withoutHostMatch = staticRules[pathname];
|
|
69
|
-
if (withHostMatch && withoutHostMatch) {
|
|
70
|
-
if (withHostMatch.lineNumber < withoutHostMatch.lineNumber) {
|
|
71
|
-
return withHostMatch;
|
|
72
|
-
} else {
|
|
73
|
-
return withoutHostMatch;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return withHostMatch || withoutHostMatch;
|
|
77
|
-
};
|
|
78
|
-
const generateRedirectsMatcher = () => generateRulesMatcher(
|
|
79
|
-
metadata.redirects?.version === REDIRECTS_VERSION ? metadata.redirects.rules : {},
|
|
80
|
-
({ status, to }, replacements) => ({
|
|
81
|
-
status,
|
|
82
|
-
to: replacer(to, replacements)
|
|
83
|
-
})
|
|
84
|
-
);
|
|
85
|
-
let assetEntry;
|
|
86
|
-
async function generateResponse() {
|
|
87
|
-
const match = staticRedirectsMatcher() || generateRedirectsMatcher()({ request })[0];
|
|
88
|
-
if (match) {
|
|
89
|
-
const { status, to } = match;
|
|
90
|
-
const destination = new URL(to, request.url);
|
|
91
|
-
const location = destination.origin === new URL(request.url).origin ? `${destination.pathname}${destination.search || search}${destination.hash}` : `${destination.href}${destination.search ? "" : search}${destination.hash}`;
|
|
92
|
-
switch (status) {
|
|
93
|
-
case 301:
|
|
94
|
-
return new MovedPermanentlyResponse(location);
|
|
95
|
-
case 303:
|
|
96
|
-
return new SeeOtherResponse(location);
|
|
97
|
-
case 307:
|
|
98
|
-
return new TemporaryRedirectResponse(location);
|
|
99
|
-
case 308:
|
|
100
|
-
return new PermanentRedirectResponse(location);
|
|
101
|
-
case 302:
|
|
102
|
-
default:
|
|
103
|
-
return new FoundResponse(location);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (!request.method.match(/^(get|head)$/i)) {
|
|
107
|
-
return new MethodNotAllowedResponse();
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
pathname = globalThis.decodeURIComponent(pathname);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
}
|
|
113
|
-
if (pathname.endsWith("/")) {
|
|
114
|
-
if (assetEntry = await findAssetEntryForPath(`${pathname}index.html`)) {
|
|
115
|
-
return serveAsset(assetEntry);
|
|
116
|
-
} else if (pathname.endsWith("/index/")) {
|
|
117
|
-
return new PermanentRedirectResponse(
|
|
118
|
-
`/${pathname.slice(1, -"index/".length)}${search}`
|
|
119
|
-
);
|
|
120
|
-
} else if (assetEntry = await findAssetEntryForPath(
|
|
121
|
-
`${pathname.replace(/\/$/, ".html")}`
|
|
122
|
-
)) {
|
|
123
|
-
return new PermanentRedirectResponse(
|
|
124
|
-
`/${pathname.slice(1, -1)}${search}`
|
|
125
|
-
);
|
|
126
|
-
} else {
|
|
127
|
-
return notFound();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (assetEntry = await findAssetEntryForPath(pathname)) {
|
|
131
|
-
if (pathname.endsWith(".html")) {
|
|
132
|
-
const extensionlessPath = pathname.slice(0, -".html".length);
|
|
133
|
-
if (extensionlessPath.endsWith("/index")) {
|
|
134
|
-
return new PermanentRedirectResponse(
|
|
135
|
-
`${extensionlessPath.replace(/\/index$/, "/")}${search}`
|
|
136
|
-
);
|
|
137
|
-
} else if (await findAssetEntryForPath(extensionlessPath) || extensionlessPath === "/") {
|
|
138
|
-
return serveAsset(assetEntry);
|
|
139
|
-
} else {
|
|
140
|
-
return new PermanentRedirectResponse(`${extensionlessPath}${search}`);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
return serveAsset(assetEntry);
|
|
144
|
-
}
|
|
145
|
-
} else if (pathname.endsWith("/index")) {
|
|
146
|
-
return new PermanentRedirectResponse(
|
|
147
|
-
`/${pathname.slice(1, -"index".length)}${search}`
|
|
148
|
-
);
|
|
149
|
-
} else if (assetEntry = await findAssetEntryForPath(`${pathname}.html`)) {
|
|
150
|
-
return serveAsset(assetEntry);
|
|
151
|
-
} else if (hasFileExtension(pathname)) {
|
|
152
|
-
return notFound();
|
|
153
|
-
}
|
|
154
|
-
if (assetEntry = await findAssetEntryForPath(`${pathname}/index.html`)) {
|
|
155
|
-
return new PermanentRedirectResponse(`${pathname}/${search}`);
|
|
156
|
-
} else {
|
|
157
|
-
return notFound();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async function attachHeaders(response) {
|
|
161
|
-
const existingHeaders = new Headers(response.headers);
|
|
162
|
-
const extraHeaders = new Headers({
|
|
163
|
-
"access-control-allow-origin": "*",
|
|
164
|
-
"referrer-policy": "strict-origin-when-cross-origin",
|
|
165
|
-
...existingHeaders.has("content-type") ? { "x-content-type-options": "nosniff" } : {}
|
|
166
|
-
});
|
|
167
|
-
const headers = new Headers({
|
|
168
|
-
...Object.fromEntries(existingHeaders.entries()),
|
|
169
|
-
...Object.fromEntries(extraHeaders.entries())
|
|
170
|
-
});
|
|
171
|
-
const headersMatcher = generateRulesMatcher(
|
|
172
|
-
headerRules,
|
|
173
|
-
({ set = {}, unset = [] }, replacements) => {
|
|
174
|
-
const replacedSet = {};
|
|
175
|
-
Object.keys(set).forEach((key) => {
|
|
176
|
-
replacedSet[key] = replacer(set[key], replacements);
|
|
177
|
-
});
|
|
178
|
-
return {
|
|
179
|
-
set: replacedSet,
|
|
180
|
-
unset
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
const matches = headersMatcher({ request });
|
|
185
|
-
const setMap = /* @__PURE__ */ new Set();
|
|
186
|
-
matches.forEach(({ set = {}, unset = [] }) => {
|
|
187
|
-
Object.keys(set).forEach((key) => {
|
|
188
|
-
if (setMap.has(key.toLowerCase())) {
|
|
189
|
-
headers.append(key, set[key]);
|
|
190
|
-
} else {
|
|
191
|
-
headers.set(key, set[key]);
|
|
192
|
-
setMap.add(key.toLowerCase());
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
unset.forEach((key) => {
|
|
196
|
-
headers.delete(key);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
if (earlyHintsCache) {
|
|
200
|
-
const preEarlyHintsHeaders = new Headers(headers);
|
|
201
|
-
const earlyHintsCacheKey = `${protocol}//${host}${pathname}`;
|
|
202
|
-
const earlyHintsResponse = await earlyHintsCache.match(
|
|
203
|
-
earlyHintsCacheKey
|
|
204
|
-
);
|
|
205
|
-
if (earlyHintsResponse) {
|
|
206
|
-
const earlyHintsLinkHeader = earlyHintsResponse.headers.get("Link");
|
|
207
|
-
if (earlyHintsLinkHeader) {
|
|
208
|
-
headers.set("Link", earlyHintsLinkHeader);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const clonedResponse = response.clone();
|
|
212
|
-
if (waitUntil) {
|
|
213
|
-
waitUntil(
|
|
214
|
-
(async () => {
|
|
215
|
-
try {
|
|
216
|
-
const links = [];
|
|
217
|
-
const transformedResponse = new HTMLRewriter().on("link[rel=preconnect],link[rel=preload]", {
|
|
218
|
-
element(element) {
|
|
219
|
-
const href = element.getAttribute("href") || void 0;
|
|
220
|
-
const rel = element.getAttribute("rel") || void 0;
|
|
221
|
-
const as = element.getAttribute("as") || void 0;
|
|
222
|
-
if (href && !href.startsWith("data:") && rel) {
|
|
223
|
-
links.push({ href, rel, as });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}).transform(clonedResponse);
|
|
227
|
-
await transformedResponse.text();
|
|
228
|
-
links.forEach(({ href, rel, as }) => {
|
|
229
|
-
let link = `<${href}>; rel="${rel}"`;
|
|
230
|
-
if (as) {
|
|
231
|
-
link += `; as=${as}`;
|
|
232
|
-
}
|
|
233
|
-
preEarlyHintsHeaders.append("Link", link);
|
|
234
|
-
});
|
|
235
|
-
const linkHeader = preEarlyHintsHeaders.get("Link");
|
|
236
|
-
if (linkHeader) {
|
|
237
|
-
await earlyHintsCache.put(
|
|
238
|
-
earlyHintsCacheKey,
|
|
239
|
-
new Response(null, { headers: { Link: linkHeader } })
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
} catch (err) {
|
|
243
|
-
}
|
|
244
|
-
})()
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return new Response(
|
|
249
|
-
[101, 204, 205, 304].includes(response.status) ? null : response.body,
|
|
250
|
-
{
|
|
251
|
-
headers,
|
|
252
|
-
status: response.status,
|
|
253
|
-
statusText: response.statusText
|
|
254
|
-
}
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
return await attachHeaders(await generateResponse());
|
|
258
|
-
async function serveAsset(servingAssetEntry, options = { preserve: true }) {
|
|
259
|
-
let content;
|
|
260
|
-
try {
|
|
261
|
-
content = negotiateContent(request, servingAssetEntry);
|
|
262
|
-
} catch (err) {
|
|
263
|
-
return new NotAcceptableResponse();
|
|
264
|
-
}
|
|
265
|
-
const assetKey = getAssetKey(servingAssetEntry, content);
|
|
266
|
-
const etag = `"${assetKey}"`;
|
|
267
|
-
const weakEtag = `W/${etag}`;
|
|
268
|
-
const ifNoneMatch = request.headers.get("if-none-match");
|
|
269
|
-
if (ifNoneMatch === weakEtag || ifNoneMatch === etag) {
|
|
270
|
-
return new NotModifiedResponse();
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
const asset = await fetchAsset(assetKey);
|
|
274
|
-
const headers = {
|
|
275
|
-
etag,
|
|
276
|
-
"content-type": asset.contentType
|
|
277
|
-
};
|
|
278
|
-
let encodeBody = "automatic";
|
|
279
|
-
if (xServerEnvHeader) {
|
|
280
|
-
headers["x-server-env"] = xServerEnvHeader;
|
|
281
|
-
}
|
|
282
|
-
if (content.encoding) {
|
|
283
|
-
encodeBody = "manual";
|
|
284
|
-
headers["cache-control"] = "no-transform";
|
|
285
|
-
headers["content-encoding"] = content.encoding;
|
|
286
|
-
}
|
|
287
|
-
const response = new OkResponse(
|
|
288
|
-
request.method === "HEAD" ? null : asset.body,
|
|
289
|
-
{
|
|
290
|
-
headers,
|
|
291
|
-
encodeBody
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
if (isCacheable(request)) {
|
|
295
|
-
response.headers.append("cache-control", CACHE_CONTROL_BROWSER);
|
|
296
|
-
}
|
|
297
|
-
attachAdditionalHeaders(response, content, servingAssetEntry, asset);
|
|
298
|
-
if (isPreview(new URL(request.url))) {
|
|
299
|
-
response.headers.set("x-robots-tag", "noindex");
|
|
300
|
-
}
|
|
301
|
-
if (options.preserve) {
|
|
302
|
-
const preservedResponse = new Response(
|
|
303
|
-
[101, 204, 205, 304].includes(response.status) ? null : response.clone().body,
|
|
304
|
-
response
|
|
305
|
-
);
|
|
306
|
-
preservedResponse.headers.set(
|
|
307
|
-
"cache-control",
|
|
308
|
-
CACHE_CONTROL_PRESERVATION
|
|
309
|
-
);
|
|
310
|
-
preservedResponse.headers.set("x-robots-tag", "noindex");
|
|
311
|
-
if (waitUntil && caches) {
|
|
312
|
-
waitUntil(
|
|
313
|
-
caches.open(ASSET_PRESERVATION_CACHE).then(
|
|
314
|
-
(assetPreservationCache) => assetPreservationCache.put(request.url, preservedResponse)
|
|
315
|
-
).catch((err) => {
|
|
316
|
-
logError(err);
|
|
317
|
-
})
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (asset.contentType.startsWith("text/html") && metadata.analytics?.version === ANALYTICS_VERSION) {
|
|
322
|
-
return new HTMLRewriter().on("body", {
|
|
323
|
-
element(e) {
|
|
324
|
-
e.append(
|
|
325
|
-
`<!-- Cloudflare Pages Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "${metadata.analytics?.token}"}'><\/script><!-- Cloudflare Pages Analytics -->`,
|
|
326
|
-
{ html: true }
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
}).transform(response);
|
|
330
|
-
}
|
|
331
|
-
return response;
|
|
332
|
-
} catch (err) {
|
|
333
|
-
logError(err);
|
|
334
|
-
return new InternalServerErrorResponse(err);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
async function notFound() {
|
|
338
|
-
if (caches) {
|
|
339
|
-
const assetPreservationCache = await caches.open(
|
|
340
|
-
ASSET_PRESERVATION_CACHE
|
|
341
|
-
);
|
|
342
|
-
const preservedResponse = await assetPreservationCache.match(request.url);
|
|
343
|
-
if (preservedResponse) {
|
|
344
|
-
return preservedResponse;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
let cwd = pathname;
|
|
348
|
-
while (cwd) {
|
|
349
|
-
cwd = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
350
|
-
if (assetEntry = await findAssetEntryForPath(`${cwd}/404.html`)) {
|
|
351
|
-
let content;
|
|
352
|
-
try {
|
|
353
|
-
content = negotiateContent(request, assetEntry);
|
|
354
|
-
} catch (err) {
|
|
355
|
-
return new NotAcceptableResponse();
|
|
356
|
-
}
|
|
357
|
-
const assetKey = getAssetKey(assetEntry, content);
|
|
358
|
-
try {
|
|
359
|
-
const { body, contentType } = await fetchAsset(assetKey);
|
|
360
|
-
const response = new NotFoundResponse(body);
|
|
361
|
-
response.headers.set("content-type", contentType);
|
|
362
|
-
return response;
|
|
363
|
-
} catch (err) {
|
|
364
|
-
logError(err);
|
|
365
|
-
return new InternalServerErrorResponse(err);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return await generateNotFoundResponse(
|
|
370
|
-
request,
|
|
371
|
-
findAssetEntryForPath,
|
|
372
|
-
serveAsset
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
export function parseQualityWeightedList(list = "") {
|
|
377
|
-
const items = {};
|
|
378
|
-
list.replace(/\s/g, "").split(",").forEach((el) => {
|
|
379
|
-
const [item, weight] = el.split(";q=");
|
|
380
|
-
items[item] = weight ? parseFloat(weight) : 1;
|
|
381
|
-
});
|
|
382
|
-
return items;
|
|
383
|
-
}
|
|
384
|
-
function isCacheable(request) {
|
|
385
|
-
return !request.headers.has("authorization") && !request.headers.has("range");
|
|
386
|
-
}
|
|
387
|
-
function hasFileExtension(path) {
|
|
388
|
-
return /\/.+\.[a-z0-9]+$/i.test(path);
|
|
389
|
-
}
|
|
390
|
-
function isPreview(url) {
|
|
391
|
-
if (url.hostname.endsWith(".pages.dev")) {
|
|
392
|
-
return url.hostname.split(".").length > 3 ? true : false;
|
|
393
|
-
}
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
File without changes
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
globalThis.URL = function(globalURL) {
|
|
2
|
-
PatchedURL.prototype = globalURL.prototype;
|
|
3
|
-
PatchedURL.createObjectURL = globalURL.createObjectURL;
|
|
4
|
-
PatchedURL.revokeObjectURL = globalURL.revokeObjectURL;
|
|
5
|
-
return PatchedURL;
|
|
6
|
-
function PatchedURL(input, base) {
|
|
7
|
-
const url = new globalURL(encodeURI(input), base);
|
|
8
|
-
return new Proxy(url, {
|
|
9
|
-
get(target, prop) {
|
|
10
|
-
return globalThis.decodeURIComponent(target[prop]);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
}(URL);
|
|
15
|
-
export {};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
function mergeHeaders(base, extra) {
|
|
2
|
-
base = new Headers(base ?? {});
|
|
3
|
-
extra = new Headers(extra ?? {});
|
|
4
|
-
return new Headers({
|
|
5
|
-
...Object.fromEntries(base.entries()),
|
|
6
|
-
...Object.fromEntries(extra.entries())
|
|
7
|
-
});
|
|
8
|
-
}
|
|
9
|
-
export class OkResponse extends Response {
|
|
10
|
-
constructor(...[body, init]) {
|
|
11
|
-
super(body, {
|
|
12
|
-
...init,
|
|
13
|
-
status: 200,
|
|
14
|
-
statusText: "OK"
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export class MovedPermanentlyResponse extends Response {
|
|
19
|
-
constructor(location, init) {
|
|
20
|
-
super(`Redirecting to ${location}`, {
|
|
21
|
-
...init,
|
|
22
|
-
status: 301,
|
|
23
|
-
statusText: "Moved Permanently",
|
|
24
|
-
headers: mergeHeaders(init?.headers, {
|
|
25
|
-
location
|
|
26
|
-
})
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export class FoundResponse extends Response {
|
|
31
|
-
constructor(location, init) {
|
|
32
|
-
super(`Redirecting to ${location}`, {
|
|
33
|
-
...init,
|
|
34
|
-
status: 302,
|
|
35
|
-
statusText: "Found",
|
|
36
|
-
headers: mergeHeaders(init?.headers, {
|
|
37
|
-
location
|
|
38
|
-
})
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export class NotModifiedResponse extends Response {
|
|
43
|
-
constructor(...[_body, _init]) {
|
|
44
|
-
super(void 0, {
|
|
45
|
-
status: 304,
|
|
46
|
-
statusText: "Not Modified"
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export class PermanentRedirectResponse extends Response {
|
|
51
|
-
constructor(location, init) {
|
|
52
|
-
super(void 0, {
|
|
53
|
-
...init,
|
|
54
|
-
status: 308,
|
|
55
|
-
statusText: "Permanent Redirect",
|
|
56
|
-
headers: mergeHeaders(init?.headers, {
|
|
57
|
-
location
|
|
58
|
-
})
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export class NotFoundResponse extends Response {
|
|
63
|
-
constructor(...[body, init]) {
|
|
64
|
-
super(body, {
|
|
65
|
-
...init,
|
|
66
|
-
status: 404,
|
|
67
|
-
statusText: "Not Found"
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
export class MethodNotAllowedResponse extends Response {
|
|
72
|
-
constructor(...[body, init]) {
|
|
73
|
-
super(body, {
|
|
74
|
-
...init,
|
|
75
|
-
status: 405,
|
|
76
|
-
statusText: "Method Not Allowed"
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
export class NotAcceptableResponse extends Response {
|
|
81
|
-
constructor(...[body, init]) {
|
|
82
|
-
super(body, {
|
|
83
|
-
...init,
|
|
84
|
-
status: 406,
|
|
85
|
-
statusText: "Not Acceptable"
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
export class InternalServerErrorResponse extends Response {
|
|
90
|
-
constructor(err, init) {
|
|
91
|
-
let body = void 0;
|
|
92
|
-
if (globalThis.DEBUG) {
|
|
93
|
-
body = `${err.message}
|
|
94
|
-
|
|
95
|
-
${err.stack}`;
|
|
96
|
-
}
|
|
97
|
-
super(body, {
|
|
98
|
-
...init,
|
|
99
|
-
status: 500,
|
|
100
|
-
statusText: "Internal Server Error"
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
export class SeeOtherResponse extends Response {
|
|
105
|
-
constructor(location, init) {
|
|
106
|
-
super(`Redirecting to ${location}`, {
|
|
107
|
-
...init,
|
|
108
|
-
status: 303,
|
|
109
|
-
statusText: "See Other",
|
|
110
|
-
headers: mergeHeaders(init?.headers, { location })
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
export class TemporaryRedirectResponse extends Response {
|
|
115
|
-
constructor(location, init) {
|
|
116
|
-
super(`Redirecting to ${location}`, {
|
|
117
|
-
...init,
|
|
118
|
-
status: 307,
|
|
119
|
-
statusText: "Temporary Redirect",
|
|
120
|
-
headers: mergeHeaders(init?.headers, { location })
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const ESCAPE_REGEX_CHARACTERS = /[-/\\^$*+?.()|[\]{}]/g;
|
|
2
|
-
const escapeRegex = (str) => {
|
|
3
|
-
return str.replace(ESCAPE_REGEX_CHARACTERS, "\\$&");
|
|
4
|
-
};
|
|
5
|
-
const HOST_PLACEHOLDER_REGEX = /(?<=^https:\\\/\\\/[^/]*?):([^\\]+)(?=\\)/g;
|
|
6
|
-
const PLACEHOLDER_REGEX = /:(\w+)/g;
|
|
7
|
-
export const replacer = (str, replacements) => {
|
|
8
|
-
for (const [replacement, value] of Object.entries(replacements)) {
|
|
9
|
-
str = str.replaceAll(`:${replacement}`, value);
|
|
10
|
-
}
|
|
11
|
-
return str;
|
|
12
|
-
};
|
|
13
|
-
export const generateRulesMatcher = (rules, replacerFn = (match) => match) => {
|
|
14
|
-
if (!rules)
|
|
15
|
-
return () => [];
|
|
16
|
-
const compiledRules = Object.entries(rules).map(([rule, match]) => {
|
|
17
|
-
const crossHost = rule.startsWith("https://");
|
|
18
|
-
rule = rule.split("*").map(escapeRegex).join("(?<splat>.*)");
|
|
19
|
-
const host_matches = rule.matchAll(HOST_PLACEHOLDER_REGEX);
|
|
20
|
-
for (const host_match of host_matches) {
|
|
21
|
-
rule = rule.split(host_match[0]).join(`(?<${host_match[1]}>[^/.]+)`);
|
|
22
|
-
}
|
|
23
|
-
const path_matches = rule.matchAll(PLACEHOLDER_REGEX);
|
|
24
|
-
for (const path_match of path_matches) {
|
|
25
|
-
rule = rule.split(path_match[0]).join(`(?<${path_match[1]}>[^/]+)`);
|
|
26
|
-
}
|
|
27
|
-
rule = "^" + rule + "$";
|
|
28
|
-
try {
|
|
29
|
-
const regExp = new RegExp(rule);
|
|
30
|
-
return [{ crossHost, regExp }, match];
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
33
|
-
}).filter((value) => value !== void 0);
|
|
34
|
-
return ({ request }) => {
|
|
35
|
-
const { pathname, host } = new URL(request.url);
|
|
36
|
-
return compiledRules.map(([{ crossHost, regExp }, match]) => {
|
|
37
|
-
const test = crossHost ? `https://${host}${pathname}` : pathname;
|
|
38
|
-
const result = regExp.exec(test);
|
|
39
|
-
if (result) {
|
|
40
|
-
return replacerFn(match, result.groups || {});
|
|
41
|
-
}
|
|
42
|
-
}).filter((value) => value !== void 0);
|
|
43
|
-
};
|
|
44
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
fetch as miniflareFetch,
|
|
3
|
-
Headers as MiniflareHeaders,
|
|
4
|
-
Request as MiniflareRequest,
|
|
5
|
-
Response as MiniflareResponse
|
|
6
|
-
} from "@miniflare/core";
|
|
7
|
-
import { polyfill } from ".";
|
|
8
|
-
polyfill({
|
|
9
|
-
fetch: miniflareFetch,
|
|
10
|
-
Headers: MiniflareHeaders,
|
|
11
|
-
Request: MiniflareRequest,
|
|
12
|
-
Response: MiniflareResponse
|
|
13
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const REDIRECTS_VERSION = 1;
|
|
2
|
-
export const HEADERS_VERSION = 2;
|
|
3
|
-
export const ANALYTICS_VERSION = 1;
|
|
4
|
-
export const ROUTES_JSON_VERSION = 1;
|
|
5
|
-
export const PERMITTED_STATUS_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
6
|
-
export const HEADER_SEPARATOR = ":";
|
|
7
|
-
export const MAX_LINE_LENGTH = 2e3;
|
|
8
|
-
export const MAX_HEADER_RULES = 100;
|
|
9
|
-
export const MAX_DYNAMIC_REDIRECT_RULES = 100;
|
|
10
|
-
export const MAX_STATIC_REDIRECT_RULES = 2e3;
|
|
11
|
-
export const UNSET_OPERATOR = "! ";
|
|
12
|
-
export const SPLAT_REGEX = /\*/g;
|
|
13
|
-
export const PLACEHOLDER_REGEX = /:\w+/g;
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ANALYTICS_VERSION,
|
|
3
|
-
REDIRECTS_VERSION,
|
|
4
|
-
HEADERS_VERSION,
|
|
5
|
-
SPLAT_REGEX,
|
|
6
|
-
PLACEHOLDER_REGEX
|
|
7
|
-
} from "./constants";
|
|
8
|
-
export function createMetadataObject({
|
|
9
|
-
redirects,
|
|
10
|
-
headers,
|
|
11
|
-
webAnalyticsToken,
|
|
12
|
-
deploymentId,
|
|
13
|
-
logger = (_message) => {
|
|
14
|
-
}
|
|
15
|
-
}) {
|
|
16
|
-
return {
|
|
17
|
-
...constructRedirects({ redirects, logger }),
|
|
18
|
-
...constructHeaders({ headers, logger }),
|
|
19
|
-
...constructWebAnalytics({ webAnalyticsToken, logger }),
|
|
20
|
-
deploymentId
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function constructRedirects({
|
|
24
|
-
redirects,
|
|
25
|
-
logger
|
|
26
|
-
}) {
|
|
27
|
-
if (!redirects)
|
|
28
|
-
return {};
|
|
29
|
-
const num_valid = redirects.rules.length;
|
|
30
|
-
const num_invalid = redirects.invalid.length;
|
|
31
|
-
logger(
|
|
32
|
-
`Parsed ${num_valid} valid redirect rule${num_valid === 1 ? "" : "s"}.`
|
|
33
|
-
);
|
|
34
|
-
if (num_invalid > 0) {
|
|
35
|
-
logger(`Found invalid redirect lines:`);
|
|
36
|
-
for (const { line, lineNumber, message } of redirects.invalid) {
|
|
37
|
-
if (line)
|
|
38
|
-
logger(` - ${lineNumber ? `#${lineNumber}: ` : ""}${line}`);
|
|
39
|
-
logger(` ${message}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (num_valid === 0) {
|
|
43
|
-
return {};
|
|
44
|
-
}
|
|
45
|
-
const staticRedirects = {};
|
|
46
|
-
const dynamicRedirects = {};
|
|
47
|
-
let canCreateStaticRule = true;
|
|
48
|
-
for (const rule of redirects.rules) {
|
|
49
|
-
if (!rule.from.match(SPLAT_REGEX) && !rule.from.match(PLACEHOLDER_REGEX)) {
|
|
50
|
-
if (canCreateStaticRule) {
|
|
51
|
-
staticRedirects[rule.from] = { status: rule.status, to: rule.to };
|
|
52
|
-
continue;
|
|
53
|
-
} else {
|
|
54
|
-
logger(
|
|
55
|
-
`Info: the redirect rule ${rule.from} \u2192 ${rule.status} ${rule.to} could be made more performant by bringing it above any lines with splats or placeholders.`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
dynamicRedirects[rule.from] = { status: rule.status, to: rule.to };
|
|
60
|
-
canCreateStaticRule = false;
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
redirects: {
|
|
64
|
-
version: REDIRECTS_VERSION,
|
|
65
|
-
staticRules: staticRedirects,
|
|
66
|
-
rules: dynamicRedirects
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function constructHeaders({
|
|
71
|
-
headers,
|
|
72
|
-
logger
|
|
73
|
-
}) {
|
|
74
|
-
if (!headers)
|
|
75
|
-
return {};
|
|
76
|
-
const num_valid = headers.rules.length;
|
|
77
|
-
const num_invalid = headers.invalid.length;
|
|
78
|
-
logger(`Parsed ${num_valid} valid header rule${num_valid === 1 ? "" : "s"}.`);
|
|
79
|
-
if (num_invalid > 0) {
|
|
80
|
-
logger(`Found invalid header lines:`);
|
|
81
|
-
for (const { line, lineNumber, message } of headers.invalid) {
|
|
82
|
-
if (line)
|
|
83
|
-
logger(` - ${lineNumber ? `#${lineNumber}: ` : ""} ${line}`);
|
|
84
|
-
logger(` ${message}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (num_valid === 0) {
|
|
88
|
-
return {};
|
|
89
|
-
}
|
|
90
|
-
const rules = {};
|
|
91
|
-
for (const rule of headers.rules) {
|
|
92
|
-
rules[rule.path] = {};
|
|
93
|
-
if (Object.keys(rule.headers).length) {
|
|
94
|
-
rules[rule.path].set = rule.headers;
|
|
95
|
-
}
|
|
96
|
-
if (rule.unsetHeaders.length) {
|
|
97
|
-
rules[rule.path].unset = rule.unsetHeaders;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
headers: {
|
|
102
|
-
version: HEADERS_VERSION,
|
|
103
|
-
rules
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function constructWebAnalytics({
|
|
108
|
-
webAnalyticsToken
|
|
109
|
-
}) {
|
|
110
|
-
if (!webAnalyticsToken)
|
|
111
|
-
return {};
|
|
112
|
-
return {
|
|
113
|
-
analytics: {
|
|
114
|
-
version: ANALYTICS_VERSION,
|
|
115
|
-
token: webAnalyticsToken
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MAX_LINE_LENGTH,
|
|
3
|
-
MAX_HEADER_RULES,
|
|
4
|
-
HEADER_SEPARATOR,
|
|
5
|
-
UNSET_OPERATOR
|
|
6
|
-
} from "./constants";
|
|
7
|
-
import { validateUrl } from "./validateURL";
|
|
8
|
-
const LINE_IS_PROBABLY_A_PATH = new RegExp(/^([^\s]+:\/\/|^\/)/);
|
|
9
|
-
export function parseHeaders(input) {
|
|
10
|
-
const lines = input.split("\n");
|
|
11
|
-
const rules = [];
|
|
12
|
-
const invalid = [];
|
|
13
|
-
let rule = void 0;
|
|
14
|
-
for (let i = 0; i < lines.length; i++) {
|
|
15
|
-
const line = lines[i].trim();
|
|
16
|
-
if (line.length === 0 || line.startsWith("#"))
|
|
17
|
-
continue;
|
|
18
|
-
if (line.length > MAX_LINE_LENGTH) {
|
|
19
|
-
invalid.push({
|
|
20
|
-
message: `Ignoring line ${i + 1} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`
|
|
21
|
-
});
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (LINE_IS_PROBABLY_A_PATH.test(line)) {
|
|
25
|
-
if (rules.length >= MAX_HEADER_RULES) {
|
|
26
|
-
invalid.push({
|
|
27
|
-
message: `Maximum number of rules supported is ${MAX_HEADER_RULES}. Skipping remaining ${lines.length - i} lines of file.`
|
|
28
|
-
});
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
if (rule) {
|
|
32
|
-
if (isValidRule(rule)) {
|
|
33
|
-
rules.push({
|
|
34
|
-
path: rule.path,
|
|
35
|
-
headers: rule.headers,
|
|
36
|
-
unsetHeaders: rule.unsetHeaders
|
|
37
|
-
});
|
|
38
|
-
} else {
|
|
39
|
-
invalid.push({
|
|
40
|
-
line: rule.line,
|
|
41
|
-
lineNumber: i + 1,
|
|
42
|
-
message: "No headers specified"
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const [path, pathError] = validateUrl(line);
|
|
47
|
-
if (pathError) {
|
|
48
|
-
invalid.push({
|
|
49
|
-
line,
|
|
50
|
-
lineNumber: i + 1,
|
|
51
|
-
message: pathError
|
|
52
|
-
});
|
|
53
|
-
rule = void 0;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
rule = {
|
|
57
|
-
path,
|
|
58
|
-
line,
|
|
59
|
-
headers: {},
|
|
60
|
-
unsetHeaders: []
|
|
61
|
-
};
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (!line.includes(HEADER_SEPARATOR)) {
|
|
65
|
-
if (!rule) {
|
|
66
|
-
invalid.push({
|
|
67
|
-
line,
|
|
68
|
-
lineNumber: i + 1,
|
|
69
|
-
message: "Expected a path beginning with at least one forward-slash"
|
|
70
|
-
});
|
|
71
|
-
} else {
|
|
72
|
-
if (line.trim().startsWith(UNSET_OPERATOR)) {
|
|
73
|
-
rule.unsetHeaders.push(line.trim().replace(UNSET_OPERATOR, ""));
|
|
74
|
-
} else {
|
|
75
|
-
invalid.push({
|
|
76
|
-
line,
|
|
77
|
-
lineNumber: i + 1,
|
|
78
|
-
message: "Expected a colon-separated header pair (e.g. name: value)"
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const [rawName, ...rawValue] = line.split(HEADER_SEPARATOR);
|
|
85
|
-
const name = rawName.trim().toLowerCase();
|
|
86
|
-
if (name.includes(" ")) {
|
|
87
|
-
invalid.push({
|
|
88
|
-
line,
|
|
89
|
-
lineNumber: i + 1,
|
|
90
|
-
message: "Header name cannot include spaces"
|
|
91
|
-
});
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const value = rawValue.join(HEADER_SEPARATOR).trim();
|
|
95
|
-
if (name === "") {
|
|
96
|
-
invalid.push({
|
|
97
|
-
line,
|
|
98
|
-
lineNumber: i + 1,
|
|
99
|
-
message: "No header name specified"
|
|
100
|
-
});
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (value === "") {
|
|
104
|
-
invalid.push({
|
|
105
|
-
line,
|
|
106
|
-
lineNumber: i + 1,
|
|
107
|
-
message: "No header value specified"
|
|
108
|
-
});
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
if (!rule) {
|
|
112
|
-
invalid.push({
|
|
113
|
-
line,
|
|
114
|
-
lineNumber: i + 1,
|
|
115
|
-
message: `Path should come before header (${name}: ${value})`
|
|
116
|
-
});
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const existingValues = rule.headers[name];
|
|
120
|
-
rule.headers[name] = existingValues ? `${existingValues}, ${value}` : value;
|
|
121
|
-
}
|
|
122
|
-
if (rule) {
|
|
123
|
-
if (isValidRule(rule)) {
|
|
124
|
-
rules.push({
|
|
125
|
-
path: rule.path,
|
|
126
|
-
headers: rule.headers,
|
|
127
|
-
unsetHeaders: rule.unsetHeaders
|
|
128
|
-
});
|
|
129
|
-
} else {
|
|
130
|
-
invalid.push({ line: rule.line, message: "No headers specified" });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
rules,
|
|
135
|
-
invalid
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function isValidRule(rule) {
|
|
139
|
-
return Object.keys(rule.headers).length > 0 || rule.unsetHeaders.length > 0;
|
|
140
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MAX_LINE_LENGTH,
|
|
3
|
-
MAX_DYNAMIC_REDIRECT_RULES,
|
|
4
|
-
MAX_STATIC_REDIRECT_RULES,
|
|
5
|
-
PERMITTED_STATUS_CODES,
|
|
6
|
-
SPLAT_REGEX,
|
|
7
|
-
PLACEHOLDER_REGEX
|
|
8
|
-
} from "./constants";
|
|
9
|
-
import { validateUrl } from "./validateURL";
|
|
10
|
-
export function parseRedirects(input) {
|
|
11
|
-
const lines = input.split("\n");
|
|
12
|
-
const rules = [];
|
|
13
|
-
const seen_paths = /* @__PURE__ */ new Set();
|
|
14
|
-
const invalid = [];
|
|
15
|
-
let staticRules = 0;
|
|
16
|
-
let dynamicRules = 0;
|
|
17
|
-
let canCreateStaticRule = true;
|
|
18
|
-
for (let i = 0; i < lines.length; i++) {
|
|
19
|
-
const line = lines[i].trim();
|
|
20
|
-
if (line.length === 0 || line.startsWith("#"))
|
|
21
|
-
continue;
|
|
22
|
-
if (line.length > MAX_LINE_LENGTH) {
|
|
23
|
-
invalid.push({
|
|
24
|
-
message: `Ignoring line ${i + 1} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`
|
|
25
|
-
});
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const tokens = line.split(/\s+/);
|
|
29
|
-
if (tokens.length < 2 || tokens.length > 3) {
|
|
30
|
-
invalid.push({
|
|
31
|
-
line,
|
|
32
|
-
lineNumber: i + 1,
|
|
33
|
-
message: `Expected exactly 2 or 3 whitespace-separated tokens. Got ${tokens.length}.`
|
|
34
|
-
});
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const [str_from, str_to, str_status = "302"] = tokens;
|
|
38
|
-
const fromResult = validateUrl(str_from, true, false, false);
|
|
39
|
-
if (fromResult[0] === void 0) {
|
|
40
|
-
invalid.push({
|
|
41
|
-
line,
|
|
42
|
-
lineNumber: i + 1,
|
|
43
|
-
message: fromResult[1]
|
|
44
|
-
});
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const from = fromResult[0];
|
|
48
|
-
if (canCreateStaticRule && !from.match(SPLAT_REGEX) && !from.match(PLACEHOLDER_REGEX)) {
|
|
49
|
-
staticRules += 1;
|
|
50
|
-
if (staticRules > MAX_STATIC_REDIRECT_RULES) {
|
|
51
|
-
invalid.push({
|
|
52
|
-
message: `Maximum number of static rules supported is ${MAX_STATIC_REDIRECT_RULES}. Skipping line.`
|
|
53
|
-
});
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
dynamicRules += 1;
|
|
58
|
-
canCreateStaticRule = false;
|
|
59
|
-
if (dynamicRules > MAX_DYNAMIC_REDIRECT_RULES) {
|
|
60
|
-
invalid.push({
|
|
61
|
-
message: `Maximum number of dynamic rules supported is ${MAX_DYNAMIC_REDIRECT_RULES}. Skipping remaining ${lines.length - i} lines of file.`
|
|
62
|
-
});
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const toResult = validateUrl(str_to, false, true, true);
|
|
67
|
-
if (toResult[0] === void 0) {
|
|
68
|
-
invalid.push({
|
|
69
|
-
line,
|
|
70
|
-
lineNumber: i + 1,
|
|
71
|
-
message: toResult[1]
|
|
72
|
-
});
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const to = toResult[0];
|
|
76
|
-
const status = Number(str_status);
|
|
77
|
-
if (isNaN(status) || !PERMITTED_STATUS_CODES.has(status)) {
|
|
78
|
-
invalid.push({
|
|
79
|
-
line,
|
|
80
|
-
lineNumber: i + 1,
|
|
81
|
-
message: `Valid status codes are 301, 302 (default), 303, 307, or 308. Got ${str_status}.`
|
|
82
|
-
});
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (seen_paths.has(from)) {
|
|
86
|
-
invalid.push({
|
|
87
|
-
line,
|
|
88
|
-
lineNumber: i + 1,
|
|
89
|
-
message: `Ignoring duplicate rule for path ${from}.`
|
|
90
|
-
});
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
seen_paths.add(from);
|
|
94
|
-
rules.push({ from, to, status, lineNumber: i + 1 });
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
rules,
|
|
98
|
-
invalid
|
|
99
|
-
};
|
|
100
|
-
}
|
|
File without changes
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export const extractPathname = (path = "/", includeSearch, includeHash) => {
|
|
2
|
-
if (!path.startsWith("/"))
|
|
3
|
-
path = `/${path}`;
|
|
4
|
-
const url = new URL(`//${path}`, "relative://");
|
|
5
|
-
return `${url.pathname}${includeSearch ? url.search : ""}${includeHash ? url.hash : ""}`;
|
|
6
|
-
};
|
|
7
|
-
const URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/;
|
|
8
|
-
const PATH_REGEX = /^\//;
|
|
9
|
-
export const validateUrl = (token, onlyRelative = false, includeSearch = false, includeHash = false) => {
|
|
10
|
-
const host = URL_REGEX.exec(token);
|
|
11
|
-
if (host && host.groups && host.groups.host) {
|
|
12
|
-
if (onlyRelative)
|
|
13
|
-
return [
|
|
14
|
-
void 0,
|
|
15
|
-
`Only relative URLs are allowed. Skipping absolute URL ${token}.`
|
|
16
|
-
];
|
|
17
|
-
return [
|
|
18
|
-
`https://${host.groups.host}${extractPathname(
|
|
19
|
-
host.groups.path,
|
|
20
|
-
includeSearch,
|
|
21
|
-
includeHash
|
|
22
|
-
)}`,
|
|
23
|
-
void 0
|
|
24
|
-
];
|
|
25
|
-
} else {
|
|
26
|
-
if (!token.startsWith("/") && onlyRelative)
|
|
27
|
-
token = `/${token}`;
|
|
28
|
-
const path = PATH_REGEX.exec(token);
|
|
29
|
-
if (path) {
|
|
30
|
-
try {
|
|
31
|
-
return [extractPathname(token, includeSearch, includeHash), void 0];
|
|
32
|
-
} catch {
|
|
33
|
-
return [void 0, `Error parsing URL segment ${token}. Skipping.`];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return [
|
|
38
|
-
void 0,
|
|
39
|
-
onlyRelative ? "URLs should begin with a forward-slash." : 'URLs should either be relative (e.g. begin with a forward-slash), or use HTTPS (e.g. begin with "https://").'
|
|
40
|
-
];
|
|
41
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|