@cloudflare/pages-shared 0.0.0-ship-js
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/asset-server/handler.ts +625 -0
- package/asset-server/metadata.ts +67 -0
- package/asset-server/patchUrl.ts +22 -0
- package/asset-server/responses.ts +196 -0
- package/asset-server/rulesEngine.ts +82 -0
- package/dist/__tests__/asset-server/handler.test.d.ts +2 -0
- package/dist/__tests__/asset-server/handler.test.d.ts.map +1 -0
- package/dist/__tests__/asset-server/handler.test.js +430 -0
- package/dist/__tests__/asset-server/responses.test.d.ts +2 -0
- package/dist/__tests__/asset-server/responses.test.d.ts.map +1 -0
- package/dist/__tests__/asset-server/responses.test.js +22 -0
- package/dist/__tests__/asset-server/rulesEngine.test.d.ts +2 -0
- package/dist/__tests__/asset-server/rulesEngine.test.d.ts.map +1 -0
- package/dist/__tests__/asset-server/rulesEngine.test.js +70 -0
- package/dist/__tests__/jest.setup.d.ts +2 -0
- package/dist/__tests__/jest.setup.d.ts.map +1 -0
- package/dist/__tests__/jest.setup.js +7 -0
- package/dist/__tests__/metadata-generator/createMetadataObject.test.d.ts +2 -0
- package/dist/__tests__/metadata-generator/createMetadataObject.test.d.ts.map +1 -0
- package/dist/__tests__/metadata-generator/createMetadataObject.test.js +293 -0
- package/dist/__tests__/metadata-generator/parseHeaders.invalid.test.d.ts +2 -0
- package/dist/__tests__/metadata-generator/parseHeaders.invalid.test.d.ts.map +1 -0
- package/dist/__tests__/metadata-generator/parseHeaders.invalid.test.js +194 -0
- package/dist/__tests__/metadata-generator/parseHeaders.valid.test.d.ts +2 -0
- package/dist/__tests__/metadata-generator/parseHeaders.valid.test.d.ts.map +1 -0
- package/dist/__tests__/metadata-generator/parseHeaders.valid.test.js +132 -0
- package/dist/__tests__/metadata-generator/parseRedirects.invalid.test.d.ts +2 -0
- package/dist/__tests__/metadata-generator/parseRedirects.invalid.test.d.ts.map +1 -0
- package/dist/__tests__/metadata-generator/parseRedirects.invalid.test.js +243 -0
- package/dist/__tests__/metadata-generator/parseRedirects.valid.test.d.ts +2 -0
- package/dist/__tests__/metadata-generator/parseRedirects.valid.test.d.ts.map +1 -0
- package/dist/__tests__/metadata-generator/parseRedirects.valid.test.js +88 -0
- package/dist/asset-server/handler.d.ts +44 -0
- package/dist/asset-server/handler.d.ts.map +1 -0
- package/dist/asset-server/handler.js +420 -0
- package/dist/asset-server/metadata.d.ts +52 -0
- package/dist/asset-server/metadata.d.ts.map +1 -0
- package/dist/asset-server/metadata.js +3 -0
- package/dist/asset-server/patchUrl.d.ts +2 -0
- package/dist/asset-server/patchUrl.d.ts.map +1 -0
- package/dist/asset-server/patchUrl.js +19 -0
- package/dist/asset-server/responses.d.ts +45 -0
- package/dist/asset-server/responses.d.ts.map +1 -0
- package/dist/asset-server/responses.js +165 -0
- package/dist/asset-server/rulesEngine.d.ts +7 -0
- package/dist/asset-server/rulesEngine.d.ts.map +1 -0
- package/dist/asset-server/rulesEngine.js +68 -0
- package/dist/environment-polyfills/index.d.ts +3 -0
- package/dist/environment-polyfills/index.d.ts.map +1 -0
- package/dist/environment-polyfills/index.js +14 -0
- package/dist/environment-polyfills/miniflare-tre.d.ts +3 -0
- package/dist/environment-polyfills/miniflare-tre.d.ts.map +1 -0
- package/dist/environment-polyfills/miniflare-tre.js +35 -0
- package/dist/environment-polyfills/miniflare.d.ts +3 -0
- package/dist/environment-polyfills/miniflare.d.ts.map +1 -0
- package/dist/environment-polyfills/miniflare.js +35 -0
- package/dist/environment-polyfills/types.d.ts +34 -0
- package/dist/environment-polyfills/types.d.ts.map +1 -0
- package/dist/environment-polyfills/types.js +5 -0
- package/dist/metadata-generator/constants.d.ts +14 -0
- package/dist/metadata-generator/constants.d.ts.map +1 -0
- package/dist/metadata-generator/constants.js +16 -0
- package/dist/metadata-generator/createMetadataObject.d.ts +10 -0
- package/dist/metadata-generator/createMetadataObject.d.ts.map +1 -0
- package/dist/metadata-generator/createMetadataObject.js +105 -0
- package/dist/metadata-generator/parseHeaders.d.ts +3 -0
- package/dist/metadata-generator/parseHeaders.d.ts.map +1 -0
- package/dist/metadata-generator/parseHeaders.js +146 -0
- package/dist/metadata-generator/parseRedirects.d.ts +3 -0
- package/dist/metadata-generator/parseRedirects.d.ts.map +1 -0
- package/dist/metadata-generator/parseRedirects.js +100 -0
- package/dist/metadata-generator/types.d.ts +74 -0
- package/dist/metadata-generator/types.d.ts.map +1 -0
- package/dist/metadata-generator/types.js +3 -0
- package/dist/metadata-generator/validateURL.d.ts +3 -0
- package/dist/metadata-generator/validateURL.d.ts.map +1 -0
- package/dist/metadata-generator/validateURL.js +46 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/environment-polyfills/index.ts +14 -0
- package/environment-polyfills/miniflare-tre.ts +12 -0
- package/environment-polyfills/miniflare.ts +12 -0
- package/environment-polyfills/types.ts +42 -0
- package/metadata-generator/constants.ts +15 -0
- package/metadata-generator/createMetadataObject.ts +164 -0
- package/metadata-generator/parseHeaders.ts +168 -0
- package/metadata-generator/parseRedirects.ts +129 -0
- package/metadata-generator/types.ts +90 -0
- package/metadata-generator/validateURL.ts +57 -0
- package/package.json +63 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ANALYTICS_VERSION,
|
|
3
|
+
REDIRECTS_VERSION,
|
|
4
|
+
HEADERS_VERSION,
|
|
5
|
+
SPLAT_REGEX,
|
|
6
|
+
PLACEHOLDER_REGEX,
|
|
7
|
+
} from "./constants";
|
|
8
|
+
import type { MetadataStaticRedirects } from "../asset-server/metadata";
|
|
9
|
+
import type {
|
|
10
|
+
Metadata,
|
|
11
|
+
MetadataRedirects,
|
|
12
|
+
MetadataHeaders,
|
|
13
|
+
ParsedRedirects,
|
|
14
|
+
ParsedHeaders,
|
|
15
|
+
Logger,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
export function createMetadataObject({
|
|
19
|
+
redirects,
|
|
20
|
+
headers,
|
|
21
|
+
webAnalyticsToken,
|
|
22
|
+
deploymentId,
|
|
23
|
+
failOpen,
|
|
24
|
+
logger = (_message: string) => {},
|
|
25
|
+
}: {
|
|
26
|
+
redirects?: ParsedRedirects;
|
|
27
|
+
headers?: ParsedHeaders;
|
|
28
|
+
webAnalyticsToken?: string;
|
|
29
|
+
deploymentId?: string;
|
|
30
|
+
failOpen?: boolean;
|
|
31
|
+
logger?: Logger;
|
|
32
|
+
}): Metadata {
|
|
33
|
+
return {
|
|
34
|
+
...constructRedirects({ redirects, logger }),
|
|
35
|
+
...constructHeaders({ headers, logger }),
|
|
36
|
+
...constructWebAnalytics({ webAnalyticsToken, logger }),
|
|
37
|
+
deploymentId,
|
|
38
|
+
failOpen,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function constructRedirects({
|
|
43
|
+
redirects,
|
|
44
|
+
logger,
|
|
45
|
+
}: {
|
|
46
|
+
redirects?: ParsedRedirects;
|
|
47
|
+
logger: Logger;
|
|
48
|
+
}): Metadata {
|
|
49
|
+
if (!redirects) return {};
|
|
50
|
+
|
|
51
|
+
const num_valid = redirects.rules.length;
|
|
52
|
+
const num_invalid = redirects.invalid.length;
|
|
53
|
+
|
|
54
|
+
logger(
|
|
55
|
+
`Parsed ${num_valid} valid redirect rule${num_valid === 1 ? "" : "s"}.`
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (num_invalid > 0) {
|
|
59
|
+
logger(`Found invalid redirect lines:`);
|
|
60
|
+
for (const { line, lineNumber, message } of redirects.invalid) {
|
|
61
|
+
if (line) logger(` - ${lineNumber ? `#${lineNumber}: ` : ""}${line}`);
|
|
62
|
+
logger(` ${message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Better to return no Redirects object at all than one with empty rules */
|
|
67
|
+
if (num_valid === 0) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const staticRedirects: MetadataStaticRedirects = {};
|
|
72
|
+
const dynamicRedirects: MetadataRedirects = {};
|
|
73
|
+
let canCreateStaticRule = true;
|
|
74
|
+
for (const rule of redirects.rules) {
|
|
75
|
+
if (!rule.from.match(SPLAT_REGEX) && !rule.from.match(PLACEHOLDER_REGEX)) {
|
|
76
|
+
if (canCreateStaticRule) {
|
|
77
|
+
staticRedirects[rule.from] = {
|
|
78
|
+
status: rule.status,
|
|
79
|
+
to: rule.to,
|
|
80
|
+
lineNumber: rule.lineNumber,
|
|
81
|
+
};
|
|
82
|
+
continue;
|
|
83
|
+
} else {
|
|
84
|
+
logger(
|
|
85
|
+
`Info: the redirect rule ${rule.from} → ${rule.status} ${rule.to} could be made more performant by bringing it above any lines with splats or placeholders.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
dynamicRedirects[rule.from] = { status: rule.status, to: rule.to };
|
|
91
|
+
canCreateStaticRule = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
redirects: {
|
|
96
|
+
version: REDIRECTS_VERSION,
|
|
97
|
+
staticRules: staticRedirects,
|
|
98
|
+
rules: dynamicRedirects,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function constructHeaders({
|
|
104
|
+
headers,
|
|
105
|
+
logger,
|
|
106
|
+
}: {
|
|
107
|
+
headers?: ParsedHeaders;
|
|
108
|
+
logger: Logger;
|
|
109
|
+
}): Metadata {
|
|
110
|
+
if (!headers) return {};
|
|
111
|
+
|
|
112
|
+
const num_valid = headers.rules.length;
|
|
113
|
+
const num_invalid = headers.invalid.length;
|
|
114
|
+
|
|
115
|
+
logger(`Parsed ${num_valid} valid header rule${num_valid === 1 ? "" : "s"}.`);
|
|
116
|
+
|
|
117
|
+
if (num_invalid > 0) {
|
|
118
|
+
logger(`Found invalid header lines:`);
|
|
119
|
+
for (const { line, lineNumber, message } of headers.invalid) {
|
|
120
|
+
if (line) logger(` - ${lineNumber ? `#${lineNumber}: ` : ""} ${line}`);
|
|
121
|
+
logger(` ${message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Better to return no Headers object at all than one with empty rules */
|
|
126
|
+
if (num_valid === 0) {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rules: MetadataHeaders = {};
|
|
131
|
+
for (const rule of headers.rules) {
|
|
132
|
+
rules[rule.path] = {};
|
|
133
|
+
|
|
134
|
+
if (Object.keys(rule.headers).length) {
|
|
135
|
+
rules[rule.path].set = rule.headers;
|
|
136
|
+
}
|
|
137
|
+
if (rule.unsetHeaders.length) {
|
|
138
|
+
rules[rule.path].unset = rule.unsetHeaders;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
headers: {
|
|
144
|
+
version: HEADERS_VERSION,
|
|
145
|
+
rules,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function constructWebAnalytics({
|
|
151
|
+
webAnalyticsToken,
|
|
152
|
+
}: {
|
|
153
|
+
webAnalyticsToken?: string;
|
|
154
|
+
logger: Logger;
|
|
155
|
+
}) {
|
|
156
|
+
if (!webAnalyticsToken) return {};
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
analytics: {
|
|
160
|
+
version: ANALYTICS_VERSION,
|
|
161
|
+
token: webAnalyticsToken,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
import type { InvalidHeadersRule, ParsedHeaders, HeadersRule } from "./types";
|
|
9
|
+
|
|
10
|
+
// Not strictly necessary to check for all protocols-like beginnings, since _technically_ that could be a legit header (e.g. name=http, value=://I'm a value).
|
|
11
|
+
// But we're checking here since some people might be caught out and it'll help 99.9% of people who get it wrong.
|
|
12
|
+
// We do the proper validation in `validateUrl` anyway :)
|
|
13
|
+
const LINE_IS_PROBABLY_A_PATH = new RegExp(/^([^\s]+:\/\/|^\/)/);
|
|
14
|
+
|
|
15
|
+
export function parseHeaders(input: string): ParsedHeaders {
|
|
16
|
+
const lines = input.split("\n");
|
|
17
|
+
const rules: HeadersRule[] = [];
|
|
18
|
+
const invalid: InvalidHeadersRule[] = [];
|
|
19
|
+
|
|
20
|
+
let rule: (HeadersRule & { line: string }) | undefined = undefined;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < lines.length; i++) {
|
|
23
|
+
const line = lines[i].trim();
|
|
24
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
25
|
+
|
|
26
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
27
|
+
invalid.push({
|
|
28
|
+
message: `Ignoring line ${
|
|
29
|
+
i + 1
|
|
30
|
+
} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`,
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (LINE_IS_PROBABLY_A_PATH.test(line)) {
|
|
36
|
+
if (rules.length >= MAX_HEADER_RULES) {
|
|
37
|
+
invalid.push({
|
|
38
|
+
message: `Maximum number of rules supported is ${MAX_HEADER_RULES}. Skipping remaining ${
|
|
39
|
+
lines.length - i
|
|
40
|
+
} lines of file.`,
|
|
41
|
+
});
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (rule) {
|
|
46
|
+
if (isValidRule(rule)) {
|
|
47
|
+
rules.push({
|
|
48
|
+
path: rule.path,
|
|
49
|
+
headers: rule.headers,
|
|
50
|
+
unsetHeaders: rule.unsetHeaders,
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
invalid.push({
|
|
54
|
+
line: rule.line,
|
|
55
|
+
lineNumber: i + 1,
|
|
56
|
+
message: "No headers specified",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const [path, pathError] = validateUrl(line);
|
|
62
|
+
if (pathError) {
|
|
63
|
+
invalid.push({
|
|
64
|
+
line,
|
|
65
|
+
lineNumber: i + 1,
|
|
66
|
+
message: pathError,
|
|
67
|
+
});
|
|
68
|
+
rule = undefined;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
rule = {
|
|
73
|
+
path: path as string,
|
|
74
|
+
line,
|
|
75
|
+
headers: {},
|
|
76
|
+
unsetHeaders: [],
|
|
77
|
+
};
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!line.includes(HEADER_SEPARATOR)) {
|
|
82
|
+
if (!rule) {
|
|
83
|
+
invalid.push({
|
|
84
|
+
line,
|
|
85
|
+
lineNumber: i + 1,
|
|
86
|
+
message: "Expected a path beginning with at least one forward-slash",
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
if (line.trim().startsWith(UNSET_OPERATOR)) {
|
|
90
|
+
rule.unsetHeaders.push(line.trim().replace(UNSET_OPERATOR, ""));
|
|
91
|
+
} else {
|
|
92
|
+
invalid.push({
|
|
93
|
+
line,
|
|
94
|
+
lineNumber: i + 1,
|
|
95
|
+
message:
|
|
96
|
+
"Expected a colon-separated header pair (e.g. name: value)",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const [rawName, ...rawValue] = line.split(HEADER_SEPARATOR);
|
|
104
|
+
const name = rawName.trim().toLowerCase();
|
|
105
|
+
|
|
106
|
+
if (name.includes(" ")) {
|
|
107
|
+
invalid.push({
|
|
108
|
+
line,
|
|
109
|
+
lineNumber: i + 1,
|
|
110
|
+
message: "Header name cannot include spaces",
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const value = rawValue.join(HEADER_SEPARATOR).trim();
|
|
116
|
+
|
|
117
|
+
if (name === "") {
|
|
118
|
+
invalid.push({
|
|
119
|
+
line,
|
|
120
|
+
lineNumber: i + 1,
|
|
121
|
+
message: "No header name specified",
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (value === "") {
|
|
127
|
+
invalid.push({
|
|
128
|
+
line,
|
|
129
|
+
lineNumber: i + 1,
|
|
130
|
+
message: "No header value specified",
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!rule) {
|
|
136
|
+
invalid.push({
|
|
137
|
+
line,
|
|
138
|
+
lineNumber: i + 1,
|
|
139
|
+
message: `Path should come before header (${name}: ${value})`,
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const existingValues = rule.headers[name];
|
|
145
|
+
rule.headers[name] = existingValues ? `${existingValues}, ${value}` : value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (rule) {
|
|
149
|
+
if (isValidRule(rule)) {
|
|
150
|
+
rules.push({
|
|
151
|
+
path: rule.path,
|
|
152
|
+
headers: rule.headers,
|
|
153
|
+
unsetHeaders: rule.unsetHeaders,
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
invalid.push({ line: rule.line, message: "No headers specified" });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
rules,
|
|
162
|
+
invalid,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isValidRule(rule: HeadersRule) {
|
|
167
|
+
return Object.keys(rule.headers).length > 0 || rule.unsetHeaders.length > 0;
|
|
168
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
import type {
|
|
11
|
+
InvalidRedirectRule,
|
|
12
|
+
ParsedRedirects,
|
|
13
|
+
RedirectLine,
|
|
14
|
+
RedirectRule,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
export function parseRedirects(input: string): ParsedRedirects {
|
|
18
|
+
const lines = input.split("\n");
|
|
19
|
+
const rules: RedirectRule[] = [];
|
|
20
|
+
const seen_paths = new Set<string>();
|
|
21
|
+
const invalid: InvalidRedirectRule[] = [];
|
|
22
|
+
|
|
23
|
+
let staticRules = 0;
|
|
24
|
+
let dynamicRules = 0;
|
|
25
|
+
let canCreateStaticRule = true;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i].trim();
|
|
29
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
30
|
+
|
|
31
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
32
|
+
invalid.push({
|
|
33
|
+
message: `Ignoring line ${
|
|
34
|
+
i + 1
|
|
35
|
+
} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`,
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const tokens = line.split(/\s+/);
|
|
41
|
+
|
|
42
|
+
if (tokens.length < 2 || tokens.length > 3) {
|
|
43
|
+
invalid.push({
|
|
44
|
+
line,
|
|
45
|
+
lineNumber: i + 1,
|
|
46
|
+
message: `Expected exactly 2 or 3 whitespace-separated tokens. Got ${tokens.length}.`,
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const [str_from, str_to, str_status = "302"] = tokens as RedirectLine;
|
|
52
|
+
|
|
53
|
+
const fromResult = validateUrl(str_from, true, false, false);
|
|
54
|
+
if (fromResult[0] === undefined) {
|
|
55
|
+
invalid.push({
|
|
56
|
+
line,
|
|
57
|
+
lineNumber: i + 1,
|
|
58
|
+
message: fromResult[1],
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const from = fromResult[0];
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
canCreateStaticRule &&
|
|
66
|
+
!from.match(SPLAT_REGEX) &&
|
|
67
|
+
!from.match(PLACEHOLDER_REGEX)
|
|
68
|
+
) {
|
|
69
|
+
staticRules += 1;
|
|
70
|
+
|
|
71
|
+
if (staticRules > MAX_STATIC_REDIRECT_RULES) {
|
|
72
|
+
invalid.push({
|
|
73
|
+
message: `Maximum number of static rules supported is ${MAX_STATIC_REDIRECT_RULES}. Skipping line.`,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
dynamicRules += 1;
|
|
79
|
+
canCreateStaticRule = false;
|
|
80
|
+
|
|
81
|
+
if (dynamicRules > MAX_DYNAMIC_REDIRECT_RULES) {
|
|
82
|
+
invalid.push({
|
|
83
|
+
message: `Maximum number of dynamic rules supported is ${MAX_DYNAMIC_REDIRECT_RULES}. Skipping remaining ${
|
|
84
|
+
lines.length - i
|
|
85
|
+
} lines of file.`,
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const toResult = validateUrl(str_to, false, true, true);
|
|
92
|
+
if (toResult[0] === undefined) {
|
|
93
|
+
invalid.push({
|
|
94
|
+
line,
|
|
95
|
+
lineNumber: i + 1,
|
|
96
|
+
message: toResult[1],
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const to = toResult[0];
|
|
101
|
+
|
|
102
|
+
const status = Number(str_status);
|
|
103
|
+
if (isNaN(status) || !PERMITTED_STATUS_CODES.has(status)) {
|
|
104
|
+
invalid.push({
|
|
105
|
+
line,
|
|
106
|
+
lineNumber: i + 1,
|
|
107
|
+
message: `Valid status codes are 301, 302 (default), 303, 307, or 308. Got ${str_status}.`,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (seen_paths.has(from)) {
|
|
113
|
+
invalid.push({
|
|
114
|
+
line,
|
|
115
|
+
lineNumber: i + 1,
|
|
116
|
+
message: `Ignoring duplicate rule for path ${from}.`,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
seen_paths.add(from);
|
|
121
|
+
|
|
122
|
+
rules.push({ from, to, status, lineNumber: i + 1 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
rules,
|
|
127
|
+
invalid,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* REDIRECT PARSING TYPES */
|
|
2
|
+
|
|
3
|
+
export type RedirectLine = [from: string, to: string, status?: number];
|
|
4
|
+
export type RedirectRule = {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
status: number;
|
|
8
|
+
lineNumber: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type Headers = Record<string, string>;
|
|
12
|
+
export type HeadersRule = {
|
|
13
|
+
path: string;
|
|
14
|
+
headers: Headers;
|
|
15
|
+
unsetHeaders: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type InvalidRedirectRule = {
|
|
19
|
+
line?: string;
|
|
20
|
+
lineNumber?: number;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type InvalidHeadersRule = {
|
|
25
|
+
line?: string;
|
|
26
|
+
lineNumber?: number;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ParsedRedirects = {
|
|
31
|
+
invalid: InvalidRedirectRule[];
|
|
32
|
+
rules: RedirectRule[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Parsed redirects and input file */
|
|
36
|
+
export type ParsedRedirectsWithFile = {
|
|
37
|
+
parsedRedirects?: ParsedRedirects;
|
|
38
|
+
file?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ParsedHeaders = {
|
|
42
|
+
invalid: InvalidHeadersRule[];
|
|
43
|
+
rules: HeadersRule[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Parsed headers and input file */
|
|
47
|
+
export type ParsedHeadersWithFile = {
|
|
48
|
+
parsedHeaders?: ParsedHeaders;
|
|
49
|
+
file?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* METADATA TYPES*/
|
|
53
|
+
|
|
54
|
+
export type MetadataRedirectEntry = {
|
|
55
|
+
status: number;
|
|
56
|
+
to: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type MetadataRedirects = {
|
|
60
|
+
[path: string]: MetadataRedirectEntry;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type MetadataHeaders = {
|
|
64
|
+
[path: string]: MetadataHeaderEntry;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type MetadataHeaderEntry = {
|
|
68
|
+
set?: Record<string, string>;
|
|
69
|
+
unset?: Array<string>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type Metadata = {
|
|
73
|
+
redirects?: {
|
|
74
|
+
version: number;
|
|
75
|
+
staticRules: MetadataRedirects;
|
|
76
|
+
rules: MetadataRedirects;
|
|
77
|
+
};
|
|
78
|
+
headers?: {
|
|
79
|
+
version: number;
|
|
80
|
+
rules: MetadataHeaders;
|
|
81
|
+
};
|
|
82
|
+
analytics?: {
|
|
83
|
+
version: number;
|
|
84
|
+
token: string;
|
|
85
|
+
};
|
|
86
|
+
deploymentId?: string;
|
|
87
|
+
failOpen?: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type Logger = (message: string) => void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const extractPathname = (
|
|
2
|
+
path = "/",
|
|
3
|
+
includeSearch: boolean,
|
|
4
|
+
includeHash: boolean
|
|
5
|
+
): string => {
|
|
6
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
7
|
+
const url = new URL(`//${path}`, "relative://");
|
|
8
|
+
return `${url.pathname}${includeSearch ? url.search : ""}${
|
|
9
|
+
includeHash ? url.hash : ""
|
|
10
|
+
}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/;
|
|
14
|
+
const PATH_REGEX = /^\//;
|
|
15
|
+
|
|
16
|
+
export const validateUrl = (
|
|
17
|
+
token: string,
|
|
18
|
+
onlyRelative = false,
|
|
19
|
+
includeSearch = false,
|
|
20
|
+
includeHash = false
|
|
21
|
+
): [undefined, string] | [string, undefined] => {
|
|
22
|
+
const host = URL_REGEX.exec(token);
|
|
23
|
+
if (host && host.groups && host.groups.host) {
|
|
24
|
+
if (onlyRelative)
|
|
25
|
+
return [
|
|
26
|
+
undefined,
|
|
27
|
+
`Only relative URLs are allowed. Skipping absolute URL ${token}.`,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
`https://${host.groups.host}${extractPathname(
|
|
32
|
+
host.groups.path,
|
|
33
|
+
includeSearch,
|
|
34
|
+
includeHash
|
|
35
|
+
)}`,
|
|
36
|
+
undefined,
|
|
37
|
+
];
|
|
38
|
+
} else {
|
|
39
|
+
if (!token.startsWith("/") && onlyRelative) token = `/${token}`;
|
|
40
|
+
|
|
41
|
+
const path = PATH_REGEX.exec(token);
|
|
42
|
+
if (path) {
|
|
43
|
+
try {
|
|
44
|
+
return [extractPathname(token, includeSearch, includeHash), undefined];
|
|
45
|
+
} catch {
|
|
46
|
+
return [undefined, `Error parsing URL segment ${token}. Skipping.`];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
undefined,
|
|
53
|
+
onlyRelative
|
|
54
|
+
? "URLs should begin with a forward-slash."
|
|
55
|
+
: 'URLs should either be relative (e.g. begin with a forward-slash), or use HTTPS (e.g. begin with "https://").',
|
|
56
|
+
];
|
|
57
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloudflare/pages-shared",
|
|
3
|
+
"version": "0.0.0-ship-js",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/cloudflare/workers-sdk.git",
|
|
7
|
+
"directory": "packages/pages-shared"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
"./asset-server/": "./dist/asset-server/",
|
|
11
|
+
"./environment-polyfills/": "./dist/environment-polyfills/",
|
|
12
|
+
"./metadata-generator/": "./dist/metadata-generator/"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**/*",
|
|
16
|
+
"tsconfig.json",
|
|
17
|
+
"asset-server/**/*",
|
|
18
|
+
"environment-polyfills/**/*",
|
|
19
|
+
"metadata-generator/**/*"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"check:type": "tsc",
|
|
24
|
+
"test": "jest --forceExit",
|
|
25
|
+
"test:ci": "jest --forceExit"
|
|
26
|
+
},
|
|
27
|
+
"jest": {
|
|
28
|
+
"coverageReporters": [
|
|
29
|
+
"json",
|
|
30
|
+
"html",
|
|
31
|
+
"text",
|
|
32
|
+
"cobertura"
|
|
33
|
+
],
|
|
34
|
+
"restoreMocks": true,
|
|
35
|
+
"setupFilesAfterEnv": [
|
|
36
|
+
"<rootDir>/__tests__/jest.setup.ts"
|
|
37
|
+
],
|
|
38
|
+
"testRegex": ".*.(test|spec)\\.[jt]sx?$",
|
|
39
|
+
"testTimeout": 30000,
|
|
40
|
+
"transform": {
|
|
41
|
+
"^.+\\.c?(t|j)sx?$": [
|
|
42
|
+
"esbuild-jest",
|
|
43
|
+
{
|
|
44
|
+
"sourcemap": true
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"transformIgnorePatterns": [
|
|
49
|
+
"node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@miniflare/core": "2.13.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@miniflare/cache": "2.13.0",
|
|
57
|
+
"@miniflare/html-rewriter": "2.13.0",
|
|
58
|
+
"@types/service-worker-mock": "^2.0.1",
|
|
59
|
+
"concurrently": "^7.3.0",
|
|
60
|
+
"glob": "^8.0.3",
|
|
61
|
+
"service-worker-mock": "^2.0.5"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": false,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"module": "CommonJS",
|
|
9
|
+
"types": ["jest"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["**/*.ts"],
|
|
12
|
+
"exclude": ["dist/**/*"]
|
|
13
|
+
}
|