@zudojs/http 1.1.0 → 1.3.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/README.md +53 -3
- package/dist/httpAdapter/http.adapter.d.ts +34 -3
- package/dist/httpAdapter/http.adapter.js +22 -6
- package/dist/httpAdapter/http.adapters.d.ts +24 -3
- package/dist/httpAdapter/http.adapters.js +24 -45
- package/dist/httpAdapter/httpAdapter.errorResponse.d.ts +2 -2
- package/dist/httpAdapter/httpAdapter.errorResponse.js +26 -9
- package/dist/httpAdapter/httpAdapter.logger.d.ts +19 -0
- package/dist/httpAdapter/httpAdapter.logger.js +21 -0
- package/dist/httpAdapter/node/httpNode.adapter.d.ts +12 -1
- package/dist/httpAdapter/node/httpNode.adapter.js +28 -14
- package/dist/httpAdapter/node/httpNode.request.d.ts +8 -7
- package/dist/httpAdapter/node/httpNode.request.js +17 -27
- package/dist/httpAdapter/node/httpNode.server.d.ts +6 -0
- package/dist/httpAdapter/node/httpNode.server.js +6 -0
- package/dist/httpAdapter/node/httpNode.type.d.ts +2 -1
- package/dist/httpAgent/http.agent.d.ts +22 -3
- package/dist/httpAgent/http.agent.js +52 -14
- package/dist/httpCacheControl/httpCacheControl.freshness.d.ts +7 -1
- package/dist/httpCacheControl/httpCacheControl.freshness.js +30 -3
- package/dist/httpCookies/http.cookies.d.ts +29 -2
- package/dist/httpCookies/http.cookies.js +44 -24
- package/dist/httpCookies/httpCookies.defaults.d.ts +40 -0
- package/dist/httpCookies/httpCookies.defaults.js +42 -0
- package/dist/httpCookies/index.d.ts +1 -0
- package/dist/httpCookies/index.js +1 -0
- package/dist/httpKeepAlive/httpKeepAlive.core.js +14 -2
- package/dist/httpMiddleware/builtin/conditional/httpMiddleware.conditional.d.ts +22 -1
- package/dist/httpMiddleware/builtin/conditional/httpMiddleware.conditional.js +19 -4
- package/dist/httpMiddleware/builtin/conditional/index.d.ts +1 -0
- package/dist/httpMiddleware/builtin/cors/httpMiddleware.cors.d.ts +8 -0
- package/dist/httpMiddleware/builtin/cors/httpMiddleware.cors.js +25 -22
- package/dist/httpMiddleware/builtin/helpers/httpMiddleware.accessor.js +2 -6
- package/dist/httpMiddleware/builtin/helpers/httpMiddleware.media.d.ts +33 -0
- package/dist/httpMiddleware/builtin/helpers/httpMiddleware.media.js +59 -0
- package/dist/httpMiddleware/builtin/helpers/index.d.ts +1 -0
- package/dist/httpMiddleware/builtin/helpers/index.js +1 -0
- package/dist/httpMiddleware/builtin/image/httpMiddleware.image.d.ts +13 -0
- package/dist/httpMiddleware/builtin/image/httpMiddleware.image.js +31 -53
- package/dist/httpMiddleware/builtin/index.d.ts +1 -0
- package/dist/httpMiddleware/builtin/index.js +1 -0
- package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.d.ts +18 -0
- package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.js +17 -1
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +47 -0
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +61 -0
- package/dist/httpMiddleware/builtin/rateLimit/index.d.ts +7 -0
- package/dist/httpMiddleware/builtin/rateLimit/index.js +7 -0
- package/dist/httpMiddleware/builtin/security/httpMiddleware.security.d.ts +11 -0
- package/dist/httpMiddleware/builtin/security/httpMiddleware.security.js +25 -12
- package/dist/httpMiddleware/builtin/static/httpMiddleware.static.js +18 -4
- package/dist/httpMiddleware/builtin/video/httpMiddleware.video.d.ts +19 -1
- package/dist/httpMiddleware/builtin/video/httpMiddleware.video.js +38 -59
- package/dist/httpMiddleware/builtin/video/httpMiddleware.video.options.d.ts +12 -0
- package/dist/httpMiddleware/builtin/video/httpMiddleware.video.options.js +31 -0
- package/dist/httpMiddleware/httpMiddleware.error.d.ts +5 -31
- package/dist/httpMiddleware/httpMiddleware.error.js +5 -53
- package/dist/httpNegotiation/httpNegotiation.core.d.ts +13 -0
- package/dist/httpNegotiation/httpNegotiation.core.js +60 -8
- package/dist/httpProxy/http.proxy.d.ts +16 -0
- package/dist/httpProxy/http.proxy.js +44 -3
- package/dist/httpProxy/httpProxy.pathGuard.d.ts +23 -0
- package/dist/httpProxy/httpProxy.pathGuard.js +43 -0
- package/dist/httpProxy/index.d.ts +1 -0
- package/dist/httpProxy/index.js +1 -0
- package/dist/httpQuery/index.d.ts +11 -2
- package/dist/httpQuery/index.js +11 -2
- package/dist/httpQuery/queryParse/index.d.ts +10 -0
- package/dist/httpQuery/queryParse/index.js +10 -0
- package/dist/httpQuery/queryParse/queryParse.flat.d.ts +13 -0
- package/dist/httpQuery/queryParse/queryParse.flat.js +44 -0
- package/dist/httpQuery/queryParse/queryParse.nested.d.ts +25 -0
- package/dist/httpQuery/queryParse/queryParse.nested.js +112 -0
- package/dist/httpQuery/queryParse/queryParse.tokenizer.d.ts +37 -0
- package/dist/httpQuery/queryParse/queryParse.tokenizer.js +95 -0
- package/dist/httpQuery/queryRequest/index.d.ts +9 -0
- package/dist/httpQuery/queryRequest/index.js +9 -0
- package/dist/httpQuery/queryRequest/query.request.d.ts +43 -0
- package/dist/httpQuery/queryRequest/query.request.js +96 -0
- package/dist/httpQuery/querySerialize/index.d.ts +10 -0
- package/dist/httpQuery/querySerialize/index.js +10 -0
- package/dist/httpQuery/querySerialize/query.util.d.ts +21 -0
- package/dist/httpQuery/querySerialize/query.util.js +67 -0
- package/dist/httpQuery/querySerialize/querySerialize.core.d.ts +12 -0
- package/dist/httpQuery/querySerialize/querySerialize.core.js +97 -0
- package/dist/httpQuery/queryTypes/index.d.ts +11 -0
- package/dist/httpQuery/queryTypes/index.js +9 -0
- package/dist/httpQuery/queryTypes/query.container.d.ts +16 -0
- package/dist/httpQuery/queryTypes/query.container.js +51 -0
- package/dist/httpQuery/queryTypes/query.limit.d.ts +25 -0
- package/dist/httpQuery/queryTypes/query.limit.js +32 -0
- package/dist/httpQuery/queryTypes/query.type.d.ts +62 -0
- package/dist/httpQuery/queryTypes/query.type.js +2 -0
- package/dist/httpRedirect/http.redirect.d.ts +6 -0
- package/dist/httpRedirect/http.redirect.js +53 -2
- package/dist/httpRequest/http.request.d.ts +61 -2
- package/dist/httpRequest/http.request.js +86 -35
- package/dist/httpRequest/httpRequest.context.d.ts +7 -0
- package/dist/httpRequest/httpRequest.context.js +26 -34
- package/dist/httpRequest/index.d.ts +1 -0
- package/dist/httpRequest/index.js +1 -0
- package/dist/httpRequest/target/httpRequest.target.d.ts +45 -0
- package/dist/httpRequest/target/httpRequest.target.js +111 -0
- package/dist/httpRequest/target/index.d.ts +9 -0
- package/dist/httpRequest/target/index.js +9 -0
- package/dist/httpResponse/httpResponse.helper.js +2 -1
- package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +18 -1
- package/dist/httpRouter/core/factory/httpRoute.factory.base.js +49 -6
- package/dist/httpRouter/core/factory/httpRoute.factory.js +3 -3
- package/dist/httpRouter/core/register/httpRouter.register.js +16 -21
- package/dist/httpRouter/core/types/httpRouter.type.d.ts +6 -0
- package/dist/httpRouter/core/util/httpRoute.util.d.ts +61 -0
- package/dist/httpRouter/core/util/httpRoute.util.js +102 -17
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.d.ts +11 -0
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +5 -3
- package/dist/httpRouter/matching/httpRoute.matcher.core.js +51 -6
- package/dist/httpRouter/matching/httpRoute.matcher.d.ts +0 -1
- package/dist/httpRouter/matching/httpRoute.matcher.js +64 -26
- package/dist/httpRouter/pattern/httpRoute.pattern.parse.d.ts +16 -0
- package/dist/httpRouter/pattern/httpRoute.pattern.parse.js +40 -11
- package/dist/httpRouter/pattern/index.d.ts +1 -1
- package/dist/httpRouter/pattern/index.js +1 -1
- package/dist/httpSecurity/httpSecurity.config.d.ts +6 -0
- package/dist/httpSecurity/httpSecurity.config.js +1 -0
- package/dist/httpSecurity/httpSecurity.guard.d.ts +7 -9
- package/dist/httpSecurity/httpSecurity.guard.js +7 -14
- package/dist/httpSecurity/httpSecurity.nodeGuard.d.ts +48 -0
- package/dist/httpSecurity/httpSecurity.nodeGuard.js +32 -0
- package/dist/httpSecurity/httpSecurity.validator.js +20 -9
- package/dist/httpSecurity/index.d.ts +2 -0
- package/dist/httpSecurity/index.js +1 -0
- package/dist/httpServer/core/httpServer.core.d.ts +11 -0
- package/dist/httpServer/core/httpServer.core.js +30 -4
- package/dist/httpTrustProxy/httpTrustProxy.compilation.d.ts +5 -2
- package/dist/httpTrustProxy/httpTrustProxy.compilation.js +19 -2
- package/dist/httpTrustProxy/httpTrustProxy.type.d.ts +3 -1
- package/dist/index.js +0 -1
- package/package.json +5 -5
|
@@ -4,19 +4,34 @@
|
|
|
4
4
|
* @module httpMiddleware/builtin/security
|
|
5
5
|
*/
|
|
6
6
|
import { isValidHeaderFieldValue } from "../../../httpHeaders/security/index.js";
|
|
7
|
+
import { createSecurityHeaders } from "../../../httpSecurityHeaders/httpSecurityHeader.factory.js";
|
|
8
|
+
import { createDefaultSecurityHeaderOptions } from "../../../httpSecurityHeaders/httpSecurityHeader.recommended.js";
|
|
7
9
|
import { withResponseHeaders } from "../helpers/index.js";
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* The headers applied when the caller does not override them.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
13
|
+
* This is the package's declared safe baseline — the very set
|
|
14
|
+
* `createDefaultSecurityHeaderOptions` was written for and, until now, was
|
|
15
|
+
* never called for. The middleware used to re-derive its own three-entry
|
|
16
|
+
* list, so `pipeline.use(createSecurityMiddleware())` emitted no
|
|
17
|
+
* `Content-Security-Policy`, no `Strict-Transport-Security`, no
|
|
18
|
+
* `Permissions-Policy` and none of the cross-origin isolation headers while
|
|
19
|
+
* reading as evidence that the control was in force.
|
|
20
|
+
*/
|
|
21
|
+
function defaultSecurityHeaders() {
|
|
22
|
+
return createSecurityHeaders(createDefaultSecurityHeaderOptions());
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates the response-hardening middleware.
|
|
26
|
+
*
|
|
27
|
+
* Called with no options it emits the package's default security header set
|
|
28
|
+
* ({@link createDefaultSecurityHeaderOptions}); the options below layer over
|
|
29
|
+
* that set, and `useDefaults: false` drops it entirely.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Explicit header values, layered over the baseline.
|
|
32
|
+
* @returns A middleware that adds the headers to the downstream response.
|
|
33
|
+
* @throws {TypeError} If a configured value contains a control character.
|
|
14
34
|
*/
|
|
15
|
-
const DEFAULT_SECURITY_HEADERS = Object.freeze({
|
|
16
|
-
"x-content-type-options": "nosniff",
|
|
17
|
-
"x-frame-options": "DENY",
|
|
18
|
-
"referrer-policy": "strict-origin-when-cross-origin",
|
|
19
|
-
});
|
|
20
35
|
export function createSecurityMiddleware(options = {}) {
|
|
21
36
|
const configured = {
|
|
22
37
|
"strict-transport-security": options.strictTransportSecurity,
|
|
@@ -26,9 +41,7 @@ export function createSecurityMiddleware(options = {}) {
|
|
|
26
41
|
"content-security-policy": options.contentSecurityPolicy,
|
|
27
42
|
"referrer-policy": options.referrerPolicy,
|
|
28
43
|
};
|
|
29
|
-
const resolved = {
|
|
30
|
-
...(options.useDefaults === false ? {} : DEFAULT_SECURITY_HEADERS),
|
|
31
|
-
};
|
|
44
|
+
const resolved = options.useDefaults === false ? {} : defaultSecurityHeaders();
|
|
32
45
|
for (const [name, value] of Object.entries(configured)) {
|
|
33
46
|
if (value === undefined) {
|
|
34
47
|
continue;
|
|
@@ -7,6 +7,8 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { extname, join, resolve, sep } from "node:path";
|
|
9
9
|
import { applyHeadersToResponse } from "../helpers/index.js";
|
|
10
|
+
import { parseRequestTarget } from "../../../httpRequest/target/httpRequest.target.js";
|
|
11
|
+
import { evaluateConditionalRequest } from "../../../httpConditional/httpConditional.core.js";
|
|
10
12
|
const DEFAULT_INDEX = "index.html";
|
|
11
13
|
const DEFAULT_MAX_AGE = 3600;
|
|
12
14
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
@@ -141,7 +143,7 @@ export function createStaticMiddleware(options) {
|
|
|
141
143
|
*/
|
|
142
144
|
let url;
|
|
143
145
|
try {
|
|
144
|
-
url =
|
|
146
|
+
url = parseRequestTarget(context.request.url);
|
|
145
147
|
}
|
|
146
148
|
catch {
|
|
147
149
|
return next();
|
|
@@ -200,9 +202,21 @@ export function createStaticMiddleware(options) {
|
|
|
200
202
|
* returns the whole body and has no 206 path, so advertising range
|
|
201
203
|
* support makes range-aware clients misbehave.
|
|
202
204
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
/*
|
|
206
|
+
* RFC 9110 section 13.2.2 evaluation: `If-None-Match` lists, weak tags
|
|
207
|
+
* and `*`, then `If-Modified-Since`, plus `If-Match` /
|
|
208
|
+
* `If-Unmodified-Since` (412). An exact string compare missed all of
|
|
209
|
+
* those and re-sent the whole file.
|
|
210
|
+
*/
|
|
211
|
+
const headers = context.request.headers;
|
|
212
|
+
const conditional = evaluateConditionalRequest(context.request.method, {
|
|
213
|
+
ifMatch: headers["if-match"],
|
|
214
|
+
ifNoneMatch: headers["if-none-match"],
|
|
215
|
+
ifModifiedSince: headers["if-modified-since"],
|
|
216
|
+
ifUnmodifiedSince: headers["if-unmodified-since"],
|
|
217
|
+
}, { etag, lastModified });
|
|
218
|
+
if (conditional.statusCode !== undefined) {
|
|
219
|
+
return applyHeadersToResponse(context.response, responseHeaders).setStatus(conditional.statusCode);
|
|
206
220
|
}
|
|
207
221
|
return applyHeadersToResponse(context.response, responseHeaders).setBody(fileData);
|
|
208
222
|
};
|
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module httpMiddleware/builtin/video
|
|
5
5
|
*
|
|
6
|
-
* Requires: npm install fluent-ffmpeg
|
|
6
|
+
* Requires: npm install fluent-ffmpeg (and an ffmpeg binary)
|
|
7
7
|
*/
|
|
8
8
|
import type { HttpMiddleware } from "../../httpMiddleware.type.js";
|
|
9
|
+
import { type MediaCompressionErrorHandler } from "../helpers/index.js";
|
|
9
10
|
export interface VideoCompressionOptions {
|
|
10
11
|
readonly bitrate?: string;
|
|
11
12
|
readonly preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
|
|
12
13
|
readonly crf?: number;
|
|
13
14
|
readonly format?: "mp4" | "webm" | "mov";
|
|
15
|
+
/** Input container; defaults to `mp4`. */
|
|
16
|
+
readonly inputFormat?: string;
|
|
14
17
|
readonly scale?: {
|
|
15
18
|
readonly width?: number;
|
|
16
19
|
readonly height?: number;
|
|
@@ -20,7 +23,22 @@ export interface VideoCompressionMiddlewareOptions {
|
|
|
20
23
|
readonly enabled?: boolean;
|
|
21
24
|
readonly contentTypeMap?: Record<string, VideoCompressionOptions>;
|
|
22
25
|
readonly tempDir?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Called when compression fails. The original response is still served.
|
|
28
|
+
*/
|
|
29
|
+
readonly onError?: MediaCompressionErrorHandler;
|
|
23
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Transcodes a video held in memory.
|
|
33
|
+
*
|
|
34
|
+
* `fluent-ffmpeg`'s export is a factory: `input()` exists only on the
|
|
35
|
+
* command it returns. Calling it on the module (as this did) threw
|
|
36
|
+
* `ffmpegInstance.input is not a function` on every call.
|
|
37
|
+
*/
|
|
24
38
|
export declare function compressVideo(inputBuffer: Buffer, options?: VideoCompressionOptions): Promise<Buffer>;
|
|
39
|
+
/**
|
|
40
|
+
* Compresses `video/*` responses, reading status, headers and body from the
|
|
41
|
+
* response returned by `next()`.
|
|
42
|
+
*/
|
|
25
43
|
export declare function createVideoCompressionMiddleware(options?: VideoCompressionMiddlewareOptions): HttpMiddleware;
|
|
26
44
|
//# sourceMappingURL=httpMiddleware.video.d.ts.map
|
|
@@ -3,79 +3,58 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module httpMiddleware/builtin/video
|
|
5
5
|
*
|
|
6
|
-
* Requires: npm install fluent-ffmpeg
|
|
6
|
+
* Requires: npm install fluent-ffmpeg (and an ffmpeg binary)
|
|
7
|
+
*/
|
|
8
|
+
import { Readable } from "node:stream";
|
|
9
|
+
import { applyCompressedBody, getResponseBytes, getResponseMediaType, loadOptionalModule, } from "../helpers/index.js";
|
|
10
|
+
import { CONTAINER_TYPES, DEFAULT_VIDEO_FORMAT as DEFAULT_FORMAT, buildOutputOptions, } from "./httpMiddleware.video.options.js";
|
|
11
|
+
/**
|
|
12
|
+
* Transcodes a video held in memory.
|
|
13
|
+
*
|
|
14
|
+
* `fluent-ffmpeg`'s export is a factory: `input()` exists only on the
|
|
15
|
+
* command it returns. Calling it on the module (as this did) threw
|
|
16
|
+
* `ffmpegInstance.input is not a function` on every call.
|
|
7
17
|
*/
|
|
8
|
-
import { applyHeadersToResponse } from "../helpers/index.js";
|
|
9
|
-
const DEFAULT_CRF = 28;
|
|
10
|
-
const DEFAULT_PRESET = "medium";
|
|
11
|
-
const DEFAULT_FORMAT = "mp4";
|
|
12
|
-
async function getFfmpeg() {
|
|
13
|
-
try {
|
|
14
|
-
const mod = await import("fluent-ffmpeg");
|
|
15
|
-
return mod.default ?? mod;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
throw new Error("fluent-ffmpeg is not installed. Run: npm install fluent-ffmpeg");
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
18
|
export async function compressVideo(inputBuffer, options = {}) {
|
|
22
|
-
const
|
|
23
|
-
const crf = options.crf ?? DEFAULT_CRF;
|
|
24
|
-
const preset = options.preset ?? DEFAULT_PRESET;
|
|
25
|
-
const format = options.format ?? DEFAULT_FORMAT;
|
|
19
|
+
const ffmpeg = await loadOptionalModule("fluent-ffmpeg", "npm install fluent-ffmpeg");
|
|
26
20
|
return new Promise((resolve, reject) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
.input(inputBuffer)
|
|
30
|
-
.inputFormat("mp4")
|
|
31
|
-
.outputOptions(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
resolve(Buffer.concat(outputBuffer));
|
|
39
|
-
})
|
|
40
|
-
.on("error", (err) => {
|
|
41
|
-
reject(err);
|
|
42
|
-
})
|
|
43
|
-
.pipe((err, stdout) => {
|
|
44
|
-
if (err) {
|
|
45
|
-
reject(err);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
stdout.on("data", (chunk) => {
|
|
49
|
-
outputBuffer.push(chunk);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
21
|
+
const chunks = [];
|
|
22
|
+
const output = ffmpeg()
|
|
23
|
+
.input(Readable.from([inputBuffer]))
|
|
24
|
+
.inputFormat(options.inputFormat ?? "mp4")
|
|
25
|
+
.outputOptions(buildOutputOptions(options))
|
|
26
|
+
.format(options.format ?? DEFAULT_FORMAT)
|
|
27
|
+
.on("error", reject)
|
|
28
|
+
.pipe();
|
|
29
|
+
output.on("data", (chunk) => chunks.push(chunk));
|
|
30
|
+
output.on("end", () => resolve(Buffer.concat(chunks)));
|
|
31
|
+
output.on("error", reject);
|
|
52
32
|
});
|
|
53
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Compresses `video/*` responses, reading status, headers and body from the
|
|
36
|
+
* response returned by `next()`.
|
|
37
|
+
*/
|
|
54
38
|
export function createVideoCompressionMiddleware(options = {}) {
|
|
55
39
|
const enabled = options.enabled ?? true;
|
|
56
|
-
return async (
|
|
57
|
-
if (!enabled) {
|
|
58
|
-
return next();
|
|
59
|
-
}
|
|
40
|
+
return async (_context, next) => {
|
|
60
41
|
const response = await next();
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
42
|
+
const mediaType = getResponseMediaType(response);
|
|
43
|
+
if (!enabled || !mediaType?.startsWith("video/")) {
|
|
63
44
|
return response;
|
|
64
45
|
}
|
|
65
|
-
const body =
|
|
66
|
-
if (!
|
|
46
|
+
const body = getResponseBytes(response);
|
|
47
|
+
if (!body || body.length === 0) {
|
|
67
48
|
return response;
|
|
68
49
|
}
|
|
69
|
-
const
|
|
50
|
+
const compression = options.contentTypeMap?.[mediaType] ?? {};
|
|
51
|
+
const format = compression.format ?? DEFAULT_FORMAT;
|
|
70
52
|
try {
|
|
71
|
-
const compressed = await compressVideo(body,
|
|
72
|
-
|
|
73
|
-
const headers = new Headers(response.headers);
|
|
74
|
-
headers.set("content-type", newContentType);
|
|
75
|
-
headers.set("cache-control", headers.get("cache-control") ?? "public, max-age=86400");
|
|
76
|
-
return applyHeadersToResponse(response, headers).setBody(compressed);
|
|
53
|
+
const compressed = await compressVideo(body, compression);
|
|
54
|
+
return applyCompressedBody(response, compressed, CONTAINER_TYPES[format] ?? "video/mp4");
|
|
77
55
|
}
|
|
78
|
-
catch {
|
|
56
|
+
catch (error) {
|
|
57
|
+
options.onError?.(error, response);
|
|
79
58
|
return response;
|
|
80
59
|
}
|
|
81
60
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg argument building for the video compression middleware.
|
|
3
|
+
*
|
|
4
|
+
* @module httpMiddleware/builtin/video/options
|
|
5
|
+
*/
|
|
6
|
+
import type { VideoCompressionOptions } from "./httpMiddleware.video.js";
|
|
7
|
+
export declare const DEFAULT_VIDEO_FORMAT = "mp4";
|
|
8
|
+
/** Media type per output container. */
|
|
9
|
+
export declare const CONTAINER_TYPES: Readonly<Record<string, string>>;
|
|
10
|
+
/** Builds the ffmpeg output options for a compression request. */
|
|
11
|
+
export declare function buildOutputOptions(options: VideoCompressionOptions): string[];
|
|
12
|
+
//# sourceMappingURL=httpMiddleware.video.options.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg argument building for the video compression middleware.
|
|
3
|
+
*
|
|
4
|
+
* @module httpMiddleware/builtin/video/options
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_CRF = 28;
|
|
7
|
+
const DEFAULT_PRESET = "medium";
|
|
8
|
+
export const DEFAULT_VIDEO_FORMAT = "mp4";
|
|
9
|
+
/** Media type per output container. */
|
|
10
|
+
export const CONTAINER_TYPES = Object.freeze({
|
|
11
|
+
mp4: "video/mp4",
|
|
12
|
+
webm: "video/webm",
|
|
13
|
+
mov: "video/quicktime",
|
|
14
|
+
});
|
|
15
|
+
/** Builds the ffmpeg output options for a compression request. */
|
|
16
|
+
export function buildOutputOptions(options) {
|
|
17
|
+
const output = [
|
|
18
|
+
`-crf ${options.crf ?? DEFAULT_CRF}`,
|
|
19
|
+
`-preset ${options.preset ?? DEFAULT_PRESET}`,
|
|
20
|
+
/* A pipe is not seekable, so the moov atom must be fragmented. */
|
|
21
|
+
"-movflags frag_keyframe+empty_moov",
|
|
22
|
+
];
|
|
23
|
+
if (options.bitrate) {
|
|
24
|
+
output.push(`-b:v ${options.bitrate}`);
|
|
25
|
+
}
|
|
26
|
+
if (options.scale?.width || options.scale?.height) {
|
|
27
|
+
output.push(`-vf scale=${options.scale.width ?? -2}:${options.scale.height ?? -2}`);
|
|
28
|
+
}
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=httpMiddleware.video.options.js.map
|
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP middleware error types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `HttpMiddlewareError` and `HttpMiddlewarePipelineError` live in
|
|
5
|
+
* `@zudojs/errors` (both `BaseError`s, 500, not exposed, codes
|
|
6
|
+
* `HTTP_MIDDLEWARE_ERROR` / `MIDDLEWARE_PIPELINE_ERROR`); they are
|
|
7
|
+
* re-exported here so existing imports keep working.
|
|
6
8
|
*
|
|
7
9
|
* @module httpMiddleware/errors
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Error raised by an HTTP middleware function.
|
|
12
|
-
*/
|
|
13
|
-
export declare class HttpMiddlewareError extends BaseError {
|
|
14
|
-
/**
|
|
15
|
-
* The unique identifier of the middleware.
|
|
16
|
-
*/
|
|
17
|
-
readonly middlewareId: string | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* The name of the middleware.
|
|
20
|
-
*/
|
|
21
|
-
readonly middlewareName: string | undefined;
|
|
22
|
-
constructor(message: string, options?: {
|
|
23
|
-
readonly middlewareId?: string;
|
|
24
|
-
readonly middlewareName?: string;
|
|
25
|
-
readonly cause?: unknown;
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Error raised when the HTTP middleware pipeline fails.
|
|
30
|
-
*/
|
|
31
|
-
export declare class HttpMiddlewarePipelineError extends BaseError {
|
|
32
|
-
/**
|
|
33
|
-
* The errors that occurred during pipeline execution.
|
|
34
|
-
*/
|
|
35
|
-
readonly errors: readonly HttpMiddlewareError[];
|
|
36
|
-
constructor(errors: readonly HttpMiddlewareError[]);
|
|
37
|
-
}
|
|
11
|
+
export { HttpMiddlewareError, HttpMiddlewarePipelineError, type HttpMiddlewareErrorOptions, } from "@zudojs/errors";
|
|
38
12
|
//# sourceMappingURL=httpMiddleware.error.d.ts.map
|
|
@@ -1,60 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP middleware error types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `HttpMiddlewareError` and `HttpMiddlewarePipelineError` live in
|
|
5
|
+
* `@zudojs/errors` (both `BaseError`s, 500, not exposed, codes
|
|
6
|
+
* `HTTP_MIDDLEWARE_ERROR` / `MIDDLEWARE_PIPELINE_ERROR`); they are
|
|
7
|
+
* re-exported here so existing imports keep working.
|
|
6
8
|
*
|
|
7
9
|
* @module httpMiddleware/errors
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
/* -------------------------------------------------------------------------- */
|
|
11
|
-
/* Errors */
|
|
12
|
-
/* -------------------------------------------------------------------------- */
|
|
13
|
-
/**
|
|
14
|
-
* Error raised by an HTTP middleware function.
|
|
15
|
-
*/
|
|
16
|
-
export class HttpMiddlewareError extends BaseError {
|
|
17
|
-
/**
|
|
18
|
-
* The unique identifier of the middleware.
|
|
19
|
-
*/
|
|
20
|
-
middlewareId;
|
|
21
|
-
/**
|
|
22
|
-
* The name of the middleware.
|
|
23
|
-
*/
|
|
24
|
-
middlewareName;
|
|
25
|
-
constructor(message, options = {}) {
|
|
26
|
-
super(message, {
|
|
27
|
-
code: "HTTP_MIDDLEWARE_ERROR",
|
|
28
|
-
statusCode: 500,
|
|
29
|
-
expose: false,
|
|
30
|
-
cause: options.cause,
|
|
31
|
-
});
|
|
32
|
-
this.name = "HttpMiddlewareError";
|
|
33
|
-
this.middlewareId = options.middlewareId;
|
|
34
|
-
this.middlewareName = options.middlewareName;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Error raised when the HTTP middleware pipeline fails.
|
|
39
|
-
*/
|
|
40
|
-
export class HttpMiddlewarePipelineError extends BaseError {
|
|
41
|
-
/**
|
|
42
|
-
* The errors that occurred during pipeline execution.
|
|
43
|
-
*/
|
|
44
|
-
errors;
|
|
45
|
-
constructor(errors) {
|
|
46
|
-
const message = errors.length === 1
|
|
47
|
-
? `HTTP middleware pipeline failed: ${errors[0]?.message ?? "Unknown error"}`
|
|
48
|
-
: `HTTP middleware pipeline failed with ${errors.length} errors: ${errors
|
|
49
|
-
.map((e) => e.message)
|
|
50
|
-
.join(", ")}`;
|
|
51
|
-
super(message, {
|
|
52
|
-
code: "MIDDLEWARE_PIPELINE_ERROR",
|
|
53
|
-
statusCode: 500,
|
|
54
|
-
expose: false,
|
|
55
|
-
});
|
|
56
|
-
this.name = "HttpMiddlewarePipelineError";
|
|
57
|
-
this.errors = Object.freeze([...errors]);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
11
|
+
export { HttpMiddlewareError, HttpMiddlewarePipelineError, } from "@zudojs/errors";
|
|
60
12
|
//# sourceMappingURL=httpMiddleware.error.js.map
|
|
@@ -99,6 +99,19 @@ export declare function negotiateCharset(header: string | undefined | null, avai
|
|
|
99
99
|
* @returns The selected alternative, or `undefined` if none is acceptable.
|
|
100
100
|
*/
|
|
101
101
|
export declare function negotiate<T>(preferences: readonly NegotiationPreference[], available: readonly T[], matcher: (accepted: string, available: T) => boolean): T | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Returns the weight a preference list assigns to one value.
|
|
104
|
+
*
|
|
105
|
+
* The **most specific** match wins, and only then the highest weight, per
|
|
106
|
+
* RFC 9110 section 12.4.2. Ranking by weight first let `*;q=1` override an
|
|
107
|
+
* explicit `gzip;q=0`, so a coding the client had refused came back with
|
|
108
|
+
* full quality.
|
|
109
|
+
*
|
|
110
|
+
* @param preferences - The parsed preferences.
|
|
111
|
+
* @param value - The alternative to weigh.
|
|
112
|
+
* @param matcher - Matches a preference value against an alternative.
|
|
113
|
+
* @returns The quality in `[0, 1]`, or `0` when nothing matches.
|
|
114
|
+
*/
|
|
102
115
|
export declare function getPreferenceQuality<T>(preferences: readonly NegotiationPreference[], value: T, matcher: (accepted: string, available: T) => boolean): number;
|
|
103
116
|
export declare function normalizeMediaType(value: string): string;
|
|
104
117
|
export declare function splitMediaType(value: string): [string, string] | undefined;
|
|
@@ -214,7 +214,36 @@ export function negotiateEncoding(header, available) {
|
|
|
214
214
|
if (preferences.length === 0) {
|
|
215
215
|
return available[0];
|
|
216
216
|
}
|
|
217
|
-
|
|
217
|
+
const selected = negotiate(preferences, available, matchesEncoding);
|
|
218
|
+
if (selected !== undefined) {
|
|
219
|
+
return selected;
|
|
220
|
+
}
|
|
221
|
+
/*
|
|
222
|
+
* RFC 9110 section 12.5.3: a representation with no content coding is
|
|
223
|
+
* acceptable unless the field explicitly excludes it with `identity;q=0`
|
|
224
|
+
* or a `*;q=0` that no identity entry overrides. Returning `undefined`
|
|
225
|
+
* here made `Accept-Encoding: zstd` look like "nothing is acceptable", so
|
|
226
|
+
* a caller answered 406 for a request it could have served uncompressed.
|
|
227
|
+
*/
|
|
228
|
+
const identity = available.find((value) => isIdentityEncoding(value));
|
|
229
|
+
if (identity === undefined || isIdentityRejected(preferences)) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return identity;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Reports whether an `Accept-Encoding` field rejects the identity coding.
|
|
236
|
+
*
|
|
237
|
+
* @param preferences - The parsed preferences.
|
|
238
|
+
* @returns `true` when identity must not be served.
|
|
239
|
+
*/
|
|
240
|
+
function isIdentityRejected(preferences) {
|
|
241
|
+
const explicit = preferences.find((preference) => isIdentityEncoding(preference.value));
|
|
242
|
+
if (explicit) {
|
|
243
|
+
return !isAcceptableQuality(explicit.quality);
|
|
244
|
+
}
|
|
245
|
+
const wildcard = preferences.find((preference) => isWildcardEncoding(preference.value));
|
|
246
|
+
return wildcard !== undefined && !isAcceptableQuality(wildcard.quality);
|
|
218
247
|
}
|
|
219
248
|
export function getEncodingQuality(header, encoding) {
|
|
220
249
|
const preferences = parseAcceptEncoding(header);
|
|
@@ -319,20 +348,43 @@ function isExcluded(candidate, selected, rejections, matcher) {
|
|
|
319
348
|
return rejections.some((rejection) => rejection.specificity >= selected.specificity &&
|
|
320
349
|
matcher(rejection.value, candidate));
|
|
321
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Returns the weight a preference list assigns to one value.
|
|
353
|
+
*
|
|
354
|
+
* The **most specific** match wins, and only then the highest weight, per
|
|
355
|
+
* RFC 9110 section 12.4.2. Ranking by weight first let `*;q=1` override an
|
|
356
|
+
* explicit `gzip;q=0`, so a coding the client had refused came back with
|
|
357
|
+
* full quality.
|
|
358
|
+
*
|
|
359
|
+
* @param preferences - The parsed preferences.
|
|
360
|
+
* @param value - The alternative to weigh.
|
|
361
|
+
* @param matcher - Matches a preference value against an alternative.
|
|
362
|
+
* @returns The quality in `[0, 1]`, or `0` when nothing matches.
|
|
363
|
+
*/
|
|
322
364
|
export function getPreferenceQuality(preferences, value, matcher) {
|
|
323
365
|
let best;
|
|
324
366
|
for (const preference of preferences) {
|
|
325
|
-
if (matcher(preference.value, value)) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
best = preference;
|
|
331
|
-
}
|
|
367
|
+
if (!matcher(preference.value, value)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!best || isMoreRelevant(preference, best)) {
|
|
371
|
+
best = preference;
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
374
|
return best?.quality ?? 0;
|
|
335
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Orders two matching preferences: specificity, then weight, then position.
|
|
378
|
+
*/
|
|
379
|
+
function isMoreRelevant(candidate, best) {
|
|
380
|
+
if (candidate.specificity !== best.specificity) {
|
|
381
|
+
return candidate.specificity > best.specificity;
|
|
382
|
+
}
|
|
383
|
+
if (candidate.quality !== best.quality) {
|
|
384
|
+
return candidate.quality > best.quality;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
336
388
|
/* -------------------------------------------------------------------------- */
|
|
337
389
|
/* Media Type Helpers */
|
|
338
390
|
/* -------------------------------------------------------------------------- */
|
|
@@ -128,6 +128,13 @@ export declare function assertSafeProxyTarget(target: string | URL, options?: Pr
|
|
|
128
128
|
export declare function assertSafeProxyRedirect(location: string | URL, currentURL: string | URL, options?: ProxySecurityOptions): URL;
|
|
129
129
|
export declare function resolveProxyTarget(target: string | URL, options?: ProxySecurityOptions): ProxyTarget;
|
|
130
130
|
export declare function isValidProxyTarget(target: string | URL | undefined | null): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Joins a request path onto a proxy base path.
|
|
133
|
+
*
|
|
134
|
+
* @throws {HttpError} 400 when `requestPath` contains a `.` / `..` segment
|
|
135
|
+
* (plain or percent-encoded), which an upstream would resolve outside
|
|
136
|
+
* `basePath`.
|
|
137
|
+
*/
|
|
131
138
|
export declare function joinProxyPath(basePath: string, requestPath: string): string;
|
|
132
139
|
export declare function rewriteProxyPath(path: string, options?: ProxyRewriteOptions): string;
|
|
133
140
|
export declare function normalizeProxyPath(path: string): string;
|
|
@@ -160,6 +167,15 @@ export declare function applyProxyHeaders(headers: readonly HTTPHeader[], additi
|
|
|
160
167
|
* with `httpTrustProxy.getClientIp` rather than copying the raw header.
|
|
161
168
|
*/
|
|
162
169
|
export declare function setForwardedHeaders(headers: readonly HTTPHeader[], target: ProxyTarget, client?: ProxyClientContext): HTTPHeader[];
|
|
170
|
+
/**
|
|
171
|
+
* Builds an RFC 7239 `Forwarded` field value.
|
|
172
|
+
*
|
|
173
|
+
* Every parameter is escaped as a `quoted-string` when it is not a bare
|
|
174
|
+
* token, and the finished value is checked the same way `setForwardedHeaders`
|
|
175
|
+
* checks the values it writes.
|
|
176
|
+
*
|
|
177
|
+
* @throws {TypeError} If any parameter contains a control character.
|
|
178
|
+
*/
|
|
163
179
|
export declare function createForwardedHeader(address: ForwardedAddress): string;
|
|
164
180
|
export declare function parseForwardedHeader(value: string | undefined | null): ForwardedAddress[];
|
|
165
181
|
/**
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { appendHeader, deleteHeader, getHeader, setHeader, } from "../httpProtocol/http.protocol.js";
|
|
11
11
|
import { isValidHTTPURL, isValidHeaderValue } from "../httpValidation/index.js";
|
|
12
12
|
import { isLinkLocalAddress, isLoopbackAddress, isUniqueLocalAddress, parseIpAddress, } from "../httpTrustProxy/httpTrustProxy.ip.js";
|
|
13
|
-
import { assertSafeHeaderValue } from "../httpHeaders/security/index.js";
|
|
13
|
+
import { assertSafeHeaderValue, escapeHeaderQuotedString, } from "../httpHeaders/security/index.js";
|
|
14
|
+
import { assertProxyPathContained } from "./httpProxy.pathGuard.js";
|
|
14
15
|
/* -------------------------------------------------------------------------- */
|
|
15
16
|
/* Target */
|
|
16
17
|
/* -------------------------------------------------------------------------- */
|
|
@@ -71,6 +72,16 @@ function isBlockedLiteralAddress(hostname) {
|
|
|
71
72
|
(address.bytes[1] ?? 0) <= 127) {
|
|
72
73
|
return true;
|
|
73
74
|
}
|
|
75
|
+
/* 192.0.0.0/24 IETF protocol assignments (192.0.0.8, 192.0.0.170, …). */
|
|
76
|
+
if (first === 192 &&
|
|
77
|
+
(address.bytes[1] ?? 0) === 0 &&
|
|
78
|
+
(address.bytes[2] ?? 0) === 0) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/* 198.18.0.0/15 benchmarking. */
|
|
82
|
+
if (first === 198 && ((address.bytes[1] ?? 0) & 0xfe) === 18) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
74
85
|
return false;
|
|
75
86
|
}
|
|
76
87
|
const first = address.bytes[0] ?? 0;
|
|
@@ -204,7 +215,15 @@ function parseProxyURL(target) {
|
|
|
204
215
|
/* -------------------------------------------------------------------------- */
|
|
205
216
|
/* Path Handling */
|
|
206
217
|
/* -------------------------------------------------------------------------- */
|
|
218
|
+
/**
|
|
219
|
+
* Joins a request path onto a proxy base path.
|
|
220
|
+
*
|
|
221
|
+
* @throws {HttpError} 400 when `requestPath` contains a `.` / `..` segment
|
|
222
|
+
* (plain or percent-encoded), which an upstream would resolve outside
|
|
223
|
+
* `basePath`.
|
|
224
|
+
*/
|
|
207
225
|
export function joinProxyPath(basePath, requestPath) {
|
|
226
|
+
assertProxyPathContained(requestPath);
|
|
208
227
|
const base = normalizeProxyPath(basePath);
|
|
209
228
|
const request = requestPath.startsWith("/") ? requestPath : `/${requestPath}`;
|
|
210
229
|
if (base === "/") {
|
|
@@ -352,6 +371,15 @@ function appendForwardedValue(headers, name, value) {
|
|
|
352
371
|
/* -------------------------------------------------------------------------- */
|
|
353
372
|
/* Standard Forwarded Header */
|
|
354
373
|
/* -------------------------------------------------------------------------- */
|
|
374
|
+
/**
|
|
375
|
+
* Builds an RFC 7239 `Forwarded` field value.
|
|
376
|
+
*
|
|
377
|
+
* Every parameter is escaped as a `quoted-string` when it is not a bare
|
|
378
|
+
* token, and the finished value is checked the same way `setForwardedHeaders`
|
|
379
|
+
* checks the values it writes.
|
|
380
|
+
*
|
|
381
|
+
* @throws {TypeError} If any parameter contains a control character.
|
|
382
|
+
*/
|
|
355
383
|
export function createForwardedHeader(address) {
|
|
356
384
|
const parts = [];
|
|
357
385
|
if (address.for) {
|
|
@@ -366,7 +394,9 @@ export function createForwardedHeader(address) {
|
|
|
366
394
|
if (address.protocol) {
|
|
367
395
|
parts.push(`proto=${formatForwardedValue(address.protocol)}`);
|
|
368
396
|
}
|
|
369
|
-
|
|
397
|
+
const value = parts.join("; ");
|
|
398
|
+
assertSafeHeaderValue(value);
|
|
399
|
+
return value;
|
|
370
400
|
}
|
|
371
401
|
export function parseForwardedHeader(value) {
|
|
372
402
|
if (!value || value.trim().length === 0) {
|
|
@@ -525,11 +555,22 @@ function formatForwardedIdentifier(value) {
|
|
|
525
555
|
}
|
|
526
556
|
return formatForwardedValue(value);
|
|
527
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Emits an RFC 7239 parameter value.
|
|
560
|
+
*
|
|
561
|
+
* Wrapping a value in quotes does not neutralise a CR or LF — the control
|
|
562
|
+
* character survives into the field value and an attacker-chosen header
|
|
563
|
+
* follows it on the wire. `escapeHeaderQuotedString` rejects those characters
|
|
564
|
+
* rather than escaping them, which is what every other quoted-parameter
|
|
565
|
+
* emitter in this package already uses.
|
|
566
|
+
*
|
|
567
|
+
* @throws {TypeError} If the value contains a forbidden control character.
|
|
568
|
+
*/
|
|
528
569
|
function formatForwardedValue(value) {
|
|
529
570
|
if (/^[A-Za-z0-9._:-]+$/.test(value)) {
|
|
530
571
|
return value;
|
|
531
572
|
}
|
|
532
|
-
return `"${value
|
|
573
|
+
return `"${escapeHeaderQuotedString(value)}"`;
|
|
533
574
|
}
|
|
534
575
|
function unquoteForwardedValue(value) {
|
|
535
576
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|