@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
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { HttpRequestContext, createRequestContext, } from "../../httpRequest/httpRequest.context.js";
|
|
7
7
|
import { getClientIp, isTrustedProxy, } from "../../httpTrustProxy/httpTrustProxy.core.js";
|
|
8
8
|
import { removePort, extractPort } from "./httpNode.server.js";
|
|
9
|
-
import {
|
|
9
|
+
import { parseQueryString } from "../../httpQuery/index.js";
|
|
10
|
+
import { findRequestTargetViolation } from "../../httpRequest/target/httpRequest.target.js";
|
|
10
11
|
/* -------------------------------------------------------------------------- */
|
|
11
12
|
/* Proxy Trust */
|
|
12
13
|
/* -------------------------------------------------------------------------- */
|
|
@@ -122,53 +123,42 @@ export function getNodeRemoteAddress(request, options = {}) {
|
|
|
122
123
|
/**
|
|
123
124
|
* Parses the request-target's query string into a flat record.
|
|
124
125
|
*
|
|
125
|
-
* Every value is attacker-controlled
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
126
|
+
* Every value is attacker-controlled, so decoding never throws (a malformed
|
|
127
|
+
* `%E0` is kept raw) and `+` has its form-encoding meaning. This uses the same
|
|
128
|
+
* parser as the router's `ctx.query` (`parseQueryString`), so both agree: a
|
|
129
|
+
* repeated name is an array (`?role=user&role=admin` gives
|
|
130
|
+
* `["user", "admin"]`), the record has a `null` prototype, and
|
|
131
|
+
* `__proto__` / `constructor` / `prototype` are dropped. Previously the last
|
|
132
|
+
* value silently won here while the router returned the array.
|
|
131
133
|
*/
|
|
132
134
|
export function parseNodeQuery(request) {
|
|
133
135
|
const url = request.url;
|
|
134
136
|
if (!url) {
|
|
135
|
-
return Object.freeze(
|
|
137
|
+
return Object.freeze(Object.create(null));
|
|
136
138
|
}
|
|
137
139
|
const questionIndex = url.indexOf("?");
|
|
138
140
|
if (questionIndex === -1) {
|
|
139
|
-
return Object.freeze(
|
|
141
|
+
return Object.freeze(Object.create(null));
|
|
140
142
|
}
|
|
141
143
|
const hashIndex = url.indexOf("#", questionIndex + 1);
|
|
142
144
|
const queryString = url.slice(questionIndex + 1, hashIndex === -1 ? undefined : hashIndex);
|
|
143
|
-
|
|
144
|
-
return Object.freeze({});
|
|
145
|
-
}
|
|
146
|
-
const params = {};
|
|
147
|
-
for (const pair of queryString.split("&")) {
|
|
148
|
-
if (pair === "") {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const separator = pair.indexOf("=");
|
|
152
|
-
const rawKey = separator === -1 ? pair : pair.slice(0, separator);
|
|
153
|
-
const rawValue = separator === -1 ? "" : pair.slice(separator + 1);
|
|
154
|
-
const key = decodeQueryComponent(rawKey);
|
|
155
|
-
if (key) {
|
|
156
|
-
params[key] = decodeQueryComponent(rawValue);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return Object.freeze(params);
|
|
145
|
+
return Object.freeze(parseQueryString(queryString));
|
|
160
146
|
}
|
|
161
147
|
/* -------------------------------------------------------------------------- */
|
|
162
148
|
/* Request Context */
|
|
163
149
|
/* -------------------------------------------------------------------------- */
|
|
164
150
|
export function createNodeRequestContext(request, options = {}) {
|
|
151
|
+
const url = request.url ?? "/";
|
|
152
|
+
const violation = findRequestTargetViolation(url);
|
|
153
|
+
if (violation !== undefined) {
|
|
154
|
+
throw new TypeError(violation);
|
|
155
|
+
}
|
|
165
156
|
const headers = getNodeRequestHeaders(request);
|
|
166
157
|
const protocol = getNodeRequestProtocol(request, options);
|
|
167
158
|
const hostname = getNodeRequestHostname(request, options);
|
|
168
159
|
const port = getNodeRequestPort(request, options);
|
|
169
160
|
const remoteAddress = getNodeRemoteAddress(request, options);
|
|
170
161
|
const query = parseNodeQuery(request);
|
|
171
|
-
const url = request.url ?? "/";
|
|
172
162
|
return createRequestContext({
|
|
173
163
|
method: request.method?.toUpperCase() ?? "GET",
|
|
174
164
|
url,
|
|
@@ -43,6 +43,12 @@ export declare function getSearchPart(url: string): string;
|
|
|
43
43
|
import { RequestBodyTooLargeError as NodeRequestBodyTooLargeError } from "@zudojs/errors";
|
|
44
44
|
export { NodeRequestBodyTooLargeError };
|
|
45
45
|
export declare function readNodeRequestBody(request: IncomingMessage, maxBodySize?: number): Promise<Uint8Array>;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a value has the shape of a response init.
|
|
48
|
+
*
|
|
49
|
+
* @deprecated No longer used by the Node adapter: a plain object returned
|
|
50
|
+
* from a handler is always sent as JSON. Kept for API compatibility.
|
|
51
|
+
*/
|
|
46
52
|
export declare function isResponseContextLike(value: unknown): value is {
|
|
47
53
|
readonly status?: number;
|
|
48
54
|
readonly statusText?: string;
|
|
@@ -250,6 +250,12 @@ export function readNodeRequestBody(request, maxBodySize = DEFAULT_MAX_BODY_SIZE
|
|
|
250
250
|
/* -------------------------------------------------------------------------- */
|
|
251
251
|
/* Response Result Detection */
|
|
252
252
|
/* -------------------------------------------------------------------------- */
|
|
253
|
+
/**
|
|
254
|
+
* Whether a value has the shape of a response init.
|
|
255
|
+
*
|
|
256
|
+
* @deprecated No longer used by the Node adapter: a plain object returned
|
|
257
|
+
* from a handler is always sent as JSON. Kept for API compatibility.
|
|
258
|
+
*/
|
|
253
259
|
export function isResponseContextLike(value) {
|
|
254
260
|
if (value === null || typeof value !== "object") {
|
|
255
261
|
return false;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import type { Server } from "node:http";
|
|
7
7
|
import type { HttpAdapterOptions } from "../http.adapter.js";
|
|
8
8
|
import type { TrustProxy } from "../../httpTrustProxy/httpTrustProxy.core.js";
|
|
9
|
-
|
|
9
|
+
import type { NodeAdapterSecurityOptions } from "../../httpSecurity/httpSecurity.nodeGuard.js";
|
|
10
|
+
export interface NodeAdapterOptions extends HttpAdapterOptions, NodeAdapterSecurityOptions {
|
|
10
11
|
readonly host?: string;
|
|
11
12
|
readonly port?: number;
|
|
12
13
|
readonly server?: Server;
|
|
@@ -46,9 +46,28 @@ export interface AgentRegistryKey {
|
|
|
46
46
|
readonly name?: string;
|
|
47
47
|
}
|
|
48
48
|
export declare function getOrCreateAgent(key: string | AgentRegistryKey, options?: HTTPAgentConfig | HTTPSAgentConfig): HTTPAgentInstance;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Looks up a registered agent.
|
|
51
|
+
*
|
|
52
|
+
* `options` must be the same TLS-relevant options `getOrCreateAgent` was
|
|
53
|
+
* given, because they are part of the key; omitting them looks up the agent
|
|
54
|
+
* created with no options.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getAgent(key: string | AgentRegistryKey, options?: HTTPAgentConfig | HTTPSAgentConfig): HTTPAgentInstance | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Whether an agent is registered for this key and TLS option set.
|
|
59
|
+
*/
|
|
60
|
+
export declare function hasAgent(key: string | AgentRegistryKey, options?: HTTPAgentConfig | HTTPSAgentConfig): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Removes registered agents for a key.
|
|
63
|
+
*
|
|
64
|
+
* With `options` the single matching agent is removed; without them every
|
|
65
|
+
* agent registered for that host is removed, whatever TLS options it was
|
|
66
|
+
* created with, so a per-host teardown releases all of its sockets.
|
|
67
|
+
*
|
|
68
|
+
* @returns Whether anything was removed.
|
|
69
|
+
*/
|
|
70
|
+
export declare function removeAgent(key: string | AgentRegistryKey, destroy?: boolean, options?: HTTPAgentConfig | HTTPSAgentConfig): boolean;
|
|
52
71
|
export declare function clearAgents(destroy?: boolean): void;
|
|
53
72
|
export declare function getRegisteredAgentKeys(): string[];
|
|
54
73
|
export declare function getDefaultHTTPAgent(): HTTPAgent;
|
|
@@ -113,7 +113,7 @@ export function getOrCreateAgent(key, options = {}) {
|
|
|
113
113
|
* passing `rejectUnauthorized: false` would have disabled certificate
|
|
114
114
|
* verification for a caller that had pinned a CA, with no way to detect it.
|
|
115
115
|
*/
|
|
116
|
-
const cacheKey =
|
|
116
|
+
const cacheKey = agentCacheKey(registryKey, options);
|
|
117
117
|
const existing = agentRegistry.get(cacheKey);
|
|
118
118
|
if (existing) {
|
|
119
119
|
return existing;
|
|
@@ -127,6 +127,18 @@ export function getOrCreateAgent(key, options = {}) {
|
|
|
127
127
|
agentRegistry.set(cacheKey, agent);
|
|
128
128
|
return agent;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Builds the registry key an agent is stored under.
|
|
132
|
+
*
|
|
133
|
+
* Every read and write goes through this one helper. `getOrCreateAgent` used
|
|
134
|
+
* to append the TLS fingerprint while `getAgent`, `hasAgent` and
|
|
135
|
+
* `removeAgent` looked up the bare registry key, so every lookup missed and
|
|
136
|
+
* the documented per-host teardown silently leaked the agent and its
|
|
137
|
+
* keep-alive sockets.
|
|
138
|
+
*/
|
|
139
|
+
function agentCacheKey(registryKey, options) {
|
|
140
|
+
return `${registryKey}|${tlsFingerprint(options)}`;
|
|
141
|
+
}
|
|
130
142
|
/**
|
|
131
143
|
* Builds a stable fingerprint of the TLS-relevant fields of an agent config.
|
|
132
144
|
*/
|
|
@@ -156,23 +168,49 @@ function describeCredential(value) {
|
|
|
156
168
|
}
|
|
157
169
|
return "opaque";
|
|
158
170
|
}
|
|
159
|
-
|
|
160
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Looks up a registered agent.
|
|
173
|
+
*
|
|
174
|
+
* `options` must be the same TLS-relevant options `getOrCreateAgent` was
|
|
175
|
+
* given, because they are part of the key; omitting them looks up the agent
|
|
176
|
+
* created with no options.
|
|
177
|
+
*/
|
|
178
|
+
export function getAgent(key, options = {}) {
|
|
179
|
+
return agentRegistry.get(agentCacheKey(normalizeRegistryKey(key), options));
|
|
161
180
|
}
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Whether an agent is registered for this key and TLS option set.
|
|
183
|
+
*/
|
|
184
|
+
export function hasAgent(key, options = {}) {
|
|
185
|
+
return agentRegistry.has(agentCacheKey(normalizeRegistryKey(key), options));
|
|
164
186
|
}
|
|
165
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Removes registered agents for a key.
|
|
189
|
+
*
|
|
190
|
+
* With `options` the single matching agent is removed; without them every
|
|
191
|
+
* agent registered for that host is removed, whatever TLS options it was
|
|
192
|
+
* created with, so a per-host teardown releases all of its sockets.
|
|
193
|
+
*
|
|
194
|
+
* @returns Whether anything was removed.
|
|
195
|
+
*/
|
|
196
|
+
export function removeAgent(key, destroy = true, options) {
|
|
166
197
|
const registryKey = normalizeRegistryKey(key);
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
agent
|
|
198
|
+
const cacheKeys = options === undefined
|
|
199
|
+
? Array.from(agentRegistry.keys()).filter((candidate) => candidate.startsWith(`${registryKey}|`))
|
|
200
|
+
: [agentCacheKey(registryKey, options)];
|
|
201
|
+
let removed = false;
|
|
202
|
+
for (const cacheKey of cacheKeys) {
|
|
203
|
+
const agent = agentRegistry.get(cacheKey);
|
|
204
|
+
if (!agent) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
agentRegistry.delete(cacheKey);
|
|
208
|
+
if (destroy) {
|
|
209
|
+
agent.destroy();
|
|
210
|
+
}
|
|
211
|
+
removed = true;
|
|
174
212
|
}
|
|
175
|
-
return
|
|
213
|
+
return removed;
|
|
176
214
|
}
|
|
177
215
|
export function clearAgents(destroy = true) {
|
|
178
216
|
if (destroy) {
|
|
@@ -12,11 +12,17 @@ import type { CacheFreshness } from "./core/httpCacheControl.type.js";
|
|
|
12
12
|
* the origin marked must-revalidate-before-reuse must never be reported
|
|
13
13
|
* fresh.
|
|
14
14
|
*
|
|
15
|
+
* The response's current age follows RFC 9111 section 4.2.3: the `Age`
|
|
16
|
+
* header plus the time elapsed since the response's `Date`. Reading `Age`
|
|
17
|
+
* alone — as this used to — left every response without that header aged
|
|
18
|
+
* `0` forever, so `isFresh()` answered `true` for a response of any age.
|
|
19
|
+
*
|
|
15
20
|
* @param responseHeaders - The cached response's headers.
|
|
16
21
|
* @param responseDate - The response's `Date`, if already parsed.
|
|
22
|
+
* @param now - The current time, defaulting to `Date.now()`.
|
|
17
23
|
* @returns The freshness calculation.
|
|
18
24
|
*/
|
|
19
|
-
export declare function calculateFreshness(responseHeaders: Readonly<Record<string, string>>, responseDate?: Date): CacheFreshness;
|
|
25
|
+
export declare function calculateFreshness(responseHeaders: Readonly<Record<string, string>>, responseDate?: Date, now?: Date): CacheFreshness;
|
|
20
26
|
/**
|
|
21
27
|
* Determines if a cached response is still fresh.
|
|
22
28
|
*/
|
|
@@ -12,24 +12,37 @@ import { parseCacheControl } from "./core/httpCacheControl.parse.js";
|
|
|
12
12
|
* the origin marked must-revalidate-before-reuse must never be reported
|
|
13
13
|
* fresh.
|
|
14
14
|
*
|
|
15
|
+
* The response's current age follows RFC 9111 section 4.2.3: the `Age`
|
|
16
|
+
* header plus the time elapsed since the response's `Date`. Reading `Age`
|
|
17
|
+
* alone — as this used to — left every response without that header aged
|
|
18
|
+
* `0` forever, so `isFresh()` answered `true` for a response of any age.
|
|
19
|
+
*
|
|
15
20
|
* @param responseHeaders - The cached response's headers.
|
|
16
21
|
* @param responseDate - The response's `Date`, if already parsed.
|
|
22
|
+
* @param now - The current time, defaulting to `Date.now()`.
|
|
17
23
|
* @returns The freshness calculation.
|
|
18
24
|
*/
|
|
19
|
-
export function calculateFreshness(responseHeaders, responseDate) {
|
|
25
|
+
export function calculateFreshness(responseHeaders, responseDate, now = new Date()) {
|
|
20
26
|
const cacheControl = responseHeaders["cache-control"];
|
|
21
27
|
const directives = parseCacheControl(cacheControl);
|
|
22
|
-
const date = responseDate ??
|
|
28
|
+
const date = responseDate ?? parseDateHeader(responseHeaders["date"], now);
|
|
23
29
|
/*
|
|
24
30
|
* RFC 9111 section 5.1: Age is a non-negative delta-seconds. A negative or
|
|
25
31
|
* suffixed value would otherwise inflate freshness without bound, which
|
|
26
32
|
* pins a poisoned response in cache far past the origin's TTL.
|
|
27
33
|
*/
|
|
28
34
|
const rawAge = (responseHeaders["age"] ?? "").trim();
|
|
29
|
-
const
|
|
35
|
+
const ageHeader = /^\d+$/.test(rawAge) ? Number(rawAge) : 0;
|
|
36
|
+
const residentSeconds = Math.max(0, Math.floor((now.getTime() - date.getTime()) / 1_000));
|
|
37
|
+
const age = ageHeader + residentSeconds;
|
|
30
38
|
const expiresHeader = responseHeaders["expires"];
|
|
31
39
|
const expires = expiresHeader ? parseExpires(expiresHeader) : undefined;
|
|
32
40
|
const effectiveMaxAge = resolveMaxAge(directives, expires, date);
|
|
41
|
+
/*
|
|
42
|
+
* With an `Expires`-derived lifetime this reduces to `expires - now`,
|
|
43
|
+
* because the lifetime is measured from `Date` and the age is measured to
|
|
44
|
+
* `now`.
|
|
45
|
+
*/
|
|
33
46
|
const remaining = Math.max(0, effectiveMaxAge - age);
|
|
34
47
|
const stale = remaining <= 0 ||
|
|
35
48
|
directives.noCache === true ||
|
|
@@ -43,6 +56,20 @@ export function calculateFreshness(responseHeaders, responseDate) {
|
|
|
43
56
|
remaining,
|
|
44
57
|
};
|
|
45
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses a `Date` header, falling back to the current time.
|
|
61
|
+
*
|
|
62
|
+
* @param value - The raw `Date` value.
|
|
63
|
+
* @param now - The fallback instant.
|
|
64
|
+
* @returns The parsed date.
|
|
65
|
+
*/
|
|
66
|
+
function parseDateHeader(value, now) {
|
|
67
|
+
if (value === undefined) {
|
|
68
|
+
return now;
|
|
69
|
+
}
|
|
70
|
+
const parsed = new Date(value);
|
|
71
|
+
return Number.isNaN(parsed.getTime()) ? now : parsed;
|
|
72
|
+
}
|
|
46
73
|
/**
|
|
47
74
|
* Parses an `Expires` header, treating an unparseable value as expired.
|
|
48
75
|
*
|
|
@@ -57,7 +57,14 @@ export declare const MAX_COOKIE_HEADER_LENGTH: number;
|
|
|
57
57
|
* @returns The parsed cookie jar.
|
|
58
58
|
*/
|
|
59
59
|
export declare function parseCookies(header: string | undefined): CookieCollection;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Serializes a `Set-Cookie` header value.
|
|
62
|
+
*
|
|
63
|
+
* Unset attributes take the secure defaults (`Path=/; HttpOnly; Secure;
|
|
64
|
+
* SameSite=Lax`, see `DEFAULT_COOKIE_ATTRIBUTES`); pass `httpOnly: false`,
|
|
65
|
+
* `secure: false`, another `sameSite` or `path` to override them.
|
|
66
|
+
*/
|
|
67
|
+
export declare function serializeCookie(name: string, value: CookieValue, cookieOptions?: CookieOptions): string;
|
|
61
68
|
export interface CookieManager {
|
|
62
69
|
get(name: string): string | undefined;
|
|
63
70
|
has(name: string): boolean;
|
|
@@ -78,11 +85,31 @@ export interface SignedCookie {
|
|
|
78
85
|
readonly value: string;
|
|
79
86
|
readonly signature: string;
|
|
80
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Serializes a signed cookie. The MAC covers the cookie **name** as well as
|
|
90
|
+
* the value, so a signature minted for one cookie is not accepted for
|
|
91
|
+
* another; read it back with `parseSignedCookie(value, secret, name)`.
|
|
92
|
+
*/
|
|
81
93
|
export declare function serializeSignedCookie(name: string, value: string, options: SignedCookieOptions): string;
|
|
82
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Verifies a signed cookie value and returns the original value, or
|
|
96
|
+
* `undefined` when the signature does not match.
|
|
97
|
+
*
|
|
98
|
+
* Pass the cookie's `name`: the signature produced by
|
|
99
|
+
* `serializeSignedCookie` is bound to it, so a value lifted from another
|
|
100
|
+
* signed cookie (for example a user-chosen display name replayed as
|
|
101
|
+
* `session_user`) is rejected. Without `name` only a legacy, value-only
|
|
102
|
+
* signature (from `signCookieValue(value, secret)`) verifies, and
|
|
103
|
+
* name-bound signatures never do.
|
|
104
|
+
*/
|
|
105
|
+
export declare function parseSignedCookie(value: string | undefined, secret: string, name?: string): string | undefined;
|
|
83
106
|
/**
|
|
84
107
|
* Signs a cookie value with HMAC-SHA256.
|
|
85
108
|
*
|
|
109
|
+
* Synchronous by contract, so the MAC is computed with `node:crypto`: every
|
|
110
|
+
* `@zudojs/crypto` HMAC helper is asynchronous. Verification compares with
|
|
111
|
+
* `@zudojs/crypto`'s constant-time `timingSafeEqualString`.
|
|
112
|
+
*
|
|
86
113
|
* @param value - The value to authenticate.
|
|
87
114
|
* @param secret - The signing key.
|
|
88
115
|
* @returns The base64url signature.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { createHmac
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { timingSafeEqualString } from "@zudojs/crypto";
|
|
3
|
+
import { withSecureCookieDefaults } from "./httpCookies.defaults.js";
|
|
2
4
|
/* -------------------------------------------------------------------------- */
|
|
3
5
|
/* Cookie Collection */
|
|
4
6
|
/* -------------------------------------------------------------------------- */
|
|
@@ -123,7 +125,15 @@ export function parseCookies(header) {
|
|
|
123
125
|
/* -------------------------------------------------------------------------- */
|
|
124
126
|
/* Serialize Cookie */
|
|
125
127
|
/* -------------------------------------------------------------------------- */
|
|
126
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Serializes a `Set-Cookie` header value.
|
|
130
|
+
*
|
|
131
|
+
* Unset attributes take the secure defaults (`Path=/; HttpOnly; Secure;
|
|
132
|
+
* SameSite=Lax`, see `DEFAULT_COOKIE_ATTRIBUTES`); pass `httpOnly: false`,
|
|
133
|
+
* `secure: false`, another `sameSite` or `path` to override them.
|
|
134
|
+
*/
|
|
135
|
+
export function serializeCookie(name, value, cookieOptions = {}) {
|
|
136
|
+
const options = withSecureCookieDefaults(cookieOptions, "lax");
|
|
127
137
|
validateCookieName(name);
|
|
128
138
|
validateCookiePrefix(name, options);
|
|
129
139
|
const encodedName = name;
|
|
@@ -229,11 +239,27 @@ export function createCookieManager(request, response) {
|
|
|
229
239
|
},
|
|
230
240
|
};
|
|
231
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Serializes a signed cookie. The MAC covers the cookie **name** as well as
|
|
244
|
+
* the value, so a signature minted for one cookie is not accepted for
|
|
245
|
+
* another; read it back with `parseSignedCookie(value, secret, name)`.
|
|
246
|
+
*/
|
|
232
247
|
export function serializeSignedCookie(name, value, options) {
|
|
233
|
-
const signature = signCookieValue(value, options.secret);
|
|
248
|
+
const signature = signCookieValue(bindCookieName(name, value), options.secret);
|
|
234
249
|
return serializeCookie(name, `${value}.${signature}`, options);
|
|
235
250
|
}
|
|
236
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Verifies a signed cookie value and returns the original value, or
|
|
253
|
+
* `undefined` when the signature does not match.
|
|
254
|
+
*
|
|
255
|
+
* Pass the cookie's `name`: the signature produced by
|
|
256
|
+
* `serializeSignedCookie` is bound to it, so a value lifted from another
|
|
257
|
+
* signed cookie (for example a user-chosen display name replayed as
|
|
258
|
+
* `session_user`) is rejected. Without `name` only a legacy, value-only
|
|
259
|
+
* signature (from `signCookieValue(value, secret)`) verifies, and
|
|
260
|
+
* name-bound signatures never do.
|
|
261
|
+
*/
|
|
262
|
+
export function parseSignedCookie(value, secret, name) {
|
|
237
263
|
if (!value) {
|
|
238
264
|
return undefined;
|
|
239
265
|
}
|
|
@@ -243,15 +269,27 @@ export function parseSignedCookie(value, secret) {
|
|
|
243
269
|
}
|
|
244
270
|
const originalValue = value.slice(0, separator);
|
|
245
271
|
const signature = value.slice(separator + 1);
|
|
246
|
-
const expected = signCookieValue(originalValue, secret);
|
|
247
|
-
if (!
|
|
272
|
+
const expected = signCookieValue(name === undefined ? originalValue : bindCookieName(name, originalValue), secret);
|
|
273
|
+
if (!timingSafeEqualString(signature, expected)) {
|
|
248
274
|
return undefined;
|
|
249
275
|
}
|
|
250
276
|
return originalValue;
|
|
251
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* The MAC input for a name-bound signature. A cookie name is an RFC 6265
|
|
280
|
+
* token and cannot contain `=`, so the encoding is unambiguous.
|
|
281
|
+
*/
|
|
282
|
+
function bindCookieName(name, value) {
|
|
283
|
+
validateCookieName(name);
|
|
284
|
+
return `${name}=${value}`;
|
|
285
|
+
}
|
|
252
286
|
/**
|
|
253
287
|
* Signs a cookie value with HMAC-SHA256.
|
|
254
288
|
*
|
|
289
|
+
* Synchronous by contract, so the MAC is computed with `node:crypto`: every
|
|
290
|
+
* `@zudojs/crypto` HMAC helper is asynchronous. Verification compares with
|
|
291
|
+
* `@zudojs/crypto`'s constant-time `timingSafeEqualString`.
|
|
292
|
+
*
|
|
255
293
|
* @param value - The value to authenticate.
|
|
256
294
|
* @param secret - The signing key.
|
|
257
295
|
* @returns The base64url signature.
|
|
@@ -396,22 +434,4 @@ function normalizePriority(value) {
|
|
|
396
434
|
throw new TypeError(`Invalid cookie priority: ${String(value)}`);
|
|
397
435
|
}
|
|
398
436
|
}
|
|
399
|
-
/* -------------------------------------------------------------------------- */
|
|
400
|
-
/* Hash Helpers */
|
|
401
|
-
/* -------------------------------------------------------------------------- */
|
|
402
|
-
/**
|
|
403
|
-
* Compares two signatures in constant time.
|
|
404
|
-
*
|
|
405
|
-
* @param left - The candidate signature.
|
|
406
|
-
* @param right - The expected signature.
|
|
407
|
-
* @returns `true` if the two are byte-identical.
|
|
408
|
-
*/
|
|
409
|
-
function timingSafeEqual(left, right) {
|
|
410
|
-
const leftBuffer = Buffer.from(left, "utf8");
|
|
411
|
-
const rightBuffer = Buffer.from(right, "utf8");
|
|
412
|
-
if (leftBuffer.length !== rightBuffer.length) {
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
return cryptoTimingSafeEqual(leftBuffer, rightBuffer);
|
|
416
|
-
}
|
|
417
437
|
//# sourceMappingURL=http.cookies.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure cookie defaults.
|
|
3
|
+
*
|
|
4
|
+
* Both cookie serializers (`serializeCookie` in this module and the
|
|
5
|
+
* `serializeResponseCookie` the adapters use for `response.cookie()`) start
|
|
6
|
+
* from these defaults, so a cookie set "the obvious way" is `HttpOnly`,
|
|
7
|
+
* `Secure`, `SameSite=Lax` and `Path=/`, matching the defaults of
|
|
8
|
+
* `@zudojs/security`'s `serializeCookie` and the AGENTS.md secure-defaults
|
|
9
|
+
* rule. Every default can be overridden explicitly (`httpOnly: false`,
|
|
10
|
+
* `secure: false`, `sameSite: "none"`, `path: "/app"`); an option left
|
|
11
|
+
* `undefined` keeps the default.
|
|
12
|
+
*
|
|
13
|
+
* @module httpCookies/defaults
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The attributes every cookie gets unless the caller overrides them.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_COOKIE_ATTRIBUTES: Readonly<{
|
|
19
|
+
readonly path: "/";
|
|
20
|
+
readonly httpOnly: true;
|
|
21
|
+
readonly secure: true;
|
|
22
|
+
readonly sameSite: "lax";
|
|
23
|
+
}>;
|
|
24
|
+
interface DefaultableCookieOptions<S extends string> {
|
|
25
|
+
readonly path?: string;
|
|
26
|
+
readonly httpOnly?: boolean;
|
|
27
|
+
readonly secure?: boolean;
|
|
28
|
+
readonly sameSite?: S;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns `options` with the secure defaults filled in for every attribute
|
|
32
|
+
* the caller left `undefined`.
|
|
33
|
+
*
|
|
34
|
+
* @param options - The caller's cookie options.
|
|
35
|
+
* @param sameSite - The default `SameSite` value in the option type's own
|
|
36
|
+
* spelling (`"lax"` or `"Lax"`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function withSecureCookieDefaults<S extends string, T extends DefaultableCookieOptions<S>>(options: T | undefined, sameSite: S): T;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=httpCookies.defaults.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure cookie defaults.
|
|
3
|
+
*
|
|
4
|
+
* Both cookie serializers (`serializeCookie` in this module and the
|
|
5
|
+
* `serializeResponseCookie` the adapters use for `response.cookie()`) start
|
|
6
|
+
* from these defaults, so a cookie set "the obvious way" is `HttpOnly`,
|
|
7
|
+
* `Secure`, `SameSite=Lax` and `Path=/`, matching the defaults of
|
|
8
|
+
* `@zudojs/security`'s `serializeCookie` and the AGENTS.md secure-defaults
|
|
9
|
+
* rule. Every default can be overridden explicitly (`httpOnly: false`,
|
|
10
|
+
* `secure: false`, `sameSite: "none"`, `path: "/app"`); an option left
|
|
11
|
+
* `undefined` keeps the default.
|
|
12
|
+
*
|
|
13
|
+
* @module httpCookies/defaults
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The attributes every cookie gets unless the caller overrides them.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_COOKIE_ATTRIBUTES = Object.freeze({
|
|
19
|
+
path: "/",
|
|
20
|
+
httpOnly: true,
|
|
21
|
+
secure: true,
|
|
22
|
+
sameSite: "lax",
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Returns `options` with the secure defaults filled in for every attribute
|
|
26
|
+
* the caller left `undefined`.
|
|
27
|
+
*
|
|
28
|
+
* @param options - The caller's cookie options.
|
|
29
|
+
* @param sameSite - The default `SameSite` value in the option type's own
|
|
30
|
+
* spelling (`"lax"` or `"Lax"`).
|
|
31
|
+
*/
|
|
32
|
+
export function withSecureCookieDefaults(options, sameSite) {
|
|
33
|
+
const source = (options ?? {});
|
|
34
|
+
return {
|
|
35
|
+
...source,
|
|
36
|
+
path: source.path ?? DEFAULT_COOKIE_ATTRIBUTES.path,
|
|
37
|
+
httpOnly: source.httpOnly ?? DEFAULT_COOKIE_ATTRIBUTES.httpOnly,
|
|
38
|
+
secure: source.secure ?? DEFAULT_COOKIE_ATTRIBUTES.secure,
|
|
39
|
+
sameSite: source.sameSite ?? sameSite,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=httpCookies.defaults.js.map
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Transport-specific socket management belongs to the server/client adapter
|
|
8
8
|
* layer. This module only handles HTTP-level semantics.
|
|
9
9
|
*/
|
|
10
|
+
import { assertSafeHeaderValue, escapeHeaderQuotedString, } from "../httpHeaders/security/index.js";
|
|
10
11
|
/* -------------------------------------------------------------------------- */
|
|
11
12
|
/* Constants */
|
|
12
13
|
/* -------------------------------------------------------------------------- */
|
|
@@ -121,7 +122,9 @@ export function formatKeepAliveHeader(parameters = {}) {
|
|
|
121
122
|
parts.push(`${key}=${quoteIfNeeded(value)}`);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
const header = parts.join(", ");
|
|
126
|
+
assertSafeHeaderValue(header);
|
|
127
|
+
return header;
|
|
125
128
|
}
|
|
126
129
|
/* -------------------------------------------------------------------------- */
|
|
127
130
|
/* Connection Header */
|
|
@@ -348,10 +351,19 @@ function unquote(value) {
|
|
|
348
351
|
}
|
|
349
352
|
return trimmed;
|
|
350
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Emits a Keep-Alive parameter value.
|
|
356
|
+
*
|
|
357
|
+
* Quoting does not neutralise a CR or LF, so the control character used to
|
|
358
|
+
* survive into the field value. `escapeHeaderQuotedString` rejects it, the
|
|
359
|
+
* same helper every other quoted-parameter emitter in this package uses.
|
|
360
|
+
*
|
|
361
|
+
* @throws {TypeError} If the value contains a forbidden control character.
|
|
362
|
+
*/
|
|
351
363
|
function quoteIfNeeded(value) {
|
|
352
364
|
if (/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value)) {
|
|
353
365
|
return value;
|
|
354
366
|
}
|
|
355
|
-
return `"${value
|
|
367
|
+
return `"${escapeHeaderQuotedString(value)}"`;
|
|
356
368
|
}
|
|
357
369
|
//# sourceMappingURL=httpKeepAlive.core.js.map
|
|
@@ -7,7 +7,28 @@ import type { HttpMiddleware, HttpMiddlewareContext } from "../../httpMiddleware
|
|
|
7
7
|
import type { HttpResponseContext as ResponseContext } from "../../../httpResponse/httpResponse.context.js";
|
|
8
8
|
export declare function createAsyncMiddleware(factory: (context: HttpMiddlewareContext) => Promise<HttpMiddleware>): HttpMiddleware;
|
|
9
9
|
export declare function createConditionalMiddleware(predicate: (context: HttpMiddlewareContext) => boolean | Promise<boolean>, middleware: HttpMiddleware): HttpMiddleware;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Options for {@link createPathMiddleware}.
|
|
12
|
+
*/
|
|
13
|
+
export interface PathMiddlewareOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Match the path case-sensitively. Defaults to `false`, the router's
|
|
16
|
+
* default, so a guard scoped to `/admin` also covers `/Admin`, which the
|
|
17
|
+
* router would dispatch to the same route.
|
|
18
|
+
*/
|
|
19
|
+
readonly caseSensitive?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs `middleware` only for requests addressed to `path`.
|
|
23
|
+
*
|
|
24
|
+
* The request path is normalised exactly as the router normalises it before
|
|
25
|
+
* matching: it is read with the canonical request-target parser, repeated
|
|
26
|
+
* slashes are collapsed, a trailing slash is ignored and (by default) case is
|
|
27
|
+
* ignored. An exact, case-sensitive comparison let `/Admin`, `/admin/` and
|
|
28
|
+
* `/admin//` skip a guard on `/admin` while the router still served the
|
|
29
|
+
* protected route.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createPathMiddleware(path: string, middleware: HttpMiddleware, options?: PathMiddlewareOptions): HttpMiddleware;
|
|
11
32
|
export declare function createMethodMiddleware(method: string, middleware: HttpMiddleware): HttpMiddleware;
|
|
12
33
|
export declare function createResponseMiddleware(response: Response | ResponseContext): HttpMiddleware;
|
|
13
34
|
export declare function createShortCircuitMiddleware(predicate: (context: HttpMiddlewareContext) => boolean | Promise<boolean>, response: Response | ResponseContext): HttpMiddleware;
|