@c9up/ream 0.1.25 → 0.1.27
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 +65 -0
- package/dist/ConfigLoader.d.ts +46 -5
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +60 -9
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +46 -6
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +115 -7
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +254 -40
- package/dist/Ignitor.js.map +1 -1
- package/dist/console/BaseCommand.d.ts +217 -0
- package/dist/console/BaseCommand.d.ts.map +1 -0
- package/dist/console/BaseCommand.js +340 -0
- package/dist/console/BaseCommand.js.map +1 -0
- package/dist/console/Console.d.ts +86 -0
- package/dist/console/Console.d.ts.map +1 -0
- package/dist/console/Console.js +134 -0
- package/dist/console/Console.js.map +1 -0
- package/dist/console/ExceptionHandler.d.ts +28 -0
- package/dist/console/ExceptionHandler.d.ts.map +1 -0
- package/dist/console/ExceptionHandler.js +76 -0
- package/dist/console/ExceptionHandler.js.map +1 -0
- package/dist/console/HelpCommand.d.ts +16 -0
- package/dist/console/HelpCommand.d.ts.map +1 -0
- package/dist/console/HelpCommand.js +40 -0
- package/dist/console/HelpCommand.js.map +1 -0
- package/dist/console/IndexGenerator.d.ts +24 -0
- package/dist/console/IndexGenerator.d.ts.map +1 -0
- package/dist/console/IndexGenerator.js +65 -0
- package/dist/console/IndexGenerator.js.map +1 -0
- package/dist/console/Kernel.d.ts +298 -0
- package/dist/console/Kernel.d.ts.map +1 -0
- package/dist/console/Kernel.js +1120 -0
- package/dist/console/Kernel.js.map +1 -0
- package/dist/console/ListCommand.d.ts +18 -0
- package/dist/console/ListCommand.d.ts.map +1 -0
- package/dist/console/ListCommand.js +60 -0
- package/dist/console/ListCommand.js.map +1 -0
- package/dist/console/assertions.d.ts +20 -0
- package/dist/console/assertions.d.ts.map +1 -0
- package/dist/console/assertions.js +84 -0
- package/dist/console/assertions.js.map +1 -0
- package/dist/console/cliui.d.ts +248 -0
- package/dist/console/cliui.d.ts.map +1 -0
- package/dist/console/cliui.js +587 -0
- package/dist/console/cliui.js.map +1 -0
- package/dist/console/decorators.d.ts +90 -0
- package/dist/console/decorators.d.ts.map +1 -0
- package/dist/console/decorators.js +142 -0
- package/dist/console/decorators.js.map +1 -0
- package/dist/console/index.d.ts +32 -0
- package/dist/console/index.d.ts.map +1 -0
- package/dist/console/index.js +24 -0
- package/dist/console/index.js.map +1 -0
- package/dist/console/loaders.d.ts +52 -0
- package/dist/console/loaders.d.ts.map +1 -0
- package/dist/console/loaders.js +131 -0
- package/dist/console/loaders.js.map +1 -0
- package/dist/console/parser.d.ts +86 -0
- package/dist/console/parser.d.ts.map +1 -0
- package/dist/console/parser.js +440 -0
- package/dist/console/parser.js.map +1 -0
- package/dist/console/prompts.d.ts +124 -0
- package/dist/console/prompts.d.ts.map +1 -0
- package/dist/console/prompts.js +356 -0
- package/dist/console/prompts.js.map +1 -0
- package/dist/console/tracing.d.ts +17 -0
- package/dist/console/tracing.d.ts.map +1 -0
- package/dist/console/tracing.js +11 -0
- package/dist/console/tracing.js.map +1 -0
- package/dist/console/types.d.ts +255 -0
- package/dist/console/types.d.ts.map +1 -0
- package/dist/console/types.js +25 -0
- package/dist/console/types.js.map +1 -0
- package/dist/console/ui.d.ts +20 -0
- package/dist/console/ui.d.ts.map +1 -0
- package/dist/console/ui.js +31 -0
- package/dist/console/ui.js.map +1 -0
- package/dist/console/utils.d.ts +27 -0
- package/dist/console/utils.d.ts.map +1 -0
- package/dist/console/utils.js +56 -0
- package/dist/console/utils.js.map +1 -0
- package/dist/container/Container.d.ts +23 -3
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +177 -70
- package/dist/container/Container.js.map +1 -1
- package/dist/container/types.d.ts +14 -2
- package/dist/container/types.d.ts.map +1 -1
- package/dist/decorators/Service.d.ts +7 -0
- package/dist/decorators/Service.d.ts.map +1 -1
- package/dist/decorators/Service.js +23 -3
- package/dist/decorators/Service.js.map +1 -1
- package/dist/env/Env.d.ts +29 -4
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +27 -4
- package/dist/env/Env.js.map +1 -1
- package/dist/env/Secret.d.ts +11 -0
- package/dist/env/Secret.d.ts.map +1 -0
- package/dist/env/Secret.js +36 -0
- package/dist/env/Secret.js.map +1 -0
- package/dist/env/interpolate.d.ts +26 -0
- package/dist/env/interpolate.d.ts.map +1 -0
- package/dist/env/interpolate.js +48 -0
- package/dist/env/interpolate.js.map +1 -0
- package/dist/env/loadEnvFiles.d.ts.map +1 -1
- package/dist/env/loadEnvFiles.js +14 -4
- package/dist/env/loadEnvFiles.js.map +1 -1
- package/dist/env/schema.d.ts +23 -6
- package/dist/env/schema.d.ts.map +1 -1
- package/dist/env/schema.js +67 -33
- package/dist/env/schema.js.map +1 -1
- package/dist/errors/ReamError.d.ts +25 -0
- package/dist/errors/ReamError.d.ts.map +1 -1
- package/dist/errors/ReamError.js +5 -0
- package/dist/errors/ReamError.js.map +1 -1
- package/dist/errors/prettyPrintError.d.ts +9 -0
- package/dist/errors/prettyPrintError.d.ts.map +1 -0
- package/dist/errors/prettyPrintError.js +22 -0
- package/dist/errors/prettyPrintError.js.map +1 -0
- package/dist/events/Emitter.d.ts +1 -1
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +1 -1
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsProvider.d.ts +2 -2
- package/dist/events/EventsProvider.d.ts.map +1 -1
- package/dist/events/EventsProvider.js +3 -3
- package/dist/events/EventsProvider.js.map +1 -1
- package/dist/graphql/GraphQLEngine.d.ts +1 -1
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
- package/dist/graphql/GraphQLEngine.js +2 -2
- package/dist/graphql/GraphQLEngine.js.map +1 -1
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
- package/dist/graphql/GraphQLProvider.js +7 -3
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/http/Exception.d.ts +63 -1
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +185 -48
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +53 -7
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +56 -15
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +4 -1
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts +141 -10
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +244 -34
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +176 -9
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +404 -16
- package/dist/http/Response.js.map +1 -1
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts +29 -8
- package/dist/middleware/Pipeline.d.ts.map +1 -1
- package/dist/middleware/Pipeline.js +40 -2
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/openapi/OpenApiProvider.js +4 -4
- package/dist/openapi/OpenApiProvider.js.map +1 -1
- package/dist/router/Router.d.ts +223 -31
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +520 -102
- package/dist/router/Router.js.map +1 -1
- package/dist/rpc/RpcProvider.d.ts +1 -0
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +7 -3
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/rpc/RpcRouter.d.ts +2 -2
- package/dist/rpc/RpcRouter.d.ts.map +1 -1
- package/dist/rpc/RpcRouter.js +17 -5
- package/dist/rpc/RpcRouter.js.map +1 -1
- package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
- package/dist/scheduler/ScheduleProvider.js +7 -15
- package/dist/scheduler/ScheduleProvider.js.map +1 -1
- package/dist/server/Server.d.ts +17 -4
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +41 -7
- package/dist/server/Server.js.map +1 -1
- package/dist/services/app.d.ts +1 -1
- package/dist/services/app.js +1 -1
- package/dist/services/console.d.ts +21 -0
- package/dist/services/console.d.ts.map +1 -0
- package/dist/services/console.js +35 -0
- package/dist/services/console.js.map +1 -0
- package/dist/session/SessionMiddleware.js +3 -3
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/storage/StaticProvider.js +1 -1
- package/dist/storage/StaticProvider.js.map +1 -1
- package/dist/testing/ApiResponse.d.ts +218 -0
- package/dist/testing/ApiResponse.d.ts.map +1 -0
- package/dist/testing/ApiResponse.js +576 -0
- package/dist/testing/ApiResponse.js.map +1 -0
- package/dist/testing/RequestBuilder.d.ts +155 -35
- package/dist/testing/RequestBuilder.d.ts.map +1 -1
- package/dist/testing/RequestBuilder.js +455 -109
- package/dist/testing/RequestBuilder.js.map +1 -1
- package/dist/testing/TestClient.d.ts +53 -37
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +84 -52
- package/dist/testing/TestClient.js.map +1 -1
- package/dist/utils/Macroable.d.ts +30 -0
- package/dist/utils/Macroable.d.ts.map +1 -0
- package/dist/utils/Macroable.js +43 -0
- package/dist/utils/Macroable.js.map +1 -0
- package/dist/utils/inflect.d.ts +17 -0
- package/dist/utils/inflect.d.ts.map +1 -0
- package/dist/utils/inflect.js +32 -0
- package/dist/utils/inflect.js.map +1 -0
- package/dist/utils/objectPath.d.ts +57 -0
- package/dist/utils/objectPath.d.ts.map +1 -0
- package/dist/utils/objectPath.js +141 -0
- package/dist/utils/objectPath.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +20 -2
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/ConfigLoader.ts +72 -9
- package/src/HttpKernel.ts +53 -15
- package/src/Ignitor.ts +364 -45
- package/src/console/BaseCommand.ts +467 -0
- package/src/console/Console.ts +174 -0
- package/src/console/ExceptionHandler.ts +91 -0
- package/src/console/HelpCommand.ts +37 -0
- package/src/console/IndexGenerator.ts +79 -0
- package/src/console/Kernel.ts +1382 -0
- package/src/console/ListCommand.ts +55 -0
- package/src/console/assertions.ts +114 -0
- package/src/console/cliui.ts +756 -0
- package/src/console/decorators.ts +199 -0
- package/src/console/index.ts +67 -0
- package/src/console/loaders.ts +145 -0
- package/src/console/parser.ts +598 -0
- package/src/console/prompts.ts +548 -0
- package/src/console/tracing.ts +22 -0
- package/src/console/types.ts +280 -0
- package/src/console/ui.ts +37 -0
- package/src/console/utils.ts +69 -0
- package/src/container/Container.ts +198 -72
- package/src/container/types.ts +15 -2
- package/src/decorators/Service.ts +28 -3
- package/src/env/Env.ts +49 -5
- package/src/env/Secret.ts +41 -0
- package/src/env/interpolate.ts +54 -0
- package/src/env/loadEnvFiles.ts +12 -4
- package/src/env/schema.ts +109 -35
- package/src/errors/ReamError.ts +31 -0
- package/src/errors/prettyPrintError.ts +20 -0
- package/src/events/Emitter.ts +2 -2
- package/src/events/EventsProvider.ts +5 -5
- package/src/graphql/GraphQLEngine.ts +4 -4
- package/src/graphql/GraphQLProvider.ts +7 -3
- package/src/http/Exception.ts +214 -37
- package/src/http/HttpContext.ts +87 -18
- package/src/http/RedirectBuilder.ts +4 -1
- package/src/http/Request.ts +264 -34
- package/src/http/Response.ts +478 -27
- package/src/index.ts +41 -6
- package/src/middleware/Pipeline.ts +77 -6
- package/src/openapi/OpenApiProvider.ts +4 -4
- package/src/router/Router.ts +631 -113
- package/src/rpc/RpcProvider.ts +6 -2
- package/src/rpc/RpcRouter.ts +22 -7
- package/src/scheduler/ScheduleProvider.ts +7 -15
- package/src/server/Server.ts +49 -10
- package/src/services/app.ts +1 -1
- package/src/services/console.ts +42 -0
- package/src/session/SessionMiddleware.ts +4 -4
- package/src/storage/StaticProvider.ts +1 -1
- package/src/testing/ApiResponse.ts +691 -0
- package/src/testing/RequestBuilder.ts +577 -137
- package/src/testing/TestClient.ts +110 -77
- package/src/utils/Macroable.ts +54 -0
- package/src/utils/inflect.ts +29 -0
- package/src/utils/objectPath.ts +142 -0
- package/dist/console/CommandRunner.d.ts +0 -18
- package/dist/console/CommandRunner.d.ts.map +0 -1
- package/dist/console/CommandRunner.js +0 -65
- package/dist/console/CommandRunner.js.map +0 -1
- package/dist/decorators/Lazy.d.ts +0 -19
- package/dist/decorators/Lazy.d.ts.map +0 -1
- package/dist/decorators/Lazy.js +0 -53
- package/dist/decorators/Lazy.js.map +0 -1
- package/src/console/CommandRunner.ts +0 -82
- package/src/decorators/Lazy.ts +0 -57
package/src/http/Request.ts
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
* @implements FR21
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { CookieSigner } from '../security/CookieSigner.js'
|
|
11
|
+
import type { SignedUrl } from '../security/SignedUrl.js'
|
|
10
12
|
import type { Dict } from '../types/helpers.js'
|
|
13
|
+
import { Macroable } from '../utils/Macroable.js'
|
|
14
|
+
import { getPath, omitPaths, pickPaths } from '../utils/objectPath.js'
|
|
11
15
|
|
|
12
16
|
export interface RawRequest {
|
|
13
17
|
method: string
|
|
@@ -25,6 +29,12 @@ export interface RawRequest {
|
|
|
25
29
|
* `ip()` accessor falls back to legacy header parsing.
|
|
26
30
|
*/
|
|
27
31
|
ip?: string
|
|
32
|
+
/**
|
|
33
|
+
* Connection scheme as seen by the server (`http`/`https`), when the Rust
|
|
34
|
+
* layer ships it. Defaults to `http` in `protocol()` when absent; the
|
|
35
|
+
* `X-Forwarded-Proto` header only overrides it when trust-proxy is enabled.
|
|
36
|
+
*/
|
|
37
|
+
scheme?: 'http' | 'https'
|
|
28
38
|
/**
|
|
29
39
|
* Pre-parsed `multipart/form-data` payload — the HyperServer parses bodies
|
|
30
40
|
* server-side via `multer` and ships fields/files in this typed shape.
|
|
@@ -48,12 +58,19 @@ export interface RawRequest {
|
|
|
48
58
|
cookies?: Dict
|
|
49
59
|
}
|
|
50
60
|
|
|
51
|
-
export class Request {
|
|
61
|
+
export class Request extends Macroable {
|
|
52
62
|
#raw: RawRequest
|
|
53
63
|
#params: Dict
|
|
64
|
+
#cookieSigner?: CookieSigner
|
|
65
|
+
#signedUrl?: SignedUrl
|
|
66
|
+
#allowMethodSpoofing = false
|
|
67
|
+
#trustProxy = false
|
|
68
|
+
#routeInfo?: { name?: string; pattern: string }
|
|
69
|
+
#response?: { fresh(): boolean }
|
|
54
70
|
#parsedBody: Dict<unknown> | undefined
|
|
55
71
|
#parsedQs: Dict<unknown> | undefined
|
|
56
72
|
#merged: Dict<unknown> | undefined
|
|
73
|
+
#original: Dict<unknown> | undefined
|
|
57
74
|
#files: Map<string, import('../bodyparser/MultipartFile.js').MultipartFile[]> = new Map()
|
|
58
75
|
#validated: unknown
|
|
59
76
|
|
|
@@ -64,18 +81,83 @@ export class Request {
|
|
|
64
81
|
*/
|
|
65
82
|
csrfToken?: string
|
|
66
83
|
|
|
84
|
+
/**
|
|
85
|
+
* `true` only when CSRF was enabled, the request method guarded, the route not
|
|
86
|
+
* excepted, AND the token validated for this request — set by the
|
|
87
|
+
* `@c9up/blackhole` middleware. Unlike {@link csrfToken} (seeded on every
|
|
88
|
+
* passing request, even when verification was skipped), this is the trustworthy
|
|
89
|
+
* "the request was CSRF-verified" signal a consumer reads to fail-close on
|
|
90
|
+
* state-changing routes. `undefined` when the middleware never ran.
|
|
91
|
+
*/
|
|
92
|
+
csrfProtected?: boolean
|
|
93
|
+
|
|
67
94
|
constructor(raw: RawRequest, params: Dict = {}) {
|
|
95
|
+
super()
|
|
68
96
|
this.#raw = raw
|
|
69
97
|
this.#params = params
|
|
70
98
|
}
|
|
71
99
|
|
|
72
100
|
// ─── HTTP accessors ───────────────────────────────────────
|
|
73
101
|
|
|
74
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* The HTTP method, honouring `_method` form spoofing when enabled (AdonisJS
|
|
104
|
+
* `method`). With spoofing enabled and a real POST, the `_method` field
|
|
105
|
+
* (`PUT`/`PATCH`/`DELETE`) overrides — the classic HTML-form workaround.
|
|
106
|
+
* Spoofing is OFF by default (opt in via {@link setMethodSpoofing}); routing
|
|
107
|
+
* always uses {@link intended}.
|
|
108
|
+
*/
|
|
75
109
|
method(): string {
|
|
110
|
+
if (this.#allowMethodSpoofing && this.intended().toUpperCase() === 'POST') {
|
|
111
|
+
const spoofed = this.input('_method', this.intended())
|
|
112
|
+
if (typeof spoofed === 'string' && spoofed) return spoofed.toUpperCase()
|
|
113
|
+
}
|
|
114
|
+
return this.intended()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The real HTTP method, ignoring any `_method` spoofing (AdonisJS `intended`). */
|
|
118
|
+
intended(): string {
|
|
76
119
|
return this.#raw.method
|
|
77
120
|
}
|
|
78
121
|
|
|
122
|
+
/** @internal Give the request access to its response — wired by HttpContext (for `fresh()`). */
|
|
123
|
+
setResponse(response: { fresh(): boolean }): void {
|
|
124
|
+
this.#response = response
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @internal Record the matched route — wired by HttpContext (for `matchesRoute()`). */
|
|
128
|
+
setRouteInfo(info: { name?: string; pattern: string }): void {
|
|
129
|
+
this.#routeInfo = info
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether the client's cached copy is still fresh (AdonisJS `fresh`) —
|
|
134
|
+
* delegates to the response's `ETag`/`If-None-Match` revalidation check, so a
|
|
135
|
+
* handler can answer `304 Not Modified`. False before the response is wired.
|
|
136
|
+
*/
|
|
137
|
+
fresh(): boolean {
|
|
138
|
+
return this.#response?.fresh() ?? false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The negation of {@link fresh} (AdonisJS `stale`). */
|
|
142
|
+
stale(): boolean {
|
|
143
|
+
return !this.fresh()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** True when the matched route's name or pattern equals `identifier` (AdonisJS `matchesRoute`). */
|
|
147
|
+
matchesRoute(identifier: string): boolean {
|
|
148
|
+
if (!this.#routeInfo) return false
|
|
149
|
+
return this.#routeInfo.name === identifier || this.#routeInfo.pattern === identifier
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Enable/disable `_method` form spoofing (AdonisJS gates this behind the
|
|
154
|
+
* `allowMethodSpoofing` config; ream exposes it as an explicit opt-in, off by
|
|
155
|
+
* default, so an attacker can never silently rewrite a POST into a DELETE).
|
|
156
|
+
*/
|
|
157
|
+
setMethodSpoofing(enabled: boolean): void {
|
|
158
|
+
this.#allowMethodSpoofing = enabled
|
|
159
|
+
}
|
|
160
|
+
|
|
79
161
|
/** Request URL (path + query string). */
|
|
80
162
|
url(includeQs = true): string {
|
|
81
163
|
if (includeQs && this.#raw.query) {
|
|
@@ -110,6 +192,88 @@ export class Request {
|
|
|
110
192
|
return this.#raw.remoteAddr ?? '127.0.0.1'
|
|
111
193
|
}
|
|
112
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Enable/disable trusting proxy headers (`X-Forwarded-Proto`/`-Host`/`-For`)
|
|
197
|
+
* for `protocol()`/`host()`/`ips()`. OFF by default — turning it on when the
|
|
198
|
+
* app is NOT behind a trusted proxy lets clients spoof their scheme/host/IP,
|
|
199
|
+
* so it's an explicit opt-in (same stance as {@link setMethodSpoofing}).
|
|
200
|
+
*/
|
|
201
|
+
setTrustProxy(enabled: boolean): void {
|
|
202
|
+
this.#trustProxy = enabled
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The request host including port (AdonisJS `host`). Honours
|
|
207
|
+
* `X-Forwarded-Host` ONLY when trust-proxy is enabled; otherwise the `Host`
|
|
208
|
+
* header. Returns null when neither is present.
|
|
209
|
+
*/
|
|
210
|
+
host(): string | null {
|
|
211
|
+
if (this.#trustProxy) {
|
|
212
|
+
const forwarded = this.#raw.headers['x-forwarded-host']
|
|
213
|
+
if (forwarded) return forwarded.split(',')[0].trim()
|
|
214
|
+
}
|
|
215
|
+
return this.#raw.headers.host ?? null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The request protocol — `http` or `https` (AdonisJS `protocol`). Honours
|
|
220
|
+
* `X-Forwarded-Proto` only under trust-proxy; otherwise the connection scheme
|
|
221
|
+
* the Rust layer reported (defaulting to `http`).
|
|
222
|
+
*/
|
|
223
|
+
protocol(): string {
|
|
224
|
+
if (this.#trustProxy) {
|
|
225
|
+
const forwarded = this.#raw.headers['x-forwarded-proto']
|
|
226
|
+
if (forwarded) return forwarded.split(',')[0].trim().toLowerCase()
|
|
227
|
+
}
|
|
228
|
+
return this.#raw.scheme ?? 'http'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** True when the request protocol is `https` (AdonisJS `secure`). */
|
|
232
|
+
secure(): boolean {
|
|
233
|
+
return this.protocol() === 'https'
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The client IP chain (AdonisJS `ips`). Under trust-proxy, the parsed
|
|
238
|
+
* `X-Forwarded-For` list (left-most = original client); otherwise just the
|
|
239
|
+
* single resolved {@link ip}.
|
|
240
|
+
*/
|
|
241
|
+
ips(): string[] {
|
|
242
|
+
if (this.#trustProxy) {
|
|
243
|
+
const forwarded = this.#raw.headers['x-forwarded-for']
|
|
244
|
+
if (forwarded) {
|
|
245
|
+
return forwarded
|
|
246
|
+
.split(',')
|
|
247
|
+
.map((value) => value.trim())
|
|
248
|
+
.filter((value) => value.length > 0)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return [this.ip()]
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** The request hostname without port (AdonisJS `hostname`), or null. */
|
|
255
|
+
hostname(): string | null {
|
|
256
|
+
const host = this.host()
|
|
257
|
+
if (host === null) return null
|
|
258
|
+
// Strip the port. IPv6 literals are bracketed (`[::1]:3000`) — keep the brackets' contents.
|
|
259
|
+
if (host.startsWith('[')) return host.slice(0, host.indexOf(']') + 1) || host
|
|
260
|
+
const colon = host.indexOf(':')
|
|
261
|
+
return colon === -1 ? host : host.slice(0, colon)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Subdomains of the hostname (AdonisJS `subdomains`). `offset` drops the
|
|
266
|
+
* registrable domain (default 2 → `example.com`); a leading `www` is removed.
|
|
267
|
+
* Empty for an IP host or a host with no subdomain.
|
|
268
|
+
*/
|
|
269
|
+
subdomains(offset = 2): string[] {
|
|
270
|
+
const hostname = this.hostname()
|
|
271
|
+
if (hostname === null || isIpLiteral(hostname)) return []
|
|
272
|
+
const parts = hostname.split('.').reverse().slice(offset)
|
|
273
|
+
if (parts.length > 0 && parts[parts.length - 1] === 'www') parts.pop()
|
|
274
|
+
return parts
|
|
275
|
+
}
|
|
276
|
+
|
|
113
277
|
/**
|
|
114
278
|
* Return all cookies sent on this request as a name → value map. The
|
|
115
279
|
* HyperServer parses the `Cookie:` header once via the `cookie` crate
|
|
@@ -138,10 +302,53 @@ export class Request {
|
|
|
138
302
|
return result
|
|
139
303
|
}
|
|
140
304
|
|
|
141
|
-
/**
|
|
142
|
-
|
|
305
|
+
/** Inject the APP_KEY-backed cookie signer — wired by HttpContext. */
|
|
306
|
+
setCookieSigner(signer: CookieSigner): void {
|
|
307
|
+
this.#cookieSigner = signer
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Signed cookie value, verified with APP_KEY (AdonisJS default). Returns
|
|
312
|
+
* `defaultValue` (or null) when absent OR when the signature is invalid
|
|
313
|
+
* (tampered / not signed).
|
|
314
|
+
*/
|
|
315
|
+
cookie(name: string, defaultValue?: string): string | null {
|
|
316
|
+
const raw = this.plainCookie(name)
|
|
317
|
+
const value = raw === null ? null : this.#cookieSigner ? this.#cookieSigner.unsign(raw) : raw
|
|
318
|
+
return value ?? defaultValue ?? null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Raw (unsigned) cookie value (AdonisJS `plainCookie`), or `defaultValue`/null when absent. */
|
|
322
|
+
plainCookie(name: string, defaultValue?: string): string | null {
|
|
143
323
|
const cookies = this.#raw.cookies ?? this.cookies()
|
|
144
|
-
return cookies[name] ?? null
|
|
324
|
+
return cookies[name] ?? defaultValue ?? null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Encrypted cookie value, decrypted with APP_KEY (AdonisJS `encryptedCookie`).
|
|
329
|
+
* Returns `defaultValue` (or null) when absent, undecryptable, or no
|
|
330
|
+
* encryption service.
|
|
331
|
+
*/
|
|
332
|
+
encryptedCookie(name: string, defaultValue?: string): string | null {
|
|
333
|
+
const raw = this.plainCookie(name)
|
|
334
|
+
const value = raw === null || !this.#cookieSigner ? null : this.#cookieSigner.decrypt(raw)
|
|
335
|
+
return value ?? defaultValue ?? null
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** @internal Inject the APP_KEY-backed signed-URL helper — wired by HttpContext. */
|
|
339
|
+
setSignedUrl(signedUrl: SignedUrl): void {
|
|
340
|
+
this.#signedUrl = signedUrl
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Verify the request's signed-URL `signature` (AdonisJS `hasValidSignature`).
|
|
345
|
+
* Delegates to the {@link SignedUrl} helper, which recomputes the HMAC over
|
|
346
|
+
* the current path + query, rejects an expired `expires`, and checks the
|
|
347
|
+
* `purpose`. Returns false without an APP_KEY-backed signer.
|
|
348
|
+
*/
|
|
349
|
+
hasValidSignature(purpose?: string): boolean {
|
|
350
|
+
if (!this.#signedUrl) return false
|
|
351
|
+
return this.#signedUrl.verify(this.url(), purpose)
|
|
145
352
|
}
|
|
146
353
|
|
|
147
354
|
/** Get the raw body as a string (decoded from base64 if binary). */
|
|
@@ -162,9 +369,9 @@ export class Request {
|
|
|
162
369
|
|
|
163
370
|
// ─── Headers ──────────────────────────────────────────────
|
|
164
371
|
|
|
165
|
-
/** Get a single request header (case-insensitive). */
|
|
166
|
-
header(key: string): string | undefined {
|
|
167
|
-
return this.#raw.headers[key.toLowerCase()]
|
|
372
|
+
/** Get a single request header (case-insensitive), or `defaultValue` when absent. */
|
|
373
|
+
header(key: string, defaultValue?: string): string | undefined {
|
|
374
|
+
return this.#raw.headers[key.toLowerCase()] ?? defaultValue
|
|
168
375
|
}
|
|
169
376
|
|
|
170
377
|
/** Get all request headers. */
|
|
@@ -204,15 +411,15 @@ export class Request {
|
|
|
204
411
|
|
|
205
412
|
// ─── Merged input (body + qs) ─────────────────────────────
|
|
206
413
|
|
|
207
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* Get a single input value from body or query string, by AdonisJS
|
|
416
|
+
* dot-notation (`input('user.address.city')`). Returns `defaultValue` when
|
|
417
|
+
* the path is absent (parity with `lodash.get`, reimplemented dependency-free).
|
|
418
|
+
*/
|
|
208
419
|
input<T = unknown>(key: string, defaultValue?: T): T {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return merged[key] as any as T
|
|
213
|
-
}
|
|
214
|
-
// biome-ignore lint/suspicious/noExplicitAny: generic default — caller brands the value type via T
|
|
215
|
-
return defaultValue as any as T
|
|
420
|
+
// Untyped request data: the caller declares the shape it expects (same
|
|
421
|
+
// contract as AdonisJS `request.input<T>`, whose return is loose `any`).
|
|
422
|
+
return getPath(this.all(), key, defaultValue) as T
|
|
216
423
|
}
|
|
217
424
|
|
|
218
425
|
/** Get all input (query string merged with body). */
|
|
@@ -220,31 +427,48 @@ export class Request {
|
|
|
220
427
|
if (!this.#merged) {
|
|
221
428
|
this.#ensureParsedBody()
|
|
222
429
|
this.#merged = { ...this.qs(), ...this.#parsedBody }
|
|
430
|
+
// Snapshot the first-seen input as the immutable "original" (flash old-input).
|
|
431
|
+
if (this.#original === undefined) this.#original = { ...this.#merged }
|
|
223
432
|
}
|
|
224
433
|
return { ...this.#merged }
|
|
225
434
|
}
|
|
226
435
|
|
|
227
|
-
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return result
|
|
436
|
+
/**
|
|
437
|
+
* The original request input, captured once and never mutated (AdonisJS
|
|
438
|
+
* `request.original`) — the basis for flash "old input" on validation errors.
|
|
439
|
+
*/
|
|
440
|
+
original(): Dict<unknown> {
|
|
441
|
+
this.all() // ensure the snapshot is captured
|
|
442
|
+
return { ...(this.#original ?? {}) }
|
|
237
443
|
}
|
|
238
444
|
|
|
239
|
-
/**
|
|
445
|
+
/**
|
|
446
|
+
* Cherry-pick specific keys from input, honouring dot-notation for nested
|
|
447
|
+
* branches (`only(['user.id'])` → `{ user: { id } }`) — AdonisJS `only`
|
|
448
|
+
* (`lodash.pick`). Absent paths are skipped.
|
|
449
|
+
*/
|
|
450
|
+
only(keys: string[]): Dict<unknown> {
|
|
451
|
+
return pickPaths(this.all(), keys)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get all input except specific keys, honouring dot-notation for nested
|
|
456
|
+
* branches — AdonisJS `except` (`lodash.omit`). Never mutates the input.
|
|
457
|
+
*/
|
|
240
458
|
except(keys: string[]): Dict<unknown> {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
459
|
+
return omitPaths(this.all(), keys)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* True when the request carries a body — a `content-length` above zero or a
|
|
464
|
+
* `transfer-encoding` header (AdonisJS `request.hasBody`). Lets a handler
|
|
465
|
+
* branch on "was anything sent" without parsing.
|
|
466
|
+
*/
|
|
467
|
+
hasBody(): boolean {
|
|
468
|
+
const transferEncoding = this.#raw.headers['transfer-encoding']
|
|
469
|
+
if (transferEncoding && transferEncoding.length > 0) return true
|
|
470
|
+
const contentLength = Number.parseInt(this.#raw.headers['content-length'] ?? '', 10)
|
|
471
|
+
return Number.isFinite(contentLength) && contentLength > 0
|
|
248
472
|
}
|
|
249
473
|
|
|
250
474
|
// ─── Content negotiation ──────────────────────────────────
|
|
@@ -427,6 +651,12 @@ function isPlainObject(value: unknown): value is Dict<unknown> {
|
|
|
427
651
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
428
652
|
}
|
|
429
653
|
|
|
654
|
+
/** True for an IPv4 dotted-quad or a (bracketed) IPv6 literal — hosts with no subdomains. */
|
|
655
|
+
function isIpLiteral(host: string): boolean {
|
|
656
|
+
if (host.startsWith('[') || host.includes('::')) return true
|
|
657
|
+
return /^(\d{1,3}\.){3}\d{1,3}$/.test(host)
|
|
658
|
+
}
|
|
659
|
+
|
|
430
660
|
/**
|
|
431
661
|
* Decode a percent-encoded segment without throwing on malformed input.
|
|
432
662
|
* Browsers happily send `q=%E0%A4%A` (truncated UTF-8 sequences) — refusing
|