@chidchanun/bcp 0.1.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 +49 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/docs/caching.md +76 -0
- package/docs/configuration.md +97 -0
- package/docs/deployment.md +74 -0
- package/docs/getting-started.md +82 -0
- package/docs/middleware.md +58 -0
- package/docs/releasing.md +299 -0
- package/docs/routing.md +103 -0
- package/docs/security.md +57 -0
- package/package.json +68 -0
- package/packages/bundler/src/client-islands.ts +1457 -0
- package/packages/bundler/src/incremental-context.ts +206 -0
- package/packages/bundler/src/index.ts +1991 -0
- package/packages/bundler/src/module-graph.ts +317 -0
- package/packages/bundler/src/partial-hydration.ts +414 -0
- package/packages/bundler/src/production.ts +974 -0
- package/packages/bundler/src/server-production-middleware.ts +447 -0
- package/packages/bundler/src/server-production.ts +1193 -0
- package/packages/bundler/src/special-files.ts +131 -0
- package/packages/cache/src/index.ts +761 -0
- package/packages/cli/bin/bcp.mjs +93 -0
- package/packages/cli/src/args.ts +305 -0
- package/packages/cli/src/bootstrap.ts +514 -0
- package/packages/cli/src/index.ts +504 -0
- package/packages/cli/src/version.ts +45 -0
- package/packages/client/src/cache.ts +11 -0
- package/packages/client/src/config.ts +18 -0
- package/packages/client/src/error-boundary.tsx +149 -0
- package/packages/client/src/hydration.ts +3 -0
- package/packages/client/src/index.tsx +57 -0
- package/packages/client/src/islands.tsx +315 -0
- package/packages/client/src/metadata.ts +281 -0
- package/packages/client/src/navigation-loading.ts +52 -0
- package/packages/client/src/navigation-state.ts +80 -0
- package/packages/client/src/not-found.ts +34 -0
- package/packages/client/src/persistent-layout-runtime.ts +273 -0
- package/packages/client/src/router-v2.tsx +969 -0
- package/packages/client/src/router.tsx +1 -0
- package/packages/config/src/index.ts +1038 -0
- package/packages/env/src/index.ts +593 -0
- package/packages/router/src/advanced-router.ts +1032 -0
- package/packages/router/src/index.ts +1 -0
- package/packages/server/src/compression.ts +249 -0
- package/packages/server/src/dev-document-metadata.ts +154 -0
- package/packages/server/src/dev-hmr.ts +225 -0
- package/packages/server/src/index.ts +2265 -0
- package/packages/server/src/metadata.ts +478 -0
- package/packages/server/src/middleware-dev-server.ts +260 -0
- package/packages/server/src/middleware-loader.ts +140 -0
- package/packages/server/src/middleware-proxy.ts +516 -0
- package/packages/server/src/middleware.ts +704 -0
- package/packages/server/src/navigation-payload.ts +471 -0
- package/packages/server/src/production-server.ts +1746 -0
- package/packages/server/src/response-cache-proxy.ts +828 -0
- package/packages/server/src/security-proxy.ts +406 -0
- package/packages/server/src/security.ts +451 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
- package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
- package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
- package/packages/server/src/standalone-production-runtime.ts +1951 -0
- package/packages/server/src/standalone-production-server.ts +6 -0
- package/packages/server/src/static-assets.ts +262 -0
- package/packages/server/src/static-dev-server.ts +854 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import type * as http from "node:http";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_BODY_LIMIT =
|
|
4
|
+
1024 * 1024;
|
|
5
|
+
|
|
6
|
+
export class PayloadTooLargeError
|
|
7
|
+
extends Error {
|
|
8
|
+
readonly limit: number;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
limit: number
|
|
12
|
+
) {
|
|
13
|
+
super(
|
|
14
|
+
`Request body exceeded the configured limit of ${limit} bytes.`
|
|
15
|
+
);
|
|
16
|
+
this.name =
|
|
17
|
+
"PayloadTooLargeError";
|
|
18
|
+
this.limit =
|
|
19
|
+
limit;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SecurityHeaderValues {
|
|
24
|
+
poweredByHeader: boolean;
|
|
25
|
+
contentSecurityPolicy: string | null;
|
|
26
|
+
frameOptions: "DENY" | "SAMEORIGIN" | null;
|
|
27
|
+
referrerPolicy: string;
|
|
28
|
+
permissionsPolicy: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getSecurityHeaderValues(
|
|
32
|
+
environment: NodeJS.ProcessEnv = process.env
|
|
33
|
+
): SecurityHeaderValues {
|
|
34
|
+
return {
|
|
35
|
+
poweredByHeader:
|
|
36
|
+
readBoolean(
|
|
37
|
+
environment.BCP_POWERED_BY_HEADER,
|
|
38
|
+
false
|
|
39
|
+
),
|
|
40
|
+
contentSecurityPolicy:
|
|
41
|
+
readOptionalHeader(
|
|
42
|
+
environment.BCP_SECURITY_CSP
|
|
43
|
+
),
|
|
44
|
+
frameOptions:
|
|
45
|
+
readFrameOptions(
|
|
46
|
+
environment.BCP_SECURITY_FRAME_OPTIONS
|
|
47
|
+
),
|
|
48
|
+
referrerPolicy:
|
|
49
|
+
readRequiredHeader(
|
|
50
|
+
environment.BCP_SECURITY_REFERRER_POLICY,
|
|
51
|
+
"strict-origin-when-cross-origin"
|
|
52
|
+
),
|
|
53
|
+
permissionsPolicy:
|
|
54
|
+
readOptionalHeader(
|
|
55
|
+
environment.BCP_SECURITY_PERMISSIONS_POLICY,
|
|
56
|
+
"camera=(), microphone=(), geolocation=()"
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function applySecurityHeaders(
|
|
62
|
+
res: http.ServerResponse,
|
|
63
|
+
environment: NodeJS.ProcessEnv = process.env
|
|
64
|
+
): void {
|
|
65
|
+
const values =
|
|
66
|
+
getSecurityHeaderValues(
|
|
67
|
+
environment
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
setIfMissing(
|
|
71
|
+
res,
|
|
72
|
+
"X-Content-Type-Options",
|
|
73
|
+
"nosniff"
|
|
74
|
+
);
|
|
75
|
+
setIfMissing(
|
|
76
|
+
res,
|
|
77
|
+
"Referrer-Policy",
|
|
78
|
+
values.referrerPolicy
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
values.frameOptions
|
|
83
|
+
) {
|
|
84
|
+
setIfMissing(
|
|
85
|
+
res,
|
|
86
|
+
"X-Frame-Options",
|
|
87
|
+
values.frameOptions
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
values.permissionsPolicy
|
|
93
|
+
) {
|
|
94
|
+
setIfMissing(
|
|
95
|
+
res,
|
|
96
|
+
"Permissions-Policy",
|
|
97
|
+
values.permissionsPolicy
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
values.contentSecurityPolicy
|
|
103
|
+
) {
|
|
104
|
+
setIfMissing(
|
|
105
|
+
res,
|
|
106
|
+
"Content-Security-Policy",
|
|
107
|
+
values.contentSecurityPolicy
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
values.poweredByHeader
|
|
113
|
+
) {
|
|
114
|
+
setIfMissing(
|
|
115
|
+
res,
|
|
116
|
+
"X-Powered-By",
|
|
117
|
+
"BCP Framework"
|
|
118
|
+
);
|
|
119
|
+
} else if (
|
|
120
|
+
res.hasHeader(
|
|
121
|
+
"X-Powered-By"
|
|
122
|
+
)
|
|
123
|
+
) {
|
|
124
|
+
res.removeHeader(
|
|
125
|
+
"X-Powered-By"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getConfiguredBodyLimit(
|
|
131
|
+
environment: NodeJS.ProcessEnv = process.env
|
|
132
|
+
): number {
|
|
133
|
+
const value =
|
|
134
|
+
environment.BCP_BODY_LIMIT;
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
value === undefined ||
|
|
138
|
+
value.trim() === ""
|
|
139
|
+
) {
|
|
140
|
+
return DEFAULT_BODY_LIMIT;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const parsed =
|
|
144
|
+
Number(
|
|
145
|
+
value
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
!Number.isInteger(parsed) ||
|
|
150
|
+
parsed < 1
|
|
151
|
+
) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"BCP Framework: BCP_BODY_LIMIT must be a positive integer number of bytes."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return parsed;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function readRequestBodyWithLimit(
|
|
161
|
+
req: http.IncomingMessage,
|
|
162
|
+
limit = getConfiguredBodyLimit()
|
|
163
|
+
): Promise<Buffer> {
|
|
164
|
+
const contentLength =
|
|
165
|
+
readContentLength(
|
|
166
|
+
req.headers[
|
|
167
|
+
"content-length"
|
|
168
|
+
]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
contentLength !== null &&
|
|
173
|
+
contentLength > limit
|
|
174
|
+
) {
|
|
175
|
+
req.resume();
|
|
176
|
+
throw new PayloadTooLargeError(
|
|
177
|
+
limit
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const chunks:
|
|
182
|
+
Buffer[] = [];
|
|
183
|
+
let total = 0;
|
|
184
|
+
|
|
185
|
+
for await (
|
|
186
|
+
const chunk
|
|
187
|
+
of req
|
|
188
|
+
) {
|
|
189
|
+
const buffer =
|
|
190
|
+
Buffer.isBuffer(
|
|
191
|
+
chunk
|
|
192
|
+
)
|
|
193
|
+
? chunk
|
|
194
|
+
: Buffer.from(
|
|
195
|
+
chunk
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
total +=
|
|
199
|
+
buffer.length;
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
total > limit
|
|
203
|
+
) {
|
|
204
|
+
req.resume();
|
|
205
|
+
throw new PayloadTooLargeError(
|
|
206
|
+
limit
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
chunks.push(
|
|
211
|
+
buffer
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return Buffer.concat(
|
|
216
|
+
chunks,
|
|
217
|
+
total
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function isPayloadTooLargeError(
|
|
222
|
+
error: unknown
|
|
223
|
+
): error is PayloadTooLargeError {
|
|
224
|
+
return (
|
|
225
|
+
error instanceof
|
|
226
|
+
PayloadTooLargeError ||
|
|
227
|
+
(
|
|
228
|
+
error instanceof Error &&
|
|
229
|
+
error.name ===
|
|
230
|
+
"PayloadTooLargeError"
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function assertSafeMiddlewareTarget(
|
|
236
|
+
target: URL,
|
|
237
|
+
originalUrl: URL,
|
|
238
|
+
kind: "redirect" | "rewrite"
|
|
239
|
+
): void {
|
|
240
|
+
if (
|
|
241
|
+
target.protocol !== "http:" &&
|
|
242
|
+
target.protocol !== "https:"
|
|
243
|
+
) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`BCP Framework: middleware ${kind} only supports http: and https: URLs.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
target.username ||
|
|
251
|
+
target.password
|
|
252
|
+
) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`BCP Framework: middleware ${kind} URLs must not contain credentials.`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
kind === "rewrite" &&
|
|
260
|
+
target.origin !==
|
|
261
|
+
originalUrl.origin
|
|
262
|
+
) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"BCP Framework: middleware rewrites must stay on the same origin."
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readContentLength(
|
|
270
|
+
value:
|
|
271
|
+
| string
|
|
272
|
+
| string[]
|
|
273
|
+
| undefined
|
|
274
|
+
): number | null {
|
|
275
|
+
const raw =
|
|
276
|
+
Array.isArray(
|
|
277
|
+
value
|
|
278
|
+
)
|
|
279
|
+
? value[0]
|
|
280
|
+
: value;
|
|
281
|
+
|
|
282
|
+
if (!raw) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const parsed =
|
|
287
|
+
Number(
|
|
288
|
+
raw
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (
|
|
292
|
+
!Number.isInteger(parsed) ||
|
|
293
|
+
parsed < 0
|
|
294
|
+
) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return parsed;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function readBoolean(
|
|
302
|
+
value: string | undefined,
|
|
303
|
+
fallback: boolean
|
|
304
|
+
): boolean {
|
|
305
|
+
if (
|
|
306
|
+
value === undefined ||
|
|
307
|
+
value.trim() === ""
|
|
308
|
+
) {
|
|
309
|
+
return fallback;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const normalized =
|
|
313
|
+
value
|
|
314
|
+
.trim()
|
|
315
|
+
.toLowerCase();
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
normalized === "1" ||
|
|
319
|
+
normalized === "true" ||
|
|
320
|
+
normalized === "yes" ||
|
|
321
|
+
normalized === "on"
|
|
322
|
+
) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
normalized === "0" ||
|
|
328
|
+
normalized === "false" ||
|
|
329
|
+
normalized === "no" ||
|
|
330
|
+
normalized === "off"
|
|
331
|
+
) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return fallback;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function readFrameOptions(
|
|
339
|
+
value: string | undefined
|
|
340
|
+
): "DENY" | "SAMEORIGIN" | null {
|
|
341
|
+
if (
|
|
342
|
+
value === undefined ||
|
|
343
|
+
value.trim() === ""
|
|
344
|
+
) {
|
|
345
|
+
return "SAMEORIGIN";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const normalized =
|
|
349
|
+
value
|
|
350
|
+
.trim()
|
|
351
|
+
.toUpperCase();
|
|
352
|
+
|
|
353
|
+
if (
|
|
354
|
+
normalized === "0" ||
|
|
355
|
+
normalized === "FALSE" ||
|
|
356
|
+
normalized === "OFF"
|
|
357
|
+
) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (
|
|
362
|
+
normalized === "DENY" ||
|
|
363
|
+
normalized === "SAMEORIGIN"
|
|
364
|
+
) {
|
|
365
|
+
return normalized;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return "SAMEORIGIN";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function readRequiredHeader(
|
|
372
|
+
value: string | undefined,
|
|
373
|
+
fallback: string
|
|
374
|
+
): string {
|
|
375
|
+
const result =
|
|
376
|
+
value?.trim() ||
|
|
377
|
+
fallback;
|
|
378
|
+
|
|
379
|
+
assertHeaderValue(
|
|
380
|
+
result
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function readOptionalHeader(
|
|
387
|
+
value: string | undefined,
|
|
388
|
+
fallback: string | null = null
|
|
389
|
+
): string | null {
|
|
390
|
+
if (
|
|
391
|
+
value !== undefined
|
|
392
|
+
) {
|
|
393
|
+
const normalized =
|
|
394
|
+
value.trim();
|
|
395
|
+
|
|
396
|
+
if (
|
|
397
|
+
normalized === "" ||
|
|
398
|
+
normalized === "0" ||
|
|
399
|
+
normalized.toLowerCase() ===
|
|
400
|
+
"false" ||
|
|
401
|
+
normalized.toLowerCase() ===
|
|
402
|
+
"off"
|
|
403
|
+
) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
assertHeaderValue(
|
|
408
|
+
normalized
|
|
409
|
+
);
|
|
410
|
+
return normalized;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (fallback) {
|
|
414
|
+
assertHeaderValue(
|
|
415
|
+
fallback
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return fallback;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function assertHeaderValue(
|
|
423
|
+
value: string
|
|
424
|
+
): void {
|
|
425
|
+
if (
|
|
426
|
+
value.includes("\r") ||
|
|
427
|
+
value.includes("\n") ||
|
|
428
|
+
value.includes("\0")
|
|
429
|
+
) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"BCP Framework: security header values must not contain control characters."
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function setIfMissing(
|
|
437
|
+
res: http.ServerResponse,
|
|
438
|
+
name: string,
|
|
439
|
+
value: string
|
|
440
|
+
): void {
|
|
441
|
+
if (
|
|
442
|
+
!res.hasHeader(
|
|
443
|
+
name
|
|
444
|
+
)
|
|
445
|
+
) {
|
|
446
|
+
res.setHeader(
|
|
447
|
+
name,
|
|
448
|
+
value
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|