@samebase/convex-hosting 0.3.2 → 0.4.0
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/CHANGELOG.md +10 -134
- package/README.md +136 -398
- package/dist/cli/convex.d.ts +63 -0
- package/dist/cli/convex.js +108 -0
- package/dist/cli/convex.js.map +1 -0
- package/dist/cli/files.d.ts +18 -0
- package/dist/cli/files.js +106 -0
- package/dist/cli/files.js.map +1 -0
- package/dist/cli/index.d.ts +1 -11
- package/dist/cli/index.js +3 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/main.d.ts +5 -0
- package/dist/cli/main.js +346 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/index.d.ts +8 -115
- package/dist/client/index.js +65 -242
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +5 -0
- package/dist/client/version-module.d.ts +6 -0
- package/dist/component/_generated/api.d.ts +0 -5
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +20 -18
- package/dist/component/_generated/dataModel.d.ts +0 -1
- package/dist/component/_generated/server.d.ts +12 -1
- package/dist/component/_generated/server.js +2 -0
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/convex.config.d.ts +0 -1
- package/dist/component/lib.d.ts +175 -156
- package/dist/component/lib.js +396 -710
- package/dist/component/lib.js.map +1 -1
- package/dist/component/schema.d.ts +76 -72
- package/dist/component/schema.js +35 -59
- package/dist/component/schema.js.map +1 -1
- package/dist/workers-shared/asset-worker/src/analytics.d.ts +39 -0
- package/dist/workers-shared/asset-worker/src/analytics.js +86 -0
- package/dist/workers-shared/asset-worker/src/analytics.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.d.ts +19 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.js +29 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/configuration.d.ts +3 -0
- package/dist/workers-shared/asset-worker/src/configuration.js +30 -0
- package/dist/workers-shared/asset-worker/src/configuration.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/constants.d.ts +1 -0
- package/dist/workers-shared/asset-worker/src/constants.js +3 -0
- package/dist/workers-shared/asset-worker/src/constants.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/handler.d.ts +28 -0
- package/dist/workers-shared/asset-worker/src/handler.js +717 -0
- package/dist/workers-shared/asset-worker/src/handler.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/types.d.ts +13 -0
- package/dist/workers-shared/asset-worker/src/types.js +2 -0
- package/dist/workers-shared/asset-worker/src/types.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.d.ts +29 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.js +56 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.d.ts +11 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.js +77 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.d.ts +23 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.js +133 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/worker.d.ts +14 -0
- package/dist/workers-shared/asset-worker/src/worker.js +12 -0
- package/dist/workers-shared/asset-worker/src/worker.js.map +1 -0
- package/dist/workers-shared/utils/base-path.d.ts +32 -0
- package/dist/workers-shared/utils/base-path.js +94 -0
- package/dist/workers-shared/utils/base-path.js.map +1 -0
- package/dist/workers-shared/utils/configuration/constants.d.ts +15 -0
- package/dist/workers-shared/utils/configuration/constants.js +16 -0
- package/dist/workers-shared/utils/configuration/constants.js.map +1 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.d.ts +12 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.js +103 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.d.ts +5 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.js +181 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.d.ts +8 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.js +129 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.d.ts +2 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.js +94 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.js.map +1 -0
- package/dist/workers-shared/utils/configuration/types.d.ts +38 -0
- package/dist/workers-shared/utils/configuration/types.js +2 -0
- package/dist/workers-shared/utils/configuration/types.js.map +1 -0
- package/dist/workers-shared/utils/configuration/validateURL.d.ts +3 -0
- package/dist/workers-shared/utils/configuration/validateURL.js +56 -0
- package/dist/workers-shared/utils/configuration/validateURL.js.map +1 -0
- package/dist/workers-shared/utils/constants.d.ts +28 -0
- package/dist/workers-shared/utils/constants.js +34 -0
- package/dist/workers-shared/utils/constants.js.map +1 -0
- package/dist/workers-shared/utils/helpers.d.ts +22 -0
- package/dist/workers-shared/utils/helpers.js +73 -0
- package/dist/workers-shared/utils/helpers.js.map +1 -0
- package/dist/workers-shared/utils/responses.d.ts +43 -0
- package/dist/workers-shared/utils/responses.js +125 -0
- package/dist/workers-shared/utils/responses.js.map +1 -0
- package/dist/workers-shared/utils/tracing.d.ts +3 -0
- package/dist/workers-shared/utils/tracing.js +29 -0
- package/dist/workers-shared/utils/tracing.js.map +1 -0
- package/dist/workers-shared/utils/types.d.ts +131 -0
- package/dist/workers-shared/utils/types.js +72 -0
- package/dist/workers-shared/utils/types.js.map +1 -0
- package/package.json +16 -32
- package/INTEGRATION.md +0 -399
- package/dist/cli/args.d.ts +0 -28
- package/dist/cli/args.d.ts.map +0 -1
- package/dist/cli/args.js +0 -148
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/argumentChunks.d.ts +0 -3
- package/dist/cli/argumentChunks.d.ts.map +0 -1
- package/dist/cli/argumentChunks.js +0 -29
- package/dist/cli/argumentChunks.js.map +0 -1
- package/dist/cli/collectFiles.d.ts +0 -10
- package/dist/cli/collectFiles.d.ts.map +0 -1
- package/dist/cli/collectFiles.js +0 -53
- package/dist/cli/collectFiles.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -8
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -56
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/componentName.d.ts +0 -32
- package/dist/cli/componentName.d.ts.map +0 -1
- package/dist/cli/componentName.js +0 -43
- package/dist/cli/componentName.js.map +0 -1
- package/dist/cli/deploy.d.ts +0 -16
- package/dist/cli/deploy.d.ts.map +0 -1
- package/dist/cli/deploy.js +0 -283
- package/dist/cli/deploy.js.map +0 -1
- package/dist/cli/deployEnvironment.d.ts +0 -10
- package/dist/cli/deployEnvironment.d.ts.map +0 -1
- package/dist/cli/deployEnvironment.js +0 -12
- package/dist/cli/deployEnvironment.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/upload.d.ts +0 -22
- package/dist/cli/upload.d.ts.map +0 -1
- package/dist/cli/upload.js +0 -630
- package/dist/cli/upload.js.map +0 -1
- package/dist/client/_generated/_ignore.d.ts +0 -1
- package/dist/client/_generated/_ignore.d.ts.map +0 -1
- package/dist/client/_generated/_ignore.js +0 -3
- package/dist/client/_generated/_ignore.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/component/_generated/api.d.ts.map +0 -1
- package/dist/component/_generated/component.d.ts.map +0 -1
- package/dist/component/_generated/dataModel.d.ts.map +0 -1
- package/dist/component/_generated/server.d.ts.map +0 -1
- package/dist/component/convex.config.d.ts.map +0 -1
- package/dist/component/http.d.ts +0 -4
- package/dist/component/http.d.ts.map +0 -1
- package/dist/component/http.js +0 -113
- package/dist/component/http.js.map +0 -1
- package/dist/component/lib.d.ts.map +0 -1
- package/dist/component/schema.d.ts.map +0 -1
- package/dist/component/serving.d.ts +0 -23
- package/dist/component/serving.d.ts.map +0 -1
- package/dist/component/serving.js +0 -161
- package/dist/component/serving.js.map +0 -1
- package/dist/react/index.d.ts +0 -59
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -137
- package/dist/react/index.js.map +0 -1
- package/src/cli/args.test.ts +0 -79
- package/src/cli/args.ts +0 -153
- package/src/cli/argumentChunks.test.ts +0 -27
- package/src/cli/argumentChunks.ts +0 -40
- package/src/cli/collectFiles.test.ts +0 -26
- package/src/cli/collectFiles.ts +0 -59
- package/src/cli/commands.ts +0 -68
- package/src/cli/componentName.test.ts +0 -41
- package/src/cli/componentName.ts +0 -46
- package/src/cli/deploy.ts +0 -336
- package/src/cli/deployEnvironment.test.ts +0 -27
- package/src/cli/deployEnvironment.ts +0 -17
- package/src/cli/index.ts +0 -65
- package/src/cli/upload.ts +0 -837
- package/src/client/_generated/_ignore.ts +0 -1
- package/src/client/index.test.ts +0 -549
- package/src/client/index.ts +0 -344
- package/src/client/setup.test.ts +0 -22
- package/src/component/_generated/api.ts +0 -54
- package/src/component/_generated/component.ts +0 -55
- package/src/component/_generated/dataModel.ts +0 -60
- package/src/component/_generated/server.ts +0 -156
- package/src/component/convex.config.ts +0 -3
- package/src/component/http.test.ts +0 -254
- package/src/component/http.ts +0 -133
- package/src/component/lib.test.ts +0 -1007
- package/src/component/lib.ts +0 -862
- package/src/component/schema.ts +0 -75
- package/src/component/serving.ts +0 -170
- package/src/component/setup.test.ts +0 -11
- package/src/react/index.tsx +0 -197
- package/src/test.ts +0 -23
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
import { FoundResponse, InternalServerErrorResponse, MethodNotAllowedResponse, MovedPermanentlyResponse, NoIntentResponse, NotFoundResponse, NotModifiedResponse, OkResponse, PermanentRedirectResponse, SeeOtherResponse, TemporaryRedirectResponse, } from "../../utils/responses.js";
|
|
2
|
+
import { mockJaegerBinding } from "../../utils/tracing.js";
|
|
3
|
+
import { flagIsEnabled, SEC_FETCH_MODE_NAVIGATE_HEADER_PREFERS_ASSET_SERVING, } from "./compatibility-flags.js";
|
|
4
|
+
import { addBasePath, stripBasePath } from "./utils/base-path.js";
|
|
5
|
+
import { attachCustomHeaders, getAssetHeaders } from "./utils/headers.js";
|
|
6
|
+
import { generateRedirectsMatcher, staticRedirectsMatcher, } from "./utils/rules-engine.js";
|
|
7
|
+
export const REDIRECTS_VERSION = 1;
|
|
8
|
+
export const HEADERS_VERSION = 2;
|
|
9
|
+
const getResponseOrAssetIntent = async (request, env, configuration, exists, originalDecodedPathname, analytics) => {
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
const { search } = url;
|
|
12
|
+
const base = configuration.base_path;
|
|
13
|
+
// The caller has already enforced the base against the original public
|
|
14
|
+
// pathname. Redirect matching keeps the full public pathname; a non-`200`
|
|
15
|
+
// authored redirect returns unchanged, while a `200` rewrite is checked
|
|
16
|
+
// again below as an authoritative public target.
|
|
17
|
+
const redirectResult = handleRedirects(env, request, configuration, url.host, url.pathname, search);
|
|
18
|
+
if (redirectResult instanceof Response) {
|
|
19
|
+
analytics?.setData({ servedBy: "redirect" });
|
|
20
|
+
return redirectResult;
|
|
21
|
+
}
|
|
22
|
+
const { proxied, pathname } = redirectResult;
|
|
23
|
+
// A `200` proxy rewrite defines the effective public path used for asset
|
|
24
|
+
// lookup. The original request has already passed the public entry-point
|
|
25
|
+
// boundary check, but the rewritten target must be checked independently.
|
|
26
|
+
const effectiveDecodedPathname = proxied
|
|
27
|
+
? decodePath(pathname)
|
|
28
|
+
: originalDecodedPathname;
|
|
29
|
+
const { inPrefix, assetPath } = stripBasePath(effectiveDecodedPathname, base);
|
|
30
|
+
if (!inPrefix) {
|
|
31
|
+
return noIntent(env, analytics, configuration, effectiveDecodedPathname, proxied);
|
|
32
|
+
}
|
|
33
|
+
const baseRootIntent = proxied
|
|
34
|
+
? null
|
|
35
|
+
: await getBaseRootIntent(effectiveDecodedPathname, base, request, configuration, exists);
|
|
36
|
+
const intent = baseRootIntent ??
|
|
37
|
+
(await getIntent(assetPath, request, configuration, exists));
|
|
38
|
+
if (!intent) {
|
|
39
|
+
return noIntent(env, analytics, configuration, effectiveDecodedPathname, proxied);
|
|
40
|
+
}
|
|
41
|
+
const method = request.method.toUpperCase();
|
|
42
|
+
if (!["GET", "HEAD"].includes(method)) {
|
|
43
|
+
analytics?.setData({ servedBy: "method-not-allowed" });
|
|
44
|
+
return env.JAEGER.enterSpan("method_not_allowed", (span) => {
|
|
45
|
+
span.setTags({
|
|
46
|
+
method,
|
|
47
|
+
status: MethodNotAllowedResponse.status,
|
|
48
|
+
});
|
|
49
|
+
return new MethodNotAllowedResponse();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// `intent.redirect` is an asset-relative location generated by the asset
|
|
53
|
+
// Worker; re-prefix it with the public base exactly once. The non-redirect
|
|
54
|
+
// fallback is the effective public pathname and must NOT be re-prefixed.
|
|
55
|
+
const decodedDestination = intent.redirect !== null
|
|
56
|
+
? addBasePath(intent.redirect, base)
|
|
57
|
+
: effectiveDecodedPathname;
|
|
58
|
+
const encodedDestination = encodePath(decodedDestination);
|
|
59
|
+
/**
|
|
60
|
+
* The canonical path we serve an asset at is the decoded and re-encoded version.
|
|
61
|
+
* Thus we need to redirect if that is different from the decoded version.
|
|
62
|
+
* We combine this with other redirects (e.g. for html_handling) to avoid multiple redirects.
|
|
63
|
+
*/
|
|
64
|
+
if ((encodedDestination !== pathname && intent.asset) ||
|
|
65
|
+
intent.redirect !== null) {
|
|
66
|
+
analytics?.setData({ servedBy: "redirect" });
|
|
67
|
+
return env.JAEGER.enterSpan("redirect", (span) => {
|
|
68
|
+
span.setTags({
|
|
69
|
+
originalPath: pathname,
|
|
70
|
+
location: encodedDestination !== pathname
|
|
71
|
+
? encodedDestination
|
|
72
|
+
: (intent.redirect ?? "<unknown>"),
|
|
73
|
+
status: TemporaryRedirectResponse.status,
|
|
74
|
+
});
|
|
75
|
+
return new TemporaryRedirectResponse(encodedDestination + search);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (!intent.asset) {
|
|
79
|
+
analytics?.setData({ servedBy: "error" });
|
|
80
|
+
return env.JAEGER.enterSpan("unknown_action", (span) => {
|
|
81
|
+
span.setTags({
|
|
82
|
+
pathname,
|
|
83
|
+
status: InternalServerErrorResponse.status,
|
|
84
|
+
});
|
|
85
|
+
return new InternalServerErrorResponse(new Error("Unknown action"));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return { ...intent.asset, resolver: intent.resolver };
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Return the protocol-level response for a request the Asset Worker does not
|
|
92
|
+
* intend to serve, while recording the public decoded pathname in telemetry.
|
|
93
|
+
* Proxied rewrites are claimed as 404s unless the caller explicitly opts out.
|
|
94
|
+
*/
|
|
95
|
+
function noIntent(env, analytics, configuration, decodedPathname, proxied, claimProxiedRequest = true) {
|
|
96
|
+
const response = proxied && claimProxiedRequest
|
|
97
|
+
? new NotFoundResponse()
|
|
98
|
+
: new NoIntentResponse();
|
|
99
|
+
analytics?.setData({ servedBy: "none" });
|
|
100
|
+
return env.JAEGER.enterSpan("no_intent", (span) => {
|
|
101
|
+
span.setTags({
|
|
102
|
+
decodedPathname,
|
|
103
|
+
configuration: JSON.stringify(configuration),
|
|
104
|
+
proxied,
|
|
105
|
+
status: response.status,
|
|
106
|
+
});
|
|
107
|
+
return response;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const resolveAssetIntentToResponse = async (assetIntent, request, env, configuration, getByETag, analytics) => {
|
|
111
|
+
const { pathname } = new URL(request.url);
|
|
112
|
+
const method = request.method.toUpperCase();
|
|
113
|
+
const asset = await env.JAEGER.enterSpan("getByETag", async (span) => {
|
|
114
|
+
span.setTags({
|
|
115
|
+
pathname,
|
|
116
|
+
eTag: assetIntent.eTag,
|
|
117
|
+
status: assetIntent.status,
|
|
118
|
+
});
|
|
119
|
+
return await getByETag(assetIntent.eTag, request);
|
|
120
|
+
});
|
|
121
|
+
const headers = getAssetHeaders(assetIntent, asset.contentType, asset.cacheStatus, request, configuration);
|
|
122
|
+
analytics.setData({ cacheStatus: asset.cacheStatus });
|
|
123
|
+
const strongETag = `"${assetIntent.eTag}"`;
|
|
124
|
+
const weakETag = `W/${strongETag}`;
|
|
125
|
+
const ifNoneMatch = request.headers.get("If-None-Match") || "";
|
|
126
|
+
if ([weakETag, strongETag].includes(ifNoneMatch)) {
|
|
127
|
+
analytics.setData({ servedBy: "not-modified" });
|
|
128
|
+
return env.JAEGER.enterSpan("matched_etag", (span) => {
|
|
129
|
+
span.setTags({
|
|
130
|
+
matchedEtag: ifNoneMatch,
|
|
131
|
+
status: NotModifiedResponse.status,
|
|
132
|
+
});
|
|
133
|
+
return new NotModifiedResponse(null, { headers });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
analytics.setData({
|
|
137
|
+
servedBy: servedByForResolver(assetIntent.resolver, configuration),
|
|
138
|
+
});
|
|
139
|
+
return env.JAEGER.enterSpan("response", (span) => {
|
|
140
|
+
span.setTags({
|
|
141
|
+
etag: assetIntent.eTag,
|
|
142
|
+
status: assetIntent.status,
|
|
143
|
+
head: method === "HEAD",
|
|
144
|
+
});
|
|
145
|
+
const body = method === "HEAD" ? null : asset.readableStream;
|
|
146
|
+
switch (assetIntent.status) {
|
|
147
|
+
case NotFoundResponse.status:
|
|
148
|
+
return new NotFoundResponse(body, { headers });
|
|
149
|
+
case OkResponse.status:
|
|
150
|
+
return new OkResponse(body, { headers });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Decode the request pathname exactly once and map it from the public URL
|
|
156
|
+
* namespace into the asset-directory namespace. The returned decodedPathname
|
|
157
|
+
* always remains public and is suitable for request-facing telemetry.
|
|
158
|
+
*/
|
|
159
|
+
function getRequestBasePath(request, base) {
|
|
160
|
+
const decodedPathname = decodePath(new URL(request.url).pathname);
|
|
161
|
+
return {
|
|
162
|
+
decodedPathname,
|
|
163
|
+
...stripBasePath(decodedPathname, base),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export const canFetch = async (request, env, configuration, exists) => {
|
|
167
|
+
const requestPath = getRequestBasePath(request, configuration.base_path);
|
|
168
|
+
if (!requestPath.inPrefix) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const shouldKeepNotFoundHandling = configuration.has_static_routing ||
|
|
172
|
+
(flagIsEnabled(configuration, SEC_FETCH_MODE_NAVIGATE_HEADER_PREFERS_ASSET_SERVING) &&
|
|
173
|
+
request.headers.get("Sec-Fetch-Mode") === "navigate");
|
|
174
|
+
if (!shouldKeepNotFoundHandling) {
|
|
175
|
+
configuration = {
|
|
176
|
+
...configuration,
|
|
177
|
+
not_found_handling: "none",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const responseOrAssetIntent = await getResponseOrAssetIntent(request, env, configuration, exists, requestPath.decodedPathname);
|
|
181
|
+
if (responseOrAssetIntent instanceof NoIntentResponse) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
export const handleRequest = async (request, env, configuration, exists, getByETag, analytics) => {
|
|
187
|
+
const requestPath = getRequestBasePath(request, configuration.base_path);
|
|
188
|
+
if (!requestPath.inPrefix) {
|
|
189
|
+
return noIntent(env, analytics, configuration, requestPath.decodedPathname, false);
|
|
190
|
+
}
|
|
191
|
+
const responseOrAssetIntent = await getResponseOrAssetIntent(request, env, configuration, exists, requestPath.decodedPathname, analytics);
|
|
192
|
+
const response = responseOrAssetIntent instanceof Response
|
|
193
|
+
? responseOrAssetIntent
|
|
194
|
+
: await resolveAssetIntentToResponse(responseOrAssetIntent, request, env, configuration, getByETag, analytics);
|
|
195
|
+
return attachCustomHeaders(request, response, configuration, env);
|
|
196
|
+
};
|
|
197
|
+
function servedByForResolver(resolver, configuration) {
|
|
198
|
+
if (resolver === "html-handling") {
|
|
199
|
+
return "asset";
|
|
200
|
+
}
|
|
201
|
+
switch (configuration.not_found_handling) {
|
|
202
|
+
case "single-page-application":
|
|
203
|
+
return "spa";
|
|
204
|
+
case "404-page":
|
|
205
|
+
return "404-page";
|
|
206
|
+
case "none":
|
|
207
|
+
return "none";
|
|
208
|
+
default:
|
|
209
|
+
configuration.not_found_handling;
|
|
210
|
+
return "error";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// TODO: Trace this
|
|
214
|
+
export const getIntent = async (pathname, request, configuration, exists, skipRedirects = false) => {
|
|
215
|
+
switch (configuration.html_handling) {
|
|
216
|
+
case "auto-trailing-slash": {
|
|
217
|
+
return htmlHandlingAutoTrailingSlash(pathname, request, configuration, exists, skipRedirects);
|
|
218
|
+
}
|
|
219
|
+
case "force-trailing-slash": {
|
|
220
|
+
return htmlHandlingForceTrailingSlash(pathname, request, configuration, exists, skipRedirects);
|
|
221
|
+
}
|
|
222
|
+
case "drop-trailing-slash": {
|
|
223
|
+
return htmlHandlingDropTrailingSlash(pathname, request, configuration, exists, skipRedirects);
|
|
224
|
+
}
|
|
225
|
+
case "none": {
|
|
226
|
+
return htmlHandlingNone(pathname, request, configuration, exists);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Resolve the generated redirect, if any, for the public base root. The
|
|
232
|
+
* returned redirect is asset-relative and must be prefixed exactly once by the
|
|
233
|
+
* top-level handler. Authored redirects and proxy rewrites are handled before
|
|
234
|
+
* this helper is called.
|
|
235
|
+
*/
|
|
236
|
+
async function getBaseRootIntent(publicDecodedPath, base, request, configuration, exists) {
|
|
237
|
+
let pathname;
|
|
238
|
+
switch (configuration.html_handling) {
|
|
239
|
+
case "force-trailing-slash":
|
|
240
|
+
case "auto-trailing-slash":
|
|
241
|
+
pathname =
|
|
242
|
+
base !== "/" && publicDecodedPath === base.slice(0, -1) ? "/" : null;
|
|
243
|
+
break;
|
|
244
|
+
case "drop-trailing-slash":
|
|
245
|
+
pathname = base !== "/" && publicDecodedPath === base ? "" : null;
|
|
246
|
+
break;
|
|
247
|
+
case "none":
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
if (pathname === null || !(await exists("/index.html", request))) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
asset: null,
|
|
255
|
+
redirect: pathname,
|
|
256
|
+
resolver: "html-handling",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const htmlHandlingAutoTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
|
|
260
|
+
let redirectResult = null;
|
|
261
|
+
let eTagResult = null;
|
|
262
|
+
const exactETag = await exists(pathname, request);
|
|
263
|
+
if (pathname.endsWith("/index")) {
|
|
264
|
+
if (exactETag) {
|
|
265
|
+
// there's a binary /index file
|
|
266
|
+
return {
|
|
267
|
+
asset: {
|
|
268
|
+
eTag: exactETag,
|
|
269
|
+
status: OkResponse.status,
|
|
270
|
+
},
|
|
271
|
+
redirect: null,
|
|
272
|
+
resolver: "html-handling",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
277
|
+
// /foo/index.html exists so redirect to /foo/
|
|
278
|
+
return redirectResult;
|
|
279
|
+
}
|
|
280
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
281
|
+
// /foo.html exists so redirect to /foo
|
|
282
|
+
return redirectResult;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else if (pathname.endsWith("/index.html")) {
|
|
287
|
+
if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
288
|
+
// /foo/index.html exists so redirect to /foo/
|
|
289
|
+
return redirectResult;
|
|
290
|
+
}
|
|
291
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
292
|
+
// /foo.html exists so redirect to /foo
|
|
293
|
+
return redirectResult;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (pathname.endsWith("/")) {
|
|
297
|
+
if ((eTagResult = await exists(`${pathname}index.html`, request))) {
|
|
298
|
+
// /foo/index.html exists so serve at /foo/
|
|
299
|
+
return {
|
|
300
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
301
|
+
redirect: null,
|
|
302
|
+
resolver: "html-handling",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
306
|
+
// /foo.html exists so redirect to /foo
|
|
307
|
+
return redirectResult;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else if (pathname.endsWith(".html")) {
|
|
311
|
+
if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
312
|
+
// /foo.html exists so redirect to /foo
|
|
313
|
+
return redirectResult;
|
|
314
|
+
}
|
|
315
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
316
|
+
// request for /foo.html but /foo/index.html exists so redirect to /foo/
|
|
317
|
+
return redirectResult;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (exactETag) {
|
|
321
|
+
// there's a binary /foo file
|
|
322
|
+
return {
|
|
323
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
324
|
+
redirect: null,
|
|
325
|
+
resolver: "html-handling",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
else if ((eTagResult = await exists(`${pathname}.html`, request))) {
|
|
329
|
+
// foo.html exists so serve at /foo
|
|
330
|
+
return {
|
|
331
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
332
|
+
redirect: null,
|
|
333
|
+
resolver: "html-handling",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
else if ((redirectResult = await safeRedirect(`${pathname}/index.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
337
|
+
// /foo/index.html exists so redirect to /foo/
|
|
338
|
+
return redirectResult;
|
|
339
|
+
}
|
|
340
|
+
return notFound(pathname, request, configuration, exists);
|
|
341
|
+
};
|
|
342
|
+
const htmlHandlingForceTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
|
|
343
|
+
let redirectResult = null;
|
|
344
|
+
let eTagResult = null;
|
|
345
|
+
const exactETag = await exists(pathname, request);
|
|
346
|
+
if (pathname.endsWith("/index")) {
|
|
347
|
+
if (exactETag) {
|
|
348
|
+
// there's a binary /index file
|
|
349
|
+
return {
|
|
350
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
351
|
+
redirect: null,
|
|
352
|
+
resolver: "html-handling",
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
357
|
+
// /foo/index.html exists so redirect to /foo/
|
|
358
|
+
return redirectResult;
|
|
359
|
+
}
|
|
360
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
361
|
+
// /foo.html exists so redirect to /foo/
|
|
362
|
+
return redirectResult;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (pathname.endsWith("/index.html")) {
|
|
367
|
+
if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
368
|
+
// /foo/index.html exists so redirect to /foo/
|
|
369
|
+
return redirectResult;
|
|
370
|
+
}
|
|
371
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
372
|
+
// /foo.html exists so redirect to /foo/
|
|
373
|
+
return redirectResult;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (pathname.endsWith("/")) {
|
|
377
|
+
if ((eTagResult = await exists(`${pathname}index.html`, request))) {
|
|
378
|
+
// /foo/index.html exists so serve at /foo/
|
|
379
|
+
return {
|
|
380
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
381
|
+
redirect: null,
|
|
382
|
+
resolver: "html-handling",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
else if ((eTagResult = await exists(`${pathname.slice(0, -"/".length)}.html`, request))) {
|
|
386
|
+
// /foo.html exists so serve at /foo/
|
|
387
|
+
return {
|
|
388
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
389
|
+
redirect: null,
|
|
390
|
+
resolver: "html-handling",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else if (pathname.endsWith(".html")) {
|
|
395
|
+
if ((redirectResult = await safeRedirect(pathname, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
396
|
+
// /foo.html exists so redirect to /foo/
|
|
397
|
+
return redirectResult;
|
|
398
|
+
}
|
|
399
|
+
else if (exactETag) {
|
|
400
|
+
// there's both /foo.html and /foo/index.html so we serve /foo.html at /foo.html only
|
|
401
|
+
return {
|
|
402
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
403
|
+
redirect: null,
|
|
404
|
+
resolver: "html-handling",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
408
|
+
// /foo/index.html exists so redirect to /foo/
|
|
409
|
+
return redirectResult;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (exactETag) {
|
|
413
|
+
// there's a binary /foo file
|
|
414
|
+
return {
|
|
415
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
416
|
+
redirect: null,
|
|
417
|
+
resolver: "html-handling",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
else if ((redirectResult = await safeRedirect(`${pathname}.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
421
|
+
// /foo.html exists so redirect to /foo/
|
|
422
|
+
return redirectResult;
|
|
423
|
+
}
|
|
424
|
+
else if ((redirectResult = await safeRedirect(`${pathname}/index.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
|
|
425
|
+
// /foo/index.html exists so redirect to /foo/
|
|
426
|
+
return redirectResult;
|
|
427
|
+
}
|
|
428
|
+
return notFound(pathname, request, configuration, exists);
|
|
429
|
+
};
|
|
430
|
+
const htmlHandlingDropTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
|
|
431
|
+
let redirectResult = null;
|
|
432
|
+
let eTagResult = null;
|
|
433
|
+
const exactETag = await exists(pathname, request);
|
|
434
|
+
if (pathname.endsWith("/index")) {
|
|
435
|
+
if (exactETag) {
|
|
436
|
+
// there's a binary /index file
|
|
437
|
+
return {
|
|
438
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
439
|
+
redirect: null,
|
|
440
|
+
resolver: "html-handling",
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
if (pathname === "/index") {
|
|
445
|
+
if ((redirectResult = await safeRedirect("/index.html", request, "/", configuration, exists, skipRedirects, "html-handling"))) {
|
|
446
|
+
return redirectResult;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
450
|
+
// /foo.html exists so redirect to /foo
|
|
451
|
+
return redirectResult;
|
|
452
|
+
}
|
|
453
|
+
else if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
454
|
+
// /foo/index.html exists so redirect to /foo
|
|
455
|
+
return redirectResult;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else if (pathname.endsWith("/index.html")) {
|
|
460
|
+
// special handling so you don't drop / if the path is just /
|
|
461
|
+
if (pathname === "/index.html") {
|
|
462
|
+
if ((redirectResult = await safeRedirect("/index.html", request, "/", configuration, exists, skipRedirects, "html-handling"))) {
|
|
463
|
+
return redirectResult;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
467
|
+
// /foo/index.html exists so redirect to /foo
|
|
468
|
+
return redirectResult;
|
|
469
|
+
}
|
|
470
|
+
else if (exactETag) {
|
|
471
|
+
// there's both /foo.html and /foo/index.html so we serve /foo/index.html at /foo/index.html only
|
|
472
|
+
return {
|
|
473
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
474
|
+
redirect: null,
|
|
475
|
+
resolver: "html-handling",
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
479
|
+
// /foo.html exists so redirect to /foo
|
|
480
|
+
return redirectResult;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
else if (pathname.endsWith("/")) {
|
|
484
|
+
if (pathname === "/") {
|
|
485
|
+
if ((eTagResult = await exists("/index.html", request))) {
|
|
486
|
+
// /index.html exists so serve at /
|
|
487
|
+
return {
|
|
488
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
489
|
+
redirect: null,
|
|
490
|
+
resolver: "html-handling",
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
495
|
+
// /foo.html exists so redirect to /foo
|
|
496
|
+
return redirectResult;
|
|
497
|
+
}
|
|
498
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}/index.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
499
|
+
// /foo/index.html exists so redirect to /foo
|
|
500
|
+
return redirectResult;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else if (pathname.endsWith(".html")) {
|
|
504
|
+
if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
505
|
+
// /foo.html exists so redirect to /foo
|
|
506
|
+
return redirectResult;
|
|
507
|
+
}
|
|
508
|
+
else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
|
|
509
|
+
// /foo/index.html exists so redirect to /foo
|
|
510
|
+
return redirectResult;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (exactETag) {
|
|
514
|
+
// there's a binary /foo file
|
|
515
|
+
return {
|
|
516
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
517
|
+
redirect: null,
|
|
518
|
+
resolver: "html-handling",
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
else if ((eTagResult = await exists(`${pathname}.html`, request))) {
|
|
522
|
+
// /foo.html exists so serve at /foo
|
|
523
|
+
return {
|
|
524
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
525
|
+
redirect: null,
|
|
526
|
+
resolver: "html-handling",
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
else if ((eTagResult = await exists(`${pathname}/index.html`, request))) {
|
|
530
|
+
// /foo/index.html exists so serve at /foo
|
|
531
|
+
return {
|
|
532
|
+
asset: { eTag: eTagResult, status: OkResponse.status },
|
|
533
|
+
redirect: null,
|
|
534
|
+
resolver: "html-handling",
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
return notFound(pathname, request, configuration, exists);
|
|
538
|
+
};
|
|
539
|
+
const htmlHandlingNone = async (pathname, request, configuration, exists) => {
|
|
540
|
+
const exactETag = await exists(pathname, request);
|
|
541
|
+
if (exactETag) {
|
|
542
|
+
return {
|
|
543
|
+
asset: { eTag: exactETag, status: OkResponse.status },
|
|
544
|
+
redirect: null,
|
|
545
|
+
resolver: "html-handling",
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
return notFound(pathname, request, configuration, exists);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
const notFound = async (pathname, request, configuration, exists) => {
|
|
553
|
+
switch (configuration.not_found_handling) {
|
|
554
|
+
case "single-page-application": {
|
|
555
|
+
const eTag = await exists("/index.html", request);
|
|
556
|
+
if (eTag) {
|
|
557
|
+
return {
|
|
558
|
+
asset: { eTag, status: OkResponse.status },
|
|
559
|
+
redirect: null,
|
|
560
|
+
resolver: "not-found",
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
case "404-page": {
|
|
566
|
+
let cwd = pathname;
|
|
567
|
+
while (cwd) {
|
|
568
|
+
cwd = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
569
|
+
const eTag = await exists(`${cwd}/404.html`, request);
|
|
570
|
+
if (eTag) {
|
|
571
|
+
return {
|
|
572
|
+
asset: { eTag, status: NotFoundResponse.status },
|
|
573
|
+
redirect: null,
|
|
574
|
+
resolver: "not-found",
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
case "none":
|
|
581
|
+
default: {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
const safeRedirect = async (file, request, destination, configuration, exists, skip, resolver) => {
|
|
587
|
+
if (skip) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
if (!(await exists(destination, request))) {
|
|
591
|
+
const intent = await getIntent(destination, request, configuration, exists, true);
|
|
592
|
+
// return only if the eTag matches - i.e. not the 404 case
|
|
593
|
+
if (intent?.asset && intent.asset.eTag === (await exists(file, request))) {
|
|
594
|
+
return {
|
|
595
|
+
asset: null,
|
|
596
|
+
redirect: destination,
|
|
597
|
+
resolver,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return null;
|
|
602
|
+
};
|
|
603
|
+
/**
|
|
604
|
+
*
|
|
605
|
+
* +===========================================+===========+======================+
|
|
606
|
+
* | character type | fetch() | encodeURIComponent() |
|
|
607
|
+
* +===========================================+===========+======================+
|
|
608
|
+
* | unreserved ASCII e.g. a-z | unchanged | unchanged |
|
|
609
|
+
* +-------------------------------------------+-----------+----------------------+
|
|
610
|
+
* | reserved (sometimes encoded) | unchanged | encoded |
|
|
611
|
+
* | e.g. [ ] @ $ ! ' ( ) * + , ; = : ? # & % | | |
|
|
612
|
+
* +-------------------------------------------+-----------+----------------------+
|
|
613
|
+
* | non-ASCII e.g. ü. and space | encoded | encoded |
|
|
614
|
+
* +-------------------------------------------+-----------+----------------------+
|
|
615
|
+
*
|
|
616
|
+
* 1. Decode incoming path to handle non-ASCII characters or optionally encoded characters (e.g. square brackets)
|
|
617
|
+
* 2. Match decoded path to manifest
|
|
618
|
+
* 3. Re-encode the path and redirect if the re-encoded path is different from the original path
|
|
619
|
+
*
|
|
620
|
+
* If the user uploads a file that is already URL-encoded, that is accessible only at the (double) encoded path.
|
|
621
|
+
* e.g. /%5Bboop%5D.html is served at /%255Bboop%255D only
|
|
622
|
+
*
|
|
623
|
+
* */
|
|
624
|
+
/**
|
|
625
|
+
* Decode all incoming paths to ensure that we can handle paths with non-ASCII characters.
|
|
626
|
+
*/
|
|
627
|
+
const decodePath = (pathname) => {
|
|
628
|
+
return (pathname
|
|
629
|
+
.split("/")
|
|
630
|
+
.map((x) => {
|
|
631
|
+
try {
|
|
632
|
+
const decoded = decodeURIComponent(x);
|
|
633
|
+
return decoded;
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
return x;
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
.join("/")
|
|
640
|
+
// normalize the path; remove multiple slashes which could lead to same-schema redirects
|
|
641
|
+
.replace(/\/+/g, "/"));
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* Use the encoded path as the canonical path for sometimes-encoded characters
|
|
645
|
+
* e.g. /[boop] -> /%5Bboop%5D 307
|
|
646
|
+
*/
|
|
647
|
+
const encodePath = (pathname) => {
|
|
648
|
+
return pathname
|
|
649
|
+
.split("/")
|
|
650
|
+
.map((x) => {
|
|
651
|
+
try {
|
|
652
|
+
const encoded = encodeURIComponent(x);
|
|
653
|
+
return encoded;
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
return x;
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
.join("/");
|
|
660
|
+
};
|
|
661
|
+
const handleRedirects = (env, request, configuration, host, pathname, search) => {
|
|
662
|
+
const jaeger = env.JAEGER ?? mockJaegerBinding();
|
|
663
|
+
return jaeger.enterSpan("handle_redirects", (span) => {
|
|
664
|
+
const redirectMatch = staticRedirectsMatcher(configuration, host, pathname) ||
|
|
665
|
+
generateRedirectsMatcher(configuration)({ request })[0];
|
|
666
|
+
let proxied = false;
|
|
667
|
+
if (redirectMatch) {
|
|
668
|
+
if (redirectMatch.status === 200) {
|
|
669
|
+
// A 200 redirect means that we are proxying/rewriting to a different asset, for example,
|
|
670
|
+
// a request with url /users/12345 could be pointed to /users/id.html. In order to
|
|
671
|
+
// do this, we overwrite the pathname, and instead match for assets with that url,
|
|
672
|
+
// and importantly, do not use the regular redirect handler - as the url visible to
|
|
673
|
+
// the user does not change
|
|
674
|
+
pathname = new URL(redirectMatch.to, request.url).pathname;
|
|
675
|
+
proxied = true;
|
|
676
|
+
span.setTags({
|
|
677
|
+
matched: true,
|
|
678
|
+
proxied: true,
|
|
679
|
+
new_path: pathname,
|
|
680
|
+
status: redirectMatch.status,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
const { status, to } = redirectMatch;
|
|
685
|
+
const destination = new URL(to, request.url);
|
|
686
|
+
const location = destination.origin === new URL(request.url).origin
|
|
687
|
+
? `${destination.pathname}${destination.search || search}${destination.hash}`
|
|
688
|
+
: `${destination.href.slice(0, destination.href.length - (destination.search.length + destination.hash.length))}${destination.search ? destination.search : search}${destination.hash}`;
|
|
689
|
+
span.setTags({
|
|
690
|
+
matched: true,
|
|
691
|
+
destination: location,
|
|
692
|
+
status,
|
|
693
|
+
});
|
|
694
|
+
switch (status) {
|
|
695
|
+
case MovedPermanentlyResponse.status:
|
|
696
|
+
return new MovedPermanentlyResponse(location);
|
|
697
|
+
case SeeOtherResponse.status:
|
|
698
|
+
return new SeeOtherResponse(location);
|
|
699
|
+
case TemporaryRedirectResponse.status:
|
|
700
|
+
return new TemporaryRedirectResponse(location);
|
|
701
|
+
case PermanentRedirectResponse.status:
|
|
702
|
+
return new PermanentRedirectResponse(location);
|
|
703
|
+
case FoundResponse.status:
|
|
704
|
+
default:
|
|
705
|
+
return new FoundResponse(location);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
span.setTags({
|
|
711
|
+
matched: false,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return { proxied, pathname };
|
|
715
|
+
});
|
|
716
|
+
};
|
|
717
|
+
//# sourceMappingURL=handler.js.map
|