flowingts 0.0.0 → 0.19.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/LICENSE +15 -0
- package/README.md +74 -3
- package/dist/core/abort.d.ts +23 -0
- package/dist/core/abort.d.ts.map +1 -0
- package/dist/core/abort.js +23 -0
- package/dist/core/abort.js.map +1 -0
- package/dist/core/app/core.d.ts +35 -0
- package/dist/core/app/core.d.ts.map +1 -0
- package/dist/core/app/core.js +2 -0
- package/dist/core/app/core.js.map +1 -0
- package/dist/core/app/options.d.ts +178 -0
- package/dist/core/app/options.d.ts.map +1 -0
- package/dist/core/app/options.js +2 -0
- package/dist/core/app/options.js.map +1 -0
- package/dist/core/app.d.ts +143 -0
- package/dist/core/app.d.ts.map +1 -0
- package/dist/core/app.js +283 -0
- package/dist/core/app.js.map +1 -0
- package/dist/core/body-parser.d.ts +14 -0
- package/dist/core/body-parser.d.ts.map +1 -0
- package/dist/core/body-parser.js +76 -0
- package/dist/core/body-parser.js.map +1 -0
- package/dist/core/compiler.d.ts +104 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +151 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/compose.d.ts +16 -0
- package/dist/core/compose.d.ts.map +1 -0
- package/dist/core/compose.js +41 -0
- package/dist/core/compose.js.map +1 -0
- package/dist/core/context.d.ts +290 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +460 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/contracts.d.ts +76 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +87 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/errors.d.ts +50 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +25 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/guard.d.ts +10 -0
- package/dist/core/guard.d.ts.map +1 -0
- package/dist/core/guard.js +14 -0
- package/dist/core/guard.js.map +1 -0
- package/dist/core/health.d.ts +37 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +68 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/hooks.d.ts +34 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +27 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/index.d.ts +30 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/infer.d.ts +68 -0
- package/dist/core/infer.d.ts.map +1 -0
- package/dist/core/infer.js +2 -0
- package/dist/core/infer.js.map +1 -0
- package/dist/core/inject.d.ts +27 -0
- package/dist/core/inject.d.ts.map +1 -0
- package/dist/core/inject.js +85 -0
- package/dist/core/inject.js.map +1 -0
- package/dist/core/logger.d.ts +57 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +134 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/metadata.d.ts +83 -0
- package/dist/core/metadata.d.ts.map +1 -0
- package/dist/core/metadata.js +91 -0
- package/dist/core/metadata.js.map +1 -0
- package/dist/core/mounted-group.d.ts +77 -0
- package/dist/core/mounted-group.d.ts.map +1 -0
- package/dist/core/mounted-group.js +131 -0
- package/dist/core/mounted-group.js.map +1 -0
- package/dist/core/registry/framework-routes.d.ts +28 -0
- package/dist/core/registry/framework-routes.d.ts.map +1 -0
- package/dist/core/registry/framework-routes.js +83 -0
- package/dist/core/registry/framework-routes.js.map +1 -0
- package/dist/core/registry/registry.d.ts +58 -0
- package/dist/core/registry/registry.d.ts.map +1 -0
- package/dist/core/registry/registry.js +208 -0
- package/dist/core/registry/registry.js.map +1 -0
- package/dist/core/registry/route-program.d.ts +16 -0
- package/dist/core/registry/route-program.d.ts.map +1 -0
- package/dist/core/registry/route-program.js +62 -0
- package/dist/core/registry/route-program.js.map +1 -0
- package/dist/core/request-validation.d.ts +15 -0
- package/dist/core/request-validation.d.ts.map +1 -0
- package/dist/core/request-validation.js +67 -0
- package/dist/core/request-validation.js.map +1 -0
- package/dist/core/response/pipeline.d.ts +51 -0
- package/dist/core/response/pipeline.d.ts.map +1 -0
- package/dist/core/response/pipeline.js +169 -0
- package/dist/core/response/pipeline.js.map +1 -0
- package/dist/core/route-table.d.ts +58 -0
- package/dist/core/route-table.d.ts.map +1 -0
- package/dist/core/route-table.js +108 -0
- package/dist/core/route-table.js.map +1 -0
- package/dist/core/runtime/observer.d.ts +47 -0
- package/dist/core/runtime/observer.d.ts.map +1 -0
- package/dist/core/runtime/observer.js +125 -0
- package/dist/core/runtime/observer.js.map +1 -0
- package/dist/core/runtime/request-handler.d.ts +62 -0
- package/dist/core/runtime/request-handler.d.ts.map +1 -0
- package/dist/core/runtime/request-handler.js +256 -0
- package/dist/core/runtime/request-handler.js.map +1 -0
- package/dist/core/runtime/server.d.ts +59 -0
- package/dist/core/runtime/server.d.ts.map +1 -0
- package/dist/core/runtime/server.js +180 -0
- package/dist/core/runtime/server.js.map +1 -0
- package/dist/core/shutdown.d.ts +41 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +53 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/streaming/sse.d.ts +35 -0
- package/dist/core/streaming/sse.d.ts.map +1 -0
- package/dist/core/streaming/sse.js +121 -0
- package/dist/core/streaming/sse.js.map +1 -0
- package/dist/core/symbols.d.ts +28 -0
- package/dist/core/symbols.d.ts.map +1 -0
- package/dist/core/symbols.js +28 -0
- package/dist/core/symbols.js.map +1 -0
- package/dist/core/telemetry.d.ts +83 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +96 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/uploads/receive.d.ts +63 -0
- package/dist/core/uploads/receive.d.ts.map +1 -0
- package/dist/core/uploads/receive.js +342 -0
- package/dist/core/uploads/receive.js.map +1 -0
- package/dist/core/uploads/types.d.ts +79 -0
- package/dist/core/uploads/types.d.ts.map +1 -0
- package/dist/core/uploads/types.js +2 -0
- package/dist/core/uploads/types.js.map +1 -0
- package/dist/decorators/hooks.d.ts +25 -0
- package/dist/decorators/hooks.d.ts.map +1 -0
- package/dist/decorators/hooks.js +36 -0
- package/dist/decorators/hooks.js.map +1 -0
- package/dist/decorators/http.d.ts +21 -0
- package/dist/decorators/http.d.ts.map +1 -0
- package/dist/decorators/http.js +21 -0
- package/dist/decorators/http.js.map +1 -0
- package/dist/decorators/index.d.ts +7 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +7 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/log.d.ts +10 -0
- package/dist/decorators/log.d.ts.map +1 -0
- package/dist/decorators/log.js +32 -0
- package/dist/decorators/log.js.map +1 -0
- package/dist/decorators/middlewares.d.ts +28 -0
- package/dist/decorators/middlewares.d.ts.map +1 -0
- package/dist/decorators/middlewares.js +40 -0
- package/dist/decorators/middlewares.js.map +1 -0
- package/dist/decorators/router.d.ts +12 -0
- package/dist/decorators/router.d.ts.map +1 -0
- package/dist/decorators/router.js +20 -0
- package/dist/decorators/router.js.map +1 -0
- package/dist/decorators/schema.d.ts +8 -0
- package/dist/decorators/schema.d.ts.map +1 -0
- package/dist/decorators/schema.js +14 -0
- package/dist/decorators/schema.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/auth.d.ts +41 -0
- package/dist/middlewares/auth.d.ts.map +1 -0
- package/dist/middlewares/auth.js +74 -0
- package/dist/middlewares/auth.js.map +1 -0
- package/dist/middlewares/cors.d.ts +30 -0
- package/dist/middlewares/cors.d.ts.map +1 -0
- package/dist/middlewares/cors.js +69 -0
- package/dist/middlewares/cors.js.map +1 -0
- package/dist/middlewares/csrf.d.ts +42 -0
- package/dist/middlewares/csrf.d.ts.map +1 -0
- package/dist/middlewares/csrf.js +70 -0
- package/dist/middlewares/csrf.js.map +1 -0
- package/dist/middlewares/index.d.ts +6 -0
- package/dist/middlewares/index.d.ts.map +1 -0
- package/dist/middlewares/index.js +6 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/middlewares/rate-limit.d.ts +35 -0
- package/dist/middlewares/rate-limit.d.ts.map +1 -0
- package/dist/middlewares/rate-limit.js +45 -0
- package/dist/middlewares/rate-limit.js.map +1 -0
- package/dist/middlewares/security-headers.d.ts +28 -0
- package/dist/middlewares/security-headers.d.ts.map +1 -0
- package/dist/middlewares/security-headers.js +42 -0
- package/dist/middlewares/security-headers.js.map +1 -0
- package/dist/openapi/generate.d.ts +51 -0
- package/dist/openapi/generate.d.ts.map +1 -0
- package/dist/openapi/generate.js +175 -0
- package/dist/openapi/generate.js.map +1 -0
- package/dist/schema/serialization.d.ts +2 -0
- package/dist/schema/serialization.d.ts.map +1 -0
- package/dist/schema/serialization.js +11 -0
- package/dist/schema/serialization.js.map +1 -0
- package/dist/schema/validation.d.ts +22 -0
- package/dist/schema/validation.d.ts.map +1 -0
- package/dist/schema/validation.js +68 -0
- package/dist/schema/validation.js.map +1 -0
- package/dist/server/static.d.ts +45 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +204 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/tls.d.ts +48 -0
- package/dist/server/tls.d.ts.map +1 -0
- package/dist/server/tls.js +60 -0
- package/dist/server/tls.js.map +1 -0
- package/dist/shims/symbol-metadata.d.ts +2 -0
- package/dist/shims/symbol-metadata.d.ts.map +1 -0
- package/dist/shims/symbol-metadata.js +8 -0
- package/dist/shims/symbol-metadata.js.map +1 -0
- package/dist/utils/content-type.d.ts +13 -0
- package/dist/utils/content-type.d.ts.map +1 -0
- package/dist/utils/content-type.js +75 -0
- package/dist/utils/content-type.js.map +1 -0
- package/dist/utils/cookie.d.ts +34 -0
- package/dist/utils/cookie.d.ts.map +1 -0
- package/dist/utils/cookie.js +94 -0
- package/dist/utils/cookie.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/mime.d.ts +2 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +52 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/origin-match.d.ts +58 -0
- package/dist/utils/origin-match.d.ts.map +1 -0
- package/dist/utils/origin-match.js +194 -0
- package/dist/utils/origin-match.js.map +1 -0
- package/dist/utils/path.d.ts +10 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +60 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/query.d.ts +28 -0
- package/dist/utils/query.d.ts.map +1 -0
- package/dist/utils/query.js +67 -0
- package/dist/utils/query.js.map +1 -0
- package/dist/utils/request-target.d.ts +31 -0
- package/dist/utils/request-target.d.ts.map +1 -0
- package/dist/utils/request-target.js +80 -0
- package/dist/utils/request-target.js.map +1 -0
- package/dist/utils/safe-json.d.ts +24 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/safe-json.js +83 -0
- package/dist/utils/safe-json.js.map +1 -0
- package/dist/utils/trace-context.d.ts +17 -0
- package/dist/utils/trace-context.d.ts.map +1 -0
- package/dist/utils/trace-context.js +30 -0
- package/dist/utils/trace-context.js.map +1 -0
- package/package.json +85 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"AA+CA,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,MAAM,CAKrG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos de conteúdo por extensão, sem dependência (F-13). Cobre o que uma API e uma SPA servem; o resto
|
|
3
|
+
* sai como `application/octet-stream` (com `nosniff`, o navegador não tenta adivinhar). Acrescente tipos
|
|
4
|
+
* com a opção `mimeTypes` do `static()`/`sendFile()`.
|
|
5
|
+
*/
|
|
6
|
+
const TYPES = {
|
|
7
|
+
html: 'text/html',
|
|
8
|
+
htm: 'text/html',
|
|
9
|
+
css: 'text/css',
|
|
10
|
+
js: 'text/javascript',
|
|
11
|
+
mjs: 'text/javascript',
|
|
12
|
+
cjs: 'text/javascript',
|
|
13
|
+
json: 'application/json',
|
|
14
|
+
map: 'application/json',
|
|
15
|
+
webmanifest: 'application/manifest+json',
|
|
16
|
+
txt: 'text/plain',
|
|
17
|
+
md: 'text/markdown',
|
|
18
|
+
csv: 'text/csv',
|
|
19
|
+
xml: 'application/xml',
|
|
20
|
+
yaml: 'application/yaml',
|
|
21
|
+
yml: 'application/yaml',
|
|
22
|
+
svg: 'image/svg+xml',
|
|
23
|
+
png: 'image/png',
|
|
24
|
+
jpg: 'image/jpeg',
|
|
25
|
+
jpeg: 'image/jpeg',
|
|
26
|
+
gif: 'image/gif',
|
|
27
|
+
webp: 'image/webp',
|
|
28
|
+
avif: 'image/avif',
|
|
29
|
+
ico: 'image/x-icon',
|
|
30
|
+
woff: 'font/woff',
|
|
31
|
+
woff2: 'font/woff2',
|
|
32
|
+
ttf: 'font/ttf',
|
|
33
|
+
otf: 'font/otf',
|
|
34
|
+
pdf: 'application/pdf',
|
|
35
|
+
zip: 'application/zip',
|
|
36
|
+
gz: 'application/gzip',
|
|
37
|
+
wasm: 'application/wasm',
|
|
38
|
+
mp4: 'video/mp4',
|
|
39
|
+
webm: 'video/webm',
|
|
40
|
+
mp3: 'audio/mpeg',
|
|
41
|
+
wav: 'audio/wav',
|
|
42
|
+
ogg: 'audio/ogg',
|
|
43
|
+
};
|
|
44
|
+
/** Tipos de texto recebem `charset=utf-8`. */
|
|
45
|
+
const TEXT = /^(text\/|application\/(json|manifest\+json|xml|yaml|javascript))|\+xml$|\+json$/;
|
|
46
|
+
export function contentTypeFor(filename, extra = {}) {
|
|
47
|
+
const dot = filename.lastIndexOf('.');
|
|
48
|
+
const ext = dot === -1 ? '' : filename.slice(dot + 1).toLowerCase();
|
|
49
|
+
const type = extra[ext] ?? TYPES[ext] ?? 'application/octet-stream';
|
|
50
|
+
return TEXT.test(type) && !type.includes('charset') ? `${type}; charset=utf-8` : type;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=mime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,KAAK,GAA2B;IACpC,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,UAAU;IACf,EAAE,EAAE,iBAAiB;IACrB,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,iBAAiB;IACtB,IAAI,EAAE,kBAAkB;IACxB,GAAG,EAAE,kBAAkB;IACvB,WAAW,EAAE,2BAA2B;IACxC,GAAG,EAAE,YAAY;IACjB,EAAE,EAAE,eAAe;IACnB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,iBAAiB;IACtB,IAAI,EAAE,kBAAkB;IACxB,GAAG,EAAE,kBAAkB;IACvB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,cAAc;IACnB,IAAI,EAAE,WAAW;IACjB,KAAK,EAAE,YAAY;IACnB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,iBAAiB;IACtB,EAAE,EAAE,kBAAkB;IACtB,IAAI,EAAE,kBAAkB;IACxB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;CACjB,CAAC;AAEF,8CAA8C;AAC9C,MAAM,IAAI,GAAG,iFAAiF,CAAC;AAE/F,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,KAAK,GAAqC,EAAE;IAC3F,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACpE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;IACpE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparação de origens (CORS) e hosts (`allowedHosts`) segura por padrão.
|
|
3
|
+
*
|
|
4
|
+
* O risco não está no volume de código, e sim na semântica da comparação. Por isso:
|
|
5
|
+
* - **strings** são normalizadas com o parser nativo de URL e comparadas por igualdade, nunca por substring;
|
|
6
|
+
* - **curinga** (`https://*.exemplo.dev`, `*.exemplo.dev`) aceita só subdomínios, nunca o domínio
|
|
7
|
+
* em si nem `exemplo.dev.atacante.com`;
|
|
8
|
+
* - **RegExp** precisa casar a string **inteira**, como se tivesse `^…$`, mesmo que o autor esqueça as
|
|
9
|
+
* âncoras. As flags `g`, `y` e `m` são descartadas, então `lastIndex` e `^`/`$` por linha não interferem.
|
|
10
|
+
*
|
|
11
|
+
* Erros de configuração (string sem esquema, curinga mal posicionado) são lançados na criação do
|
|
12
|
+
* middleware ou do app, e não na primeira requisição.
|
|
13
|
+
*/
|
|
14
|
+
export type MatchRule = string | RegExp;
|
|
15
|
+
/** Envolve a RegExp para casar a string inteira e descarta flags com estado ou por linha. */
|
|
16
|
+
export declare function toFullMatch(rule: RegExp): RegExp;
|
|
17
|
+
/** Normaliza uma origem (`Https://App.Exemplo.com:443/` → `https://app.exemplo.com`), ou `undefined` se inválida. */
|
|
18
|
+
export declare function normalizeOrigin(value: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Compila as regras de origem do CORS. Strings precisam do esquema (`https://app.exemplo.com`);
|
|
21
|
+
* `'null'` só casa com a origem `null` se estiver listada explicitamente.
|
|
22
|
+
*/
|
|
23
|
+
export declare function compileOriginMatcher(rules: readonly MatchRule[]): (origin: string) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Compila `allowedHosts`. Strings casam com o hostname ou com `host:porta`; `*.exemplo.dev` aceita só
|
|
26
|
+
* subdomínios; RegExp precisa casar o hostname inteiro.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compileHostMatcher(rules: readonly MatchRule[]): (hostname: string, host: string) => boolean;
|
|
29
|
+
/** Regra de host de uma rota (virtual host), já compilada. */
|
|
30
|
+
export interface RouteHostRule {
|
|
31
|
+
/** Como foi escrita, para mensagens. */
|
|
32
|
+
label: string;
|
|
33
|
+
kind: 'exact' | 'wildcard' | 'regex';
|
|
34
|
+
/** Hostname (exato) ou domínio base (curinga); vazio em RegExp. */
|
|
35
|
+
base: string;
|
|
36
|
+
/** Chave da restrição no roteador: `api.exemplo.com`, `*.exemplo.dev` ou `re:/…/flags`. */
|
|
37
|
+
key: string;
|
|
38
|
+
test(hostname: string): boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compila a regra de host de `mount(X, { host })`/`group({ host })` (F-35), com as mesmas regras do
|
|
42
|
+
* `allowedHosts`: string exata (sem porta), `*.dominio` para subdomínios de qualquer profundidade (nunca o
|
|
43
|
+
* domínio em si), RegExp com casamento da string inteira.
|
|
44
|
+
*/
|
|
45
|
+
export declare function compileRouteHost(rule: MatchRule): RouteHostRule;
|
|
46
|
+
/**
|
|
47
|
+
* Prioridade entre regras que casam com o mesmo host (F-35), usada **igual** pelo roteador e pelo OpenAPI:
|
|
48
|
+
* exato → curinga mais específico (mais rótulos no domínio base) → RegExp, na ordem de registro.
|
|
49
|
+
* Menor vence. Rotas sem host ficam por último (são o fallback).
|
|
50
|
+
*/
|
|
51
|
+
export declare function hostRank(rule: RouteHostRule, seq: number): number;
|
|
52
|
+
export declare const NO_HOST_RANK: number;
|
|
53
|
+
/**
|
|
54
|
+
* Uma regra de rota pode ser alcançada com este `allowedHosts`? (F-35)
|
|
55
|
+
* `'yes'` quando há interseção comprovada, `'no'` quando comprovadamente não há, `'unknown'` com RegExp.
|
|
56
|
+
*/
|
|
57
|
+
export declare function hostReachability(rule: RouteHostRule, allowed: readonly MatchRule[]): 'yes' | 'no' | 'unknown';
|
|
58
|
+
//# sourceMappingURL=origin-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin-match.d.ts","sourceRoot":"","sources":["../../src/utils/origin-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AASD,qHAAqH;AACrH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASjE;AAaD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAsC7F;AAaD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CA0B3G;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACrC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,aAAa,CAc/D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjE;AACD,eAAO,MAAM,YAAY,QAA0B,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAoB7G"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparação de origens (CORS) e hosts (`allowedHosts`) segura por padrão.
|
|
3
|
+
*
|
|
4
|
+
* O risco não está no volume de código, e sim na semântica da comparação. Por isso:
|
|
5
|
+
* - **strings** são normalizadas com o parser nativo de URL e comparadas por igualdade, nunca por substring;
|
|
6
|
+
* - **curinga** (`https://*.exemplo.dev`, `*.exemplo.dev`) aceita só subdomínios, nunca o domínio
|
|
7
|
+
* em si nem `exemplo.dev.atacante.com`;
|
|
8
|
+
* - **RegExp** precisa casar a string **inteira**, como se tivesse `^…$`, mesmo que o autor esqueça as
|
|
9
|
+
* âncoras. As flags `g`, `y` e `m` são descartadas, então `lastIndex` e `^`/`$` por linha não interferem.
|
|
10
|
+
*
|
|
11
|
+
* Erros de configuração (string sem esquema, curinga mal posicionado) são lançados na criação do
|
|
12
|
+
* middleware ou do app, e não na primeira requisição.
|
|
13
|
+
*/
|
|
14
|
+
/** Envolve a RegExp para casar a string inteira e descarta flags com estado ou por linha. */
|
|
15
|
+
export function toFullMatch(rule) {
|
|
16
|
+
return new RegExp(`^(?:${rule.source})$`, rule.flags.replace(/[gym]/g, ''));
|
|
17
|
+
}
|
|
18
|
+
/** Normaliza uma origem (`Https://App.Exemplo.com:443/` → `https://app.exemplo.com`), ou `undefined` se inválida. */
|
|
19
|
+
export function normalizeOrigin(value) {
|
|
20
|
+
if (value === 'null')
|
|
21
|
+
return 'null';
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
if (url.origin === 'null')
|
|
25
|
+
return undefined;
|
|
26
|
+
return url.origin;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function parseWildcardOrigin(rule) {
|
|
33
|
+
const match = /^([a-z][a-z0-9+.-]*:)\/\/\*\.([^/:]+)(?::(\d+))?\/?$/i.exec(rule);
|
|
34
|
+
if (!match) {
|
|
35
|
+
throw new Error(`[flowing] invalid origin rule "${rule}": "*" is only allowed as the first label, e.g. "https://*.exemplo.dev".`);
|
|
36
|
+
}
|
|
37
|
+
const [, protocol, domain, port = ''] = match;
|
|
38
|
+
// Normaliza o domínio (maiúsculas, IDN → punycode) e a porta padrão do esquema.
|
|
39
|
+
const url = new URL(`${protocol}//x.${domain}${port ? `:${port}` : ''}`);
|
|
40
|
+
return { protocol: url.protocol, suffix: url.hostname.slice(1), port: url.port };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compila as regras de origem do CORS. Strings precisam do esquema (`https://app.exemplo.com`);
|
|
44
|
+
* `'null'` só casa com a origem `null` se estiver listada explicitamente.
|
|
45
|
+
*/
|
|
46
|
+
export function compileOriginMatcher(rules) {
|
|
47
|
+
const exact = new Set();
|
|
48
|
+
const wildcards = [];
|
|
49
|
+
const patterns = [];
|
|
50
|
+
for (const rule of rules) {
|
|
51
|
+
if (rule instanceof RegExp) {
|
|
52
|
+
patterns.push(toFullMatch(rule));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (rule === '*') {
|
|
56
|
+
throw new Error(`[flowing] cors: "*" inside a list is not allowed; use origin: '*' to allow any origin.`);
|
|
57
|
+
}
|
|
58
|
+
if (rule.includes('*')) {
|
|
59
|
+
wildcards.push(parseWildcardOrigin(rule));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const normalized = normalizeOrigin(rule);
|
|
63
|
+
if (!normalized) {
|
|
64
|
+
throw new Error(`[flowing] invalid origin rule "${rule}": use the full origin with scheme, e.g. "https://app.exemplo.com".`);
|
|
65
|
+
}
|
|
66
|
+
exact.add(normalized);
|
|
67
|
+
}
|
|
68
|
+
return (origin) => {
|
|
69
|
+
const normalized = normalizeOrigin(origin);
|
|
70
|
+
if (!normalized)
|
|
71
|
+
return false;
|
|
72
|
+
if (exact.has(normalized))
|
|
73
|
+
return true;
|
|
74
|
+
if (normalized !== 'null' && wildcards.length > 0) {
|
|
75
|
+
const url = new URL(normalized);
|
|
76
|
+
const matched = wildcards.some((w) => w.protocol === url.protocol && w.port === url.port && url.hostname.endsWith(w.suffix) && url.hostname.length > w.suffix.length);
|
|
77
|
+
if (matched)
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return patterns.some((re) => re.test(normalized));
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Normaliza um host de regra (`API.Exemplo.com:8443` → `api.exemplo.com:8443`; IDN → punycode). */
|
|
84
|
+
function normalizeHostRule(rule) {
|
|
85
|
+
try {
|
|
86
|
+
const url = new URL(`http://${rule}`);
|
|
87
|
+
if (url.pathname !== '/' || url.search || url.username)
|
|
88
|
+
throw new Error('not a host');
|
|
89
|
+
return { hostname: url.hostname };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new Error(`[flowing] invalid allowedHosts rule "${rule}": use a host like "api.exemplo.com" or "*.exemplo.dev".`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Compila `allowedHosts`. Strings casam com o hostname ou com `host:porta`; `*.exemplo.dev` aceita só
|
|
97
|
+
* subdomínios; RegExp precisa casar o hostname inteiro.
|
|
98
|
+
*/
|
|
99
|
+
export function compileHostMatcher(rules) {
|
|
100
|
+
const exact = new Set();
|
|
101
|
+
const suffixes = [];
|
|
102
|
+
const patterns = [];
|
|
103
|
+
for (const rule of rules) {
|
|
104
|
+
if (rule instanceof RegExp) {
|
|
105
|
+
patterns.push(toFullMatch(rule));
|
|
106
|
+
}
|
|
107
|
+
else if (rule.startsWith('*.')) {
|
|
108
|
+
suffixes.push(`.${normalizeHostRule(rule.slice(2)).hostname}`);
|
|
109
|
+
}
|
|
110
|
+
else if (rule.includes('*')) {
|
|
111
|
+
throw new Error(`[flowing] invalid allowedHosts rule "${rule}": "*" is only allowed as "*.domain".`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const { hostname } = normalizeHostRule(rule);
|
|
115
|
+
// Com porta, a regra exige a porta exata (mesmo a padrão); sem porta, aceita qualquer uma.
|
|
116
|
+
exact.add(rule.includes(':') ? rule.toLowerCase() : hostname);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return (hostname, host) => {
|
|
120
|
+
const h = hostname.toLowerCase();
|
|
121
|
+
const full = host.toLowerCase();
|
|
122
|
+
if (exact.has(h) || exact.has(full))
|
|
123
|
+
return true;
|
|
124
|
+
if (suffixes.some((s) => h.endsWith(s) && h.length > s.length))
|
|
125
|
+
return true;
|
|
126
|
+
return patterns.some((re) => re.test(h));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compila a regra de host de `mount(X, { host })`/`group({ host })` (F-35), com as mesmas regras do
|
|
131
|
+
* `allowedHosts`: string exata (sem porta), `*.dominio` para subdomínios de qualquer profundidade (nunca o
|
|
132
|
+
* domínio em si), RegExp com casamento da string inteira.
|
|
133
|
+
*/
|
|
134
|
+
export function compileRouteHost(rule) {
|
|
135
|
+
if (rule instanceof RegExp) {
|
|
136
|
+
const re = toFullMatch(rule);
|
|
137
|
+
return { label: String(rule), kind: 'regex', base: '', key: `re:${re.source}/${re.flags}`, test: (h) => re.test(h) };
|
|
138
|
+
}
|
|
139
|
+
if (rule.startsWith('*.')) {
|
|
140
|
+
const base = normalizeHostRule(rule.slice(2)).hostname;
|
|
141
|
+
const suffix = `.${base}`;
|
|
142
|
+
return { label: rule, kind: 'wildcard', base, key: `*.${base}`, test: (h) => h.endsWith(suffix) && h.length > suffix.length };
|
|
143
|
+
}
|
|
144
|
+
if (rule.includes('*'))
|
|
145
|
+
throw new Error(`[flowing] invalid host rule "${rule}": "*" is only allowed as "*.domain".`);
|
|
146
|
+
if (rule.includes(':'))
|
|
147
|
+
throw new Error(`[flowing] invalid host rule "${rule}": use the hostname without the port.`);
|
|
148
|
+
const { hostname } = normalizeHostRule(rule);
|
|
149
|
+
return { label: rule, kind: 'exact', base: hostname, key: hostname, test: (h) => h === hostname };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Prioridade entre regras que casam com o mesmo host (F-35), usada **igual** pelo roteador e pelo OpenAPI:
|
|
153
|
+
* exato → curinga mais específico (mais rótulos no domínio base) → RegExp, na ordem de registro.
|
|
154
|
+
* Menor vence. Rotas sem host ficam por último (são o fallback).
|
|
155
|
+
*/
|
|
156
|
+
export function hostRank(rule, seq) {
|
|
157
|
+
if (rule.kind === 'exact')
|
|
158
|
+
return 0;
|
|
159
|
+
if (rule.kind === 'wildcard')
|
|
160
|
+
return 1_000_000 - rule.base.split('.').length * 1_000 + Math.min(seq, 999);
|
|
161
|
+
return 2_000_000 + seq;
|
|
162
|
+
}
|
|
163
|
+
export const NO_HOST_RANK = Number.MAX_SAFE_INTEGER;
|
|
164
|
+
/**
|
|
165
|
+
* Uma regra de rota pode ser alcançada com este `allowedHosts`? (F-35)
|
|
166
|
+
* `'yes'` quando há interseção comprovada, `'no'` quando comprovadamente não há, `'unknown'` com RegExp.
|
|
167
|
+
*/
|
|
168
|
+
export function hostReachability(rule, allowed) {
|
|
169
|
+
if (rule.kind === 'regex')
|
|
170
|
+
return 'unknown';
|
|
171
|
+
let unknown = false;
|
|
172
|
+
for (const a of allowed) {
|
|
173
|
+
if (a instanceof RegExp) {
|
|
174
|
+
if (rule.kind === 'exact' && toFullMatch(a).test(rule.base))
|
|
175
|
+
return 'yes';
|
|
176
|
+
unknown = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (a.startsWith('*.')) {
|
|
180
|
+
const c = normalizeHostRule(a.slice(2)).hostname;
|
|
181
|
+
if (rule.kind === 'exact' && rule.base.endsWith(`.${c}`))
|
|
182
|
+
return 'yes';
|
|
183
|
+
// Dois curingas se sobrepõem quando um domínio base está dentro do outro.
|
|
184
|
+
if (rule.kind === 'wildcard' && (c === rule.base || c.endsWith(`.${rule.base}`) || rule.base.endsWith(`.${c}`)))
|
|
185
|
+
return 'yes';
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const host = normalizeHostRule(a).hostname; // `api.exemplo.com:8443` → `api.exemplo.com`
|
|
189
|
+
if (rule.test(host))
|
|
190
|
+
return 'yes';
|
|
191
|
+
}
|
|
192
|
+
return unknown ? 'unknown' : 'no';
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=origin-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin-match.js","sourceRoot":"","sources":["../../src/utils/origin-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Junta prefixo e caminho com exatamente uma barra entre eles: `('/users/', 'id')` → `/users/id`. */
|
|
2
|
+
export declare function joinPaths(prefix: string, path: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Parâmetros de um path na sintaxe do find-my-way (`/:org/users/:id(^\\d+)`, `/files/*`), em ordem (F-29).
|
|
5
|
+
* Lança erro para parâmetro sem nome (`/:`), nome repetido, regex sem fechamento e `*` fora do fim.
|
|
6
|
+
*/
|
|
7
|
+
export declare function pathParams(path: string): string[];
|
|
8
|
+
/** Forma canônica de um path para comparar rotas: sem barra final, como o roteador (`ignoreTrailingSlash`). */
|
|
9
|
+
export declare function canonicalPath(path: string): string;
|
|
10
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9D;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAmCjD;AAED,+GAA+G;AAC/G,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Junta prefixo e caminho com exatamente uma barra entre eles: `('/users/', 'id')` → `/users/id`. */
|
|
2
|
+
export function joinPaths(prefix, path) {
|
|
3
|
+
const p = prefix.replace(/\/$/, '');
|
|
4
|
+
const s = path.startsWith('/') ? path : `/${path}`;
|
|
5
|
+
return `${p}${s}` || '/';
|
|
6
|
+
}
|
|
7
|
+
const NAME_END = new Set(['/', '-', '.', '(']);
|
|
8
|
+
/**
|
|
9
|
+
* Parâmetros de um path na sintaxe do find-my-way (`/:org/users/:id(^\\d+)`, `/files/*`), em ordem (F-29).
|
|
10
|
+
* Lança erro para parâmetro sem nome (`/:`), nome repetido, regex sem fechamento e `*` fora do fim.
|
|
11
|
+
*/
|
|
12
|
+
export function pathParams(path) {
|
|
13
|
+
const names = [];
|
|
14
|
+
for (let i = 0; i < path.length; i++) {
|
|
15
|
+
const ch = path[i];
|
|
16
|
+
if (ch === '*') {
|
|
17
|
+
if (i !== path.length - 1)
|
|
18
|
+
throw new Error(`invalid path "${path}": "*" is only allowed at the end`);
|
|
19
|
+
names.push('*');
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (ch !== ':')
|
|
23
|
+
continue;
|
|
24
|
+
if (path[i + 1] === ':') {
|
|
25
|
+
i++;
|
|
26
|
+
continue;
|
|
27
|
+
} // `::` é um ':' literal
|
|
28
|
+
let j = i + 1;
|
|
29
|
+
while (j < path.length && !NAME_END.has(path[j]))
|
|
30
|
+
j++;
|
|
31
|
+
const name = path.slice(i + 1, j);
|
|
32
|
+
if (!name)
|
|
33
|
+
throw new Error(`invalid path "${path}": parameter without a name`);
|
|
34
|
+
if (names.includes(name))
|
|
35
|
+
throw new Error(`invalid path "${path}": parameter "${name}" is repeated`);
|
|
36
|
+
names.push(name);
|
|
37
|
+
if (path[j] === '(') {
|
|
38
|
+
let depth = 0;
|
|
39
|
+
for (; j < path.length; j++) {
|
|
40
|
+
if (path[j] === '\\') {
|
|
41
|
+
j++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (path[j] === '(')
|
|
45
|
+
depth++;
|
|
46
|
+
else if (path[j] === ')' && --depth === 0)
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (depth !== 0)
|
|
50
|
+
throw new Error(`invalid path "${path}": unterminated regex for "${name}"`);
|
|
51
|
+
}
|
|
52
|
+
i = j - (path[j] === ')' ? 0 : 1);
|
|
53
|
+
}
|
|
54
|
+
return names;
|
|
55
|
+
}
|
|
56
|
+
/** Forma canônica de um path para comparar rotas: sem barra final, como o roteador (`ignoreTrailingSlash`). */
|
|
57
|
+
export function canonicalPath(path) {
|
|
58
|
+
return path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Padrão do `node:querystring`: limita o custo de queries gigantes (S-06). */
|
|
2
|
+
export declare const DEFAULT_MAX_QUERY_KEYS = 1000;
|
|
3
|
+
export type ParsedQuery = Record<string, string | string[]>;
|
|
4
|
+
/**
|
|
5
|
+
* Converte `URLSearchParams` em objeto (B-07):
|
|
6
|
+
* - chaves repetidas viram array (`?tag=a&tag=b` → `{ tag: ['a', 'b'] }`);
|
|
7
|
+
* - `__proto__`, `constructor` e `prototype` são descartadas (S-08);
|
|
8
|
+
* - pares além de `maxKeys` são ignorados (S-06).
|
|
9
|
+
*
|
|
10
|
+
* Usa `URLSearchParams` (WHATWG) em vez do `node:querystring`, que é legado.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseQuery(params: URLSearchParams, maxKeys?: number): ParsedQuery;
|
|
13
|
+
/**
|
|
14
|
+
* Parser de query próprio (F-26): recebe a query sem o `?` e devolve um objeto. Use para sintaxes que o parser padrão
|
|
15
|
+
* não entende, como `?filter[status]=ativo` com o `qs`. Os limites (número de chaves, profundidade) passam a ser do
|
|
16
|
+
* parser: o `queryMaxKeys` vale só para o padrão.
|
|
17
|
+
*/
|
|
18
|
+
export type QueryParser = (rawQuery: string) => Record<string, unknown>;
|
|
19
|
+
/** Erro de um parser de query próprio: vira 400, sem expor a mensagem original. */
|
|
20
|
+
export declare class InvalidQueryError extends Error {
|
|
21
|
+
readonly name = "InvalidQueryError";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Roda um parser próprio e passa o resultado pelo mesmo filtro de chaves perigosas do padrão, de forma recursiva:
|
|
25
|
+
* um parser mal configurado não reabre o S-08.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseCustomQuery(parser: QueryParser, search: string): Record<string, unknown>;
|
|
28
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,SAAyB,GAAG,WAAW,CAYjG;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAgBxE,mFAAmF;AACnF,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAkB,IAAI,uBAAuB;CAC9C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU7F"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
2
|
+
/** Padrão do `node:querystring`: limita o custo de queries gigantes (S-06). */
|
|
3
|
+
export const DEFAULT_MAX_QUERY_KEYS = 1000;
|
|
4
|
+
/**
|
|
5
|
+
* Converte `URLSearchParams` em objeto (B-07):
|
|
6
|
+
* - chaves repetidas viram array (`?tag=a&tag=b` → `{ tag: ['a', 'b'] }`);
|
|
7
|
+
* - `__proto__`, `constructor` e `prototype` são descartadas (S-08);
|
|
8
|
+
* - pares além de `maxKeys` são ignorados (S-06).
|
|
9
|
+
*
|
|
10
|
+
* Usa `URLSearchParams` (WHATWG) em vez do `node:querystring`, que é legado.
|
|
11
|
+
*/
|
|
12
|
+
export function parseQuery(params, maxKeys = DEFAULT_MAX_QUERY_KEYS) {
|
|
13
|
+
const out = {};
|
|
14
|
+
let count = 0;
|
|
15
|
+
for (const [key, value] of params) {
|
|
16
|
+
if (count++ >= maxKeys)
|
|
17
|
+
break;
|
|
18
|
+
if (UNSAFE_KEYS.has(key))
|
|
19
|
+
continue;
|
|
20
|
+
const prev = out[key];
|
|
21
|
+
if (prev === undefined)
|
|
22
|
+
out[key] = value;
|
|
23
|
+
else if (Array.isArray(prev))
|
|
24
|
+
prev.push(value);
|
|
25
|
+
else
|
|
26
|
+
out[key] = [prev, value];
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Chaves que reabririam prototype pollution (S-08) num objeto aninhado. */
|
|
31
|
+
function dropUnsafe(value, seen, depth) {
|
|
32
|
+
if (typeof value !== 'object' || value === null)
|
|
33
|
+
return value;
|
|
34
|
+
if (seen.has(value) || depth > 32)
|
|
35
|
+
return undefined;
|
|
36
|
+
seen.add(value);
|
|
37
|
+
if (Array.isArray(value))
|
|
38
|
+
return value.map((v) => dropUnsafe(v, seen, depth + 1));
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const key of Object.keys(value)) {
|
|
41
|
+
if (UNSAFE_KEYS.has(key))
|
|
42
|
+
continue;
|
|
43
|
+
out[key] = dropUnsafe(value[key], seen, depth + 1);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Erro de um parser de query próprio: vira 400, sem expor a mensagem original. */
|
|
48
|
+
export class InvalidQueryError extends Error {
|
|
49
|
+
name = 'InvalidQueryError';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Roda um parser próprio e passa o resultado pelo mesmo filtro de chaves perigosas do padrão, de forma recursiva:
|
|
53
|
+
* um parser mal configurado não reabre o S-08.
|
|
54
|
+
*/
|
|
55
|
+
export function parseCustomQuery(parser, search) {
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = parser(search.startsWith('?') ? search.slice(1) : search);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new InvalidQueryError('query parser failed');
|
|
62
|
+
}
|
|
63
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
64
|
+
throw new InvalidQueryError('query parser returned a non-object');
|
|
65
|
+
return dropUnsafe(parsed, new WeakSet(), 0);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Valida um valor de header `Host`/`X-Forwarded-Host` (S-09). */
|
|
2
|
+
export declare function isValidHost(value: string | undefined): value is string;
|
|
3
|
+
/** `host` sem a porta: `api.exemplo.com:8080` → `api.exemplo.com`, `[::1]:3000` → `[::1]`. */
|
|
4
|
+
export declare function hostnameOf(host: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Extrai path e query do request-target **sem** deixar o cliente escolher o host (S-09).
|
|
7
|
+
*
|
|
8
|
+
* - `//evil.com/x` continua sendo o path `//evil.com/x`; não vira uma URL relativa ao protocolo.
|
|
9
|
+
* - `http://outro/x` (absolute-form, usado por proxies) só tem o path e a query aproveitados.
|
|
10
|
+
* - Segmentos `.` e `..` são normalizados como no WHATWG URL.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseRequestTarget(raw: string | undefined): URL;
|
|
13
|
+
/** Path e query do request-target, como strings (`search` inclui o `?`, ou é `''`). */
|
|
14
|
+
export interface RequestTarget {
|
|
15
|
+
pathname: string;
|
|
16
|
+
search: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* O mesmo resultado de {@link parseRequestTarget} (S-09), sem criar um objeto `URL` no caso comum
|
|
20
|
+
* (F-41): para um path simples, já normalizado, o URL não mudaria nada, e basta cortar a string no `?`.
|
|
21
|
+
* Qualquer outro caso (absolute-form, `%`, `\\`, `//`, segmentos `.`/`..`, caracteres especiais) usa o
|
|
22
|
+
* caminho completo, com exatamente o comportamento de antes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function splitRequestTarget(raw: string | undefined): RequestTarget;
|
|
25
|
+
/**
|
|
26
|
+
* Escolhe o valor de um header encaminhado (`X-Forwarded-For`, `-Host`, `-Proto`) segundo `trustProxy`:
|
|
27
|
+
* - `true`: confia em toda a cadeia e usa o primeiro valor (o cliente original);
|
|
28
|
+
* - `n` (número): confia em `n` proxies e usa o valor adicionado pelo mais externo deles.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pickForwarded(header: string | string[] | undefined, trust: boolean | number): string | undefined;
|
|
31
|
+
//# sourceMappingURL=request-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-target.d.ts","sourceRoot":"","sources":["../../src/utils/request-target.ts"],"names":[],"mappings":"AAGA,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAa/D;AAED,uFAAuF;AACvF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAWzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAShH"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** `host[:port]`: nome (letras, dígitos, `.` e `-`) ou IPv6 entre colchetes, com porta opcional. */
|
|
2
|
+
const VALID_HOST = /^(?:[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?|\[[0-9A-Fa-f:.]{2,45}\])(?::\d{1,5})?$/;
|
|
3
|
+
/** Valida um valor de header `Host`/`X-Forwarded-Host` (S-09). */
|
|
4
|
+
export function isValidHost(value) {
|
|
5
|
+
return typeof value === 'string' && VALID_HOST.test(value);
|
|
6
|
+
}
|
|
7
|
+
/** `host` sem a porta: `api.exemplo.com:8080` → `api.exemplo.com`, `[::1]:3000` → `[::1]`. */
|
|
8
|
+
export function hostnameOf(host) {
|
|
9
|
+
return host.startsWith('[') ? host.slice(0, host.indexOf(']') + 1) : host.split(':')[0];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Extrai path e query do request-target **sem** deixar o cliente escolher o host (S-09).
|
|
13
|
+
*
|
|
14
|
+
* - `//evil.com/x` continua sendo o path `//evil.com/x`; não vira uma URL relativa ao protocolo.
|
|
15
|
+
* - `http://outro/x` (absolute-form, usado por proxies) só tem o path e a query aproveitados.
|
|
16
|
+
* - Segmentos `.` e `..` são normalizados como no WHATWG URL.
|
|
17
|
+
*/
|
|
18
|
+
export function parseRequestTarget(raw) {
|
|
19
|
+
let target = raw || '/';
|
|
20
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) {
|
|
21
|
+
try {
|
|
22
|
+
const abs = new URL(target);
|
|
23
|
+
target = abs.pathname + abs.search;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
target = '/';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!target.startsWith('/'))
|
|
30
|
+
target = `/${target}`;
|
|
31
|
+
// Base fixa concatenada (e não como segundo argumento): `//x` nunca é lido como host.
|
|
32
|
+
return new URL(`http://localhost${target}`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Caracteres de path que o WHATWG URL mantém exatamente como estão. Ficam de fora, e vão para o
|
|
36
|
+
* caminho completo com `URL`: `%` (decodificação), `\\` (vira `/`), aspas, espaços, `#`, `<`, `>`,
|
|
37
|
+
* `{`, `}`, `^`, `|`, `` ` `` e não-ASCII.
|
|
38
|
+
*/
|
|
39
|
+
const SIMPLE_PATH = /^\/[A-Za-z0-9\-._~/:@!$&()*+,;=]*$/;
|
|
40
|
+
/** Query com caracteres que o URL não reescreve (sem `'`, que ele codifica em URLs especiais). */
|
|
41
|
+
const SIMPLE_QUERY = /^[A-Za-z0-9\-._~/:@!$&()*+,;=?%]*$/;
|
|
42
|
+
/** `//`, ou um segmento `.`/`..`: o URL normalizaria, então vai para o caminho completo. */
|
|
43
|
+
const NEEDS_NORMALIZATION = /\/\/|(?:^|\/)\.\.?(?:\/|$)/;
|
|
44
|
+
/**
|
|
45
|
+
* O mesmo resultado de {@link parseRequestTarget} (S-09), sem criar um objeto `URL` no caso comum
|
|
46
|
+
* (F-41): para um path simples, já normalizado, o URL não mudaria nada, e basta cortar a string no `?`.
|
|
47
|
+
* Qualquer outro caso (absolute-form, `%`, `\\`, `//`, segmentos `.`/`..`, caracteres especiais) usa o
|
|
48
|
+
* caminho completo, com exatamente o comportamento de antes.
|
|
49
|
+
*/
|
|
50
|
+
export function splitRequestTarget(raw) {
|
|
51
|
+
const target = raw || '/';
|
|
52
|
+
const q = target.indexOf('?');
|
|
53
|
+
const path = q === -1 ? target : target.slice(0, q);
|
|
54
|
+
const query = q === -1 ? '' : target.slice(q + 1);
|
|
55
|
+
if (SIMPLE_PATH.test(path) && !NEEDS_NORMALIZATION.test(path) && SIMPLE_QUERY.test(query)) {
|
|
56
|
+
// O URL descarta o `?` final sem query (`/a?` → search `''`).
|
|
57
|
+
return { pathname: path, search: query ? `?${query}` : '' };
|
|
58
|
+
}
|
|
59
|
+
const url = parseRequestTarget(raw);
|
|
60
|
+
return { pathname: url.pathname, search: url.search };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Escolhe o valor de um header encaminhado (`X-Forwarded-For`, `-Host`, `-Proto`) segundo `trustProxy`:
|
|
64
|
+
* - `true`: confia em toda a cadeia e usa o primeiro valor (o cliente original);
|
|
65
|
+
* - `n` (número): confia em `n` proxies e usa o valor adicionado pelo mais externo deles.
|
|
66
|
+
*/
|
|
67
|
+
export function pickForwarded(header, trust) {
|
|
68
|
+
if (!trust || header === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
const list = (Array.isArray(header) ? header.join(',') : header)
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((v) => v.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
if (list.length === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (trust === true)
|
|
77
|
+
return list[0];
|
|
78
|
+
return list[Math.max(0, list.length - trust)];
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=request-target.js.map
|