@adonisjs/http-server 7.0.0-1 → 7.0.0-2
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/build/chunk-Z63E3STR.js +4436 -0
- package/build/chunk-Z63E3STR.js.map +1 -0
- package/build/factories/main.js +332 -14
- package/build/factories/main.js.map +1 -0
- package/build/index.js +309 -22
- package/build/index.js.map +1 -0
- package/build/src/router/lookup_store/main.d.ts +1 -3
- package/build/src/router/lookup_store/route_finder.d.ts +5 -1
- package/build/src/router/main.d.ts +2 -2
- package/build/src/router/resource.d.ts +19 -7
- package/build/src/types/main.js +1 -15
- package/build/src/types/main.js.map +1 -0
- package/package.json +52 -51
- package/build/factories/http_context.js +0 -51
- package/build/factories/http_server.js +0 -26
- package/build/factories/qs_parser_factory.js +0 -44
- package/build/factories/request.js +0 -73
- package/build/factories/response.js +0 -77
- package/build/factories/router.js +0 -45
- package/build/factories/server_factory.js +0 -65
- package/build/src/cookies/client.js +0 -84
- package/build/src/cookies/drivers/encrypted.js +0 -36
- package/build/src/cookies/drivers/plain.js +0 -33
- package/build/src/cookies/drivers/signed.js +0 -36
- package/build/src/cookies/parser.js +0 -167
- package/build/src/cookies/serializer.js +0 -79
- package/build/src/debug.js +0 -10
- package/build/src/define_config.js +0 -68
- package/build/src/define_middleware.js +0 -35
- package/build/src/exception_handler.js +0 -306
- package/build/src/exceptions.js +0 -38
- package/build/src/helpers.js +0 -105
- package/build/src/http_context/local_storage.js +0 -39
- package/build/src/http_context/main.js +0 -105
- package/build/src/qs.js +0 -25
- package/build/src/redirect.js +0 -140
- package/build/src/request.js +0 -865
- package/build/src/response.js +0 -1208
- package/build/src/router/brisk.js +0 -85
- package/build/src/router/executor.js +0 -30
- package/build/src/router/factories/use_return_value.js +0 -22
- package/build/src/router/group.js +0 -207
- package/build/src/router/lookup_store/main.js +0 -86
- package/build/src/router/lookup_store/route_finder.js +0 -49
- package/build/src/router/lookup_store/url_builder.js +0 -209
- package/build/src/router/main.js +0 -316
- package/build/src/router/matchers.js +0 -36
- package/build/src/router/parser.js +0 -17
- package/build/src/router/resource.js +0 -216
- package/build/src/router/route.js +0 -293
- package/build/src/router/store.js +0 -195
- package/build/src/server/factories/final_handler.js +0 -30
- package/build/src/server/factories/middleware_handler.js +0 -16
- package/build/src/server/factories/write_response.js +0 -24
- package/build/src/server/main.js +0 -292
- package/build/src/types/base.js +0 -9
- package/build/src/types/middleware.js +0 -9
- package/build/src/types/qs.js +0 -9
- package/build/src/types/request.js +0 -9
- package/build/src/types/response.js +0 -9
- package/build/src/types/route.js +0 -9
- package/build/src/types/server.js +0 -9
|
@@ -0,0 +1,4436 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/router/route.ts
|
|
8
|
+
import is from "@sindresorhus/is";
|
|
9
|
+
import Macroable4 from "@poppinss/macroable";
|
|
10
|
+
import Middleware from "@poppinss/middleware";
|
|
11
|
+
import { RuntimeException as RuntimeException2 } from "@poppinss/utils";
|
|
12
|
+
import { moduleCaller, moduleExpression, moduleImporter } from "@adonisjs/fold";
|
|
13
|
+
|
|
14
|
+
// src/router/factories/use_return_value.ts
|
|
15
|
+
function useReturnValue(ctx) {
|
|
16
|
+
return function(value) {
|
|
17
|
+
if (value !== void 0 && // Return value is explicitly defined
|
|
18
|
+
!ctx.response.hasLazyBody && // Lazy body is not set
|
|
19
|
+
value !== ctx.response) {
|
|
20
|
+
ctx.response.send(value);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/router/executor.ts
|
|
26
|
+
function execute(route, resolver, ctx, errorResponder) {
|
|
27
|
+
return route.middleware.runner().errorHandler((error) => errorResponder(error, ctx)).finalHandler(async () => {
|
|
28
|
+
if (typeof route.handler === "function") {
|
|
29
|
+
return Promise.resolve(route.handler(ctx)).then(useReturnValue(ctx));
|
|
30
|
+
}
|
|
31
|
+
return route.handler.handle(resolver, ctx).then(useReturnValue(ctx));
|
|
32
|
+
}).run(async (middleware, next) => {
|
|
33
|
+
if (typeof middleware === "function") {
|
|
34
|
+
return middleware(ctx, next);
|
|
35
|
+
}
|
|
36
|
+
return middleware.handle(resolver, ctx, next, middleware.args);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/helpers.ts
|
|
41
|
+
import Cache from "tmp-cache";
|
|
42
|
+
import { InvalidArgumentsException } from "@poppinss/utils";
|
|
43
|
+
|
|
44
|
+
// src/router/brisk.ts
|
|
45
|
+
import Macroable from "@poppinss/macroable";
|
|
46
|
+
var BriskRoute = class extends Macroable {
|
|
47
|
+
/**
|
|
48
|
+
* Route pattern
|
|
49
|
+
*/
|
|
50
|
+
#pattern;
|
|
51
|
+
/**
|
|
52
|
+
* Matchers inherited from the router
|
|
53
|
+
*/
|
|
54
|
+
#globalMatchers;
|
|
55
|
+
/**
|
|
56
|
+
* Reference to the AdonisJS application
|
|
57
|
+
*/
|
|
58
|
+
#app;
|
|
59
|
+
/**
|
|
60
|
+
* Middleware registered on the router
|
|
61
|
+
*/
|
|
62
|
+
#routerMiddleware;
|
|
63
|
+
/**
|
|
64
|
+
* Reference to route instance. Set after `setHandler` is called
|
|
65
|
+
*/
|
|
66
|
+
route = null;
|
|
67
|
+
constructor(app, routerMiddleware, options) {
|
|
68
|
+
super();
|
|
69
|
+
this.#app = app;
|
|
70
|
+
this.#routerMiddleware = routerMiddleware;
|
|
71
|
+
this.#pattern = options.pattern;
|
|
72
|
+
this.#globalMatchers = options.globalMatchers;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set handler for the brisk route
|
|
76
|
+
*/
|
|
77
|
+
setHandler(handler) {
|
|
78
|
+
this.route = new Route(this.#app, this.#routerMiddleware, {
|
|
79
|
+
pattern: this.#pattern,
|
|
80
|
+
globalMatchers: this.#globalMatchers,
|
|
81
|
+
methods: ["GET", "HEAD"],
|
|
82
|
+
handler
|
|
83
|
+
});
|
|
84
|
+
return this.route;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Redirects to a given route. Params from the original request will
|
|
88
|
+
* be used when no custom params are defined.
|
|
89
|
+
*/
|
|
90
|
+
redirect(identifier, params, options) {
|
|
91
|
+
return this.setHandler(async function redirectsToRoute(ctx) {
|
|
92
|
+
const redirector = ctx.response.redirect();
|
|
93
|
+
if (options?.status) {
|
|
94
|
+
redirector.status(options.status);
|
|
95
|
+
}
|
|
96
|
+
return redirector.toRoute(identifier, params || ctx.params, options);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Redirect request to a fixed URL
|
|
101
|
+
*/
|
|
102
|
+
redirectToPath(url, options) {
|
|
103
|
+
return this.setHandler(async function redirectsToPath(ctx) {
|
|
104
|
+
const redirector = ctx.response.redirect();
|
|
105
|
+
if (options?.status) {
|
|
106
|
+
redirector.status(options.status);
|
|
107
|
+
}
|
|
108
|
+
return redirector.toPath(url);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/router/group.ts
|
|
114
|
+
import Macroable3 from "@poppinss/macroable";
|
|
115
|
+
|
|
116
|
+
// src/router/resource.ts
|
|
117
|
+
import string from "@poppinss/utils/string";
|
|
118
|
+
import Macroable2 from "@poppinss/macroable";
|
|
119
|
+
import { RuntimeException } from "@poppinss/utils";
|
|
120
|
+
var RouteResource = class extends Macroable2 {
|
|
121
|
+
/**
|
|
122
|
+
* Resource identifier. Nested resources are separated
|
|
123
|
+
* with a dot notation
|
|
124
|
+
*/
|
|
125
|
+
#resource;
|
|
126
|
+
/**
|
|
127
|
+
* The controller to handle resource routing requests
|
|
128
|
+
*/
|
|
129
|
+
#controller;
|
|
130
|
+
/**
|
|
131
|
+
* Is it a shallow resource? Shallow resources URLs do not have parent
|
|
132
|
+
* resource name and id once they can be identified with the id.
|
|
133
|
+
*/
|
|
134
|
+
#shallow = false;
|
|
135
|
+
/**
|
|
136
|
+
* Matchers inherited from the router
|
|
137
|
+
*/
|
|
138
|
+
#globalMatchers;
|
|
139
|
+
/**
|
|
140
|
+
* Reference to the AdonisJS application
|
|
141
|
+
*/
|
|
142
|
+
#app;
|
|
143
|
+
/**
|
|
144
|
+
* Middleware registered on the router
|
|
145
|
+
*/
|
|
146
|
+
#routerMiddleware;
|
|
147
|
+
/**
|
|
148
|
+
* Parameter names for the resources. Defaults to `id` for
|
|
149
|
+
* a singular resource and `resource_id` for nested
|
|
150
|
+
* resources.
|
|
151
|
+
*/
|
|
152
|
+
#params = {};
|
|
153
|
+
/**
|
|
154
|
+
* Base name for the routes. We suffix action names
|
|
155
|
+
* on top of the base name
|
|
156
|
+
*/
|
|
157
|
+
#routesBaseName;
|
|
158
|
+
/**
|
|
159
|
+
* A collection of routes instances that belongs to this resource
|
|
160
|
+
*/
|
|
161
|
+
routes = [];
|
|
162
|
+
constructor(app, routerMiddleware, options) {
|
|
163
|
+
super();
|
|
164
|
+
this.#validateResourceName(options.resource);
|
|
165
|
+
this.#app = app;
|
|
166
|
+
this.#shallow = options.shallow;
|
|
167
|
+
this.#routerMiddleware = routerMiddleware;
|
|
168
|
+
this.#controller = options.controller;
|
|
169
|
+
this.#globalMatchers = options.globalMatchers;
|
|
170
|
+
this.#resource = this.#normalizeResourceName(options.resource);
|
|
171
|
+
this.#routesBaseName = this.#getRoutesBaseName();
|
|
172
|
+
this.#buildRoutes();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Normalizes the resource name to dropping leading and trailing
|
|
176
|
+
* slashes.
|
|
177
|
+
*/
|
|
178
|
+
#normalizeResourceName(resource) {
|
|
179
|
+
return resource.replace(/^\//, "").replace(/\/$/, "");
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Ensure resource name is not an empty string
|
|
183
|
+
*/
|
|
184
|
+
#validateResourceName(resource) {
|
|
185
|
+
if (!resource || resource === "/") {
|
|
186
|
+
throw new RuntimeException(`Invalid resource name "${resource}"`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Converting segments of a resource to snake case to
|
|
191
|
+
* make the route name.
|
|
192
|
+
*/
|
|
193
|
+
#getRoutesBaseName() {
|
|
194
|
+
return this.#resource.split(".").map((token) => string.snakeCase(token)).join(".");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create a new route for the given pattern, methods and controller action
|
|
198
|
+
*/
|
|
199
|
+
#createRoute(pattern, methods, action) {
|
|
200
|
+
const route = new Route(this.#app, this.#routerMiddleware, {
|
|
201
|
+
pattern,
|
|
202
|
+
methods,
|
|
203
|
+
handler: typeof this.#controller === "string" ? `${this.#controller}.${action}` : [this.#controller, action],
|
|
204
|
+
globalMatchers: this.#globalMatchers
|
|
205
|
+
});
|
|
206
|
+
route.as(`${this.#routesBaseName}.${action}`);
|
|
207
|
+
this.routes.push(route);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Returns the `resource_id` name for a given resource. The
|
|
211
|
+
* resource name is converted to singular form and
|
|
212
|
+
* transformed to snake case.
|
|
213
|
+
*
|
|
214
|
+
* photos becomes photo_id
|
|
215
|
+
* users becomes user_id
|
|
216
|
+
*/
|
|
217
|
+
#getResourceId(resource) {
|
|
218
|
+
return `${string.snakeCase(string.singular(resource))}_id`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Build routes for the given resource
|
|
222
|
+
*/
|
|
223
|
+
#buildRoutes() {
|
|
224
|
+
const resources = this.#resource.split(".");
|
|
225
|
+
const mainResource = resources.pop();
|
|
226
|
+
this.#params[mainResource] = ":id";
|
|
227
|
+
const baseURI = `${resources.map((resource) => {
|
|
228
|
+
const paramName = `:${this.#getResourceId(resource)}`;
|
|
229
|
+
this.#params[resource] = paramName;
|
|
230
|
+
return `${resource}/${paramName}`;
|
|
231
|
+
}).join("/")}/${mainResource}`;
|
|
232
|
+
this.#createRoute(baseURI, ["GET", "HEAD"], "index");
|
|
233
|
+
this.#createRoute(`${baseURI}/create`, ["GET", "HEAD"], "create");
|
|
234
|
+
this.#createRoute(baseURI, ["POST"], "store");
|
|
235
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["GET", "HEAD"], "show");
|
|
236
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id/edit`, ["GET", "HEAD"], "edit");
|
|
237
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["PUT", "PATCH"], "update");
|
|
238
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["DELETE"], "destroy");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Filter the routes based on their partial names
|
|
242
|
+
*/
|
|
243
|
+
#filter(names, inverse) {
|
|
244
|
+
const actions = Array.isArray(names) ? names : [names];
|
|
245
|
+
return this.routes.filter((route) => {
|
|
246
|
+
const match = actions.find((name) => route.getName().endsWith(name));
|
|
247
|
+
return inverse ? !match : match;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Register only given routes and remove others
|
|
252
|
+
*/
|
|
253
|
+
only(names) {
|
|
254
|
+
this.#filter(names, true).forEach((route) => route.markAsDeleted());
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Register all routes, except the one's defined
|
|
259
|
+
*/
|
|
260
|
+
except(names) {
|
|
261
|
+
this.#filter(names, false).forEach((route) => route.markAsDeleted());
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Register api only routes. The `create` and `edit` routes, which
|
|
266
|
+
* are meant to show forms will not be registered
|
|
267
|
+
*/
|
|
268
|
+
apiOnly() {
|
|
269
|
+
return this.except(["create", "edit"]);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Define matcher for params inside the resource
|
|
273
|
+
*/
|
|
274
|
+
where(key, matcher) {
|
|
275
|
+
this.routes.forEach((route) => {
|
|
276
|
+
route.where(key, matcher);
|
|
277
|
+
});
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
tap(actions, callback) {
|
|
281
|
+
if (typeof actions === "function") {
|
|
282
|
+
this.routes.forEach((route) => {
|
|
283
|
+
if (!route.isDeleted()) {
|
|
284
|
+
actions(route);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
this.#filter(actions, false).forEach((route) => {
|
|
290
|
+
if (!route.isDeleted()) {
|
|
291
|
+
callback(route);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Set the param name for a given resource
|
|
298
|
+
*/
|
|
299
|
+
params(resources) {
|
|
300
|
+
Object.keys(resources).forEach((resource) => {
|
|
301
|
+
const param = resources[resource];
|
|
302
|
+
const existingParam = this.#params[resource];
|
|
303
|
+
this.#params[resource] = `:${param}`;
|
|
304
|
+
this.routes.forEach((route) => {
|
|
305
|
+
route.setPattern(
|
|
306
|
+
route.getPattern().replace(`${resource}/${existingParam}`, `${resource}/:${param}`)
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Define one or more middleware on the routes created by
|
|
314
|
+
* the resource.
|
|
315
|
+
*
|
|
316
|
+
* Calling this method multiple times will append middleware
|
|
317
|
+
* to existing list.
|
|
318
|
+
*/
|
|
319
|
+
use(actions, middleware) {
|
|
320
|
+
if (actions === "*") {
|
|
321
|
+
this.tap((route) => route.use(middleware));
|
|
322
|
+
} else {
|
|
323
|
+
this.tap(actions, (route) => route.use(middleware));
|
|
324
|
+
}
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @alias use
|
|
329
|
+
*/
|
|
330
|
+
middleware(actions, middleware) {
|
|
331
|
+
return this.use(actions, middleware);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Prepend name to all the routes
|
|
335
|
+
*/
|
|
336
|
+
as(name, normalizeName = true) {
|
|
337
|
+
name = normalizeName ? string.snakeCase(name) : name;
|
|
338
|
+
this.routes.forEach((route) => {
|
|
339
|
+
route.as(route.getName().replace(this.#routesBaseName, name), false);
|
|
340
|
+
});
|
|
341
|
+
this.#routesBaseName = name;
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/router/group.ts
|
|
347
|
+
var RouteGroup = class _RouteGroup extends Macroable3 {
|
|
348
|
+
constructor(routes) {
|
|
349
|
+
super();
|
|
350
|
+
this.routes = routes;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Array of middleware registered on the group.
|
|
354
|
+
*/
|
|
355
|
+
#middleware = [];
|
|
356
|
+
/**
|
|
357
|
+
* Shares midldeware stack with the routes. The method is invoked recursively
|
|
358
|
+
* to only register middleware with the route class and not with the
|
|
359
|
+
* resource or the child group
|
|
360
|
+
*/
|
|
361
|
+
#shareMiddlewareStackWithRoutes(route) {
|
|
362
|
+
if (route instanceof _RouteGroup) {
|
|
363
|
+
route.routes.forEach((child) => this.#shareMiddlewareStackWithRoutes(child));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (route instanceof RouteResource) {
|
|
367
|
+
route.routes.forEach((child) => child.getMiddleware().unshift(this.#middleware));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (route instanceof BriskRoute) {
|
|
371
|
+
route.route.getMiddleware().unshift(this.#middleware);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
route.getMiddleware().unshift(this.#middleware);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Updates the route name. The method is invoked recursively to only update
|
|
378
|
+
* the name with the route class and not with the resource or the child
|
|
379
|
+
* group.
|
|
380
|
+
*/
|
|
381
|
+
#updateRouteName(route, name) {
|
|
382
|
+
if (route instanceof _RouteGroup) {
|
|
383
|
+
route.routes.forEach((child) => this.#updateRouteName(child, name));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (route instanceof RouteResource) {
|
|
387
|
+
route.routes.forEach((child) => child.as(name, true));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (route instanceof BriskRoute) {
|
|
391
|
+
route.route.as(name, true);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
route.as(name, true);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Sets prefix on the route. The method is invoked recursively to only set
|
|
398
|
+
* the prefix with the route class and not with the resource or the
|
|
399
|
+
* child group.
|
|
400
|
+
*/
|
|
401
|
+
#setRoutePrefix(route, prefix) {
|
|
402
|
+
if (route instanceof _RouteGroup) {
|
|
403
|
+
route.routes.forEach((child) => this.#setRoutePrefix(child, prefix));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (route instanceof RouteResource) {
|
|
407
|
+
route.routes.forEach((child) => child.prefix(prefix));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (route instanceof BriskRoute) {
|
|
411
|
+
route.route.prefix(prefix);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
route.prefix(prefix);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Updates domain on the route. The method is invoked recursively to only update
|
|
418
|
+
* the domain with the route class and not with the resource or the child
|
|
419
|
+
* group.
|
|
420
|
+
*/
|
|
421
|
+
#updateRouteDomain(route, domain) {
|
|
422
|
+
if (route instanceof _RouteGroup) {
|
|
423
|
+
route.routes.forEach((child) => this.#updateRouteDomain(child, domain));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (route instanceof RouteResource) {
|
|
427
|
+
route.routes.forEach((child) => child.domain(domain));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (route instanceof BriskRoute) {
|
|
431
|
+
route.route.domain(domain, false);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
route.domain(domain, false);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Updates matchers on the route. The method is invoked recursively to only update
|
|
438
|
+
* the matchers with the route class and not with the resource or the child
|
|
439
|
+
* group.
|
|
440
|
+
*/
|
|
441
|
+
#updateRouteMatchers(route, param, matcher) {
|
|
442
|
+
if (route instanceof _RouteGroup) {
|
|
443
|
+
route.routes.forEach((child) => this.#updateRouteMatchers(child, param, matcher));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (route instanceof RouteResource) {
|
|
447
|
+
route.routes.forEach((child) => child.where(param, matcher));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (route instanceof BriskRoute) {
|
|
451
|
+
route.route.where(param, matcher);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
route.where(param, matcher);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Define route param matcher
|
|
458
|
+
*
|
|
459
|
+
* ```ts
|
|
460
|
+
* Route.group(() => {
|
|
461
|
+
* }).where('id', /^[0-9]+/)
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
where(param, matcher) {
|
|
465
|
+
this.routes.forEach((route) => this.#updateRouteMatchers(route, param, matcher));
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Define prefix all the routes in the group.
|
|
470
|
+
*
|
|
471
|
+
* ```ts
|
|
472
|
+
* Route.group(() => {
|
|
473
|
+
* }).prefix('v1')
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
prefix(prefix) {
|
|
477
|
+
this.routes.forEach((route) => this.#setRoutePrefix(route, prefix));
|
|
478
|
+
return this;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Define domain for all the routes.
|
|
482
|
+
*
|
|
483
|
+
* ```ts
|
|
484
|
+
* Route.group(() => {
|
|
485
|
+
* }).domain(':name.adonisjs.com')
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
domain(domain) {
|
|
489
|
+
this.routes.forEach((route) => this.#updateRouteDomain(route, domain));
|
|
490
|
+
return this;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Prepend name to the routes name.
|
|
494
|
+
*
|
|
495
|
+
* ```ts
|
|
496
|
+
* Route.group(() => {
|
|
497
|
+
* }).as('version1')
|
|
498
|
+
* ```
|
|
499
|
+
*/
|
|
500
|
+
as(name) {
|
|
501
|
+
this.routes.forEach((route) => this.#updateRouteName(route, name));
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Prepend an array of middleware to all routes middleware.
|
|
506
|
+
*
|
|
507
|
+
* ```ts
|
|
508
|
+
* Route.group(() => {
|
|
509
|
+
* }).use(middleware.auth())
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
use(middleware) {
|
|
513
|
+
if (!this.#middleware.length) {
|
|
514
|
+
this.routes.forEach((route) => this.#shareMiddlewareStackWithRoutes(route));
|
|
515
|
+
}
|
|
516
|
+
if (Array.isArray(middleware)) {
|
|
517
|
+
for (let one of middleware) {
|
|
518
|
+
this.#middleware.push(one);
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
this.#middleware.push(middleware);
|
|
522
|
+
}
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* @alias use
|
|
527
|
+
*/
|
|
528
|
+
middleware(middleware) {
|
|
529
|
+
return this.use(middleware);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/helpers.ts
|
|
534
|
+
var proxyCache = new Cache({ max: 200 });
|
|
535
|
+
function dropSlash(input) {
|
|
536
|
+
if (input === "/") {
|
|
537
|
+
return "/";
|
|
538
|
+
}
|
|
539
|
+
return `/${input.replace(/^\//, "").replace(/\/$/, "")}`;
|
|
540
|
+
}
|
|
541
|
+
function toRoutesJSON(routes) {
|
|
542
|
+
return routes.reduce((list, route) => {
|
|
543
|
+
if (route instanceof RouteGroup) {
|
|
544
|
+
list = list.concat(toRoutesJSON(route.routes));
|
|
545
|
+
return list;
|
|
546
|
+
}
|
|
547
|
+
if (route instanceof RouteResource) {
|
|
548
|
+
list = list.concat(toRoutesJSON(route.routes));
|
|
549
|
+
return list;
|
|
550
|
+
}
|
|
551
|
+
if (route instanceof BriskRoute) {
|
|
552
|
+
if (route.route && !route.route.isDeleted()) {
|
|
553
|
+
list.push(route.route.toJSON());
|
|
554
|
+
}
|
|
555
|
+
return list;
|
|
556
|
+
}
|
|
557
|
+
if (!route.isDeleted()) {
|
|
558
|
+
list.push(route.toJSON());
|
|
559
|
+
}
|
|
560
|
+
return list;
|
|
561
|
+
}, []);
|
|
562
|
+
}
|
|
563
|
+
function trustProxy(remoteAddress, proxyFn) {
|
|
564
|
+
if (proxyCache.has(remoteAddress)) {
|
|
565
|
+
return proxyCache.get(remoteAddress);
|
|
566
|
+
}
|
|
567
|
+
const result = proxyFn(remoteAddress, 0);
|
|
568
|
+
proxyCache.set(remoteAddress, result);
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
function parseRange(range, value) {
|
|
572
|
+
const parts = range.split("..");
|
|
573
|
+
const min = Number(parts[0]);
|
|
574
|
+
const max = Number(parts[1]);
|
|
575
|
+
if (parts.length === 1 && !Number.isNaN(min)) {
|
|
576
|
+
return {
|
|
577
|
+
[min]: value
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (Number.isNaN(min) || Number.isNaN(max)) {
|
|
581
|
+
return {};
|
|
582
|
+
}
|
|
583
|
+
if (min === max) {
|
|
584
|
+
return {
|
|
585
|
+
[min]: value
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
if (max < min) {
|
|
589
|
+
throw new InvalidArgumentsException(`Invalid range "${range}"`);
|
|
590
|
+
}
|
|
591
|
+
return [...Array(max - min + 1).keys()].reduce(
|
|
592
|
+
(result, step) => {
|
|
593
|
+
result[min + step] = value;
|
|
594
|
+
return result;
|
|
595
|
+
},
|
|
596
|
+
{}
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/debug.ts
|
|
601
|
+
import { debuglog } from "node:util";
|
|
602
|
+
var debug_default = debuglog("adonisjs:http");
|
|
603
|
+
|
|
604
|
+
// src/router/route.ts
|
|
605
|
+
var Route = class extends Macroable4 {
|
|
606
|
+
/**
|
|
607
|
+
* Route pattern
|
|
608
|
+
*/
|
|
609
|
+
#pattern;
|
|
610
|
+
/**
|
|
611
|
+
* HTTP Methods for the route
|
|
612
|
+
*/
|
|
613
|
+
#methods;
|
|
614
|
+
/**
|
|
615
|
+
* A unique name for the route
|
|
616
|
+
*/
|
|
617
|
+
#name;
|
|
618
|
+
/**
|
|
619
|
+
* A boolean to prevent route from getting registered within
|
|
620
|
+
* the store.
|
|
621
|
+
*
|
|
622
|
+
* This flag must be set before "Router.commit" method
|
|
623
|
+
*/
|
|
624
|
+
#isDeleted = false;
|
|
625
|
+
/**
|
|
626
|
+
* Route handler
|
|
627
|
+
*/
|
|
628
|
+
#handler;
|
|
629
|
+
/**
|
|
630
|
+
* Matchers inherited from the router
|
|
631
|
+
*/
|
|
632
|
+
#globalMatchers;
|
|
633
|
+
/**
|
|
634
|
+
* Reference to the AdonisJS application
|
|
635
|
+
*/
|
|
636
|
+
#app;
|
|
637
|
+
/**
|
|
638
|
+
* Middleware registered on the router
|
|
639
|
+
*/
|
|
640
|
+
#routerMiddleware;
|
|
641
|
+
/**
|
|
642
|
+
* By default the route is part of the `root` domain. Root domain is used
|
|
643
|
+
* when no domain is defined
|
|
644
|
+
*/
|
|
645
|
+
#routeDomain = "root";
|
|
646
|
+
/**
|
|
647
|
+
* An object of matchers to be forwarded to the store. The matchers
|
|
648
|
+
* list is populated by calling `where` method
|
|
649
|
+
*/
|
|
650
|
+
#matchers = {};
|
|
651
|
+
/**
|
|
652
|
+
* Custom prefixes defined on the route or the route parent
|
|
653
|
+
* groups
|
|
654
|
+
*/
|
|
655
|
+
#prefixes = [];
|
|
656
|
+
/**
|
|
657
|
+
* Middleware defined directly on the route or the route parent
|
|
658
|
+
* routes. We mantain an array for each layer of the stack
|
|
659
|
+
*/
|
|
660
|
+
#middleware = [];
|
|
661
|
+
constructor(app, routerMiddleware, options) {
|
|
662
|
+
super();
|
|
663
|
+
this.#app = app;
|
|
664
|
+
this.#routerMiddleware = routerMiddleware;
|
|
665
|
+
this.#pattern = options.pattern;
|
|
666
|
+
this.#methods = options.methods;
|
|
667
|
+
this.#handler = this.#resolveRouteHandle(options.handler);
|
|
668
|
+
this.#globalMatchers = options.globalMatchers;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolves the route handler string expression to a
|
|
672
|
+
* handler method object
|
|
673
|
+
*/
|
|
674
|
+
#resolveRouteHandle(handler) {
|
|
675
|
+
if (typeof handler === "string") {
|
|
676
|
+
return {
|
|
677
|
+
reference: handler,
|
|
678
|
+
...moduleExpression(handler, this.#app.appRoot).toHandleMethod()
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (Array.isArray(handler)) {
|
|
682
|
+
if (is.class_(handler[0])) {
|
|
683
|
+
return {
|
|
684
|
+
reference: handler,
|
|
685
|
+
...moduleCaller(handler[0], handler[1] || "handle").toHandleMethod()
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
reference: handler,
|
|
690
|
+
...moduleImporter(handler[0], handler[1] || "handle").toHandleMethod()
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return handler;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Returns an object of param matchers by merging global and local
|
|
697
|
+
* matchers. The local copy is given preference over the global
|
|
698
|
+
* one's
|
|
699
|
+
*/
|
|
700
|
+
#getMatchers() {
|
|
701
|
+
return { ...this.#globalMatchers, ...this.#matchers };
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Returns a normalized pattern string by prefixing the `prefix` (if defined).
|
|
705
|
+
*/
|
|
706
|
+
#computePattern() {
|
|
707
|
+
const pattern = dropSlash(this.#pattern);
|
|
708
|
+
const prefix = this.#prefixes.slice().reverse().map((one) => dropSlash(one)).join("");
|
|
709
|
+
return prefix ? `${prefix}${pattern === "/" ? "" : pattern}` : pattern;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Define matcher for a given param. If a matcher exists, then we do not
|
|
713
|
+
* override that, since the routes inside a group will set matchers
|
|
714
|
+
* before the group, so they should have priority over the group
|
|
715
|
+
* matchers.
|
|
716
|
+
*
|
|
717
|
+
* ```ts
|
|
718
|
+
* Route.group(() => {
|
|
719
|
+
* Route.get('/:id', 'handler').where('id', /^[0-9]$/)
|
|
720
|
+
* }).where('id', /[^a-z$]/)
|
|
721
|
+
* ```
|
|
722
|
+
*
|
|
723
|
+
* The `/^[0-9]$/` will win over the matcher defined by the group
|
|
724
|
+
*/
|
|
725
|
+
where(param, matcher) {
|
|
726
|
+
if (this.#matchers[param]) {
|
|
727
|
+
return this;
|
|
728
|
+
}
|
|
729
|
+
if (typeof matcher === "string") {
|
|
730
|
+
this.#matchers[param] = { match: new RegExp(matcher) };
|
|
731
|
+
} else if (is.regExp(matcher)) {
|
|
732
|
+
this.#matchers[param] = { match: matcher };
|
|
733
|
+
} else {
|
|
734
|
+
this.#matchers[param] = matcher;
|
|
735
|
+
}
|
|
736
|
+
return this;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Define prefix for the route. Calling this method multiple times
|
|
740
|
+
* applies multiple prefixes in the reverse order.
|
|
741
|
+
*/
|
|
742
|
+
prefix(prefix) {
|
|
743
|
+
this.#prefixes.push(prefix);
|
|
744
|
+
return this;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Define a custom domain for the route. We do not overwrite the domain
|
|
748
|
+
* unless `overwrite` flag is set to true.
|
|
749
|
+
*/
|
|
750
|
+
domain(domain, overwrite = false) {
|
|
751
|
+
if (this.#routeDomain === "root" || overwrite) {
|
|
752
|
+
this.#routeDomain = domain;
|
|
753
|
+
}
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Define one or more middleware to be executed before the route
|
|
758
|
+
* handler.
|
|
759
|
+
*
|
|
760
|
+
* Named middleware can be referenced using the name registered with
|
|
761
|
+
* the router middleware store.
|
|
762
|
+
*/
|
|
763
|
+
use(middleware) {
|
|
764
|
+
this.#middleware.push(Array.isArray(middleware) ? middleware : [middleware]);
|
|
765
|
+
return this;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* @alias use
|
|
769
|
+
*/
|
|
770
|
+
middleware(middleware) {
|
|
771
|
+
return this.use(middleware);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Give a unique name to the route. Assinging a new unique removes the
|
|
775
|
+
* existing name of the route.
|
|
776
|
+
*
|
|
777
|
+
* Setting prepends to true prefixes the name to the existing name.
|
|
778
|
+
*/
|
|
779
|
+
as(name, prepend = false) {
|
|
780
|
+
if (prepend) {
|
|
781
|
+
if (!this.#name) {
|
|
782
|
+
throw new RuntimeException2(
|
|
783
|
+
`Routes inside a group must have names before calling "router.group.as"`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
this.#name = `${name}.${this.#name}`;
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
this.#name = name;
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Check if the route was marked to be deleted
|
|
794
|
+
*/
|
|
795
|
+
isDeleted() {
|
|
796
|
+
return this.#isDeleted;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Mark route as deleted. Deleted routes are not registered
|
|
800
|
+
* with the route store
|
|
801
|
+
*/
|
|
802
|
+
markAsDeleted() {
|
|
803
|
+
this.#isDeleted = true;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get the route name
|
|
807
|
+
*/
|
|
808
|
+
getName() {
|
|
809
|
+
return this.#name;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get the route pattern
|
|
813
|
+
*/
|
|
814
|
+
getPattern() {
|
|
815
|
+
return this.#pattern;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Set the route pattern
|
|
819
|
+
*/
|
|
820
|
+
setPattern(pattern) {
|
|
821
|
+
this.#pattern = pattern;
|
|
822
|
+
return this;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Returns the stack of middleware registered on the route.
|
|
826
|
+
* The value is shared by reference.
|
|
827
|
+
*/
|
|
828
|
+
getMiddleware() {
|
|
829
|
+
return this.#middleware;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Returns the middleware instance for persistence inside the
|
|
833
|
+
* store
|
|
834
|
+
*/
|
|
835
|
+
#getMiddlewareForStore() {
|
|
836
|
+
const middleware = new Middleware();
|
|
837
|
+
this.#routerMiddleware.forEach((one) => {
|
|
838
|
+
debug_default("adding global middleware to route %s, %O", this.#pattern, one);
|
|
839
|
+
middleware.add(one);
|
|
840
|
+
});
|
|
841
|
+
this.#middleware.flat().forEach((one) => {
|
|
842
|
+
debug_default("adding named middleware to route %s, %O", this.#pattern, one);
|
|
843
|
+
middleware.add(one);
|
|
844
|
+
});
|
|
845
|
+
return middleware;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Returns JSON representation of the route
|
|
849
|
+
*/
|
|
850
|
+
toJSON() {
|
|
851
|
+
return {
|
|
852
|
+
domain: this.#routeDomain,
|
|
853
|
+
pattern: this.#computePattern(),
|
|
854
|
+
matchers: this.#getMatchers(),
|
|
855
|
+
meta: {},
|
|
856
|
+
name: this.#name,
|
|
857
|
+
handler: this.#handler,
|
|
858
|
+
methods: this.#methods,
|
|
859
|
+
middleware: this.#getMiddlewareForStore(),
|
|
860
|
+
execute
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
// src/cookies/drivers/plain.ts
|
|
866
|
+
import { base64, MessageBuilder } from "@poppinss/utils";
|
|
867
|
+
function pack(value) {
|
|
868
|
+
if (value === void 0 || value === null) {
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
return base64.urlEncode(new MessageBuilder().build(value));
|
|
872
|
+
}
|
|
873
|
+
function canUnpack(encodedValue) {
|
|
874
|
+
return typeof encodedValue === "string";
|
|
875
|
+
}
|
|
876
|
+
function unpack(encodedValue) {
|
|
877
|
+
return new MessageBuilder().verify(base64.urlDecode(encodedValue, "utf-8", false));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/cookies/drivers/signed.ts
|
|
881
|
+
function pack2(key, value, encryption) {
|
|
882
|
+
if (value === void 0 || value === null) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
return `s:${encryption.verifier.sign(value, void 0, key)}`;
|
|
886
|
+
}
|
|
887
|
+
function canUnpack2(signedValue) {
|
|
888
|
+
return typeof signedValue === "string" && signedValue.substring(0, 2) === "s:";
|
|
889
|
+
}
|
|
890
|
+
function unpack2(key, signedValue, encryption) {
|
|
891
|
+
const value = signedValue.slice(2);
|
|
892
|
+
if (!value) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
return encryption.verifier.unsign(value, key);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/cookies/drivers/encrypted.ts
|
|
899
|
+
function pack3(key, value, encryption) {
|
|
900
|
+
if (value === void 0 || value === null) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
return `e:${encryption.encrypt(value, void 0, key)}`;
|
|
904
|
+
}
|
|
905
|
+
function canUnpack3(encryptedValue) {
|
|
906
|
+
return typeof encryptedValue === "string" && encryptedValue.substring(0, 2) === "e:";
|
|
907
|
+
}
|
|
908
|
+
function unpack3(key, encryptedValue, encryption) {
|
|
909
|
+
const value = encryptedValue.slice(2);
|
|
910
|
+
if (!value) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
return encryption.decrypt(value, key);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/cookies/client.ts
|
|
917
|
+
var CookieClient = class {
|
|
918
|
+
#encryption;
|
|
919
|
+
constructor(encryption) {
|
|
920
|
+
this.#encryption = encryption;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Encrypt a key value pair to be sent in the cookie header
|
|
924
|
+
*/
|
|
925
|
+
encrypt(key, value) {
|
|
926
|
+
return pack3(key, value, this.#encryption);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Sign a key value pair to be sent in the cookie header
|
|
930
|
+
*/
|
|
931
|
+
sign(key, value) {
|
|
932
|
+
return pack2(key, value, this.#encryption);
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Encode a key value pair to be sent in the cookie header
|
|
936
|
+
*/
|
|
937
|
+
encode(_, value) {
|
|
938
|
+
return pack(value);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Unsign a signed cookie value
|
|
942
|
+
*/
|
|
943
|
+
unsign(key, value) {
|
|
944
|
+
return canUnpack2(value) ? unpack2(key, value, this.#encryption) : null;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Decrypt an encrypted cookie value
|
|
948
|
+
*/
|
|
949
|
+
decrypt(key, value) {
|
|
950
|
+
return canUnpack3(value) ? unpack3(key, value, this.#encryption) : null;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Decode an encoded cookie value
|
|
954
|
+
*/
|
|
955
|
+
decode(_, value) {
|
|
956
|
+
return canUnpack(value) ? unpack(value) : null;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Parse response cookie
|
|
960
|
+
*/
|
|
961
|
+
parse(key, value) {
|
|
962
|
+
if (canUnpack2(value)) {
|
|
963
|
+
return unpack2(key, value, this.#encryption);
|
|
964
|
+
}
|
|
965
|
+
if (canUnpack3(value)) {
|
|
966
|
+
return unpack3(key, value, this.#encryption);
|
|
967
|
+
}
|
|
968
|
+
if (canUnpack(value)) {
|
|
969
|
+
return unpack(value);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// src/request.ts
|
|
975
|
+
import fresh from "fresh";
|
|
976
|
+
import typeIs from "type-is";
|
|
977
|
+
import accepts from "accepts";
|
|
978
|
+
import { isIP } from "node:net";
|
|
979
|
+
import is2 from "@sindresorhus/is";
|
|
980
|
+
import proxyaddr from "proxy-addr";
|
|
981
|
+
import { safeEqual } from "@poppinss/utils";
|
|
982
|
+
import Macroable5 from "@poppinss/macroable";
|
|
983
|
+
import lodash from "@poppinss/utils/lodash";
|
|
984
|
+
import { createId } from "@paralleldrive/cuid2";
|
|
985
|
+
import { parse } from "node:url";
|
|
986
|
+
|
|
987
|
+
// src/cookies/parser.ts
|
|
988
|
+
import cookie from "cookie";
|
|
989
|
+
var CookieParser = class {
|
|
990
|
+
#client;
|
|
991
|
+
/**
|
|
992
|
+
* A copy of cached cookies, they are cached during a request after
|
|
993
|
+
* initial decoding, unsigning or decrypting.
|
|
994
|
+
*/
|
|
995
|
+
#cachedCookies = {
|
|
996
|
+
signedCookies: {},
|
|
997
|
+
plainCookies: {},
|
|
998
|
+
encryptedCookies: {}
|
|
999
|
+
};
|
|
1000
|
+
/**
|
|
1001
|
+
* An object of key-value pair collected by parsing
|
|
1002
|
+
* the request cookie header.
|
|
1003
|
+
*/
|
|
1004
|
+
#cookies;
|
|
1005
|
+
constructor(cookieHeader, encryption) {
|
|
1006
|
+
this.#client = new CookieClient(encryption);
|
|
1007
|
+
this.#cookies = this.#parse(cookieHeader);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Parses the request `cookie` header
|
|
1011
|
+
*/
|
|
1012
|
+
#parse(cookieHeader) {
|
|
1013
|
+
if (!cookieHeader) {
|
|
1014
|
+
return {};
|
|
1015
|
+
}
|
|
1016
|
+
return cookie.parse(cookieHeader);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Attempts to decode a cookie by the name. When calling this method,
|
|
1020
|
+
* you are assuming that the cookie was just encoded in the first
|
|
1021
|
+
* place and not signed or encrypted.
|
|
1022
|
+
*/
|
|
1023
|
+
decode(key, encoded = true) {
|
|
1024
|
+
const value = this.#cookies[key];
|
|
1025
|
+
if (value === null || value === void 0) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
const cache = this.#cachedCookies.plainCookies;
|
|
1029
|
+
if (cache[key] !== void 0) {
|
|
1030
|
+
return cache[key];
|
|
1031
|
+
}
|
|
1032
|
+
const parsed = encoded ? this.#client.decode(key, value) : value;
|
|
1033
|
+
if (parsed !== null) {
|
|
1034
|
+
cache[key] = parsed;
|
|
1035
|
+
}
|
|
1036
|
+
return parsed;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Attempts to unsign a cookie by the name. When calling this method,
|
|
1040
|
+
* you are assuming that the cookie was signed in the first place.
|
|
1041
|
+
*/
|
|
1042
|
+
unsign(key) {
|
|
1043
|
+
const value = this.#cookies[key];
|
|
1044
|
+
if (value === null || value === void 0) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
const cache = this.#cachedCookies.signedCookies;
|
|
1048
|
+
if (cache[key] !== void 0) {
|
|
1049
|
+
return cache[key];
|
|
1050
|
+
}
|
|
1051
|
+
const parsed = this.#client.unsign(key, value);
|
|
1052
|
+
if (parsed !== null) {
|
|
1053
|
+
cache[key] = parsed;
|
|
1054
|
+
}
|
|
1055
|
+
return parsed;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Attempts to decrypt a cookie by the name. When calling this method,
|
|
1059
|
+
* you are assuming that the cookie was encrypted in the first place.
|
|
1060
|
+
*/
|
|
1061
|
+
decrypt(key) {
|
|
1062
|
+
const value = this.#cookies[key];
|
|
1063
|
+
if (value === null || value === void 0) {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
const cache = this.#cachedCookies.encryptedCookies;
|
|
1067
|
+
if (cache[key] !== void 0) {
|
|
1068
|
+
return cache[key];
|
|
1069
|
+
}
|
|
1070
|
+
const parsed = this.#client.decrypt(key, value);
|
|
1071
|
+
if (parsed !== null) {
|
|
1072
|
+
cache[key] = parsed;
|
|
1073
|
+
}
|
|
1074
|
+
return parsed;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Returns an object of cookies key-value pair. Do note, the
|
|
1078
|
+
* cookies are not decoded, unsigned or decrypted inside this
|
|
1079
|
+
* list.
|
|
1080
|
+
*/
|
|
1081
|
+
list() {
|
|
1082
|
+
return this.#cookies;
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// src/request.ts
|
|
1087
|
+
var Request = class extends Macroable5 {
|
|
1088
|
+
constructor(request, response, encryption, config, qsParser) {
|
|
1089
|
+
super();
|
|
1090
|
+
this.request = request;
|
|
1091
|
+
this.response = response;
|
|
1092
|
+
this.#qsParser = qsParser;
|
|
1093
|
+
this.#config = config;
|
|
1094
|
+
this.#encryption = encryption;
|
|
1095
|
+
this.parsedUrl = parse(this.request.url, false);
|
|
1096
|
+
this.#parseQueryString();
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Query string parser
|
|
1100
|
+
*/
|
|
1101
|
+
#qsParser;
|
|
1102
|
+
/**
|
|
1103
|
+
* Encryption module to verify signed URLs and unsign/decrypt
|
|
1104
|
+
* cookies
|
|
1105
|
+
*/
|
|
1106
|
+
#encryption;
|
|
1107
|
+
/**
|
|
1108
|
+
* Request config
|
|
1109
|
+
*/
|
|
1110
|
+
#config;
|
|
1111
|
+
/**
|
|
1112
|
+
* Request body set using `setBody` method
|
|
1113
|
+
*/
|
|
1114
|
+
#requestBody = {};
|
|
1115
|
+
/**
|
|
1116
|
+
* A merged copy of `request body` and `querystring`
|
|
1117
|
+
*/
|
|
1118
|
+
#requestData = {};
|
|
1119
|
+
/**
|
|
1120
|
+
* Original merged copy of `request body` and `querystring`.
|
|
1121
|
+
* Further mutation to this object are not allowed
|
|
1122
|
+
*/
|
|
1123
|
+
#originalRequestData = {};
|
|
1124
|
+
/**
|
|
1125
|
+
* Parsed query string
|
|
1126
|
+
*/
|
|
1127
|
+
#requestQs = {};
|
|
1128
|
+
/**
|
|
1129
|
+
* Raw request body as text
|
|
1130
|
+
*/
|
|
1131
|
+
#rawRequestBody;
|
|
1132
|
+
/**
|
|
1133
|
+
* Cached copy of `accepts` fn to do content
|
|
1134
|
+
* negotiation.
|
|
1135
|
+
*/
|
|
1136
|
+
#lazyAccepts;
|
|
1137
|
+
/**
|
|
1138
|
+
* Copy of lazily parsed signed and plain cookies.
|
|
1139
|
+
*/
|
|
1140
|
+
#cookieParser;
|
|
1141
|
+
/**
|
|
1142
|
+
* Parses copy of the URL with query string as a string and not
|
|
1143
|
+
* object. This is done to build URL's with query string without
|
|
1144
|
+
* stringifying the object
|
|
1145
|
+
*/
|
|
1146
|
+
parsedUrl;
|
|
1147
|
+
/**
|
|
1148
|
+
* The ctx will be set by the context itself. It creates a circular
|
|
1149
|
+
* reference
|
|
1150
|
+
*/
|
|
1151
|
+
ctx;
|
|
1152
|
+
/**
|
|
1153
|
+
* Parses the query string
|
|
1154
|
+
*/
|
|
1155
|
+
#parseQueryString() {
|
|
1156
|
+
if (this.parsedUrl.query) {
|
|
1157
|
+
this.updateQs(this.#qsParser.parse(this.parsedUrl.query));
|
|
1158
|
+
this.#originalRequestData = { ...this.#requestData };
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Initiates the cookie parser lazily
|
|
1163
|
+
*/
|
|
1164
|
+
#initiateCookieParser() {
|
|
1165
|
+
if (!this.#cookieParser) {
|
|
1166
|
+
this.#cookieParser = new CookieParser(this.header("cookie"), this.#encryption);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Lazily initiates the `accepts` module to make sure to parse
|
|
1171
|
+
* the request headers only when one of the content-negotiation
|
|
1172
|
+
* methods are used.
|
|
1173
|
+
*/
|
|
1174
|
+
#initiateAccepts() {
|
|
1175
|
+
this.#lazyAccepts = this.#lazyAccepts || accepts(this.request);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Returns the request id from the `x-request-id` header. The
|
|
1179
|
+
* header is untouched, if it already exists.
|
|
1180
|
+
*/
|
|
1181
|
+
id() {
|
|
1182
|
+
let requestId = this.header("x-request-id");
|
|
1183
|
+
if (!requestId && this.#config.generateRequestId) {
|
|
1184
|
+
requestId = createId();
|
|
1185
|
+
this.request.headers["x-request-id"] = requestId;
|
|
1186
|
+
}
|
|
1187
|
+
return requestId;
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Set initial request body. A copy of the input will be maintained as the original
|
|
1191
|
+
* request body. Since the request body and query string is subject to mutations, we
|
|
1192
|
+
* keep one original reference to flash old data (whenever required).
|
|
1193
|
+
*
|
|
1194
|
+
* This method is supposed to be invoked by the body parser and must be called only
|
|
1195
|
+
* once. For further mutations make use of `updateBody` method.
|
|
1196
|
+
*/
|
|
1197
|
+
setInitialBody(body) {
|
|
1198
|
+
if (this.#originalRequestData && Object.isFrozen(this.#originalRequestData)) {
|
|
1199
|
+
throw new Error('Cannot re-set initial body. Use "request.updateBody" instead');
|
|
1200
|
+
}
|
|
1201
|
+
this.updateBody(body);
|
|
1202
|
+
this.#originalRequestData = Object.freeze(lodash.cloneDeep(this.#requestData));
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Update the request body with new data object. The `all` property
|
|
1206
|
+
* will be re-computed by merging the query string and request
|
|
1207
|
+
* body.
|
|
1208
|
+
*/
|
|
1209
|
+
updateBody(body) {
|
|
1210
|
+
this.#requestBody = body;
|
|
1211
|
+
this.#requestData = { ...this.#requestBody, ...this.#requestQs };
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Update the request raw body. Bodyparser sets this when unable to parse
|
|
1215
|
+
* the request body or when request is multipart/form-data.
|
|
1216
|
+
*/
|
|
1217
|
+
updateRawBody(rawBody) {
|
|
1218
|
+
this.#rawRequestBody = rawBody;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Update the query string with the new data object. The `all` property
|
|
1222
|
+
* will be re-computed by merging the query and the request body.
|
|
1223
|
+
*/
|
|
1224
|
+
updateQs(data) {
|
|
1225
|
+
this.#requestQs = data;
|
|
1226
|
+
this.#requestData = { ...this.#requestBody, ...this.#requestQs };
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Returns route params
|
|
1230
|
+
*/
|
|
1231
|
+
params() {
|
|
1232
|
+
return this.ctx?.params || {};
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Returns the query string object by reference
|
|
1236
|
+
*/
|
|
1237
|
+
qs() {
|
|
1238
|
+
return this.#requestQs;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Returns reference to the request body
|
|
1242
|
+
*/
|
|
1243
|
+
body() {
|
|
1244
|
+
return this.#requestBody;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Returns reference to the merged copy of request body
|
|
1248
|
+
* and query string
|
|
1249
|
+
*/
|
|
1250
|
+
all() {
|
|
1251
|
+
return this.#requestData;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Returns reference to the merged copy of original request
|
|
1255
|
+
* query string and body
|
|
1256
|
+
*/
|
|
1257
|
+
original() {
|
|
1258
|
+
return this.#originalRequestData;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Returns the request raw body (if exists), or returns `null`.
|
|
1262
|
+
*
|
|
1263
|
+
* Ideally you must be dealing with the parsed body accessed using [[input]], [[all]] or
|
|
1264
|
+
* [[post]] methods. The `raw` body is always a string.
|
|
1265
|
+
*/
|
|
1266
|
+
raw() {
|
|
1267
|
+
return this.#rawRequestBody || null;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Returns value for a given key from the request body or query string.
|
|
1271
|
+
* The `defaultValue` is used when original value is `undefined`.
|
|
1272
|
+
*
|
|
1273
|
+
* @example
|
|
1274
|
+
* ```js
|
|
1275
|
+
* request.input('username')
|
|
1276
|
+
*
|
|
1277
|
+
* // with default value
|
|
1278
|
+
* request.input('username', 'virk')
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
input(key, defaultValue) {
|
|
1282
|
+
return lodash.get(this.#requestData, key, defaultValue);
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Returns value for a given key from route params
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```js
|
|
1289
|
+
* request.param('id')
|
|
1290
|
+
*
|
|
1291
|
+
* // with default value
|
|
1292
|
+
* request.param('id', 1)
|
|
1293
|
+
* ```
|
|
1294
|
+
*/
|
|
1295
|
+
param(key, defaultValue) {
|
|
1296
|
+
return lodash.get(this.params(), key, defaultValue);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Get everything from the request body except the given keys.
|
|
1300
|
+
*
|
|
1301
|
+
* @example
|
|
1302
|
+
* ```js
|
|
1303
|
+
* request.except(['_csrf'])
|
|
1304
|
+
* ```
|
|
1305
|
+
*/
|
|
1306
|
+
except(keys) {
|
|
1307
|
+
return lodash.omit(this.#requestData, keys);
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Get value for specified keys.
|
|
1311
|
+
*
|
|
1312
|
+
* @example
|
|
1313
|
+
* ```js
|
|
1314
|
+
* request.only(['username', 'age'])
|
|
1315
|
+
* ```
|
|
1316
|
+
*/
|
|
1317
|
+
only(keys) {
|
|
1318
|
+
return lodash.pick(this.#requestData, keys);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Returns the HTTP request method. This is the original
|
|
1322
|
+
* request method. For spoofed request method, make
|
|
1323
|
+
* use of [[method]].
|
|
1324
|
+
*
|
|
1325
|
+
* @example
|
|
1326
|
+
* ```js
|
|
1327
|
+
* request.intended()
|
|
1328
|
+
* ```
|
|
1329
|
+
*/
|
|
1330
|
+
intended() {
|
|
1331
|
+
return this.request.method;
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Returns the request HTTP method by taking method spoofing into account.
|
|
1335
|
+
*
|
|
1336
|
+
* Method spoofing works when all of the following are true.
|
|
1337
|
+
*
|
|
1338
|
+
* 1. `app.http.allowMethodSpoofing` config value is true.
|
|
1339
|
+
* 2. request query string has `_method`.
|
|
1340
|
+
* 3. The [[intended]] request method is `POST`.
|
|
1341
|
+
*
|
|
1342
|
+
* @example
|
|
1343
|
+
* ```js
|
|
1344
|
+
* request.method()
|
|
1345
|
+
* ```
|
|
1346
|
+
*/
|
|
1347
|
+
method() {
|
|
1348
|
+
if (this.#config.allowMethodSpoofing && this.intended() === "POST") {
|
|
1349
|
+
return this.input("_method", this.intended()).toUpperCase();
|
|
1350
|
+
}
|
|
1351
|
+
return this.intended();
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Returns a copy of headers as an object
|
|
1355
|
+
*/
|
|
1356
|
+
headers() {
|
|
1357
|
+
return this.request.headers;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Returns value for a given header key. The default value is
|
|
1361
|
+
* used when original value is `undefined`.
|
|
1362
|
+
*/
|
|
1363
|
+
header(key, defaultValue) {
|
|
1364
|
+
key = key.toLowerCase();
|
|
1365
|
+
const headers = this.headers();
|
|
1366
|
+
switch (key) {
|
|
1367
|
+
case "referer":
|
|
1368
|
+
case "referrer":
|
|
1369
|
+
return headers.referrer || headers.referer || defaultValue;
|
|
1370
|
+
default:
|
|
1371
|
+
return headers[key] || defaultValue;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Returns the ip address of the user. This method is optimize to fetch
|
|
1376
|
+
* ip address even when running your AdonisJs app behind a proxy.
|
|
1377
|
+
*
|
|
1378
|
+
* You can also define your own custom function to compute the ip address by
|
|
1379
|
+
* defining `app.http.getIp` as a function inside the config file.
|
|
1380
|
+
*
|
|
1381
|
+
* ```js
|
|
1382
|
+
* {
|
|
1383
|
+
* http: {
|
|
1384
|
+
* getIp (request) {
|
|
1385
|
+
* // I am using nginx as a proxy server and want to trust 'x-real-ip'
|
|
1386
|
+
* return request.header('x-real-ip')
|
|
1387
|
+
* }
|
|
1388
|
+
* }
|
|
1389
|
+
* }
|
|
1390
|
+
* ```
|
|
1391
|
+
*
|
|
1392
|
+
* You can control the behavior of trusting the proxy values by defining it
|
|
1393
|
+
* inside the `config/app.js` file.
|
|
1394
|
+
*
|
|
1395
|
+
* ```js
|
|
1396
|
+
* {
|
|
1397
|
+
* http: {
|
|
1398
|
+
* trustProxy: '127.0.0.1'
|
|
1399
|
+
* }
|
|
1400
|
+
* }
|
|
1401
|
+
* ```
|
|
1402
|
+
*
|
|
1403
|
+
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
1404
|
+
*/
|
|
1405
|
+
ip() {
|
|
1406
|
+
const ipFn = this.#config.getIp;
|
|
1407
|
+
if (typeof ipFn === "function") {
|
|
1408
|
+
return ipFn(this);
|
|
1409
|
+
}
|
|
1410
|
+
return proxyaddr(this.request, this.#config.trustProxy);
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Returns an array of ip addresses from most to least trusted one.
|
|
1414
|
+
* This method is optimize to fetch ip address even when running
|
|
1415
|
+
* your AdonisJs app behind a proxy.
|
|
1416
|
+
*
|
|
1417
|
+
* You can control the behavior of trusting the proxy values by defining it
|
|
1418
|
+
* inside the `config/app.js` file.
|
|
1419
|
+
*
|
|
1420
|
+
* ```js
|
|
1421
|
+
* {
|
|
1422
|
+
* http: {
|
|
1423
|
+
* trustProxy: '127.0.0.1'
|
|
1424
|
+
* }
|
|
1425
|
+
* }
|
|
1426
|
+
* ```
|
|
1427
|
+
*
|
|
1428
|
+
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
1429
|
+
*/
|
|
1430
|
+
ips() {
|
|
1431
|
+
return proxyaddr.all(this.request, this.#config.trustProxy);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Returns the request protocol by checking for the URL protocol or
|
|
1435
|
+
* `X-Forwarded-Proto` header.
|
|
1436
|
+
*
|
|
1437
|
+
* If the `trust` is evaluated to `false`, then URL protocol is returned,
|
|
1438
|
+
* otherwise `X-Forwarded-Proto` header is used (if exists).
|
|
1439
|
+
*
|
|
1440
|
+
* You can control the behavior of trusting the proxy values by defining it
|
|
1441
|
+
* inside the `config/app.js` file.
|
|
1442
|
+
*
|
|
1443
|
+
* ```js
|
|
1444
|
+
* {
|
|
1445
|
+
* http: {
|
|
1446
|
+
* trustProxy: '127.0.0.1'
|
|
1447
|
+
* }
|
|
1448
|
+
* }
|
|
1449
|
+
* ```
|
|
1450
|
+
*
|
|
1451
|
+
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
1452
|
+
*/
|
|
1453
|
+
protocol() {
|
|
1454
|
+
if ("encrypted" in this.request.socket) {
|
|
1455
|
+
return "https";
|
|
1456
|
+
}
|
|
1457
|
+
if (!trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
|
|
1458
|
+
return this.parsedUrl.protocol || "http";
|
|
1459
|
+
}
|
|
1460
|
+
const forwardedProtocol = this.header("X-Forwarded-Proto");
|
|
1461
|
+
return forwardedProtocol ? forwardedProtocol.split(/\s*,\s*/)[0] : "http";
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Returns a boolean telling if request is served over `https`
|
|
1465
|
+
* or not. Check [[protocol]] method to know how protocol is
|
|
1466
|
+
* fetched.
|
|
1467
|
+
*/
|
|
1468
|
+
secure() {
|
|
1469
|
+
return this.protocol() === "https";
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Returns the request host. If proxy headers are trusted, then
|
|
1473
|
+
* `X-Forwarded-Host` is given priority over the `Host` header.
|
|
1474
|
+
*
|
|
1475
|
+
* You can control the behavior of trusting the proxy values by defining it
|
|
1476
|
+
* inside the `config/app.js` file.
|
|
1477
|
+
*
|
|
1478
|
+
* ```js
|
|
1479
|
+
* {
|
|
1480
|
+
* http: {
|
|
1481
|
+
* trustProxy: '127.0.0.1'
|
|
1482
|
+
* }
|
|
1483
|
+
* }
|
|
1484
|
+
* ```
|
|
1485
|
+
*
|
|
1486
|
+
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
1487
|
+
*/
|
|
1488
|
+
host() {
|
|
1489
|
+
let host = this.header("host");
|
|
1490
|
+
if (trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
|
|
1491
|
+
host = this.header("X-Forwarded-Host") || host;
|
|
1492
|
+
}
|
|
1493
|
+
if (!host) {
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
return host;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Returns the request hostname. If proxy headers are trusted, then
|
|
1500
|
+
* `X-Forwarded-Host` is given priority over the `Host` header.
|
|
1501
|
+
*
|
|
1502
|
+
* You can control the behavior of trusting the proxy values by defining it
|
|
1503
|
+
* inside the `config/app.js` file.
|
|
1504
|
+
*
|
|
1505
|
+
* ```js
|
|
1506
|
+
* {
|
|
1507
|
+
* http: {
|
|
1508
|
+
* trustProxy: '127.0.0.1'
|
|
1509
|
+
* }
|
|
1510
|
+
* }
|
|
1511
|
+
* ```
|
|
1512
|
+
*
|
|
1513
|
+
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
1514
|
+
*/
|
|
1515
|
+
hostname() {
|
|
1516
|
+
const host = this.host();
|
|
1517
|
+
if (!host) {
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
const offset = host[0] === "[" ? host.indexOf("]") + 1 : 0;
|
|
1521
|
+
const index = host.indexOf(":", offset);
|
|
1522
|
+
return index !== -1 ? host.substring(0, index) : host;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Returns an array of subdomains for the given host. An empty array is
|
|
1526
|
+
* returned if [[hostname]] is `null` or is an IP address.
|
|
1527
|
+
*
|
|
1528
|
+
* Also `www` is not considered as a subdomain
|
|
1529
|
+
*/
|
|
1530
|
+
subdomains() {
|
|
1531
|
+
const hostname = this.hostname();
|
|
1532
|
+
if (!hostname || isIP(hostname)) {
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
const offset = this.#config.subdomainOffset;
|
|
1536
|
+
const subdomains = hostname.split(".").reverse().slice(offset);
|
|
1537
|
+
if (subdomains[subdomains.length - 1] === "www") {
|
|
1538
|
+
subdomains.splice(subdomains.length - 1, 1);
|
|
1539
|
+
}
|
|
1540
|
+
return subdomains;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Returns a boolean telling, if request `X-Requested-With === 'xmlhttprequest'`
|
|
1544
|
+
* or not.
|
|
1545
|
+
*/
|
|
1546
|
+
ajax() {
|
|
1547
|
+
const xRequestedWith = this.header("X-Requested-With", "");
|
|
1548
|
+
return xRequestedWith.toLowerCase() === "xmlhttprequest";
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Returns a boolean telling, if request has `X-Pjax` header
|
|
1552
|
+
* set or not
|
|
1553
|
+
*/
|
|
1554
|
+
pjax() {
|
|
1555
|
+
return !!this.header("X-Pjax");
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Returns the request relative URL.
|
|
1559
|
+
*
|
|
1560
|
+
* @example
|
|
1561
|
+
* ```js
|
|
1562
|
+
* request.url()
|
|
1563
|
+
*
|
|
1564
|
+
* // include query string
|
|
1565
|
+
* request.url(true)
|
|
1566
|
+
* ```
|
|
1567
|
+
*/
|
|
1568
|
+
url(includeQueryString) {
|
|
1569
|
+
const pathname = this.parsedUrl.pathname;
|
|
1570
|
+
return includeQueryString && this.parsedUrl.query ? `${pathname}?${this.parsedUrl.query}` : pathname;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Returns the complete HTTP url by combining
|
|
1574
|
+
* [[protocol]]://[[hostname]]/[[url]]
|
|
1575
|
+
*
|
|
1576
|
+
* @example
|
|
1577
|
+
* ```js
|
|
1578
|
+
* request.completeUrl()
|
|
1579
|
+
*
|
|
1580
|
+
* // include query string
|
|
1581
|
+
* request.completeUrl(true)
|
|
1582
|
+
* ```
|
|
1583
|
+
*/
|
|
1584
|
+
completeUrl(includeQueryString) {
|
|
1585
|
+
const protocol = this.protocol();
|
|
1586
|
+
const hostname = this.host();
|
|
1587
|
+
return `${protocol}://${hostname}${this.url(includeQueryString)}`;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Find if the current HTTP request is for the given route or the routes
|
|
1591
|
+
*/
|
|
1592
|
+
matchesRoute(routeIdentifier) {
|
|
1593
|
+
if (!this.ctx || !this.ctx.route) {
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
const route = this.ctx.route;
|
|
1597
|
+
return !!(Array.isArray(routeIdentifier) ? routeIdentifier : [routeIdentifier]).find(
|
|
1598
|
+
(identifier) => {
|
|
1599
|
+
if (route.pattern === identifier || route.name === identifier) {
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
if (typeof route.handler === "function") {
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
return route.handler.reference === identifier;
|
|
1606
|
+
}
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Returns the best matching content type of the request by
|
|
1611
|
+
* matching against the given types.
|
|
1612
|
+
*
|
|
1613
|
+
* The content type is picked from the `content-type` header and request
|
|
1614
|
+
* must have body.
|
|
1615
|
+
*
|
|
1616
|
+
* The method response highly depends upon the types array values. Described below:
|
|
1617
|
+
*
|
|
1618
|
+
* | Type(s) | Return value |
|
|
1619
|
+
* |----------|---------------|
|
|
1620
|
+
* | ['json'] | json |
|
|
1621
|
+
* | ['application/*'] | application/json |
|
|
1622
|
+
* | ['vnd+json'] | application/json |
|
|
1623
|
+
*
|
|
1624
|
+
* @example
|
|
1625
|
+
* ```js
|
|
1626
|
+
* const bodyType = request.is(['json', 'xml'])
|
|
1627
|
+
*
|
|
1628
|
+
* if (bodyType === 'json') {
|
|
1629
|
+
* // process JSON
|
|
1630
|
+
* }
|
|
1631
|
+
*
|
|
1632
|
+
* if (bodyType === 'xml') {
|
|
1633
|
+
* // process XML
|
|
1634
|
+
* }
|
|
1635
|
+
* ```
|
|
1636
|
+
*/
|
|
1637
|
+
is(types) {
|
|
1638
|
+
return typeIs(this.request, types) || null;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Returns the best type using `Accept` header and
|
|
1642
|
+
* by matching it against the given types.
|
|
1643
|
+
*
|
|
1644
|
+
* If nothing is matched, then `null` will be returned
|
|
1645
|
+
*
|
|
1646
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1647
|
+
* docs too.
|
|
1648
|
+
*
|
|
1649
|
+
* @example
|
|
1650
|
+
* ```js
|
|
1651
|
+
* switch (request.accepts(['json', 'html'])) {
|
|
1652
|
+
* case 'json':
|
|
1653
|
+
* return response.json(user)
|
|
1654
|
+
* case 'html':
|
|
1655
|
+
* return view.render('user', { user })
|
|
1656
|
+
* default:
|
|
1657
|
+
* // decide yourself
|
|
1658
|
+
* }
|
|
1659
|
+
* ```
|
|
1660
|
+
*/
|
|
1661
|
+
accepts(types) {
|
|
1662
|
+
this.#initiateAccepts();
|
|
1663
|
+
return this.#lazyAccepts.type(types) || null;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Return the types that the request accepts, in the order of the
|
|
1667
|
+
* client's preference (most preferred first).
|
|
1668
|
+
*
|
|
1669
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1670
|
+
* docs too.
|
|
1671
|
+
*/
|
|
1672
|
+
types() {
|
|
1673
|
+
this.#initiateAccepts();
|
|
1674
|
+
return this.#lazyAccepts.types();
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Returns the best language using `Accept-language` header
|
|
1678
|
+
* and by matching it against the given languages.
|
|
1679
|
+
*
|
|
1680
|
+
* If nothing is matched, then `null` will be returned
|
|
1681
|
+
*
|
|
1682
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1683
|
+
* docs too.
|
|
1684
|
+
*
|
|
1685
|
+
* @example
|
|
1686
|
+
* ```js
|
|
1687
|
+
* switch (request.language(['fr', 'de'])) {
|
|
1688
|
+
* case 'fr':
|
|
1689
|
+
* return view.render('about', { lang: 'fr' })
|
|
1690
|
+
* case 'de':
|
|
1691
|
+
* return view.render('about', { lang: 'de' })
|
|
1692
|
+
* default:
|
|
1693
|
+
* return view.render('about', { lang: 'en' })
|
|
1694
|
+
* }
|
|
1695
|
+
* ```
|
|
1696
|
+
*/
|
|
1697
|
+
language(languages) {
|
|
1698
|
+
this.#initiateAccepts();
|
|
1699
|
+
return this.#lazyAccepts.language(languages) || null;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Return the languages that the request accepts, in the order of the
|
|
1703
|
+
* client's preference (most preferred first).
|
|
1704
|
+
*
|
|
1705
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1706
|
+
* docs too.
|
|
1707
|
+
*/
|
|
1708
|
+
languages() {
|
|
1709
|
+
this.#initiateAccepts();
|
|
1710
|
+
return this.#lazyAccepts.languages();
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Returns the best charset using `Accept-charset` header
|
|
1714
|
+
* and by matching it against the given charsets.
|
|
1715
|
+
*
|
|
1716
|
+
* If nothing is matched, then `null` will be returned
|
|
1717
|
+
*
|
|
1718
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1719
|
+
* docs too.
|
|
1720
|
+
*
|
|
1721
|
+
* @example
|
|
1722
|
+
* ```js
|
|
1723
|
+
* switch (request.charset(['utf-8', 'ISO-8859-1'])) {
|
|
1724
|
+
* case 'utf-8':
|
|
1725
|
+
* // make utf-8 friendly response
|
|
1726
|
+
* case 'ISO-8859-1':
|
|
1727
|
+
* // make ISO-8859-1 friendly response
|
|
1728
|
+
* }
|
|
1729
|
+
* ```
|
|
1730
|
+
*/
|
|
1731
|
+
charset(charsets) {
|
|
1732
|
+
this.#initiateAccepts();
|
|
1733
|
+
return this.#lazyAccepts.charset(charsets) || null;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Return the charsets that the request accepts, in the order of the
|
|
1737
|
+
* client's preference (most preferred first).
|
|
1738
|
+
*
|
|
1739
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1740
|
+
* docs too.
|
|
1741
|
+
*/
|
|
1742
|
+
charsets() {
|
|
1743
|
+
this.#initiateAccepts();
|
|
1744
|
+
return this.#lazyAccepts.charsets();
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Returns the best encoding using `Accept-encoding` header
|
|
1748
|
+
* and by matching it against the given encodings.
|
|
1749
|
+
*
|
|
1750
|
+
* If nothing is matched, then `null` will be returned
|
|
1751
|
+
*
|
|
1752
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1753
|
+
* docs too.
|
|
1754
|
+
*/
|
|
1755
|
+
encoding(encodings) {
|
|
1756
|
+
this.#initiateAccepts();
|
|
1757
|
+
return this.#lazyAccepts.encoding(encodings) || null;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Return the charsets that the request accepts, in the order of the
|
|
1761
|
+
* client's preference (most preferred first).
|
|
1762
|
+
*
|
|
1763
|
+
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1764
|
+
* docs too.
|
|
1765
|
+
*/
|
|
1766
|
+
encodings() {
|
|
1767
|
+
this.#initiateAccepts();
|
|
1768
|
+
return this.#lazyAccepts.encodings();
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Returns a boolean telling if request has body
|
|
1772
|
+
*/
|
|
1773
|
+
hasBody() {
|
|
1774
|
+
return typeIs.hasBody(this.request);
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Returns a boolean telling if the new response etag evaluates same
|
|
1778
|
+
* as the request header `if-none-match`. In case of `true`, the
|
|
1779
|
+
* server must return `304` response, telling the browser to
|
|
1780
|
+
* use the client cache.
|
|
1781
|
+
*
|
|
1782
|
+
* You won't have to deal with this method directly, since AdonisJs will
|
|
1783
|
+
* handle this for you when `http.etag = true` inside `config/app.js` file.
|
|
1784
|
+
*
|
|
1785
|
+
* However, this is how you can use it manually.
|
|
1786
|
+
*
|
|
1787
|
+
* ```js
|
|
1788
|
+
* const responseBody = view.render('some-view')
|
|
1789
|
+
*
|
|
1790
|
+
* // sets the HTTP etag header for response
|
|
1791
|
+
* response.setEtag(responseBody)
|
|
1792
|
+
*
|
|
1793
|
+
* if (request.fresh()) {
|
|
1794
|
+
* response.sendStatus(304)
|
|
1795
|
+
* } else {
|
|
1796
|
+
* response.send(responseBody)
|
|
1797
|
+
* }
|
|
1798
|
+
* ```
|
|
1799
|
+
*/
|
|
1800
|
+
fresh() {
|
|
1801
|
+
if (["GET", "HEAD"].indexOf(this.intended()) === -1) {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
const status = this.response.statusCode;
|
|
1805
|
+
if (status >= 200 && status < 300 || status === 304) {
|
|
1806
|
+
return fresh(this.headers(), this.response.getHeaders());
|
|
1807
|
+
}
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Opposite of [[fresh]]
|
|
1812
|
+
*/
|
|
1813
|
+
stale() {
|
|
1814
|
+
return !this.fresh();
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Returns all parsed and signed cookies. Signed cookies ensures
|
|
1818
|
+
* that their value isn't tampered.
|
|
1819
|
+
*/
|
|
1820
|
+
cookiesList() {
|
|
1821
|
+
this.#initiateCookieParser();
|
|
1822
|
+
return this.#cookieParser.list();
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Returns value for a given key from signed cookies. Optional
|
|
1826
|
+
* defaultValue is returned when actual value is undefined.
|
|
1827
|
+
*/
|
|
1828
|
+
cookie(key, defaultValue) {
|
|
1829
|
+
this.#initiateCookieParser();
|
|
1830
|
+
return this.#cookieParser.unsign(key) || defaultValue;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Returns value for a given key from signed cookies. Optional
|
|
1834
|
+
* defaultValue is returned when actual value is undefined.
|
|
1835
|
+
*/
|
|
1836
|
+
encryptedCookie(key, defaultValue) {
|
|
1837
|
+
this.#initiateCookieParser();
|
|
1838
|
+
return this.#cookieParser.decrypt(key) || defaultValue;
|
|
1839
|
+
}
|
|
1840
|
+
plainCookie(key, defaultValueOrOptions, encoded) {
|
|
1841
|
+
this.#initiateCookieParser();
|
|
1842
|
+
if (is2.object(defaultValueOrOptions)) {
|
|
1843
|
+
return this.#cookieParser.decode(key, defaultValueOrOptions?.encoded) || defaultValueOrOptions.defaultValue;
|
|
1844
|
+
}
|
|
1845
|
+
return this.#cookieParser.decode(key, encoded) || defaultValueOrOptions;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Returns a boolean telling if a signed url as a valid signature
|
|
1849
|
+
* or not.
|
|
1850
|
+
*/
|
|
1851
|
+
hasValidSignature(purpose) {
|
|
1852
|
+
const { signature, ...rest } = this.qs();
|
|
1853
|
+
if (!signature) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
const signedUrl = this.#encryption.verifier.unsign(signature, purpose);
|
|
1857
|
+
if (!signedUrl) {
|
|
1858
|
+
return false;
|
|
1859
|
+
}
|
|
1860
|
+
const queryString = this.#qsParser.stringify(rest);
|
|
1861
|
+
return queryString ? safeEqual(signedUrl, `${this.url()}?${queryString}`) : safeEqual(signedUrl, this.url());
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Serializes request to JSON format
|
|
1865
|
+
*/
|
|
1866
|
+
serialize() {
|
|
1867
|
+
return {
|
|
1868
|
+
id: this.id(),
|
|
1869
|
+
url: this.url(),
|
|
1870
|
+
query: this.parsedUrl.query,
|
|
1871
|
+
body: this.all(),
|
|
1872
|
+
params: this.params(),
|
|
1873
|
+
headers: this.headers(),
|
|
1874
|
+
method: this.method(),
|
|
1875
|
+
protocol: this.protocol(),
|
|
1876
|
+
cookies: this.cookiesList(),
|
|
1877
|
+
hostname: this.hostname(),
|
|
1878
|
+
ip: this.ip(),
|
|
1879
|
+
subdomains: this.ctx?.subdomains || {}
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* toJSON copy of the request
|
|
1884
|
+
*/
|
|
1885
|
+
toJSON() {
|
|
1886
|
+
return this.serialize();
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
// src/redirect.ts
|
|
1891
|
+
import { parse as parse2 } from "node:url";
|
|
1892
|
+
import encodeUrl from "encodeurl";
|
|
1893
|
+
var Redirect = class {
|
|
1894
|
+
/**
|
|
1895
|
+
* A boolean to forward the existing query string
|
|
1896
|
+
*/
|
|
1897
|
+
#forwardQueryString = false;
|
|
1898
|
+
/**
|
|
1899
|
+
* The status code for the redirect
|
|
1900
|
+
*/
|
|
1901
|
+
#statusCode = 302;
|
|
1902
|
+
/**
|
|
1903
|
+
* A custom query string to forward
|
|
1904
|
+
*/
|
|
1905
|
+
#queryString = {};
|
|
1906
|
+
#request;
|
|
1907
|
+
#response;
|
|
1908
|
+
#router;
|
|
1909
|
+
#qs;
|
|
1910
|
+
constructor(request, response, router, qs) {
|
|
1911
|
+
this.#request = request;
|
|
1912
|
+
this.#response = response;
|
|
1913
|
+
this.#router = router;
|
|
1914
|
+
this.#qs = qs;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Sends response by setting require headers
|
|
1918
|
+
*/
|
|
1919
|
+
#sendResponse(url, query) {
|
|
1920
|
+
const stringified = this.#qs.stringify(query);
|
|
1921
|
+
url = stringified ? `${url}?${stringified}` : url;
|
|
1922
|
+
debug_default('redirecting to url "%s"', url);
|
|
1923
|
+
this.#response.location(encodeUrl(url));
|
|
1924
|
+
this.#response.safeStatus(this.#statusCode);
|
|
1925
|
+
this.#response.type("text/plain; charset=utf-8");
|
|
1926
|
+
this.#response.send(`Redirecting to ${url}`);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Returns the referrer url
|
|
1930
|
+
*/
|
|
1931
|
+
#getReferrerUrl() {
|
|
1932
|
+
let url = this.#request.headers["referer"] || this.#request.headers["referrer"] || "/";
|
|
1933
|
+
return Array.isArray(url) ? url[0] : url;
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Set a custom status code.
|
|
1937
|
+
*/
|
|
1938
|
+
status(statusCode) {
|
|
1939
|
+
this.#statusCode = statusCode;
|
|
1940
|
+
return this;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Clearing query string values added using the
|
|
1944
|
+
* "withQs" method
|
|
1945
|
+
*/
|
|
1946
|
+
clearQs() {
|
|
1947
|
+
this.#forwardQueryString = false;
|
|
1948
|
+
this.#queryString = {};
|
|
1949
|
+
return this;
|
|
1950
|
+
}
|
|
1951
|
+
withQs(name, value) {
|
|
1952
|
+
if (typeof name === "undefined") {
|
|
1953
|
+
this.#forwardQueryString = true;
|
|
1954
|
+
return this;
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof name === "string") {
|
|
1957
|
+
this.#queryString[name] = value;
|
|
1958
|
+
return this;
|
|
1959
|
+
}
|
|
1960
|
+
Object.assign(this.#queryString, name);
|
|
1961
|
+
return this;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Redirect to the previous path.
|
|
1965
|
+
*/
|
|
1966
|
+
back() {
|
|
1967
|
+
let query = {};
|
|
1968
|
+
const referrerUrl = this.#getReferrerUrl();
|
|
1969
|
+
const url = parse2(referrerUrl);
|
|
1970
|
+
debug_default('referrer url "%s"', referrerUrl);
|
|
1971
|
+
debug_default('referrer base url "%s"', url.pathname);
|
|
1972
|
+
if (this.#forwardQueryString) {
|
|
1973
|
+
query = this.#qs.parse(url.query || "");
|
|
1974
|
+
}
|
|
1975
|
+
Object.assign(query, this.#queryString);
|
|
1976
|
+
this.#sendResponse(url.pathname || "", query);
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Redirect the request using a route identifier.
|
|
1980
|
+
*/
|
|
1981
|
+
toRoute(routeIdentifier, params, options) {
|
|
1982
|
+
if (options && options.qs) {
|
|
1983
|
+
this.withQs(options.qs);
|
|
1984
|
+
options.qs = void 0;
|
|
1985
|
+
}
|
|
1986
|
+
const url = this.#router.makeUrl(routeIdentifier, params, options);
|
|
1987
|
+
return this.toPath(url);
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Redirect the request using a path.
|
|
1991
|
+
*/
|
|
1992
|
+
toPath(url) {
|
|
1993
|
+
let query = {};
|
|
1994
|
+
if (this.#forwardQueryString) {
|
|
1995
|
+
query = this.#qs.parse(parse2(this.#request.url).query || "");
|
|
1996
|
+
}
|
|
1997
|
+
Object.assign(query, this.#queryString);
|
|
1998
|
+
this.#sendResponse(url, query);
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
// src/exceptions.ts
|
|
2003
|
+
var exceptions_exports = {};
|
|
2004
|
+
__export(exceptions_exports, {
|
|
2005
|
+
E_CANNOT_LOOKUP_ROUTE: () => E_CANNOT_LOOKUP_ROUTE,
|
|
2006
|
+
E_HTTP_EXCEPTION: () => E_HTTP_EXCEPTION,
|
|
2007
|
+
E_HTTP_REQUEST_ABORTED: () => E_HTTP_REQUEST_ABORTED,
|
|
2008
|
+
E_ROUTE_NOT_FOUND: () => E_ROUTE_NOT_FOUND
|
|
2009
|
+
});
|
|
2010
|
+
import { createError, Exception } from "@poppinss/utils";
|
|
2011
|
+
var E_ROUTE_NOT_FOUND = createError(
|
|
2012
|
+
"Cannot %s:%s",
|
|
2013
|
+
"E_ROUTE_NOT_FOUND",
|
|
2014
|
+
404
|
|
2015
|
+
);
|
|
2016
|
+
var E_CANNOT_LOOKUP_ROUTE = createError(
|
|
2017
|
+
'Cannot lookup route "%s"',
|
|
2018
|
+
"E_CANNOT_LOOKUP_ROUTE",
|
|
2019
|
+
500
|
|
2020
|
+
);
|
|
2021
|
+
var E_HTTP_EXCEPTION = class HttpException extends Exception {
|
|
2022
|
+
body;
|
|
2023
|
+
static code = "E_HTTP_EXCEPTION";
|
|
2024
|
+
/**
|
|
2025
|
+
* This method returns an instance of the exception class
|
|
2026
|
+
*/
|
|
2027
|
+
static invoke(body, status, code = "E_HTTP_EXCEPTION") {
|
|
2028
|
+
if (body === null || body === void 0) {
|
|
2029
|
+
const error2 = new this("HTTP Exception", { status, code });
|
|
2030
|
+
error2.body = "Internal server error";
|
|
2031
|
+
return error2;
|
|
2032
|
+
}
|
|
2033
|
+
if (typeof body === "object") {
|
|
2034
|
+
const error2 = new this(body.message || "HTTP Exception", { status, code });
|
|
2035
|
+
error2.body = body;
|
|
2036
|
+
return error2;
|
|
2037
|
+
}
|
|
2038
|
+
const error = new this(body, { status, code });
|
|
2039
|
+
error.body = body;
|
|
2040
|
+
return error;
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
var E_HTTP_REQUEST_ABORTED = class AbortException extends E_HTTP_EXCEPTION {
|
|
2044
|
+
handle(error, ctx) {
|
|
2045
|
+
ctx.response.status(error.status).send(error.body);
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
|
|
2049
|
+
// src/response.ts
|
|
2050
|
+
import etag from "etag";
|
|
2051
|
+
import vary from "vary";
|
|
2052
|
+
import fresh2 from "fresh";
|
|
2053
|
+
import mime from "mime-types";
|
|
2054
|
+
import destroy from "destroy";
|
|
2055
|
+
import { extname } from "node:path";
|
|
2056
|
+
import onFinished from "on-finished";
|
|
2057
|
+
import json from "@poppinss/utils/json";
|
|
2058
|
+
import Macroable6 from "@poppinss/macroable";
|
|
2059
|
+
import { createReadStream } from "node:fs";
|
|
2060
|
+
import { stat } from "node:fs/promises";
|
|
2061
|
+
import { RuntimeException as RuntimeException3 } from "@poppinss/utils";
|
|
2062
|
+
import contentDisposition from "content-disposition";
|
|
2063
|
+
|
|
2064
|
+
// src/cookies/serializer.ts
|
|
2065
|
+
import cookie2 from "cookie";
|
|
2066
|
+
import string2 from "@poppinss/utils/string";
|
|
2067
|
+
var CookieSerializer = class {
|
|
2068
|
+
#client;
|
|
2069
|
+
constructor(encryption) {
|
|
2070
|
+
this.#client = new CookieClient(encryption);
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Serializes the key-value pair to a string, that can be set on the
|
|
2074
|
+
* `Set-Cookie` header.
|
|
2075
|
+
*/
|
|
2076
|
+
#serializeAsCookie(key, value, options) {
|
|
2077
|
+
let expires = options?.expires;
|
|
2078
|
+
if (typeof expires === "function") {
|
|
2079
|
+
expires = expires();
|
|
2080
|
+
}
|
|
2081
|
+
let maxAge = options?.maxAge ? string2.seconds.parse(options?.maxAge) : void 0;
|
|
2082
|
+
const parsedOptions = Object.assign({}, options, { maxAge, expires });
|
|
2083
|
+
return cookie2.serialize(key, value, parsedOptions);
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Encodes value as a plain cookie. By default, the plain value will be converted
|
|
2087
|
+
* to a string using "JSON.stringify" method and then encoded as a base64 string.
|
|
2088
|
+
*
|
|
2089
|
+
* You can disable encoding of the cookie by setting `options.encoded = false`.
|
|
2090
|
+
*
|
|
2091
|
+
* ```ts
|
|
2092
|
+
* serializer.encode('name', 'virk')
|
|
2093
|
+
* ```
|
|
2094
|
+
*/
|
|
2095
|
+
encode(key, value, options) {
|
|
2096
|
+
const packedValue = options?.encode === false ? value : this.#client.encode(key, value);
|
|
2097
|
+
if (packedValue === null || packedValue === void 0) {
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
return this.#serializeAsCookie(key, packedValue, options);
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Sign a key-value pair to a signed cookie. The signed value has a
|
|
2104
|
+
* verification hash attached to it to detect data tampering.
|
|
2105
|
+
*/
|
|
2106
|
+
sign(key, value, options) {
|
|
2107
|
+
const packedValue = this.#client.sign(key, value);
|
|
2108
|
+
if (packedValue === null) {
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
2111
|
+
return this.#serializeAsCookie(key, packedValue, options);
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Encrypts a key-value pair to an encrypted cookie.
|
|
2115
|
+
*/
|
|
2116
|
+
encrypt(key, value, options) {
|
|
2117
|
+
const packedValue = this.#client.encrypt(key, value);
|
|
2118
|
+
if (packedValue === null) {
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
return this.#serializeAsCookie(key, packedValue, options);
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
// src/response.ts
|
|
2126
|
+
var CACHEABLE_HTTP_METHODS = ["GET", "HEAD"];
|
|
2127
|
+
var Response = class extends Macroable6 {
|
|
2128
|
+
constructor(request, response, encryption, config, router, qs) {
|
|
2129
|
+
super();
|
|
2130
|
+
this.request = request;
|
|
2131
|
+
this.response = response;
|
|
2132
|
+
this.#qs = qs;
|
|
2133
|
+
this.#config = config;
|
|
2134
|
+
this.#router = router;
|
|
2135
|
+
this.#cookieSerializer = new CookieSerializer(encryption);
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Query string parser
|
|
2139
|
+
*/
|
|
2140
|
+
#qs;
|
|
2141
|
+
/**
|
|
2142
|
+
* Outgoing headers
|
|
2143
|
+
*/
|
|
2144
|
+
#headers = {};
|
|
2145
|
+
/**
|
|
2146
|
+
* Has explicit status been set
|
|
2147
|
+
*/
|
|
2148
|
+
#hasExplicitStatus = false;
|
|
2149
|
+
/**
|
|
2150
|
+
* Cookies serializer to serialize the outgoing cookies
|
|
2151
|
+
*/
|
|
2152
|
+
#cookieSerializer;
|
|
2153
|
+
/**
|
|
2154
|
+
* Router is used to make the redirect URLs from routes
|
|
2155
|
+
*/
|
|
2156
|
+
#router;
|
|
2157
|
+
/**
|
|
2158
|
+
* Response config
|
|
2159
|
+
*/
|
|
2160
|
+
#config;
|
|
2161
|
+
/**
|
|
2162
|
+
* Does response has body set that will written to the
|
|
2163
|
+
* response socket at the end of the request
|
|
2164
|
+
*/
|
|
2165
|
+
get hasLazyBody() {
|
|
2166
|
+
return !!(this.lazyBody.content || this.lazyBody.fileToStream || this.lazyBody.stream);
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Find if the response has non-stream content
|
|
2170
|
+
*/
|
|
2171
|
+
get hasContent() {
|
|
2172
|
+
return !!this.lazyBody.content;
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Returns true when response body is set using "response.stream"
|
|
2176
|
+
* method
|
|
2177
|
+
*/
|
|
2178
|
+
get hasStream() {
|
|
2179
|
+
return !!this.lazyBody.stream;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Returns true when response body is set using "response.download"
|
|
2183
|
+
* or "response.attachment" methods
|
|
2184
|
+
*/
|
|
2185
|
+
get hasFileToStream() {
|
|
2186
|
+
return !!this.lazyBody.fileToStream;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Returns the response content. Check if the response
|
|
2190
|
+
* has content using the "hasContent" method
|
|
2191
|
+
*/
|
|
2192
|
+
get content() {
|
|
2193
|
+
return this.lazyBody.content;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Returns reference to the stream set using "response.stream"
|
|
2197
|
+
* method
|
|
2198
|
+
*/
|
|
2199
|
+
get outgoingStream() {
|
|
2200
|
+
return this.lazyBody.stream?.[0];
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Returns reference to the file path set using "response.stream"
|
|
2204
|
+
* method.
|
|
2205
|
+
*/
|
|
2206
|
+
get fileToStream() {
|
|
2207
|
+
return this.lazyBody.fileToStream ? {
|
|
2208
|
+
path: this.lazyBody.fileToStream[0],
|
|
2209
|
+
generateEtag: this.lazyBody.fileToStream[1]
|
|
2210
|
+
} : void 0;
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Lazy body is used to set the response body. However, do not
|
|
2214
|
+
* write it on the socket immediately unless `response.finish`
|
|
2215
|
+
* is called.
|
|
2216
|
+
*/
|
|
2217
|
+
lazyBody = {};
|
|
2218
|
+
/**
|
|
2219
|
+
* The ctx will be set by the context itself. It creates a circular
|
|
2220
|
+
* reference
|
|
2221
|
+
*/
|
|
2222
|
+
ctx;
|
|
2223
|
+
/**
|
|
2224
|
+
* Returns a boolean telling if response is finished or not.
|
|
2225
|
+
* Any more attempts to update headers or body will result
|
|
2226
|
+
* in raised exceptions.
|
|
2227
|
+
*/
|
|
2228
|
+
get finished() {
|
|
2229
|
+
return this.response.writableFinished;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Returns a boolean telling if response headers has been sent or not.
|
|
2233
|
+
* Any more attempts to update headers will result in raised
|
|
2234
|
+
* exceptions.
|
|
2235
|
+
*/
|
|
2236
|
+
get headersSent() {
|
|
2237
|
+
return this.response.headersSent;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Returns a boolean telling if response headers and body is written
|
|
2241
|
+
* or not. When value is `true`, you can feel free to write headers
|
|
2242
|
+
* and body.
|
|
2243
|
+
*/
|
|
2244
|
+
get isPending() {
|
|
2245
|
+
return !this.headersSent && !this.finished;
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Normalizes header value to a string or an array of string
|
|
2249
|
+
*/
|
|
2250
|
+
#castHeaderValue(value) {
|
|
2251
|
+
return Array.isArray(value) ? value.map(String) : String(value);
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Ends the response by flushing headers and writing body
|
|
2255
|
+
*/
|
|
2256
|
+
#endResponse(body, statusCode) {
|
|
2257
|
+
this.writeHead(statusCode);
|
|
2258
|
+
const res = this.response;
|
|
2259
|
+
res.end(body, null, null);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Returns type for the content body. Only following types are allowed
|
|
2263
|
+
*
|
|
2264
|
+
* - Dates
|
|
2265
|
+
* - Arrays
|
|
2266
|
+
* - Booleans
|
|
2267
|
+
* - Objects
|
|
2268
|
+
* - Strings
|
|
2269
|
+
* - Buffer
|
|
2270
|
+
*/
|
|
2271
|
+
#getDataType(content) {
|
|
2272
|
+
if (Buffer.isBuffer(content)) {
|
|
2273
|
+
return "buffer";
|
|
2274
|
+
}
|
|
2275
|
+
if (content instanceof Date) {
|
|
2276
|
+
return "date";
|
|
2277
|
+
}
|
|
2278
|
+
if (content instanceof RegExp) {
|
|
2279
|
+
return "regexp";
|
|
2280
|
+
}
|
|
2281
|
+
const dataType = typeof content;
|
|
2282
|
+
if (dataType === "number" || dataType === "boolean" || dataType === "string" || dataType === "bigint") {
|
|
2283
|
+
return dataType;
|
|
2284
|
+
}
|
|
2285
|
+
if (dataType === "object") {
|
|
2286
|
+
return "object";
|
|
2287
|
+
}
|
|
2288
|
+
throw new RuntimeException3(`Cannot serialize "${dataType}" to HTTP response`);
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Writes the body with appropriate response headers. Etag header is set
|
|
2292
|
+
* when `generateEtag` is set to `true`.
|
|
2293
|
+
*
|
|
2294
|
+
* Empty body results in `204`.
|
|
2295
|
+
*/
|
|
2296
|
+
writeBody(content, generateEtag, jsonpCallbackName) {
|
|
2297
|
+
const hasEmptyBody = content === null || content === void 0 || content === "";
|
|
2298
|
+
if (hasEmptyBody) {
|
|
2299
|
+
this.safeStatus(204);
|
|
2300
|
+
}
|
|
2301
|
+
const statusCode = this.response.statusCode;
|
|
2302
|
+
if (statusCode && (statusCode < 200 || statusCode === 204 || statusCode === 304)) {
|
|
2303
|
+
this.removeHeader("Content-Type");
|
|
2304
|
+
this.removeHeader("Content-Length");
|
|
2305
|
+
this.removeHeader("Transfer-Encoding");
|
|
2306
|
+
this.#endResponse();
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
if (hasEmptyBody) {
|
|
2310
|
+
this.removeHeader("Content-Length");
|
|
2311
|
+
this.#endResponse();
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
const dataType = this.#getDataType(content);
|
|
2315
|
+
if (dataType === "object") {
|
|
2316
|
+
content = json.safeStringify(content);
|
|
2317
|
+
} else if (dataType === "number" || dataType === "boolean" || dataType === "bigint" || dataType === "regexp") {
|
|
2318
|
+
content = String(content);
|
|
2319
|
+
} else if (dataType === "date") {
|
|
2320
|
+
content = content.toISOString();
|
|
2321
|
+
}
|
|
2322
|
+
if (jsonpCallbackName) {
|
|
2323
|
+
content = content.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
2324
|
+
content = `/**/ typeof ${jsonpCallbackName} === 'function' && ${jsonpCallbackName}(${content});`;
|
|
2325
|
+
}
|
|
2326
|
+
if (generateEtag) {
|
|
2327
|
+
this.setEtag(content);
|
|
2328
|
+
}
|
|
2329
|
+
if (generateEtag && this.fresh()) {
|
|
2330
|
+
this.removeHeader("Content-Type");
|
|
2331
|
+
this.removeHeader("Content-Length");
|
|
2332
|
+
this.removeHeader("Transfer-Encoding");
|
|
2333
|
+
this.#endResponse(null, 304);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
this.header("Content-Length", Buffer.byteLength(content));
|
|
2337
|
+
if (jsonpCallbackName) {
|
|
2338
|
+
this.header("X-Content-Type-Options", "nosniff");
|
|
2339
|
+
this.safeHeader("Content-Type", "text/javascript; charset=utf-8");
|
|
2340
|
+
} else {
|
|
2341
|
+
switch (dataType) {
|
|
2342
|
+
case "string":
|
|
2343
|
+
const type = /^\s*</.test(content) ? "text/html" : "text/plain";
|
|
2344
|
+
this.safeHeader("Content-Type", `${type}; charset=utf-8`);
|
|
2345
|
+
break;
|
|
2346
|
+
case "number":
|
|
2347
|
+
case "boolean":
|
|
2348
|
+
case "date":
|
|
2349
|
+
case "bigint":
|
|
2350
|
+
case "regexp":
|
|
2351
|
+
this.safeHeader("Content-Type", "text/plain; charset=utf-8");
|
|
2352
|
+
break;
|
|
2353
|
+
case "buffer":
|
|
2354
|
+
this.safeHeader("Content-Type", "application/octet-stream; charset=utf-8");
|
|
2355
|
+
break;
|
|
2356
|
+
case "object":
|
|
2357
|
+
this.safeHeader("Content-Type", "application/json; charset=utf-8");
|
|
2358
|
+
break;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
this.#endResponse(content);
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Stream the body to the response and handles cleaning up the stream
|
|
2365
|
+
*/
|
|
2366
|
+
streamBody(body, errorCallback) {
|
|
2367
|
+
return new Promise((resolve) => {
|
|
2368
|
+
let finished = false;
|
|
2369
|
+
body.on("error", (error) => {
|
|
2370
|
+
if (finished) {
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
finished = true;
|
|
2374
|
+
destroy(body);
|
|
2375
|
+
this.type("text");
|
|
2376
|
+
if (!this.headersSent) {
|
|
2377
|
+
if (typeof errorCallback === "function") {
|
|
2378
|
+
this.#endResponse(...errorCallback(error));
|
|
2379
|
+
} else {
|
|
2380
|
+
this.#endResponse(
|
|
2381
|
+
error.code === "ENOENT" ? "File not found" : "Cannot process file",
|
|
2382
|
+
error.code === "ENOENT" ? 404 : 500
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
} else {
|
|
2386
|
+
this.response.destroy();
|
|
2387
|
+
}
|
|
2388
|
+
resolve();
|
|
2389
|
+
});
|
|
2390
|
+
body.on("end", () => {
|
|
2391
|
+
if (!this.headersSent) {
|
|
2392
|
+
this.#endResponse();
|
|
2393
|
+
}
|
|
2394
|
+
resolve();
|
|
2395
|
+
});
|
|
2396
|
+
onFinished(this.response, () => {
|
|
2397
|
+
finished = true;
|
|
2398
|
+
destroy(body);
|
|
2399
|
+
});
|
|
2400
|
+
this.relayHeaders();
|
|
2401
|
+
body.pipe(this.response);
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Downloads a file by streaming it to the response
|
|
2406
|
+
*/
|
|
2407
|
+
async streamFileForDownload(filePath, generateEtag, errorCallback) {
|
|
2408
|
+
try {
|
|
2409
|
+
const stats = await stat(filePath);
|
|
2410
|
+
if (!stats || !stats.isFile()) {
|
|
2411
|
+
throw new TypeError("response.download only accepts path to a file");
|
|
2412
|
+
}
|
|
2413
|
+
this.header("Last-Modified", stats.mtime.toUTCString());
|
|
2414
|
+
this.type(extname(filePath));
|
|
2415
|
+
if (generateEtag) {
|
|
2416
|
+
this.setEtag(stats, true);
|
|
2417
|
+
}
|
|
2418
|
+
if (this.request.method === "HEAD") {
|
|
2419
|
+
this.#endResponse(null, generateEtag && this.fresh() ? 304 : 200);
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
if (generateEtag && this.fresh()) {
|
|
2423
|
+
this.#endResponse(null, 304);
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
this.header("Content-length", stats.size);
|
|
2427
|
+
return this.streamBody(createReadStream(filePath), errorCallback);
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
this.type("text");
|
|
2430
|
+
this.removeHeader("Etag");
|
|
2431
|
+
if (typeof errorCallback === "function") {
|
|
2432
|
+
this.#endResponse(...errorCallback(error));
|
|
2433
|
+
} else {
|
|
2434
|
+
this.#endResponse(
|
|
2435
|
+
error.code === "ENOENT" ? "File not found" : "Cannot process file",
|
|
2436
|
+
error.code === "ENOENT" ? 404 : 500
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Writes headers with the Node.js res object using the
|
|
2443
|
+
* response.setHeader method
|
|
2444
|
+
*/
|
|
2445
|
+
relayHeaders() {
|
|
2446
|
+
if (!this.headersSent) {
|
|
2447
|
+
for (let key in this.#headers) {
|
|
2448
|
+
const value = this.#headers[key];
|
|
2449
|
+
if (value) {
|
|
2450
|
+
this.response.setHeader(key, value);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Calls res.writeHead on the Node.js res object.
|
|
2457
|
+
*/
|
|
2458
|
+
writeHead(statusCode) {
|
|
2459
|
+
this.response.writeHead(statusCode || this.response.statusCode, this.#headers);
|
|
2460
|
+
return this;
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Returns the existing value for a given HTTP response
|
|
2464
|
+
* header.
|
|
2465
|
+
*/
|
|
2466
|
+
getHeader(key) {
|
|
2467
|
+
const value = this.#headers[key.toLowerCase()];
|
|
2468
|
+
return value === void 0 ? this.response.getHeader(key) : value;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Get response headers
|
|
2472
|
+
*/
|
|
2473
|
+
getHeaders() {
|
|
2474
|
+
return {
|
|
2475
|
+
...this.response.getHeaders(),
|
|
2476
|
+
...this.#headers
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Set header on the response. To `append` values to the existing header, we suggest
|
|
2481
|
+
* using [[append]] method.
|
|
2482
|
+
*
|
|
2483
|
+
* If `value` is non existy, then header won't be set.
|
|
2484
|
+
*
|
|
2485
|
+
* @example
|
|
2486
|
+
* ```js
|
|
2487
|
+
* response.header('content-type', 'application/json')
|
|
2488
|
+
* ```
|
|
2489
|
+
*/
|
|
2490
|
+
header(key, value) {
|
|
2491
|
+
if (value === null || value === void 0) {
|
|
2492
|
+
return this;
|
|
2493
|
+
}
|
|
2494
|
+
this.#headers[key.toLowerCase()] = this.#castHeaderValue(value);
|
|
2495
|
+
return this;
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Append value to an existing header. To replace the value, we suggest using
|
|
2499
|
+
* [[header]] method.
|
|
2500
|
+
*
|
|
2501
|
+
* If `value` is not existy, then header won't be set.
|
|
2502
|
+
*
|
|
2503
|
+
* @example
|
|
2504
|
+
* ```js
|
|
2505
|
+
* response.append('set-cookie', 'username=virk')
|
|
2506
|
+
* ```
|
|
2507
|
+
*/
|
|
2508
|
+
append(key, value) {
|
|
2509
|
+
if (value === null || value === void 0) {
|
|
2510
|
+
return this;
|
|
2511
|
+
}
|
|
2512
|
+
key = key.toLowerCase();
|
|
2513
|
+
let existingHeader = this.getHeader(key);
|
|
2514
|
+
let casted = this.#castHeaderValue(value);
|
|
2515
|
+
if (!existingHeader) {
|
|
2516
|
+
this.#headers[key] = casted;
|
|
2517
|
+
return this;
|
|
2518
|
+
}
|
|
2519
|
+
existingHeader = this.#castHeaderValue(existingHeader);
|
|
2520
|
+
casted = Array.isArray(existingHeader) ? existingHeader.concat(casted) : [existingHeader].concat(casted);
|
|
2521
|
+
this.#headers[key] = casted;
|
|
2522
|
+
return this;
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* Adds HTTP response header, when it doesn't exists already.
|
|
2526
|
+
*/
|
|
2527
|
+
safeHeader(key, value) {
|
|
2528
|
+
if (!this.getHeader(key)) {
|
|
2529
|
+
this.header(key, value);
|
|
2530
|
+
}
|
|
2531
|
+
return this;
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Removes the existing response header from being sent.
|
|
2535
|
+
*/
|
|
2536
|
+
removeHeader(key) {
|
|
2537
|
+
key = key.toLowerCase();
|
|
2538
|
+
this.response.removeHeader(key);
|
|
2539
|
+
if (this.#headers[key]) {
|
|
2540
|
+
delete this.#headers[key.toLowerCase()];
|
|
2541
|
+
}
|
|
2542
|
+
return this;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Returns the status code for the response
|
|
2546
|
+
*/
|
|
2547
|
+
getStatus() {
|
|
2548
|
+
return this.response.statusCode;
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Set HTTP status code
|
|
2552
|
+
*/
|
|
2553
|
+
status(code) {
|
|
2554
|
+
this.#hasExplicitStatus = true;
|
|
2555
|
+
this.response.statusCode = code;
|
|
2556
|
+
return this;
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Set's status code only when it's not explictly
|
|
2560
|
+
* set
|
|
2561
|
+
*/
|
|
2562
|
+
safeStatus(code) {
|
|
2563
|
+
if (this.#hasExplicitStatus) {
|
|
2564
|
+
return this;
|
|
2565
|
+
}
|
|
2566
|
+
this.response.statusCode = code;
|
|
2567
|
+
return this;
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Set response type by looking up for the mime-type using
|
|
2571
|
+
* partial types like file extensions.
|
|
2572
|
+
*
|
|
2573
|
+
* Make sure to read [mime-types](https://www.npmjs.com/package/mime-types) docs
|
|
2574
|
+
* too.
|
|
2575
|
+
*
|
|
2576
|
+
* @example
|
|
2577
|
+
* ```js
|
|
2578
|
+
* response.type('.json') // Content-type: application/json
|
|
2579
|
+
* ```
|
|
2580
|
+
*/
|
|
2581
|
+
type(type, charset) {
|
|
2582
|
+
type = charset ? `${type}; charset=${charset}` : type;
|
|
2583
|
+
this.header("Content-Type", mime.contentType(type));
|
|
2584
|
+
return this;
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Set the Vary HTTP header
|
|
2588
|
+
*/
|
|
2589
|
+
vary(field) {
|
|
2590
|
+
vary(this.response, field);
|
|
2591
|
+
return this;
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Set etag by computing hash from the body. This class will set the etag automatically
|
|
2595
|
+
* when `etag = true` in the defined config object.
|
|
2596
|
+
*
|
|
2597
|
+
* Use this function, when you want to compute etag manually for some other resons.
|
|
2598
|
+
*/
|
|
2599
|
+
setEtag(body, weak = false) {
|
|
2600
|
+
this.header("Etag", etag(body, { weak }));
|
|
2601
|
+
return this;
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Returns a boolean telling if the new response etag evaluates same
|
|
2605
|
+
* as the request header `if-none-match`. In case of `true`, the
|
|
2606
|
+
* server must return `304` response, telling the browser to
|
|
2607
|
+
* use the client cache.
|
|
2608
|
+
*
|
|
2609
|
+
* You won't have to deal with this method directly, since AdonisJs will
|
|
2610
|
+
* handle this for you when `http.etag = true` inside `config/app.js` file.
|
|
2611
|
+
*
|
|
2612
|
+
* However, this is how you can use it manually.
|
|
2613
|
+
*
|
|
2614
|
+
* @example
|
|
2615
|
+
* ```js
|
|
2616
|
+
* const responseBody = view.render('some-view')
|
|
2617
|
+
*
|
|
2618
|
+
* // sets the HTTP etag header for response
|
|
2619
|
+
* response.setEtag(responseBody)
|
|
2620
|
+
*
|
|
2621
|
+
* if (response.fresh()) {
|
|
2622
|
+
* response.sendStatus(304)
|
|
2623
|
+
* } else {
|
|
2624
|
+
* response.send(responseBody)
|
|
2625
|
+
* }
|
|
2626
|
+
* ```
|
|
2627
|
+
*/
|
|
2628
|
+
fresh() {
|
|
2629
|
+
if (this.request.method && !CACHEABLE_HTTP_METHODS.includes(this.request.method)) {
|
|
2630
|
+
return false;
|
|
2631
|
+
}
|
|
2632
|
+
const status = this.response.statusCode;
|
|
2633
|
+
if (status >= 200 && status < 300 || status === 304) {
|
|
2634
|
+
return fresh2(this.request.headers, this.#headers);
|
|
2635
|
+
}
|
|
2636
|
+
return false;
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Returns the response body. Returns null when response
|
|
2640
|
+
* body is a stream
|
|
2641
|
+
*/
|
|
2642
|
+
getBody() {
|
|
2643
|
+
if (this.lazyBody.content) {
|
|
2644
|
+
return this.lazyBody.content[0];
|
|
2645
|
+
}
|
|
2646
|
+
return null;
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Send the body as response and optionally generate etag. The default value
|
|
2650
|
+
* is read from `config/app.js` file, using `http.etag` property.
|
|
2651
|
+
*
|
|
2652
|
+
* This method buffers the body if `explicitEnd = true`, which is the default
|
|
2653
|
+
* behavior and do not change, unless you know what you are doing.
|
|
2654
|
+
*/
|
|
2655
|
+
send(body, generateEtag = this.#config.etag) {
|
|
2656
|
+
this.lazyBody.content = [body, generateEtag];
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Alias of [[send]]
|
|
2660
|
+
*/
|
|
2661
|
+
json(body, generateEtag = this.#config.etag) {
|
|
2662
|
+
return this.send(body, generateEtag);
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Writes response as JSONP. The callback name is resolved as follows, with priority
|
|
2666
|
+
* from top to bottom.
|
|
2667
|
+
*
|
|
2668
|
+
* 1. Explicitly defined as 2nd Param.
|
|
2669
|
+
* 2. Fetch from request query string.
|
|
2670
|
+
* 3. Use the config value `http.jsonpCallbackName` from `config/app.js`.
|
|
2671
|
+
* 4. Fallback to `callback`.
|
|
2672
|
+
*
|
|
2673
|
+
* This method buffers the body if `explicitEnd = true`, which is the default
|
|
2674
|
+
* behavior and do not change, unless you know what you are doing.
|
|
2675
|
+
*/
|
|
2676
|
+
jsonp(body, callbackName = this.#config.jsonpCallbackName, generateEtag = this.#config.etag) {
|
|
2677
|
+
this.lazyBody.content = [body, generateEtag, callbackName];
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Pipe stream to the response. This method will gracefully destroy
|
|
2681
|
+
* the stream, avoiding memory leaks.
|
|
2682
|
+
*
|
|
2683
|
+
* If `raiseErrors=false`, then this method will self handle all the exceptions by
|
|
2684
|
+
* writing a generic HTTP response. To have more control over the error, it is
|
|
2685
|
+
* recommended to set `raiseErrors=true` and wrap this function inside a
|
|
2686
|
+
* `try/catch` statement.
|
|
2687
|
+
*
|
|
2688
|
+
* Streaming a file from the disk and showing 404 when file is missing.
|
|
2689
|
+
*
|
|
2690
|
+
* @example
|
|
2691
|
+
* ```js
|
|
2692
|
+
* // Errors handled automatically with generic HTTP response
|
|
2693
|
+
* response.stream(fs.createReadStream('file.txt'))
|
|
2694
|
+
*
|
|
2695
|
+
* // Manually handle (note the await call)
|
|
2696
|
+
* try {
|
|
2697
|
+
* await response.stream(fs.createReadStream('file.txt'))
|
|
2698
|
+
* } catch () {
|
|
2699
|
+
* response.status(404).send('File not found')
|
|
2700
|
+
* }
|
|
2701
|
+
* ```
|
|
2702
|
+
*/
|
|
2703
|
+
stream(body, errorCallback) {
|
|
2704
|
+
if (typeof body.pipe !== "function" || !body.readable || typeof body.read !== "function") {
|
|
2705
|
+
throw new TypeError("response.stream accepts a readable stream only");
|
|
2706
|
+
}
|
|
2707
|
+
this.lazyBody.stream = [body, errorCallback];
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* Download file by streaming it from the file path. This method will setup
|
|
2711
|
+
* appropriate `Content-type`, `Content-type` and `Last-modified` headers.
|
|
2712
|
+
*
|
|
2713
|
+
* Unexpected stream errors are handled gracefully to avoid memory leaks.
|
|
2714
|
+
*
|
|
2715
|
+
* If `raiseErrors=false`, then this method will self handle all the exceptions by
|
|
2716
|
+
* writing a generic HTTP response. To have more control over the error, it is
|
|
2717
|
+
* recommended to set `raiseErrors=true` and wrap this function inside a
|
|
2718
|
+
* `try/catch` statement.
|
|
2719
|
+
*
|
|
2720
|
+
* @example
|
|
2721
|
+
* ```js
|
|
2722
|
+
* // Errors handled automatically with generic HTTP response
|
|
2723
|
+
* response.download('somefile.jpg')
|
|
2724
|
+
*
|
|
2725
|
+
* // Manually handle (note the await call)
|
|
2726
|
+
* try {
|
|
2727
|
+
* await response.download('somefile.jpg')
|
|
2728
|
+
* } catch (error) {
|
|
2729
|
+
* response.status(error.code === 'ENOENT' ? 404 : 500)
|
|
2730
|
+
* response.send('Cannot process file')
|
|
2731
|
+
* }
|
|
2732
|
+
* ```
|
|
2733
|
+
*/
|
|
2734
|
+
download(filePath, generateEtag = this.#config.etag, errorCallback) {
|
|
2735
|
+
this.lazyBody.fileToStream = [filePath, generateEtag, errorCallback];
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Download the file by forcing the user to save the file vs displaying it
|
|
2739
|
+
* within the browser.
|
|
2740
|
+
*
|
|
2741
|
+
* Internally calls [[download]]
|
|
2742
|
+
*/
|
|
2743
|
+
attachment(filePath, name, disposition, generateEtag, errorCallback) {
|
|
2744
|
+
name = name || filePath;
|
|
2745
|
+
this.header("Content-Disposition", contentDisposition(name, { type: disposition }));
|
|
2746
|
+
return this.download(filePath, generateEtag, errorCallback);
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Set the location header.
|
|
2750
|
+
*
|
|
2751
|
+
* @example
|
|
2752
|
+
* ```js
|
|
2753
|
+
* response.location('/login')
|
|
2754
|
+
* ```
|
|
2755
|
+
*/
|
|
2756
|
+
location(url) {
|
|
2757
|
+
this.header("Location", url);
|
|
2758
|
+
return this;
|
|
2759
|
+
}
|
|
2760
|
+
redirect(path, forwardQueryString = false, statusCode = 302) {
|
|
2761
|
+
const handler = new Redirect(this.request, this, this.#router, this.#qs);
|
|
2762
|
+
if (forwardQueryString) {
|
|
2763
|
+
handler.withQs();
|
|
2764
|
+
}
|
|
2765
|
+
if (path === "back") {
|
|
2766
|
+
return handler.status(statusCode).back();
|
|
2767
|
+
}
|
|
2768
|
+
if (path) {
|
|
2769
|
+
return handler.status(statusCode).toPath(path);
|
|
2770
|
+
}
|
|
2771
|
+
return handler;
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Abort the request with custom body and a status code. 400 is
|
|
2775
|
+
* used when status is not defined
|
|
2776
|
+
*/
|
|
2777
|
+
abort(body, status) {
|
|
2778
|
+
throw E_HTTP_REQUEST_ABORTED.invoke(body, status || 400);
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Abort the request with custom body and a status code when
|
|
2782
|
+
* passed condition returns `true`
|
|
2783
|
+
*/
|
|
2784
|
+
abortIf(condition, body, status) {
|
|
2785
|
+
if (condition) {
|
|
2786
|
+
this.abort(body, status);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Abort the request with custom body and a status code when
|
|
2791
|
+
* passed condition returns `false`
|
|
2792
|
+
*/
|
|
2793
|
+
abortUnless(condition, body, status) {
|
|
2794
|
+
if (!condition) {
|
|
2795
|
+
this.abort(body, status);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Set signed cookie as the response header. The inline options overrides
|
|
2800
|
+
* all options from the config.
|
|
2801
|
+
*/
|
|
2802
|
+
cookie(key, value, options) {
|
|
2803
|
+
options = Object.assign({}, this.#config.cookie, options);
|
|
2804
|
+
const serialized = this.#cookieSerializer.sign(key, value, options);
|
|
2805
|
+
if (!serialized) {
|
|
2806
|
+
return this;
|
|
2807
|
+
}
|
|
2808
|
+
this.append("set-cookie", serialized);
|
|
2809
|
+
return this;
|
|
2810
|
+
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Set encrypted cookie as the response header. The inline options overrides
|
|
2813
|
+
* all options from the config.
|
|
2814
|
+
*/
|
|
2815
|
+
encryptedCookie(key, value, options) {
|
|
2816
|
+
options = Object.assign({}, this.#config.cookie, options);
|
|
2817
|
+
const serialized = this.#cookieSerializer.encrypt(key, value, options);
|
|
2818
|
+
if (!serialized) {
|
|
2819
|
+
return this;
|
|
2820
|
+
}
|
|
2821
|
+
this.append("set-cookie", serialized);
|
|
2822
|
+
return this;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Set unsigned cookie as the response header. The inline options overrides
|
|
2826
|
+
* all options from the config.
|
|
2827
|
+
*/
|
|
2828
|
+
plainCookie(key, value, options) {
|
|
2829
|
+
options = Object.assign({}, this.#config.cookie, options);
|
|
2830
|
+
const serialized = this.#cookieSerializer.encode(key, value, options);
|
|
2831
|
+
if (!serialized) {
|
|
2832
|
+
return this;
|
|
2833
|
+
}
|
|
2834
|
+
this.append("set-cookie", serialized);
|
|
2835
|
+
return this;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Clear existing cookie.
|
|
2839
|
+
*/
|
|
2840
|
+
clearCookie(key, options) {
|
|
2841
|
+
options = Object.assign({}, this.#config.cookie, options);
|
|
2842
|
+
options.expires = /* @__PURE__ */ new Date(1);
|
|
2843
|
+
options.maxAge = -1;
|
|
2844
|
+
const serialized = this.#cookieSerializer.encode(key, "", { ...options, encode: false });
|
|
2845
|
+
this.append("set-cookie", serialized);
|
|
2846
|
+
return this;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Finishes the response by writing the lazy body, when `explicitEnd = true`
|
|
2850
|
+
* and response is already pending.
|
|
2851
|
+
*
|
|
2852
|
+
* Calling this method twice or when `explicitEnd = false` is noop.
|
|
2853
|
+
*/
|
|
2854
|
+
finish() {
|
|
2855
|
+
if (!this.isPending) {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
if (this.content) {
|
|
2859
|
+
this.writeBody(...this.content);
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
if (this.lazyBody.stream) {
|
|
2863
|
+
this.streamBody(...this.lazyBody.stream);
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
if (this.lazyBody.fileToStream) {
|
|
2867
|
+
this.streamFileForDownload(...this.lazyBody.fileToStream);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
this.#endResponse();
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Shorthand method to finish request with "100" status code
|
|
2874
|
+
*/
|
|
2875
|
+
continue() {
|
|
2876
|
+
this.status(100);
|
|
2877
|
+
return this.send(null, false);
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Shorthand method to finish request with "101" status code
|
|
2881
|
+
*/
|
|
2882
|
+
switchingProtocols() {
|
|
2883
|
+
this.status(101);
|
|
2884
|
+
return this.send(null, false);
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Shorthand method to finish request with "200" status code
|
|
2888
|
+
*/
|
|
2889
|
+
ok(body, generateEtag) {
|
|
2890
|
+
this.status(200);
|
|
2891
|
+
return this.send(body, generateEtag);
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Shorthand method to finish request with "201" status code
|
|
2895
|
+
*/
|
|
2896
|
+
created(body, generateEtag) {
|
|
2897
|
+
this.status(201);
|
|
2898
|
+
return this.send(body, generateEtag);
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Shorthand method to finish request with "202" status code
|
|
2902
|
+
*/
|
|
2903
|
+
accepted(body, generateEtag) {
|
|
2904
|
+
this.status(202);
|
|
2905
|
+
return this.send(body, generateEtag);
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Shorthand method to finish request with "203" status code
|
|
2909
|
+
*/
|
|
2910
|
+
nonAuthoritativeInformation(body, generateEtag) {
|
|
2911
|
+
this.status(203);
|
|
2912
|
+
return this.send(body, generateEtag);
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Shorthand method to finish request with "204" status code
|
|
2916
|
+
*/
|
|
2917
|
+
noContent() {
|
|
2918
|
+
this.status(204);
|
|
2919
|
+
return this.send(null, false);
|
|
2920
|
+
}
|
|
2921
|
+
/**
|
|
2922
|
+
* Shorthand method to finish request with "205" status code
|
|
2923
|
+
*/
|
|
2924
|
+
resetContent() {
|
|
2925
|
+
this.status(205);
|
|
2926
|
+
return this.send(null, false);
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Shorthand method to finish request with "206" status code
|
|
2930
|
+
*/
|
|
2931
|
+
partialContent(body, generateEtag) {
|
|
2932
|
+
this.status(206);
|
|
2933
|
+
return this.send(body, generateEtag);
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Shorthand method to finish request with "300" status code
|
|
2937
|
+
*/
|
|
2938
|
+
multipleChoices(body, generateEtag) {
|
|
2939
|
+
this.status(300);
|
|
2940
|
+
return this.send(body, generateEtag);
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Shorthand method to finish request with "301" status code
|
|
2944
|
+
*/
|
|
2945
|
+
movedPermanently(body, generateEtag) {
|
|
2946
|
+
this.status(301);
|
|
2947
|
+
return this.send(body, generateEtag);
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Shorthand method to finish request with "302" status code
|
|
2951
|
+
*/
|
|
2952
|
+
movedTemporarily(body, generateEtag) {
|
|
2953
|
+
this.status(302);
|
|
2954
|
+
return this.send(body, generateEtag);
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Shorthand method to finish request with "303" status code
|
|
2958
|
+
*/
|
|
2959
|
+
seeOther(body, generateEtag) {
|
|
2960
|
+
this.status(303);
|
|
2961
|
+
return this.send(body, generateEtag);
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Shorthand method to finish request with "304" status code
|
|
2965
|
+
*/
|
|
2966
|
+
notModified(body, generateEtag) {
|
|
2967
|
+
this.status(304);
|
|
2968
|
+
return this.send(body, generateEtag);
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Shorthand method to finish request with "305" status code
|
|
2972
|
+
*/
|
|
2973
|
+
useProxy(body, generateEtag) {
|
|
2974
|
+
this.status(305);
|
|
2975
|
+
return this.send(body, generateEtag);
|
|
2976
|
+
}
|
|
2977
|
+
/**
|
|
2978
|
+
* Shorthand method to finish request with "307" status code
|
|
2979
|
+
*/
|
|
2980
|
+
temporaryRedirect(body, generateEtag) {
|
|
2981
|
+
this.status(307);
|
|
2982
|
+
return this.send(body, generateEtag);
|
|
2983
|
+
}
|
|
2984
|
+
/**
|
|
2985
|
+
* Shorthand method to finish request with "400" status code
|
|
2986
|
+
*/
|
|
2987
|
+
badRequest(body, generateEtag) {
|
|
2988
|
+
this.status(400);
|
|
2989
|
+
return this.send(body, generateEtag);
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Shorthand method to finish request with "401" status code
|
|
2993
|
+
*/
|
|
2994
|
+
unauthorized(body, generateEtag) {
|
|
2995
|
+
this.status(401);
|
|
2996
|
+
return this.send(body, generateEtag);
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Shorthand method to finish request with "402" status code
|
|
3000
|
+
*/
|
|
3001
|
+
paymentRequired(body, generateEtag) {
|
|
3002
|
+
this.status(402);
|
|
3003
|
+
return this.send(body, generateEtag);
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Shorthand method to finish request with "403" status code
|
|
3007
|
+
*/
|
|
3008
|
+
forbidden(body, generateEtag) {
|
|
3009
|
+
this.status(403);
|
|
3010
|
+
return this.send(body, generateEtag);
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* Shorthand method to finish request with "404" status code
|
|
3014
|
+
*/
|
|
3015
|
+
notFound(body, generateEtag) {
|
|
3016
|
+
this.status(404);
|
|
3017
|
+
return this.send(body, generateEtag);
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Shorthand method to finish request with "405" status code
|
|
3021
|
+
*/
|
|
3022
|
+
methodNotAllowed(body, generateEtag) {
|
|
3023
|
+
this.status(405);
|
|
3024
|
+
return this.send(body, generateEtag);
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Shorthand method to finish request with "406" status code
|
|
3028
|
+
*/
|
|
3029
|
+
notAcceptable(body, generateEtag) {
|
|
3030
|
+
this.status(406);
|
|
3031
|
+
return this.send(body, generateEtag);
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Shorthand method to finish request with "407" status code
|
|
3035
|
+
*/
|
|
3036
|
+
proxyAuthenticationRequired(body, generateEtag) {
|
|
3037
|
+
this.status(407);
|
|
3038
|
+
return this.send(body, generateEtag);
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Shorthand method to finish request with "408" status code
|
|
3042
|
+
*/
|
|
3043
|
+
requestTimeout(body, generateEtag) {
|
|
3044
|
+
this.status(408);
|
|
3045
|
+
return this.send(body, generateEtag);
|
|
3046
|
+
}
|
|
3047
|
+
/**
|
|
3048
|
+
* Shorthand method to finish request with "409" status code
|
|
3049
|
+
*/
|
|
3050
|
+
conflict(body, generateEtag) {
|
|
3051
|
+
this.status(409);
|
|
3052
|
+
return this.send(body, generateEtag);
|
|
3053
|
+
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Shorthand method to finish request with "401" status code
|
|
3056
|
+
*/
|
|
3057
|
+
gone(body, generateEtag) {
|
|
3058
|
+
this.status(410);
|
|
3059
|
+
return this.send(body, generateEtag);
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Shorthand method to finish request with "411" status code
|
|
3063
|
+
*/
|
|
3064
|
+
lengthRequired(body, generateEtag) {
|
|
3065
|
+
this.status(411);
|
|
3066
|
+
return this.send(body, generateEtag);
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Shorthand method to finish request with "412" status code
|
|
3070
|
+
*/
|
|
3071
|
+
preconditionFailed(body, generateEtag) {
|
|
3072
|
+
this.status(412);
|
|
3073
|
+
return this.send(body, generateEtag);
|
|
3074
|
+
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Shorthand method to finish request with "413" status code
|
|
3077
|
+
*/
|
|
3078
|
+
requestEntityTooLarge(body, generateEtag) {
|
|
3079
|
+
this.status(413);
|
|
3080
|
+
return this.send(body, generateEtag);
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* Shorthand method to finish request with "414" status code
|
|
3084
|
+
*/
|
|
3085
|
+
requestUriTooLong(body, generateEtag) {
|
|
3086
|
+
this.status(414);
|
|
3087
|
+
return this.send(body, generateEtag);
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Shorthand method to finish request with "415" status code
|
|
3091
|
+
*/
|
|
3092
|
+
unsupportedMediaType(body, generateEtag) {
|
|
3093
|
+
this.status(415);
|
|
3094
|
+
return this.send(body, generateEtag);
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
* Shorthand method to finish request with "416" status code
|
|
3098
|
+
*/
|
|
3099
|
+
requestedRangeNotSatisfiable(body, generateEtag) {
|
|
3100
|
+
this.status(416);
|
|
3101
|
+
return this.send(body, generateEtag);
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Shorthand method to finish request with "417" status code
|
|
3105
|
+
*/
|
|
3106
|
+
expectationFailed(body, generateEtag) {
|
|
3107
|
+
this.status(417);
|
|
3108
|
+
return this.send(body, generateEtag);
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Shorthand method to finish request with "422" status code
|
|
3112
|
+
*/
|
|
3113
|
+
unprocessableEntity(body, generateEtag) {
|
|
3114
|
+
this.status(422);
|
|
3115
|
+
return this.send(body, generateEtag);
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Shorthand method to finish request with "429" status code
|
|
3119
|
+
*/
|
|
3120
|
+
tooManyRequests(body, generateEtag) {
|
|
3121
|
+
this.status(429);
|
|
3122
|
+
return this.send(body, generateEtag);
|
|
3123
|
+
}
|
|
3124
|
+
/**
|
|
3125
|
+
* Shorthand method to finish request with "500" status code
|
|
3126
|
+
*/
|
|
3127
|
+
internalServerError(body, generateEtag) {
|
|
3128
|
+
this.status(500);
|
|
3129
|
+
return this.send(body, generateEtag);
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Shorthand method to finish request with "501" status code
|
|
3133
|
+
*/
|
|
3134
|
+
notImplemented(body, generateEtag) {
|
|
3135
|
+
this.status(501);
|
|
3136
|
+
return this.send(body, generateEtag);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Shorthand method to finish request with "502" status code
|
|
3140
|
+
*/
|
|
3141
|
+
badGateway(body, generateEtag) {
|
|
3142
|
+
this.status(502);
|
|
3143
|
+
return this.send(body, generateEtag);
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Shorthand method to finish request with "503" status code
|
|
3147
|
+
*/
|
|
3148
|
+
serviceUnavailable(body, generateEtag) {
|
|
3149
|
+
this.status(503);
|
|
3150
|
+
return this.send(body, generateEtag);
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Shorthand method to finish request with "504" status code
|
|
3154
|
+
*/
|
|
3155
|
+
gatewayTimeout(body, generateEtag) {
|
|
3156
|
+
this.status(504);
|
|
3157
|
+
return this.send(body, generateEtag);
|
|
3158
|
+
}
|
|
3159
|
+
/**
|
|
3160
|
+
* Shorthand method to finish request with "505" status code
|
|
3161
|
+
*/
|
|
3162
|
+
httpVersionNotSupported(body, generateEtag) {
|
|
3163
|
+
this.status(505);
|
|
3164
|
+
return this.send(body, generateEtag);
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
// src/router/main.ts
|
|
3169
|
+
import is3 from "@sindresorhus/is";
|
|
3170
|
+
import { moduleImporter as moduleImporter3 } from "@adonisjs/fold";
|
|
3171
|
+
import { RuntimeException as RuntimeException6 } from "@poppinss/utils";
|
|
3172
|
+
|
|
3173
|
+
// src/router/store.ts
|
|
3174
|
+
import matchit2 from "@poppinss/matchit";
|
|
3175
|
+
import lodash2 from "@poppinss/utils/lodash";
|
|
3176
|
+
import { RuntimeException as RuntimeException4 } from "@poppinss/utils";
|
|
3177
|
+
|
|
3178
|
+
// src/router/parser.ts
|
|
3179
|
+
import matchit from "@poppinss/matchit";
|
|
3180
|
+
function parseRoutePattern(pattern, matchers) {
|
|
3181
|
+
const tokens = matchit.parse(pattern, matchers);
|
|
3182
|
+
return tokens;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// src/router/store.ts
|
|
3186
|
+
var RoutesStore = class {
|
|
3187
|
+
/**
|
|
3188
|
+
* A flag to know if routes for explicit domains
|
|
3189
|
+
* have been registered
|
|
3190
|
+
*/
|
|
3191
|
+
usingDomains = false;
|
|
3192
|
+
/**
|
|
3193
|
+
* Tree of registered routes and their matchit tokens
|
|
3194
|
+
*/
|
|
3195
|
+
tree = { tokens: [], domains: {} };
|
|
3196
|
+
/**
|
|
3197
|
+
* Returns the domain node for a given domain.
|
|
3198
|
+
*/
|
|
3199
|
+
#getDomainNode(domain) {
|
|
3200
|
+
if (!this.tree.domains[domain]) {
|
|
3201
|
+
this.tree.tokens.push(parseRoutePattern(domain));
|
|
3202
|
+
this.tree.domains[domain] = {};
|
|
3203
|
+
}
|
|
3204
|
+
return this.tree.domains[domain];
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Returns the method node for a given domain and method.
|
|
3208
|
+
*/
|
|
3209
|
+
#getMethodNode(domain, method) {
|
|
3210
|
+
const domainNode = this.#getDomainNode(domain);
|
|
3211
|
+
if (!domainNode[method]) {
|
|
3212
|
+
domainNode[method] = { tokens: [], routes: {}, routeKeys: {} };
|
|
3213
|
+
}
|
|
3214
|
+
return domainNode[method];
|
|
3215
|
+
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Collects route params
|
|
3218
|
+
*/
|
|
3219
|
+
#collectRouteParams(route, tokens) {
|
|
3220
|
+
const collectedParams = /* @__PURE__ */ new Set();
|
|
3221
|
+
for (let token of tokens) {
|
|
3222
|
+
if ([1, 3].includes(token.type)) {
|
|
3223
|
+
if (collectedParams.has(token.val)) {
|
|
3224
|
+
throw new RuntimeException4(`Duplicate param "${token.val}" found in "${route.pattern}"`);
|
|
3225
|
+
} else {
|
|
3226
|
+
collectedParams.add(token.val);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
const params = [...collectedParams];
|
|
3231
|
+
collectedParams.clear();
|
|
3232
|
+
return params;
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Register route for a given domain and method
|
|
3236
|
+
*/
|
|
3237
|
+
#registerRoute(domain, method, tokens, route) {
|
|
3238
|
+
const methodRoutes = this.#getMethodNode(domain, method);
|
|
3239
|
+
if (methodRoutes.routes[route.pattern]) {
|
|
3240
|
+
throw new RuntimeException4(
|
|
3241
|
+
`Duplicate route found. "${method}: ${route.pattern}" route already exists`
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
if (debug_default.enabled) {
|
|
3245
|
+
debug_default("registering route to the store %O", route);
|
|
3246
|
+
debug_default("route middleware %O", route.middleware.all().entries());
|
|
3247
|
+
}
|
|
3248
|
+
methodRoutes.tokens.push(tokens);
|
|
3249
|
+
methodRoutes.routes[route.pattern] = route;
|
|
3250
|
+
methodRoutes.routeKeys[route.pattern] = domain !== "root" ? `${domain}-${method}-${route.pattern}` : `${method}-${route.pattern}`;
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Add a route to the store
|
|
3254
|
+
*
|
|
3255
|
+
* ```ts
|
|
3256
|
+
* store.add({
|
|
3257
|
+
* pattern: 'post/:id',
|
|
3258
|
+
* methods: ['GET'],
|
|
3259
|
+
* matchers: {},
|
|
3260
|
+
* meta: {},
|
|
3261
|
+
* handler: function handler () {
|
|
3262
|
+
* }
|
|
3263
|
+
* })
|
|
3264
|
+
* ```
|
|
3265
|
+
*/
|
|
3266
|
+
add(route) {
|
|
3267
|
+
if (route.domain !== "root") {
|
|
3268
|
+
this.usingDomains = true;
|
|
3269
|
+
}
|
|
3270
|
+
const tokens = parseRoutePattern(route.pattern, route.matchers);
|
|
3271
|
+
const routeNode = lodash2.merge(
|
|
3272
|
+
{ meta: {} },
|
|
3273
|
+
lodash2.pick(route, ["pattern", "handler", "meta", "middleware", "name", "execute"])
|
|
3274
|
+
);
|
|
3275
|
+
routeNode.meta.params = this.#collectRouteParams(routeNode, tokens);
|
|
3276
|
+
route.methods.forEach((method) => {
|
|
3277
|
+
this.#registerRoute(route.domain, method, tokens, routeNode);
|
|
3278
|
+
});
|
|
3279
|
+
return this;
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Matches the url, method and optionally domain to pull the matching
|
|
3283
|
+
* route. `null` is returned when unable to match the URL against
|
|
3284
|
+
* registered routes.
|
|
3285
|
+
*
|
|
3286
|
+
* The domain parameter has to be a registered pattern and not the fully
|
|
3287
|
+
* qualified runtime domain. You must call `matchDomain` first to fetch
|
|
3288
|
+
* the pattern for qualified domain
|
|
3289
|
+
*/
|
|
3290
|
+
match(url, method, domain) {
|
|
3291
|
+
const domainName = domain?.tokens[0]?.old || "root";
|
|
3292
|
+
const matchedDomain = this.tree.domains[domainName];
|
|
3293
|
+
if (!matchedDomain) {
|
|
3294
|
+
return null;
|
|
3295
|
+
}
|
|
3296
|
+
const matchedMethod = this.tree.domains[domainName][method];
|
|
3297
|
+
if (!matchedMethod) {
|
|
3298
|
+
return null;
|
|
3299
|
+
}
|
|
3300
|
+
const matchedRoute = matchit2.match(url, matchedMethod.tokens);
|
|
3301
|
+
if (!matchedRoute.length) {
|
|
3302
|
+
return null;
|
|
3303
|
+
}
|
|
3304
|
+
const route = matchedMethod.routes[matchedRoute[0].old];
|
|
3305
|
+
return {
|
|
3306
|
+
route,
|
|
3307
|
+
routeKey: matchedMethod.routeKeys[route.pattern],
|
|
3308
|
+
params: matchit2.exec(url, matchedRoute),
|
|
3309
|
+
subdomains: domain?.hostname ? matchit2.exec(domain.hostname, domain.tokens) : {}
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Match hostname against registered domains.
|
|
3314
|
+
*/
|
|
3315
|
+
matchDomain(hostname) {
|
|
3316
|
+
if (!hostname || !this.usingDomains) {
|
|
3317
|
+
return [];
|
|
3318
|
+
}
|
|
3319
|
+
return matchit2.match(hostname, this.tree.tokens);
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
|
|
3323
|
+
// src/router/lookup_store/url_builder.ts
|
|
3324
|
+
import { RuntimeException as RuntimeException5 } from "@poppinss/utils";
|
|
3325
|
+
var UrlBuilder = class {
|
|
3326
|
+
/**
|
|
3327
|
+
* Query string parser
|
|
3328
|
+
*/
|
|
3329
|
+
#qsParser;
|
|
3330
|
+
/**
|
|
3331
|
+
* The parameters to apply on the route
|
|
3332
|
+
*/
|
|
3333
|
+
#params = {};
|
|
3334
|
+
/**
|
|
3335
|
+
* Query string to append to the route
|
|
3336
|
+
*/
|
|
3337
|
+
#qs = {};
|
|
3338
|
+
/**
|
|
3339
|
+
* Should we perform the route lookup or just build the
|
|
3340
|
+
* given pattern as it is.
|
|
3341
|
+
*/
|
|
3342
|
+
#shouldPerformLookup = true;
|
|
3343
|
+
/**
|
|
3344
|
+
* BaseURL to append to the constructored URL
|
|
3345
|
+
*/
|
|
3346
|
+
#baseUrl;
|
|
3347
|
+
/**
|
|
3348
|
+
* Encryption class for making signed URLs
|
|
3349
|
+
*/
|
|
3350
|
+
#encryption;
|
|
3351
|
+
/**
|
|
3352
|
+
* Route finder for finding route pattern
|
|
3353
|
+
*/
|
|
3354
|
+
#routeFinder;
|
|
3355
|
+
constructor(encryption, routeFinder, qsParser) {
|
|
3356
|
+
this.#qsParser = qsParser;
|
|
3357
|
+
this.#encryption = encryption;
|
|
3358
|
+
this.#routeFinder = routeFinder;
|
|
3359
|
+
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Raises exception when wildcard values array is missing or
|
|
3362
|
+
* has length of zero.
|
|
3363
|
+
*/
|
|
3364
|
+
#ensureHasWildCardValues(pattern, values) {
|
|
3365
|
+
if (!values || !Array.isArray(values) || !values.length) {
|
|
3366
|
+
throw new RuntimeException5(
|
|
3367
|
+
`Cannot make URL for "${pattern}" route. Invalid value provided for wildcard param`
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
/*
|
|
3372
|
+
* Raises exception when value is not defined
|
|
3373
|
+
*/
|
|
3374
|
+
#ensureHasParamValue(pattern, param, value) {
|
|
3375
|
+
if (value === void 0 || value === null) {
|
|
3376
|
+
throw new RuntimeException5(
|
|
3377
|
+
`Cannot make URL for "${pattern}" route. Missing value for "${param}" param`
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Processes the pattern against the params
|
|
3383
|
+
*/
|
|
3384
|
+
#processPattern(pattern) {
|
|
3385
|
+
const uriSegments = [];
|
|
3386
|
+
const paramsArray = Array.isArray(this.#params) ? this.#params : null;
|
|
3387
|
+
const paramsObject = !Array.isArray(this.#params) ? this.#params : {};
|
|
3388
|
+
let paramsIndex = 0;
|
|
3389
|
+
const tokens = parseRoutePattern(pattern);
|
|
3390
|
+
for (const token of tokens) {
|
|
3391
|
+
if (token.type === 0) {
|
|
3392
|
+
uriSegments.push(token.val === "/" ? "" : `${token.val}${token.end}`);
|
|
3393
|
+
} else if (token.type === 2) {
|
|
3394
|
+
const values = paramsArray ? paramsArray.slice(paramsIndex) : paramsObject["*"];
|
|
3395
|
+
this.#ensureHasWildCardValues(pattern, values);
|
|
3396
|
+
uriSegments.push(`${values.join("/")}${token.end}`);
|
|
3397
|
+
break;
|
|
3398
|
+
} else {
|
|
3399
|
+
const paramName = token.val;
|
|
3400
|
+
const value = paramsArray ? paramsArray[paramsIndex] : paramsObject[paramName];
|
|
3401
|
+
if (token.type === 1) {
|
|
3402
|
+
this.#ensureHasParamValue(pattern, paramName, value);
|
|
3403
|
+
}
|
|
3404
|
+
paramsIndex++;
|
|
3405
|
+
if (value !== void 0 && value !== null) {
|
|
3406
|
+
uriSegments.push(`${value}${token.end}`);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return `/${uriSegments.join("/")}`;
|
|
3411
|
+
}
|
|
3412
|
+
/**
|
|
3413
|
+
* Suffix the query string to the URL
|
|
3414
|
+
*/
|
|
3415
|
+
#suffixQueryString(url, qs) {
|
|
3416
|
+
if (qs) {
|
|
3417
|
+
const queryString = this.#qsParser.stringify(qs);
|
|
3418
|
+
url = queryString ? `${url}?${queryString}` : url;
|
|
3419
|
+
}
|
|
3420
|
+
return url;
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Prefixes base URL to the uri string
|
|
3424
|
+
*/
|
|
3425
|
+
#prefixBaseUrl(uri) {
|
|
3426
|
+
return this.#baseUrl ? `${this.#baseUrl}${uri}` : uri;
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Prefix a custom base URL to the final URI
|
|
3430
|
+
*/
|
|
3431
|
+
prefixUrl(url) {
|
|
3432
|
+
this.#baseUrl = url;
|
|
3433
|
+
return this;
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* Disable route lookup. Calling this method considers
|
|
3437
|
+
* the "identifier" as the route pattern
|
|
3438
|
+
*/
|
|
3439
|
+
disableRouteLookup() {
|
|
3440
|
+
this.#shouldPerformLookup = false;
|
|
3441
|
+
return this;
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Append query string to the final URI
|
|
3445
|
+
*/
|
|
3446
|
+
qs(queryString) {
|
|
3447
|
+
if (!queryString) {
|
|
3448
|
+
return this;
|
|
3449
|
+
}
|
|
3450
|
+
this.#qs = queryString;
|
|
3451
|
+
return this;
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Specify params to apply to the route pattern
|
|
3455
|
+
*/
|
|
3456
|
+
params(params) {
|
|
3457
|
+
if (!params) {
|
|
3458
|
+
return this;
|
|
3459
|
+
}
|
|
3460
|
+
this.#params = params;
|
|
3461
|
+
return this;
|
|
3462
|
+
}
|
|
3463
|
+
/**
|
|
3464
|
+
* Generate URL for the given route identifier. The identifier can be the
|
|
3465
|
+
* route name, controller.method name or the route pattern
|
|
3466
|
+
* itself.
|
|
3467
|
+
*/
|
|
3468
|
+
make(identifier) {
|
|
3469
|
+
let url;
|
|
3470
|
+
if (this.#shouldPerformLookup) {
|
|
3471
|
+
const route = this.#routeFinder.findOrFail(identifier);
|
|
3472
|
+
url = this.#processPattern(route.pattern);
|
|
3473
|
+
} else {
|
|
3474
|
+
url = this.#processPattern(identifier);
|
|
3475
|
+
}
|
|
3476
|
+
return this.#suffixQueryString(this.#prefixBaseUrl(url), this.#qs);
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Generate a signed URL for the given route identifier. The identifier can be the
|
|
3480
|
+
* route name, controller.method name or the route pattern
|
|
3481
|
+
* itself.
|
|
3482
|
+
*/
|
|
3483
|
+
makeSigned(identifier, options) {
|
|
3484
|
+
let url;
|
|
3485
|
+
if (this.#shouldPerformLookup) {
|
|
3486
|
+
const route = this.#routeFinder.findOrFail(identifier);
|
|
3487
|
+
url = this.#processPattern(route.pattern);
|
|
3488
|
+
} else {
|
|
3489
|
+
url = this.#processPattern(identifier);
|
|
3490
|
+
}
|
|
3491
|
+
const signature = this.#encryption.verifier.sign(
|
|
3492
|
+
this.#suffixQueryString(url, this.#qs),
|
|
3493
|
+
options?.expiresIn,
|
|
3494
|
+
options?.purpose
|
|
3495
|
+
);
|
|
3496
|
+
const qs = Object.assign({}, this.#qs, { signature });
|
|
3497
|
+
return this.#suffixQueryString(this.#prefixBaseUrl(url), qs);
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3501
|
+
// src/router/lookup_store/route_finder.ts
|
|
3502
|
+
var RouteFinder = class {
|
|
3503
|
+
#routes = [];
|
|
3504
|
+
register(route) {
|
|
3505
|
+
this.#routes.push(route);
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Find a route by indentifier
|
|
3509
|
+
*/
|
|
3510
|
+
find(routeIdentifier) {
|
|
3511
|
+
return this.#routes.find(({ name, pattern, handler }) => {
|
|
3512
|
+
if (name === routeIdentifier || pattern === routeIdentifier) {
|
|
3513
|
+
return true;
|
|
3514
|
+
}
|
|
3515
|
+
if (typeof handler === "function") {
|
|
3516
|
+
return false;
|
|
3517
|
+
}
|
|
3518
|
+
return handler.reference === routeIdentifier;
|
|
3519
|
+
}) || null;
|
|
3520
|
+
}
|
|
3521
|
+
/**
|
|
3522
|
+
* Find a route by indentifier or fail
|
|
3523
|
+
*/
|
|
3524
|
+
findOrFail(routeIdentifier) {
|
|
3525
|
+
const route = this.find(routeIdentifier);
|
|
3526
|
+
if (!route) {
|
|
3527
|
+
throw new E_CANNOT_LOOKUP_ROUTE([routeIdentifier]);
|
|
3528
|
+
}
|
|
3529
|
+
return route;
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Find if a route exists
|
|
3533
|
+
*/
|
|
3534
|
+
has(routeIdentifier) {
|
|
3535
|
+
return !!this.find(routeIdentifier);
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Returns an array of registered routes
|
|
3539
|
+
*/
|
|
3540
|
+
toJSON() {
|
|
3541
|
+
return this.#routes;
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
|
|
3545
|
+
// src/router/lookup_store/main.ts
|
|
3546
|
+
var LookupStore = class {
|
|
3547
|
+
/**
|
|
3548
|
+
* List of route finders grouped by domains
|
|
3549
|
+
*/
|
|
3550
|
+
#routes = {};
|
|
3551
|
+
/**
|
|
3552
|
+
* Encryption for making URLs
|
|
3553
|
+
*/
|
|
3554
|
+
#encryption;
|
|
3555
|
+
/**
|
|
3556
|
+
* Query string parser for making URLs
|
|
3557
|
+
*/
|
|
3558
|
+
#qsParser;
|
|
3559
|
+
constructor(encryption, qsParser) {
|
|
3560
|
+
this.#encryption = encryption;
|
|
3561
|
+
this.#qsParser = qsParser;
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Register route JSON payload
|
|
3565
|
+
*/
|
|
3566
|
+
register(route) {
|
|
3567
|
+
this.#routes[route.domain] = this.#routes[route.domain] || new RouteFinder();
|
|
3568
|
+
this.#routes[route.domain].register(route);
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Returns an instance of the URL builder for making
|
|
3572
|
+
* route URIs
|
|
3573
|
+
*/
|
|
3574
|
+
builder() {
|
|
3575
|
+
return this.builderForDomain("root");
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Returns an instance of the URL builder for a specific
|
|
3579
|
+
* domain.
|
|
3580
|
+
*/
|
|
3581
|
+
builderForDomain(domain) {
|
|
3582
|
+
const finder = this.#routes[domain];
|
|
3583
|
+
return new UrlBuilder(this.#encryption, finder || new RouteFinder(), this.#qsParser);
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Finds a route by its identifier. The identifier can be the
|
|
3587
|
+
* route name, controller.method name or the route pattern
|
|
3588
|
+
* itself.
|
|
3589
|
+
*/
|
|
3590
|
+
find(routeIdentifier, domain) {
|
|
3591
|
+
const finder = this.#routes[domain || "root"];
|
|
3592
|
+
if (!finder) {
|
|
3593
|
+
return null;
|
|
3594
|
+
}
|
|
3595
|
+
return finder.find(routeIdentifier);
|
|
3596
|
+
}
|
|
3597
|
+
/**
|
|
3598
|
+
* Finds a route by its identifier. The identifier can be the
|
|
3599
|
+
* route name, controller.method name or the route pattern
|
|
3600
|
+
* itself.
|
|
3601
|
+
*
|
|
3602
|
+
* An error is raised when unable to find the route.
|
|
3603
|
+
*/
|
|
3604
|
+
findOrFail(routeIdentifier, domain) {
|
|
3605
|
+
const finder = this.#routes[domain || "root"];
|
|
3606
|
+
if (!finder) {
|
|
3607
|
+
throw new E_CANNOT_LOOKUP_ROUTE([routeIdentifier]);
|
|
3608
|
+
}
|
|
3609
|
+
return finder.findOrFail(routeIdentifier);
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Check if a route exists. The identifier can be the
|
|
3613
|
+
* route name, controller.method name or the route pattern
|
|
3614
|
+
* itself.
|
|
3615
|
+
*/
|
|
3616
|
+
has(routeIdentifier, domain) {
|
|
3617
|
+
const finder = this.#routes[domain || "root"];
|
|
3618
|
+
if (!finder) {
|
|
3619
|
+
return false;
|
|
3620
|
+
}
|
|
3621
|
+
return finder.has(routeIdentifier);
|
|
3622
|
+
}
|
|
3623
|
+
toJSON() {
|
|
3624
|
+
return Object.keys(this.#routes).reduce((result, domain) => {
|
|
3625
|
+
result[domain] = this.#routes[domain].toJSON();
|
|
3626
|
+
return result;
|
|
3627
|
+
}, {});
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
|
|
3631
|
+
// src/router/matchers.ts
|
|
3632
|
+
import Macroable7 from "@poppinss/macroable";
|
|
3633
|
+
var RouteMatchers = class extends Macroable7 {
|
|
3634
|
+
/**
|
|
3635
|
+
* Enforce value to be a number and also casts it to number data
|
|
3636
|
+
* type
|
|
3637
|
+
*/
|
|
3638
|
+
number() {
|
|
3639
|
+
return { match: /^[0-9]+$/, cast: (value) => Number(value) };
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Enforce value to be formatted as uuid
|
|
3643
|
+
*/
|
|
3644
|
+
uuid() {
|
|
3645
|
+
return {
|
|
3646
|
+
match: /^[0-9a-zA-F]{8}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{12}$/,
|
|
3647
|
+
cast: (value) => value.toLowerCase()
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
/**
|
|
3651
|
+
* Enforce value to be formatted as slug
|
|
3652
|
+
*/
|
|
3653
|
+
slug() {
|
|
3654
|
+
return { match: /^[^\s-_](?!.*?[-_]{2,})([a-z0-9-\\]{1,})[^\s]*[^-_\s]$/ };
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
|
|
3658
|
+
// src/define_middleware.ts
|
|
3659
|
+
import { moduleImporter as moduleImporter2 } from "@adonisjs/fold";
|
|
3660
|
+
function middlewareReferenceBuilder(name, middleware) {
|
|
3661
|
+
const handler = moduleImporter2(middleware, "handle").toHandleMethod();
|
|
3662
|
+
return function(...args) {
|
|
3663
|
+
return {
|
|
3664
|
+
name,
|
|
3665
|
+
args: args[0],
|
|
3666
|
+
...handler
|
|
3667
|
+
};
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function defineNamedMiddleware(collection) {
|
|
3671
|
+
return Object.keys(collection).reduce(
|
|
3672
|
+
(result, key) => {
|
|
3673
|
+
result[key] = middlewareReferenceBuilder(key, collection[key]);
|
|
3674
|
+
return result;
|
|
3675
|
+
},
|
|
3676
|
+
{}
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
// src/router/main.ts
|
|
3681
|
+
var Router = class extends LookupStore {
|
|
3682
|
+
/**
|
|
3683
|
+
* Application is needed to resolve string based controller expressions
|
|
3684
|
+
*/
|
|
3685
|
+
#app;
|
|
3686
|
+
/**
|
|
3687
|
+
* Store with tokenized routes
|
|
3688
|
+
*/
|
|
3689
|
+
#store = new RoutesStore();
|
|
3690
|
+
/**
|
|
3691
|
+
* Global matchers to test route params against regular expressions.
|
|
3692
|
+
*/
|
|
3693
|
+
#globalMatchers = {};
|
|
3694
|
+
/**
|
|
3695
|
+
* Middleware store to be shared with the routes
|
|
3696
|
+
*/
|
|
3697
|
+
#middleware = [];
|
|
3698
|
+
/**
|
|
3699
|
+
* A boolean to tell the router that a group is in
|
|
3700
|
+
* open state right now
|
|
3701
|
+
*/
|
|
3702
|
+
#openedGroups = [];
|
|
3703
|
+
/**
|
|
3704
|
+
* Collection of routes, including route resource and route
|
|
3705
|
+
* group. To get a flat list of routes, call `router.toJSON()`
|
|
3706
|
+
*/
|
|
3707
|
+
routes = [];
|
|
3708
|
+
/**
|
|
3709
|
+
* A flag to know if routes for explicit domains have been registered.
|
|
3710
|
+
* The boolean is computed after calling the "commit" method.
|
|
3711
|
+
*/
|
|
3712
|
+
usingDomains = false;
|
|
3713
|
+
/**
|
|
3714
|
+
* Shortcut methods for commonly used route matchers
|
|
3715
|
+
*/
|
|
3716
|
+
matchers = new RouteMatchers();
|
|
3717
|
+
constructor(app, encryption, qsParser) {
|
|
3718
|
+
super(encryption, qsParser);
|
|
3719
|
+
this.#app = app;
|
|
3720
|
+
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Push a give router entity to the list of routes or the
|
|
3723
|
+
* recently opened group.
|
|
3724
|
+
*/
|
|
3725
|
+
#pushToRoutes(entity) {
|
|
3726
|
+
const openedGroup = this.#openedGroups[this.#openedGroups.length - 1];
|
|
3727
|
+
if (openedGroup) {
|
|
3728
|
+
openedGroup.routes.push(entity);
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
this.routes.push(entity);
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Parses the route pattern
|
|
3735
|
+
*/
|
|
3736
|
+
parsePattern(pattern, matchers) {
|
|
3737
|
+
return parseRoutePattern(pattern, matchers);
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Define an array of middleware to use on all the routes.
|
|
3741
|
+
* Calling this method multiple times pushes to the
|
|
3742
|
+
* existing list of middleware
|
|
3743
|
+
*/
|
|
3744
|
+
use(middleware) {
|
|
3745
|
+
middleware.forEach(
|
|
3746
|
+
(one) => this.#middleware.push(moduleImporter3(one, "handle").toHandleMethod())
|
|
3747
|
+
);
|
|
3748
|
+
return this;
|
|
3749
|
+
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Define a collection of named middleware. The defined collection is
|
|
3752
|
+
* not registered anywhere, but instead converted in a new collection
|
|
3753
|
+
* of functions you can apply on the routes, or router groups.
|
|
3754
|
+
*/
|
|
3755
|
+
named(collection) {
|
|
3756
|
+
return defineNamedMiddleware(collection);
|
|
3757
|
+
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Add route for a given pattern and methods
|
|
3760
|
+
*/
|
|
3761
|
+
route(pattern, methods, handler) {
|
|
3762
|
+
const route = new Route(this.#app, this.#middleware, {
|
|
3763
|
+
pattern,
|
|
3764
|
+
methods,
|
|
3765
|
+
handler,
|
|
3766
|
+
globalMatchers: this.#globalMatchers
|
|
3767
|
+
});
|
|
3768
|
+
this.#pushToRoutes(route);
|
|
3769
|
+
return route;
|
|
3770
|
+
}
|
|
3771
|
+
/**
|
|
3772
|
+
* Define a route that handles all common HTTP methods
|
|
3773
|
+
*/
|
|
3774
|
+
any(pattern, handler) {
|
|
3775
|
+
return this.route(
|
|
3776
|
+
pattern,
|
|
3777
|
+
["HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
3778
|
+
handler
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
/**
|
|
3782
|
+
* Define `GET` route
|
|
3783
|
+
*/
|
|
3784
|
+
get(pattern, handler) {
|
|
3785
|
+
return this.route(pattern, ["GET", "HEAD"], handler);
|
|
3786
|
+
}
|
|
3787
|
+
/**
|
|
3788
|
+
* Define `POST` route
|
|
3789
|
+
*/
|
|
3790
|
+
post(pattern, handler) {
|
|
3791
|
+
return this.route(pattern, ["POST"], handler);
|
|
3792
|
+
}
|
|
3793
|
+
/**
|
|
3794
|
+
* Define `PUT` route
|
|
3795
|
+
*/
|
|
3796
|
+
put(pattern, handler) {
|
|
3797
|
+
return this.route(pattern, ["PUT"], handler);
|
|
3798
|
+
}
|
|
3799
|
+
/**
|
|
3800
|
+
* Define `PATCH` route
|
|
3801
|
+
*/
|
|
3802
|
+
patch(pattern, handler) {
|
|
3803
|
+
return this.route(pattern, ["PATCH"], handler);
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Define `DELETE` route
|
|
3807
|
+
*/
|
|
3808
|
+
delete(pattern, handler) {
|
|
3809
|
+
return this.route(pattern, ["DELETE"], handler);
|
|
3810
|
+
}
|
|
3811
|
+
/**
|
|
3812
|
+
* Creates a group of routes. A route group can apply transforms
|
|
3813
|
+
* to routes in bulk
|
|
3814
|
+
*/
|
|
3815
|
+
group(callback) {
|
|
3816
|
+
const group = new RouteGroup([]);
|
|
3817
|
+
this.#pushToRoutes(group);
|
|
3818
|
+
this.#openedGroups.push(group);
|
|
3819
|
+
callback();
|
|
3820
|
+
this.#openedGroups.pop();
|
|
3821
|
+
return group;
|
|
3822
|
+
}
|
|
3823
|
+
/**
|
|
3824
|
+
* Registers a route resource with conventional set of routes
|
|
3825
|
+
*/
|
|
3826
|
+
resource(resource, controller) {
|
|
3827
|
+
const resourceInstance = new RouteResource(this.#app, this.#middleware, {
|
|
3828
|
+
resource,
|
|
3829
|
+
controller,
|
|
3830
|
+
shallow: false,
|
|
3831
|
+
globalMatchers: this.#globalMatchers
|
|
3832
|
+
});
|
|
3833
|
+
this.#pushToRoutes(resourceInstance);
|
|
3834
|
+
return resourceInstance;
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Register a route resource with shallow nested routes.
|
|
3838
|
+
*/
|
|
3839
|
+
shallowResource(resource, controller) {
|
|
3840
|
+
const resourceInstance = new RouteResource(this.#app, this.#middleware, {
|
|
3841
|
+
resource,
|
|
3842
|
+
controller,
|
|
3843
|
+
shallow: true,
|
|
3844
|
+
globalMatchers: this.#globalMatchers
|
|
3845
|
+
});
|
|
3846
|
+
this.#pushToRoutes(resourceInstance);
|
|
3847
|
+
return resourceInstance;
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Returns a brisk route instance for a given URL pattern
|
|
3851
|
+
*/
|
|
3852
|
+
on(pattern) {
|
|
3853
|
+
const briskRoute = new BriskRoute(this.#app, this.#middleware, {
|
|
3854
|
+
pattern,
|
|
3855
|
+
globalMatchers: this.#globalMatchers
|
|
3856
|
+
});
|
|
3857
|
+
this.#pushToRoutes(briskRoute);
|
|
3858
|
+
return briskRoute;
|
|
3859
|
+
}
|
|
3860
|
+
/**
|
|
3861
|
+
* Define matcher for a given param. The global params are applied
|
|
3862
|
+
* on all the routes (unless overridden at the route level).
|
|
3863
|
+
*/
|
|
3864
|
+
where(param, matcher) {
|
|
3865
|
+
if (typeof matcher === "string") {
|
|
3866
|
+
this.#globalMatchers[param] = { match: new RegExp(matcher) };
|
|
3867
|
+
} else if (is3.regExp(matcher)) {
|
|
3868
|
+
this.#globalMatchers[param] = { match: matcher };
|
|
3869
|
+
} else {
|
|
3870
|
+
this.#globalMatchers[param] = matcher;
|
|
3871
|
+
}
|
|
3872
|
+
return this;
|
|
3873
|
+
}
|
|
3874
|
+
/**
|
|
3875
|
+
* Commit routes to the store. The router is freezed after the
|
|
3876
|
+
* commit method is called.
|
|
3877
|
+
*/
|
|
3878
|
+
commit() {
|
|
3879
|
+
const routeNamesByDomain = /* @__PURE__ */ new Map();
|
|
3880
|
+
toRoutesJSON(this.routes).forEach((route) => {
|
|
3881
|
+
if (!routeNamesByDomain.has(route.domain)) {
|
|
3882
|
+
routeNamesByDomain.set(route.domain, /* @__PURE__ */ new Set());
|
|
3883
|
+
}
|
|
3884
|
+
const routeNames = routeNamesByDomain.get(route.domain);
|
|
3885
|
+
if (route.name && routeNames.has(route.name)) {
|
|
3886
|
+
throw new RuntimeException6(
|
|
3887
|
+
`Route with duplicate name found. A route with name "${route.name}" already exists`
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
if (route.name) {
|
|
3891
|
+
routeNames.add(route.name);
|
|
3892
|
+
}
|
|
3893
|
+
this.register(route);
|
|
3894
|
+
this.#store.add(route);
|
|
3895
|
+
});
|
|
3896
|
+
routeNamesByDomain.clear();
|
|
3897
|
+
this.usingDomains = this.#store.usingDomains;
|
|
3898
|
+
this.routes = [];
|
|
3899
|
+
this.#globalMatchers = {};
|
|
3900
|
+
this.#middleware = [];
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Find route for a given URL, method and optionally domain
|
|
3904
|
+
*/
|
|
3905
|
+
match(url, method, hostname) {
|
|
3906
|
+
const matchingDomain = this.#store.matchDomain(hostname);
|
|
3907
|
+
return matchingDomain.length ? this.#store.match(url, method, {
|
|
3908
|
+
tokens: matchingDomain,
|
|
3909
|
+
hostname
|
|
3910
|
+
}) : this.#store.match(url, method);
|
|
3911
|
+
}
|
|
3912
|
+
/**
|
|
3913
|
+
* Make URL to a pre-registered route
|
|
3914
|
+
*/
|
|
3915
|
+
makeUrl(routeIdentifier, params, options) {
|
|
3916
|
+
const normalizedOptions = Object.assign({}, options);
|
|
3917
|
+
const builder = normalizedOptions.domain ? this.builderForDomain(normalizedOptions.domain) : this.builder();
|
|
3918
|
+
builder.params(params);
|
|
3919
|
+
builder.qs(normalizedOptions.qs);
|
|
3920
|
+
normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
|
|
3921
|
+
normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
|
|
3922
|
+
return builder.make(routeIdentifier);
|
|
3923
|
+
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Makes a signed URL to a pre-registered route.
|
|
3926
|
+
*/
|
|
3927
|
+
makeSignedUrl(routeIdentifier, params, options) {
|
|
3928
|
+
const normalizedOptions = Object.assign({}, options);
|
|
3929
|
+
const builder = normalizedOptions.domain ? this.builderForDomain(normalizedOptions.domain) : this.builder();
|
|
3930
|
+
builder.params(params);
|
|
3931
|
+
builder.qs(normalizedOptions.qs);
|
|
3932
|
+
normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
|
|
3933
|
+
normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
|
|
3934
|
+
return builder.makeSigned(routeIdentifier, normalizedOptions);
|
|
3935
|
+
}
|
|
3936
|
+
};
|
|
3937
|
+
|
|
3938
|
+
// src/http_context/main.ts
|
|
3939
|
+
import { inspect } from "node:util";
|
|
3940
|
+
import Macroable8 from "@poppinss/macroable";
|
|
3941
|
+
import { RuntimeException as RuntimeException7 } from "@poppinss/utils";
|
|
3942
|
+
|
|
3943
|
+
// src/http_context/local_storage.ts
|
|
3944
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3945
|
+
var asyncLocalStorage = {
|
|
3946
|
+
/**
|
|
3947
|
+
* Check if the async local storage for the HTTP
|
|
3948
|
+
* context is enabled or not
|
|
3949
|
+
*/
|
|
3950
|
+
isEnabled: false,
|
|
3951
|
+
/**
|
|
3952
|
+
* HTTP context storage instance for the current scope
|
|
3953
|
+
*/
|
|
3954
|
+
storage: null,
|
|
3955
|
+
/**
|
|
3956
|
+
* Create the storage instance. This method must be called only
|
|
3957
|
+
* once.
|
|
3958
|
+
*/
|
|
3959
|
+
create() {
|
|
3960
|
+
this.isEnabled = true;
|
|
3961
|
+
this.storage = new AsyncLocalStorage();
|
|
3962
|
+
return this.storage;
|
|
3963
|
+
},
|
|
3964
|
+
/**
|
|
3965
|
+
* Destroy the create storage instance
|
|
3966
|
+
*/
|
|
3967
|
+
destroy() {
|
|
3968
|
+
this.isEnabled = false;
|
|
3969
|
+
this.storage = null;
|
|
3970
|
+
}
|
|
3971
|
+
};
|
|
3972
|
+
|
|
3973
|
+
// src/http_context/main.ts
|
|
3974
|
+
var HttpContext = class extends Macroable8 {
|
|
3975
|
+
constructor(request, response, logger, containerResolver) {
|
|
3976
|
+
super();
|
|
3977
|
+
this.request = request;
|
|
3978
|
+
this.response = response;
|
|
3979
|
+
this.logger = logger;
|
|
3980
|
+
this.containerResolver = containerResolver;
|
|
3981
|
+
this.request.ctx = this;
|
|
3982
|
+
this.response.ctx = this;
|
|
3983
|
+
}
|
|
3984
|
+
/**
|
|
3985
|
+
* Find if async localstorage is enabled for HTTP requests
|
|
3986
|
+
* or not
|
|
3987
|
+
*/
|
|
3988
|
+
static get usingAsyncLocalStorage() {
|
|
3989
|
+
return asyncLocalStorage.isEnabled;
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Get access to the HTTP context. Available only when
|
|
3993
|
+
* "usingAsyncLocalStorage" is true
|
|
3994
|
+
*/
|
|
3995
|
+
static get() {
|
|
3996
|
+
if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
|
|
3997
|
+
return null;
|
|
3998
|
+
}
|
|
3999
|
+
return asyncLocalStorage.storage.getStore() || null;
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Get the HttpContext instance or raise an exception if not
|
|
4003
|
+
* available
|
|
4004
|
+
*/
|
|
4005
|
+
static getOrFail() {
|
|
4006
|
+
if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
|
|
4007
|
+
throw new RuntimeException7(
|
|
4008
|
+
'HTTP context is not available. Enable "useAsyncLocalStorage" inside "config/app.ts" file'
|
|
4009
|
+
);
|
|
4010
|
+
}
|
|
4011
|
+
const store = this.get();
|
|
4012
|
+
if (!store) {
|
|
4013
|
+
throw new RuntimeException7("Http context is not available outside of an HTTP request");
|
|
4014
|
+
}
|
|
4015
|
+
return store;
|
|
4016
|
+
}
|
|
4017
|
+
/**
|
|
4018
|
+
* Run a method that doesn't have access to HTTP context from
|
|
4019
|
+
* the async local storage.
|
|
4020
|
+
*/
|
|
4021
|
+
static runOutsideContext(callback, ...args) {
|
|
4022
|
+
if (!asyncLocalStorage.storage) {
|
|
4023
|
+
return callback(...args);
|
|
4024
|
+
}
|
|
4025
|
+
return asyncLocalStorage.storage.exit(callback, ...args);
|
|
4026
|
+
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Reference to the current route. Not available inside
|
|
4029
|
+
* server middleware
|
|
4030
|
+
*/
|
|
4031
|
+
route;
|
|
4032
|
+
/**
|
|
4033
|
+
* A unique key for the current route
|
|
4034
|
+
*/
|
|
4035
|
+
routeKey;
|
|
4036
|
+
/**
|
|
4037
|
+
* Route params
|
|
4038
|
+
*/
|
|
4039
|
+
params = {};
|
|
4040
|
+
/**
|
|
4041
|
+
* Route subdomains
|
|
4042
|
+
*/
|
|
4043
|
+
subdomains = {};
|
|
4044
|
+
/**
|
|
4045
|
+
* A helper to see top level properties on the context object
|
|
4046
|
+
*/
|
|
4047
|
+
/* c8 ignore next 3 */
|
|
4048
|
+
inspect() {
|
|
4049
|
+
return inspect(this, false, 1, true);
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
|
|
4053
|
+
// src/server/main.ts
|
|
4054
|
+
import onFinished2 from "on-finished";
|
|
4055
|
+
import Middleware2 from "@poppinss/middleware";
|
|
4056
|
+
import { moduleCaller as moduleCaller2, moduleImporter as moduleImporter4 } from "@adonisjs/fold";
|
|
4057
|
+
|
|
4058
|
+
// src/qs.ts
|
|
4059
|
+
import { parse as parse3, stringify } from "qs";
|
|
4060
|
+
var Qs = class {
|
|
4061
|
+
#config;
|
|
4062
|
+
constructor(config) {
|
|
4063
|
+
this.#config = config;
|
|
4064
|
+
}
|
|
4065
|
+
parse(value) {
|
|
4066
|
+
return parse3(value, this.#config.parse);
|
|
4067
|
+
}
|
|
4068
|
+
stringify(value) {
|
|
4069
|
+
return stringify(value, this.#config.stringify);
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
|
|
4073
|
+
// src/server/factories/final_handler.ts
|
|
4074
|
+
function finalHandler(router, resolver, ctx, errorResponder) {
|
|
4075
|
+
return function() {
|
|
4076
|
+
const url = ctx.request.url();
|
|
4077
|
+
const method = ctx.request.method();
|
|
4078
|
+
const hostname = router.usingDomains ? ctx.request.hostname() : void 0;
|
|
4079
|
+
const route = router.match(url, method, hostname);
|
|
4080
|
+
if (route) {
|
|
4081
|
+
ctx.params = route.params;
|
|
4082
|
+
ctx.subdomains = route.subdomains;
|
|
4083
|
+
ctx.route = route.route;
|
|
4084
|
+
ctx.routeKey = route.routeKey;
|
|
4085
|
+
return route.route.execute(route.route, resolver, ctx, errorResponder);
|
|
4086
|
+
}
|
|
4087
|
+
return Promise.reject(new E_ROUTE_NOT_FOUND([method, url]));
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
// src/server/factories/write_response.ts
|
|
4092
|
+
function writeResponse(ctx) {
|
|
4093
|
+
return function() {
|
|
4094
|
+
try {
|
|
4095
|
+
ctx.response.finish();
|
|
4096
|
+
} catch (error) {
|
|
4097
|
+
ctx.logger.fatal({ err: error }, "Response serialization failed");
|
|
4098
|
+
ctx.response.internalServerError(error.message);
|
|
4099
|
+
ctx.response.finish();
|
|
4100
|
+
}
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
// src/server/factories/middleware_handler.ts
|
|
4105
|
+
function middlewareHandler(resolver, ctx) {
|
|
4106
|
+
return function(fn, next) {
|
|
4107
|
+
return fn.handle(resolver, ctx, next);
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
// src/server/main.ts
|
|
4112
|
+
var Server = class {
|
|
4113
|
+
/**
|
|
4114
|
+
* The default error handler to use
|
|
4115
|
+
*/
|
|
4116
|
+
#defaultErrorHandler = {
|
|
4117
|
+
report() {
|
|
4118
|
+
},
|
|
4119
|
+
handle(error, ctx) {
|
|
4120
|
+
ctx.response.status(error.status || 500).send(error.message || "Internal server error");
|
|
4121
|
+
}
|
|
4122
|
+
};
|
|
4123
|
+
/**
|
|
4124
|
+
* Logger instance, a child logger is added
|
|
4125
|
+
* to the context to have request specific
|
|
4126
|
+
* logging capabilities.
|
|
4127
|
+
*/
|
|
4128
|
+
#logger;
|
|
4129
|
+
/**
|
|
4130
|
+
* Registered error handler (if any)
|
|
4131
|
+
*/
|
|
4132
|
+
#errorHandler;
|
|
4133
|
+
/**
|
|
4134
|
+
* Resolved error handler is an instance of the lazily imported error
|
|
4135
|
+
* handler class.
|
|
4136
|
+
*/
|
|
4137
|
+
#resolvedErrorHandler = this.#defaultErrorHandler;
|
|
4138
|
+
/**
|
|
4139
|
+
* Emitter is required to notify when a request finishes
|
|
4140
|
+
*/
|
|
4141
|
+
#emitter;
|
|
4142
|
+
/**
|
|
4143
|
+
* The application instance to be shared with the router
|
|
4144
|
+
*/
|
|
4145
|
+
#app;
|
|
4146
|
+
/**
|
|
4147
|
+
* The encryption instance to be shared with the router
|
|
4148
|
+
*/
|
|
4149
|
+
#encryption;
|
|
4150
|
+
/**
|
|
4151
|
+
* Server config
|
|
4152
|
+
*/
|
|
4153
|
+
#config;
|
|
4154
|
+
/**
|
|
4155
|
+
* Query string parser used by the server
|
|
4156
|
+
*/
|
|
4157
|
+
#qsParser;
|
|
4158
|
+
/**
|
|
4159
|
+
* Server middleware stack runs on every incoming HTTP request
|
|
4160
|
+
*/
|
|
4161
|
+
#serverMiddlewareStack;
|
|
4162
|
+
/**
|
|
4163
|
+
* Reference to the router used by the server
|
|
4164
|
+
*/
|
|
4165
|
+
#router;
|
|
4166
|
+
/**
|
|
4167
|
+
* Reference to the underlying Node HTTP server in use
|
|
4168
|
+
*/
|
|
4169
|
+
#nodeHttpServer;
|
|
4170
|
+
/**
|
|
4171
|
+
* Middleware store to be shared with the routes
|
|
4172
|
+
*/
|
|
4173
|
+
#middleware = [];
|
|
4174
|
+
/**
|
|
4175
|
+
* The request error response is attached to the middleware
|
|
4176
|
+
* pipeline to intercept errors and invoke the user
|
|
4177
|
+
* registered error handler.
|
|
4178
|
+
*
|
|
4179
|
+
* We share this with the route middleware pipeline as well,
|
|
4180
|
+
* so that it does not throw any exceptions
|
|
4181
|
+
*/
|
|
4182
|
+
#requestErrorResponder = (error, ctx) => {
|
|
4183
|
+
this.#resolvedErrorHandler.report(error, ctx);
|
|
4184
|
+
return this.#resolvedErrorHandler.handle(error, ctx);
|
|
4185
|
+
};
|
|
4186
|
+
/**
|
|
4187
|
+
* Know if async local storage is enabled or not.
|
|
4188
|
+
*/
|
|
4189
|
+
get usingAsyncLocalStorage() {
|
|
4190
|
+
return asyncLocalStorage.isEnabled;
|
|
4191
|
+
}
|
|
4192
|
+
constructor(app, encryption, emitter, logger, config) {
|
|
4193
|
+
this.#app = app;
|
|
4194
|
+
this.#emitter = emitter;
|
|
4195
|
+
this.#config = config;
|
|
4196
|
+
this.#logger = logger;
|
|
4197
|
+
this.#encryption = encryption;
|
|
4198
|
+
this.#qsParser = new Qs(this.#config.qs);
|
|
4199
|
+
this.#router = new Router(this.#app, this.#encryption, this.#qsParser);
|
|
4200
|
+
this.#createAsyncLocalStore();
|
|
4201
|
+
debug_default("server config: %O", this.#config);
|
|
4202
|
+
}
|
|
4203
|
+
/**
|
|
4204
|
+
* Create async local storage store when enabled
|
|
4205
|
+
*/
|
|
4206
|
+
#createAsyncLocalStore() {
|
|
4207
|
+
if (this.#config.useAsyncLocalStorage) {
|
|
4208
|
+
debug_default("creating ALS store for HTTP context");
|
|
4209
|
+
asyncLocalStorage.create();
|
|
4210
|
+
} else {
|
|
4211
|
+
asyncLocalStorage.destroy();
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
/**
|
|
4215
|
+
* Creates an instance of the server middleware stack
|
|
4216
|
+
*/
|
|
4217
|
+
#createServerMiddlewareStack() {
|
|
4218
|
+
this.#serverMiddlewareStack = new Middleware2();
|
|
4219
|
+
this.#middleware.forEach((middleware) => this.#serverMiddlewareStack.add(middleware));
|
|
4220
|
+
this.#serverMiddlewareStack.freeze();
|
|
4221
|
+
this.#middleware = [];
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* Handles the HTTP request
|
|
4225
|
+
*/
|
|
4226
|
+
#handleRequest(ctx, resolver) {
|
|
4227
|
+
return this.#serverMiddlewareStack.runner().errorHandler((error) => this.#requestErrorResponder(error, ctx)).finalHandler(finalHandler(this.#router, resolver, ctx, this.#requestErrorResponder)).run(middlewareHandler(resolver, ctx)).catch((error) => {
|
|
4228
|
+
ctx.logger.fatal({ err: error }, "Exception raised by error handler");
|
|
4229
|
+
return this.#defaultErrorHandler.handle(error, ctx);
|
|
4230
|
+
}).finally(writeResponse(ctx));
|
|
4231
|
+
}
|
|
4232
|
+
/**
|
|
4233
|
+
* Creates a pipeline of middleware.
|
|
4234
|
+
*/
|
|
4235
|
+
pipeline(middleware) {
|
|
4236
|
+
const middlewareStack = new Middleware2();
|
|
4237
|
+
middleware.forEach((one) => {
|
|
4238
|
+
middlewareStack.add(moduleCaller2(one, "handle").toHandleMethod());
|
|
4239
|
+
});
|
|
4240
|
+
middlewareStack.freeze();
|
|
4241
|
+
const stackRunner = middlewareStack.runner();
|
|
4242
|
+
return {
|
|
4243
|
+
finalHandler(handler) {
|
|
4244
|
+
stackRunner.finalHandler(handler);
|
|
4245
|
+
return this;
|
|
4246
|
+
},
|
|
4247
|
+
errorHandler(handler) {
|
|
4248
|
+
stackRunner.errorHandler(handler);
|
|
4249
|
+
return this;
|
|
4250
|
+
},
|
|
4251
|
+
run(ctx) {
|
|
4252
|
+
return stackRunner.run((handler, next) => {
|
|
4253
|
+
return handler.handle(ctx.containerResolver, ctx, next);
|
|
4254
|
+
});
|
|
4255
|
+
}
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
/**
|
|
4259
|
+
* Define an array of middleware to use on all the incoming HTTP request.
|
|
4260
|
+
* Calling this method multiple times pushes to the existing list
|
|
4261
|
+
* of middleware
|
|
4262
|
+
*/
|
|
4263
|
+
use(middleware) {
|
|
4264
|
+
middleware.forEach(
|
|
4265
|
+
(one) => this.#middleware.push(moduleImporter4(one, "handle").toHandleMethod())
|
|
4266
|
+
);
|
|
4267
|
+
return this;
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Register a custom error handler for HTTP requests.
|
|
4271
|
+
* All errors will be reported to this method
|
|
4272
|
+
*/
|
|
4273
|
+
errorHandler(handler) {
|
|
4274
|
+
this.#errorHandler = handler;
|
|
4275
|
+
return this;
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* Boot the server. Calling this method performs the following actions.
|
|
4279
|
+
*
|
|
4280
|
+
* - Register routes with the store.
|
|
4281
|
+
* - Resolve and construct the error handler.
|
|
4282
|
+
*/
|
|
4283
|
+
async boot() {
|
|
4284
|
+
debug_default("booting HTTP server");
|
|
4285
|
+
this.#createServerMiddlewareStack();
|
|
4286
|
+
this.#router.commit();
|
|
4287
|
+
if (this.#errorHandler) {
|
|
4288
|
+
if (debug_default.enabled) {
|
|
4289
|
+
debug_default('using custom error handler "%s"', this.#errorHandler);
|
|
4290
|
+
}
|
|
4291
|
+
const moduleExports = await this.#errorHandler();
|
|
4292
|
+
this.#resolvedErrorHandler = await this.#app.container.make(moduleExports.default);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
/**
|
|
4296
|
+
* Set the HTTP server instance used to listen for requests.
|
|
4297
|
+
*/
|
|
4298
|
+
setNodeServer(server) {
|
|
4299
|
+
this.#nodeHttpServer = server;
|
|
4300
|
+
}
|
|
4301
|
+
/**
|
|
4302
|
+
* Returns reference to the underlying HTTP server
|
|
4303
|
+
* in use
|
|
4304
|
+
*/
|
|
4305
|
+
getNodeServer() {
|
|
4306
|
+
return this.#nodeHttpServer;
|
|
4307
|
+
}
|
|
4308
|
+
/**
|
|
4309
|
+
* Returns reference to the router instance used
|
|
4310
|
+
* by the server.
|
|
4311
|
+
*/
|
|
4312
|
+
getRouter() {
|
|
4313
|
+
return this.#router;
|
|
4314
|
+
}
|
|
4315
|
+
/**
|
|
4316
|
+
* Creates an instance of the [[Request]] class
|
|
4317
|
+
*/
|
|
4318
|
+
createRequest(req, res) {
|
|
4319
|
+
return new Request(req, res, this.#encryption, this.#config, this.#qsParser);
|
|
4320
|
+
}
|
|
4321
|
+
/**
|
|
4322
|
+
* Creates an instance of the [[Response]] class
|
|
4323
|
+
*/
|
|
4324
|
+
createResponse(req, res) {
|
|
4325
|
+
return new Response(req, res, this.#encryption, this.#config, this.#router, this.#qsParser);
|
|
4326
|
+
}
|
|
4327
|
+
/**
|
|
4328
|
+
* Creates an instance of the [[HttpContext]] class
|
|
4329
|
+
*/
|
|
4330
|
+
createHttpContext(request, response, resolver) {
|
|
4331
|
+
return new HttpContext(
|
|
4332
|
+
request,
|
|
4333
|
+
response,
|
|
4334
|
+
this.#logger.child({ request_id: request.id() }),
|
|
4335
|
+
resolver
|
|
4336
|
+
);
|
|
4337
|
+
}
|
|
4338
|
+
/**
|
|
4339
|
+
* Handle request
|
|
4340
|
+
*/
|
|
4341
|
+
handle(req, res) {
|
|
4342
|
+
const hasRequestListener = this.#emitter.hasListeners("http:request_finished");
|
|
4343
|
+
const startTime = hasRequestListener ? process.hrtime() : null;
|
|
4344
|
+
const resolver = this.#app.container.createResolver();
|
|
4345
|
+
const ctx = this.createHttpContext(
|
|
4346
|
+
this.createRequest(req, res),
|
|
4347
|
+
this.createResponse(req, res),
|
|
4348
|
+
resolver
|
|
4349
|
+
);
|
|
4350
|
+
if (startTime) {
|
|
4351
|
+
onFinished2(res, () => {
|
|
4352
|
+
this.#emitter.emit("http:request_finished", {
|
|
4353
|
+
ctx,
|
|
4354
|
+
duration: process.hrtime(startTime)
|
|
4355
|
+
});
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
4358
|
+
if (this.usingAsyncLocalStorage) {
|
|
4359
|
+
return asyncLocalStorage.storage.run(ctx, () => this.#handleRequest(ctx, resolver));
|
|
4360
|
+
}
|
|
4361
|
+
return this.#handleRequest(ctx, resolver);
|
|
4362
|
+
}
|
|
4363
|
+
};
|
|
4364
|
+
|
|
4365
|
+
// src/define_config.ts
|
|
4366
|
+
import proxyAddr from "proxy-addr";
|
|
4367
|
+
import string3 from "@poppinss/utils/string";
|
|
4368
|
+
function defineConfig(config) {
|
|
4369
|
+
const { trustProxy: trustProxy2, ...rest } = config;
|
|
4370
|
+
const normalizedConfig = {
|
|
4371
|
+
allowMethodSpoofing: false,
|
|
4372
|
+
trustProxy: proxyAddr.compile("loopback"),
|
|
4373
|
+
subdomainOffset: 2,
|
|
4374
|
+
generateRequestId: false,
|
|
4375
|
+
useAsyncLocalStorage: false,
|
|
4376
|
+
etag: false,
|
|
4377
|
+
jsonpCallbackName: "callback",
|
|
4378
|
+
cookie: {
|
|
4379
|
+
maxAge: "2h",
|
|
4380
|
+
path: "/",
|
|
4381
|
+
httpOnly: true,
|
|
4382
|
+
secure: false,
|
|
4383
|
+
sameSite: false
|
|
4384
|
+
},
|
|
4385
|
+
qs: {
|
|
4386
|
+
parse: {
|
|
4387
|
+
depth: 5,
|
|
4388
|
+
parameterLimit: 1e3,
|
|
4389
|
+
allowSparse: false,
|
|
4390
|
+
arrayLimit: 20,
|
|
4391
|
+
comma: true
|
|
4392
|
+
},
|
|
4393
|
+
stringify: {
|
|
4394
|
+
encode: true,
|
|
4395
|
+
encodeValuesOnly: false,
|
|
4396
|
+
arrayFormat: "indices",
|
|
4397
|
+
skipNulls: false
|
|
4398
|
+
}
|
|
4399
|
+
},
|
|
4400
|
+
...rest
|
|
4401
|
+
};
|
|
4402
|
+
if (normalizedConfig.cookie.maxAge) {
|
|
4403
|
+
normalizedConfig.cookie.maxAge = string3.seconds.parse(normalizedConfig.cookie.maxAge);
|
|
4404
|
+
}
|
|
4405
|
+
if (typeof trustProxy2 === "boolean") {
|
|
4406
|
+
const tpValue = trustProxy2;
|
|
4407
|
+
normalizedConfig.trustProxy = (_, __) => tpValue;
|
|
4408
|
+
} else if (typeof trustProxy2 === "string") {
|
|
4409
|
+
const tpValue = trustProxy2;
|
|
4410
|
+
normalizedConfig.trustProxy = proxyAddr.compile(tpValue);
|
|
4411
|
+
}
|
|
4412
|
+
return normalizedConfig;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
export {
|
|
4416
|
+
Route,
|
|
4417
|
+
BriskRoute,
|
|
4418
|
+
RouteResource,
|
|
4419
|
+
RouteGroup,
|
|
4420
|
+
parseRange,
|
|
4421
|
+
CookieClient,
|
|
4422
|
+
Request,
|
|
4423
|
+
Redirect,
|
|
4424
|
+
E_ROUTE_NOT_FOUND,
|
|
4425
|
+
E_CANNOT_LOOKUP_ROUTE,
|
|
4426
|
+
E_HTTP_EXCEPTION,
|
|
4427
|
+
E_HTTP_REQUEST_ABORTED,
|
|
4428
|
+
exceptions_exports,
|
|
4429
|
+
Response,
|
|
4430
|
+
Qs,
|
|
4431
|
+
Router,
|
|
4432
|
+
HttpContext,
|
|
4433
|
+
Server,
|
|
4434
|
+
defineConfig
|
|
4435
|
+
};
|
|
4436
|
+
//# sourceMappingURL=chunk-Z63E3STR.js.map
|