@h3ravel/http 11.15.0-alpha.16 → 11.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs DELETED
@@ -1,4240 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
24
- let _h3ravel_musket = require("@h3ravel/musket");
25
- let _h3ravel_shared = require("@h3ravel/shared");
26
- let execa = require("execa");
27
- let preferred_pm = require("preferred-pm");
28
- preferred_pm = __toESM(preferred_pm, 1);
29
- let fs_promises = require("fs/promises");
30
- let _h3ravel_contracts = require("@h3ravel/contracts");
31
- let _h3ravel_foundation = require("@h3ravel/foundation");
32
- let _h3ravel_support_facades = require("@h3ravel/support/facades");
33
- let _h3ravel_support = require("@h3ravel/support");
34
- let h3 = require("h3");
35
- let node_path = require("node:path");
36
- node_path = __toESM(node_path, 1);
37
- //#region src/Commands/FireCommand.ts
38
- var FireCommand = class extends _h3ravel_musket.Command {
39
- /**
40
- * The name and signature of the console command.
41
- *
42
- * @var string
43
- */
44
- signature = `fire:
45
- {--a|host=localhost : The host address to serve the application on}
46
- {--p|port=3000 : The port to serve the application on}
47
- {--t|tries=10 : The max number of ports to attempt to serve from}
48
- `;
49
- /**
50
- * The console command description.
51
- *
52
- * @var string
53
- */
54
- description = "Fire up the developement server";
55
- async handle() {
56
- try {
57
- await this.fire();
58
- } catch (e) {
59
- _h3ravel_shared.Logger.error(e);
60
- }
61
- }
62
- async fire() {
63
- const outDir = env("DIST_DIR", ".h3ravel/serve");
64
- const pm = (await (0, preferred_pm.default)(base_path()))?.name ?? "pnpm";
65
- const port = this.option("port");
66
- const host = this.option("host");
67
- const tries = this.option("tries");
68
- const verbose = Number(this.option("verbose", 0));
69
- const ENV_VARS = {
70
- EXTENDED_DEBUG: verbose > 0 ? "true" : "false",
71
- CLI_BUILD: "false",
72
- NODE_ENV: "development",
73
- DIST_DIR: outDir,
74
- HOSTNAME: host,
75
- RETRIES: tries,
76
- PORT: port,
77
- VERBOSE: verbose,
78
- LOG_LEVEL: [
79
- "silent",
80
- "silent",
81
- "info",
82
- "warn",
83
- "error"
84
- ][verbose]
85
- };
86
- await (0, execa.execa)(pm, [
87
- "tsdown",
88
- ENV_VARS.LOG_LEVEL === "silent" ? "--silent" : null,
89
- "--config-loader",
90
- "unrun",
91
- "-c",
92
- "tsdown.default.config.ts"
93
- ].filter((e) => e !== null), {
94
- stdout: "inherit",
95
- stderr: "inherit",
96
- cwd: base_path(),
97
- env: Object.assign({}, process.env, ENV_VARS)
98
- });
99
- }
100
- };
101
- //#endregion
102
- //#region src/Exceptions/BadRequestException.ts
103
- var BadRequestException = class extends Error {
104
- constructor(message) {
105
- super(message);
106
- this.name = "BadRequestException";
107
- }
108
- };
109
- //#endregion
110
- //#region src/Exceptions/ConflictingHeadersException.ts
111
- var ConflictingHeadersException = class extends Error {
112
- constructor(message) {
113
- super(message);
114
- this.name = "ConflictingHeadersException";
115
- }
116
- };
117
- //#endregion
118
- //#region src/Exceptions/HttpResponseException.ts
119
- var HttpResponseException = class extends Error {
120
- /**
121
- * The underlying response instance.
122
- */
123
- response;
124
- /**
125
- * Create a new HTTP response exception instance.
126
- *
127
- * @param response
128
- * @param previous
129
- */
130
- constructor(response, previous) {
131
- super(previous?.message ?? "");
132
- this.name = "HttpResponseException";
133
- this.response = response;
134
- }
135
- /**
136
- * Get the underlying response instance.
137
- */
138
- getResponse() {
139
- return this.response;
140
- }
141
- };
142
- //#endregion
143
- //#region src/Exceptions/SuspiciousOperationException.ts
144
- var SuspiciousOperationException = class extends Error {
145
- constructor(message) {
146
- super(message);
147
- this.name = "SuspiciousOperationException";
148
- }
149
- };
150
- //#endregion
151
- //#region src/Exceptions/UnexpectedValueException.ts
152
- var UnexpectedValueException = class extends Error {
153
- constructor(message) {
154
- super(message);
155
- this.name = "UnexpectedValueException";
156
- }
157
- };
158
- //#endregion
159
- //#region src/UploadedFile.ts
160
- var UploadedFile = class UploadedFile {
161
- originalName;
162
- mimeType;
163
- size;
164
- content;
165
- constructor(originalName, mimeType, size, content) {
166
- this.originalName = originalName;
167
- this.mimeType = mimeType;
168
- this.size = size;
169
- this.content = content;
170
- }
171
- static createFromBase(file) {
172
- return new UploadedFile(file.name, file.type, file.size, file);
173
- }
174
- /**
175
- * Save to disk (Node environment only)
176
- */
177
- async moveTo(destination) {
178
- await (0, fs_promises.writeFile)(destination, Buffer.from(await this.content.arrayBuffer()));
179
- }
180
- };
181
- //#endregion
182
- //#region src/FormRequest.ts
183
- var FormRequest = class {
184
- dataset;
185
- constructor(data) {
186
- this.initialize(data);
187
- }
188
- /**
189
- * Initialize the data
190
- * @param data
191
- */
192
- initialize(data) {
193
- this.dataset = {
194
- files: {},
195
- input: {}
196
- };
197
- for (const [rawKey, value] of data.entries()) {
198
- const key = rawKey.endsWith("[]") ? rawKey.slice(0, -2) : rawKey;
199
- if (value instanceof UploadedFile || value instanceof File) {
200
- const uploaded = value instanceof UploadedFile ? value : UploadedFile.createFromBase(value);
201
- if (this.dataset.files[key]) {
202
- const existing = this.dataset.files[key];
203
- if (Array.isArray(existing)) existing.push(uploaded);
204
- else this.dataset.files[key] = [existing, uploaded];
205
- } else this.dataset.files[key] = uploaded;
206
- } else if (this.dataset.input[key]) {
207
- const existing = this.dataset.input[key];
208
- if (Array.isArray(existing)) existing.push(value);
209
- else this.dataset.input[key] = [existing, value];
210
- } else this.dataset.input[key] = value;
211
- }
212
- }
213
- /**
214
- * Get all uploaded files
215
- */
216
- files() {
217
- return this.dataset.files;
218
- }
219
- /**
220
- * Get all input fields
221
- */
222
- input() {
223
- return this.dataset.input;
224
- }
225
- /**
226
- * Get combined input and files
227
- * File entries take precedence if names overlap.
228
- */
229
- all() {
230
- return Object.assign({}, this.dataset.input, this.dataset.files);
231
- }
232
- };
233
- //#endregion
234
- //#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorateMetadata.js
235
- function __decorateMetadata(k, v) {
236
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
237
- }
238
- //#endregion
239
- //#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorate.js
240
- function __decorate(decorators, target, key, desc) {
241
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
242
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
243
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
244
- return c > 3 && r && Object.defineProperty(target, key, r), r;
245
- }
246
- //#endregion
247
- //#region src/Middleware.ts
248
- let Middleware = class Middleware extends _h3ravel_contracts.IMiddleware {
249
- app;
250
- constructor(app) {
251
- super();
252
- this.app = app;
253
- }
254
- };
255
- Middleware = __decorate([(0, _h3ravel_foundation.Injectable)(), __decorateMetadata("design:paramtypes", [typeof _h3ravel_contracts.IApplication === "undefined" ? Object : _h3ravel_contracts.IApplication])], Middleware);
256
- //#endregion
257
- //#region src/Middleware/FlashDataMiddleware.ts
258
- var FlashDataMiddleware = class extends Middleware {
259
- async handle(request, next) {
260
- const _next = await next(request);
261
- request.session().ageFlashData();
262
- return _next;
263
- }
264
- };
265
- __decorate([
266
- (0, _h3ravel_foundation.Injectable)(),
267
- __decorateMetadata("design:type", Function),
268
- __decorateMetadata("design:paramtypes", [typeof Request === "undefined" ? Object : Request, Function]),
269
- __decorateMetadata("design:returntype", Promise)
270
- ], FlashDataMiddleware.prototype, "handle", null);
271
- //#endregion
272
- //#region src/Middleware/LogRequests.ts
273
- var LogRequests = class extends Middleware {
274
- async handle(request, next) {
275
- const _next = await next(request);
276
- const code = Number(_h3ravel_support_facades.Response.getStatusCode());
277
- const method = request.method().toLowerCase();
278
- let color = "bgRed";
279
- if (code < 200) color = "bgWhite";
280
- else if (code >= 200 && code <= 300) color = "bgBlue";
281
- else if (code >= 300 && code <= 400) color = "bgYellow";
282
- let mColor = "bgYellow";
283
- if (method == "get") mColor = "bgBlue";
284
- else if (method == "head") mColor = "bgGray";
285
- else if (method == "delete") mColor = "bgRed";
286
- _h3ravel_shared.Logger.log([
287
- [` ${method.toUpperCase()} `, mColor],
288
- [request.fullUrl(), "white"],
289
- ["→", "blue"],
290
- [` ${code} `, color]
291
- ], " ");
292
- return _next;
293
- }
294
- };
295
- __decorate([
296
- (0, _h3ravel_foundation.Injectable)(),
297
- __decorateMetadata("design:type", Function),
298
- __decorateMetadata("design:paramtypes", [typeof _h3ravel_contracts.IRequest === "undefined" ? Object : _h3ravel_contracts.IRequest, Function]),
299
- __decorateMetadata("design:returntype", Promise)
300
- ], LogRequests.prototype, "handle", null);
301
- //#endregion
302
- //#region src/HttpContext.ts
303
- /**
304
- * Represents the HTTP context for a single request lifecycle.
305
- * Encapsulates the application instance, request, and response objects.
306
- */
307
- var HttpContext = class HttpContext extends _h3ravel_contracts.IHttpContext {
308
- app;
309
- request;
310
- response;
311
- static contexts = /* @__PURE__ */ new WeakMap();
312
- event;
313
- constructor(app, request, response) {
314
- super();
315
- this.app = app;
316
- this.request = request;
317
- this.response = response;
318
- this.app.bindMiddleware("LogRequests", LogRequests);
319
- this.app.bindMiddleware("FlashDataMiddleware", FlashDataMiddleware);
320
- }
321
- /**
322
- * Factory method to create a new HttpContext instance from a context object.
323
- * @param ctx - Object containing app, request, and response
324
- * @returns A new HttpContext instance
325
- */
326
- static init(ctx, event) {
327
- if (!!event && HttpContext.contexts.has(event)) return HttpContext.contexts.get(event);
328
- const instance = new HttpContext(ctx.app, ctx.request, ctx.response);
329
- instance.event = event;
330
- ctx.request.context = instance;
331
- ctx.response.context = instance;
332
- ctx.app.setHttpContext(instance);
333
- if (event) HttpContext.contexts.set(event, instance);
334
- return instance;
335
- }
336
- /**
337
- * Retrieve an existing HttpContext instance for an event, if any.
338
- */
339
- static get(event) {
340
- return HttpContext.contexts.get(event);
341
- }
342
- /**
343
- * Delete the cached context for a given event (optional cleanup).
344
- */
345
- static forget(event) {
346
- HttpContext.contexts.delete(event);
347
- }
348
- };
349
- //#endregion
350
- //#region src/Utilities/HeaderBag.ts
351
- /**
352
- * HeaderBag — A container for HTTP headers
353
- * for H3ravel App.
354
- */
355
- var HeaderBag = class HeaderBag extends _h3ravel_contracts.IHeaderBag {
356
- static UPPER = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
357
- static LOWER = "-abcdefghijklmnopqrstuvwxyz";
358
- headers = {};
359
- headerNames = {};
360
- cacheControl = {};
361
- constructor(headers = {}) {
362
- super();
363
- for (const [key, values] of Object.entries(headers)) {
364
- this.set(key, values);
365
- if (key.startsWith("HTTP_")) this.set(key.slice(5), values);
366
- }
367
- }
368
- /**
369
- * Returns all headers as string (for debugging / toString)
370
- *
371
- * @returns
372
- */
373
- toString() {
374
- const headers = this.all();
375
- if (!Object.keys(headers).length) return "";
376
- const sortedKeys = Object.keys(headers).sort();
377
- const max = Math.max(...sortedKeys.map((k) => k.length)) + 1;
378
- let content = "";
379
- for (const name of sortedKeys) {
380
- const values = headers[name] ?? [];
381
- const displayName = name.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("-");
382
- for (const value of values) content += `${displayName + ":"}`.padEnd(max + 1, " ") + `${value ?? ""}\r\n`;
383
- }
384
- return content;
385
- }
386
- /**
387
- * Returns all headers or specific header list
388
- *
389
- * @param key
390
- * @returns
391
- */
392
- all(key) {
393
- if (key !== void 0) return this.headers[this.normalizeKey(key)] ?? [];
394
- return this.headers;
395
- }
396
- /**
397
- * Returns header keys
398
- *
399
- * @returns
400
- */
401
- keys() {
402
- return Object.keys(this.headers);
403
- }
404
- /**
405
- * Replace all headers with new set
406
- *
407
- * @param headers
408
- */
409
- replace(headers = {}) {
410
- this.headers = {};
411
- this.add(headers);
412
- }
413
- /**
414
- * Add multiple headers
415
- *
416
- * @param headers
417
- */
418
- add(headers) {
419
- for (const [key, values] of Object.entries(headers)) this.set(key, values);
420
- }
421
- /**
422
- * Returns first header value by name or default
423
- *
424
- * @param key
425
- * @param defaultValue
426
- * @returns
427
- */
428
- get(key, defaultValue = null) {
429
- const headers = this.all(key) || this.all("http-" + key);
430
- if (!headers.length) return defaultValue;
431
- return headers[0];
432
- }
433
- /**
434
- * Sets a header by name.
435
- *
436
- * @param replace Whether to replace existing values (default true)
437
- */
438
- set(key, values, replace = true) {
439
- const normalized = this.normalizeKey(key);
440
- if (Array.isArray(values)) {
441
- const valList = values.map((v) => v === void 0 ? null : v);
442
- if (replace || !this.headers[normalized]) this.headers[normalized] = valList;
443
- else this.headers[normalized].push(...valList);
444
- } else {
445
- const val = values === void 0 ? null : values;
446
- if (replace || !this.headers[normalized]) this.headers[normalized] = [val];
447
- else this.headers[normalized].push(val);
448
- }
449
- if (normalized === "cache-control") this.cacheControl = this.parseCacheControl((this.headers[normalized] ?? []).join(", "));
450
- }
451
- /**
452
- * Returns true if header exists
453
- *
454
- * @param key
455
- * @returns
456
- */
457
- has(key) {
458
- return Object.prototype.hasOwnProperty.call(this.headers, this.normalizeKey(key));
459
- }
460
- /**
461
- * Returns true if header contains value
462
- *
463
- * @param key
464
- * @param value
465
- * @returns
466
- */
467
- contains(key, value) {
468
- return this.all(key).includes(value);
469
- }
470
- /**
471
- * Removes a header
472
- *
473
- * @param key
474
- */
475
- remove(key) {
476
- const normalized = this.normalizeKey(key);
477
- delete this.headers[normalized];
478
- if (normalized === "cache-control") this.cacheControl = {};
479
- }
480
- /**
481
- * Returns parsed date from header
482
- *
483
- * @param key
484
- * @param defaultValue
485
- * @returns
486
- */
487
- getDate(key, defaultValue = null) {
488
- const value = this.get(key);
489
- if (!value) return defaultValue ? _h3ravel_support.DateTime.parse(defaultValue) : void 0;
490
- const parsed = _h3ravel_support.DateTime.parse(value);
491
- if (isNaN(parsed.unix())) throw new _h3ravel_support.RuntimeException(`The "${key}" HTTP header is not parseable (${value}).`);
492
- return parsed;
493
- }
494
- /**
495
- * Adds a Cache-Control directive
496
- *
497
- * @param key
498
- * @param value
499
- */
500
- addCacheControlDirective(key, value = true) {
501
- this.cacheControl[key] = value;
502
- this.set("Cache-Control", this.getCacheControlHeader());
503
- }
504
- /**
505
- * Returns true if Cache-Control directive is defined
506
- *
507
- * @param key
508
- * @returns
509
- */
510
- hasCacheControlDirective(key) {
511
- return Object.prototype.hasOwnProperty.call(this.cacheControl, key);
512
- }
513
- /**
514
- * Returns a Cache-Control directive value by name
515
- *
516
- * @param key
517
- * @returns
518
- */
519
- getCacheControlDirective(key) {
520
- return this.cacheControl[key] ?? null;
521
- }
522
- /**
523
- * Removes a Cache-Control directive
524
- *
525
- * @param key
526
- * @returns
527
- */
528
- removeCacheControlDirective(key) {
529
- delete this.cacheControl[key];
530
- this.set("Cache-Control", this.getCacheControlHeader());
531
- }
532
- /**
533
- * Number of headers
534
- *
535
- * @param key
536
- * @returns
537
- */
538
- count() {
539
- return Object.keys(this.headers).length;
540
- }
541
- /**
542
- * Normalize header name to lowercase with hyphens
543
- *
544
- * @param key
545
- * @returns
546
- */
547
- normalizeKey(key) {
548
- return key.replace(/[A-Z_]/g, (ch) => {
549
- const idx = HeaderBag.UPPER.indexOf(ch);
550
- return idx === -1 ? ch : HeaderBag.LOWER[idx];
551
- }).toLowerCase();
552
- }
553
- /**
554
- * Generates Cache-Control header string
555
- *
556
- * @param header
557
- * @returns
558
- */
559
- getCacheControlHeader() {
560
- return Object.entries(this.cacheControl).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => v === true ? k : v === false ? "" : `${k}=${v}`).filter(Boolean).join(", ");
561
- }
562
- /**
563
- * Parses Cache-Control header
564
- *
565
- * @param header
566
- * @returns
567
- */
568
- parseCacheControl(header) {
569
- const directives = {};
570
- const parts = header.split(",").map((p) => p.trim()).filter(Boolean);
571
- for (const part of parts) {
572
- const [key, val] = part.split("=", 2);
573
- directives[key.trim()] = val !== void 0 ? val.trim() : true;
574
- }
575
- return directives;
576
- }
577
- /**
578
- * Iterator support
579
- * @returns
580
- */
581
- [Symbol.iterator]() {
582
- return Object.entries(this.headers)[Symbol.iterator]();
583
- }
584
- };
585
- //#endregion
586
- //#region src/Utilities/HeaderUtility.ts
587
- /**
588
- * HTTP header utility functions .
589
- */
590
- var HeaderUtility = class HeaderUtility {
591
- static DISPOSITION_ATTACHMENT = "attachment";
592
- static DISPOSITION_INLINE = "inline";
593
- constructor() {}
594
- /**
595
- * Splits an HTTP header by one or more separators.
596
- *
597
- * Example:
598
- * HeaderUtility.split('da, en-gb;q=0.8', ',;')
599
- * // returns [['da'], ['en-gb', 'q=0.8']]
600
- */
601
- static split(header, separators) {
602
- if (!separators) throw new Error("At least one separator must be specified.");
603
- const quotedSeparators = separators.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
604
- const regex = new RegExp(`(?!\\s)(?:(?:"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$))|[^"${quotedSeparators}"]+)+(?<!\\s)|\\s*(?<separator>[${quotedSeparators}])\\s*`, "g");
605
- const matches = [];
606
- let match;
607
- while ((match = regex.exec(header.trim())) !== null) matches.push(match.groups ? {
608
- separator: match.groups.separator ?? "",
609
- 0: match[0]
610
- } : { 0: match[0] });
611
- return this.groupParts(matches, separators);
612
- }
613
- /**
614
- * Combines an array of arrays into one associative object.
615
- * [['foo', 'abc'], ['bar']] => { foo: 'abc', bar: true }
616
- */
617
- static combine(parts) {
618
- const assoc = {};
619
- for (const part of parts) {
620
- const name = String(part[0]).toLowerCase();
621
- assoc[name] = part[1] ?? true;
622
- }
623
- return assoc;
624
- }
625
- /**
626
- * Joins an associative object into a string for use in an HTTP header.
627
- * { foo: 'abc', bar: true, baz: 'a b c' } => 'foo=abc, bar, baz="a b c"'
628
- */
629
- static toString(assoc, separator) {
630
- return Object.entries(assoc).map(([name, value]) => {
631
- return value === true ? name : `${name}=${HeaderUtility.quote(value)}`;
632
- }).join(`${separator} `);
633
- }
634
- /**
635
- * Encodes a string as a quoted string, if necessary.
636
- */
637
- static quote(s) {
638
- if (/^[a-z0-9!#$%&'*.^_`|~-]+$/i.test(s)) return s;
639
- return `"${s.replace(/(["\\])/g, "\\$1")}"`;
640
- }
641
- /**
642
- * Decodes a quoted string.
643
- */
644
- static unquote(s) {
645
- return s.replace(/\\(.)|"/g, "$1");
646
- }
647
- /**
648
- * Generates an HTTP Content-Disposition field-value.
649
- *
650
- * @see RFC 6266
651
- */
652
- static makeDisposition(disposition, filename, filenameFallback = "") {
653
- if (![HeaderUtility.DISPOSITION_ATTACHMENT, HeaderUtility.DISPOSITION_INLINE].includes(disposition)) throw new Error(`The disposition must be either "${HeaderUtility.DISPOSITION_ATTACHMENT}" or "${HeaderUtility.DISPOSITION_INLINE}".`);
654
- if (filenameFallback === "") filenameFallback = filename;
655
- if (!/^[\x20-\x7e]*$/.test(filenameFallback)) throw new _h3ravel_support.InvalidArgumentException("The filename fallback must only contain ASCII characters.");
656
- if (filenameFallback.includes("%")) throw new _h3ravel_support.InvalidArgumentException("The filename fallback cannot contain the \"%\" character.");
657
- if ([filename, filenameFallback].some((f) => f.includes("/") || f.includes("\\"))) throw new _h3ravel_support.InvalidArgumentException("The filename and the fallback cannot contain the \"/\" and \"\\\" characters.");
658
- const params = { filename: filenameFallback };
659
- if (filename !== filenameFallback) params["filename*"] = `utf-8''${encodeURIComponent(filename)}`;
660
- return `${disposition}; ${HeaderUtility.toString(params, ";")}`;
661
- }
662
- /**
663
- * Like parse_str(), but preserves dots in variable names.
664
- */
665
- static parseQuery(query, ignoreBrackets = false, separator = "&") {
666
- const q = {};
667
- const pairs = query.split(separator);
668
- for (let v of pairs) {
669
- const nullPos = v.indexOf("\0");
670
- if (nullPos !== -1) v = v.slice(0, nullPos);
671
- const eqPos = v.indexOf("=");
672
- let k;
673
- let val;
674
- if (eqPos === -1) {
675
- k = decodeURIComponent(v);
676
- val = "";
677
- } else {
678
- k = decodeURIComponent(v.slice(0, eqPos));
679
- val = v.slice(eqPos);
680
- }
681
- const nullKeyPos = k.indexOf("\0");
682
- if (nullKeyPos !== -1) k = k.slice(0, nullKeyPos);
683
- k = k.trimStart();
684
- if (ignoreBrackets) {
685
- q[k] = q[k] || [];
686
- q[k].push(decodeURIComponent(val.slice(1)));
687
- continue;
688
- }
689
- const bracketPos = k.indexOf("[");
690
- if (bracketPos === -1) q[Buffer.from(k).toString("hex") + val] = val;
691
- else {
692
- const prefix = Buffer.from(k.slice(0, bracketPos)).toString("hex");
693
- q[prefix + encodeURIComponent(k.slice(bracketPos)) + val] = val;
694
- }
695
- }
696
- if (ignoreBrackets) return q;
697
- const parsed = new URLSearchParams(Object.keys(q).join("&"));
698
- const result = {};
699
- for (const [key, value] of parsed.entries()) {
700
- const underscorePos = key.indexOf("_");
701
- if (underscorePos !== -1) {
702
- const newKey = key.slice(0, underscorePos) + Buffer.from(key.slice(0, underscorePos), "hex").toString("utf8") + "[" + key.slice(underscorePos + 1) + "]";
703
- result[newKey] = value;
704
- } else result[Buffer.from(key, "hex").toString("utf8")] = value;
705
- }
706
- return result;
707
- }
708
- static groupParts(matches, separators, first = true) {
709
- const separator = separators[0];
710
- const rest = separators.slice(1);
711
- let i = 0;
712
- if (!rest && !first) {
713
- const parts = [""];
714
- for (const match of matches) if (!i && match.separator) {
715
- i = 1;
716
- parts[1] = "";
717
- } else parts[i] += this.unquote(match[0]);
718
- return parts;
719
- }
720
- const parts = [];
721
- const grouped = {};
722
- for (const match of matches) if (match.separator === separator) ++i;
723
- else (grouped[i] ||= []).push(match);
724
- for (const group of Object.values(grouped)) if (!rest && this.unquote(group[0][0]) !== "") parts.push(this.unquote(group[0][0]));
725
- else {
726
- const sub = this.groupParts(group, rest, false);
727
- if (sub) parts.push(sub);
728
- }
729
- return parts;
730
- }
731
- };
732
- //#endregion
733
- //#region src/Utilities/Cookie.ts
734
- /**
735
- * Represents a Cookie
736
- */
737
- var Cookie = class Cookie {
738
- name;
739
- value;
740
- domain;
741
- secure;
742
- httpOnly;
743
- static SAMESITE_NONE = "none";
744
- static SAMESITE_LAX = "lax";
745
- static SAMESITE_STRICT = "strict";
746
- expire;
747
- path;
748
- sameSite;
749
- raw;
750
- partitioned;
751
- secureDefault = false;
752
- static RESERVED_CHARS_LIST = "=,; \r\n\v\f";
753
- static RESERVED_CHARS_FROM = [
754
- "=",
755
- ",",
756
- ";",
757
- " ",
758
- " ",
759
- "\r",
760
- "\n",
761
- "\v",
762
- "\f"
763
- ];
764
- static RESERVED_CHARS_TO = [
765
- "%3D",
766
- "%2C",
767
- "%3B",
768
- "%20",
769
- "%09",
770
- "%0D",
771
- "%0A",
772
- "%0B",
773
- "%0C"
774
- ];
775
- constructor(name, value, expire = 0, path = "/", domain, secure, httpOnly = true, raw = false, sameSite = Cookie.SAMESITE_LAX, partitioned = false) {
776
- this.name = name;
777
- this.value = value;
778
- this.domain = domain;
779
- this.secure = secure;
780
- this.httpOnly = httpOnly;
781
- if (raw && [...Cookie.RESERVED_CHARS_LIST].some((c) => name.includes(c))) throw new Error(`The cookie name "${name}" contains invalid characters.`);
782
- if (!name) throw new Error("The cookie name cannot be empty.");
783
- this.expire = Cookie.expiresTimestamp(expire);
784
- this.path = path || "/";
785
- this.sameSite = this.withSameSite(sameSite).sameSite;
786
- this.raw = raw;
787
- this.partitioned = partitioned;
788
- }
789
- /**
790
- * Create a Cookie instance from a Set-Cookie header string.
791
- */
792
- static fromString(cookie, decode = false) {
793
- const data = {
794
- expires: 0,
795
- path: "/",
796
- domain: null,
797
- secure: false,
798
- httponly: false,
799
- raw: !decode,
800
- samesite: null,
801
- partitioned: false
802
- };
803
- const parts = HeaderUtility.split(cookie, ";=");
804
- const part = parts.shift();
805
- const name = decode ? decodeURIComponent(part[0]) : part[0];
806
- const value = part[1] ? decode ? decodeURIComponent(part[1]) : part[1] : null;
807
- Object.assign(data, HeaderUtility.combine(parts));
808
- data.expires = Cookie.expiresTimestamp(data.expires);
809
- if (data["max-age"] && (data["max-age"] > 0 || data.expires > Date.now() / 1e3)) data.expires = Math.floor(Date.now() / 1e3) + Number(data["max-age"]);
810
- return new Cookie(name, value, data.expires, data.path, data.domain, data.secure, data.httponly, data.raw, data.samesite, data.partitioned);
811
- }
812
- /**
813
- * Convert various expiration formats into a timestamp (seconds)
814
- */
815
- static expiresTimestamp(expire = 0) {
816
- if (expire instanceof Date) return Math.floor(expire.getTime() / 1e3);
817
- if (typeof expire === "string") {
818
- const parsed = Date.parse(expire);
819
- if (isNaN(parsed)) throw new Error("The cookie expiration time is not valid.");
820
- return Math.floor(parsed / 1e3);
821
- }
822
- return expire > 0 ? expire : 0;
823
- }
824
- clone() {
825
- return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
826
- }
827
- withValue(value) {
828
- const c = this.clone();
829
- c.value = value;
830
- return c;
831
- }
832
- withDomain(domain) {
833
- const c = this.clone();
834
- c.domain = domain;
835
- return c;
836
- }
837
- withPath(path) {
838
- const c = this.clone();
839
- c.path = path || "/";
840
- return c;
841
- }
842
- withSecure(secure = true) {
843
- const c = this.clone();
844
- c.secure = secure;
845
- return c;
846
- }
847
- withHttpOnly(httpOnly = true) {
848
- const c = this.clone();
849
- c.httpOnly = httpOnly;
850
- return c;
851
- }
852
- withRaw(raw = true) {
853
- const c = this.clone();
854
- c.raw = raw;
855
- return c;
856
- }
857
- withSameSite(sameSite) {
858
- if (sameSite && ![
859
- Cookie.SAMESITE_LAX,
860
- Cookie.SAMESITE_STRICT,
861
- Cookie.SAMESITE_NONE
862
- ].includes(sameSite.toLowerCase())) throw new Error("The \"sameSite\" value must be \"lax\", \"strict\", \"none\" or null.");
863
- const c = this.clone();
864
- c.sameSite = sameSite ? sameSite.toLowerCase() : null;
865
- return c;
866
- }
867
- withPartitioned(partitioned = true) {
868
- const c = this.clone();
869
- c.partitioned = partitioned;
870
- return c;
871
- }
872
- withExpires(expire) {
873
- const c = this.clone();
874
- c.expire = Cookie.expiresTimestamp(expire);
875
- return c;
876
- }
877
- getName() {
878
- return this.name;
879
- }
880
- getValue() {
881
- return this.value;
882
- }
883
- getDomain() {
884
- return this.domain;
885
- }
886
- getPath() {
887
- return this.path;
888
- }
889
- getExpiresTime() {
890
- return this.expire;
891
- }
892
- getMaxAge() {
893
- if (this.expire === 0) return 0;
894
- return this.expire - Math.floor(Date.now() / 1e3);
895
- }
896
- /**
897
- * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
898
- */
899
- isSecure() {
900
- return this.secure ?? this.secureDefault;
901
- }
902
- isHttpOnly() {
903
- return this.httpOnly;
904
- }
905
- isRaw() {
906
- return this.raw;
907
- }
908
- getSameSite() {
909
- return this.sameSite;
910
- }
911
- isPartitioned() {
912
- return this.partitioned;
913
- }
914
- /**
915
- * Whether this cookie is about to be cleared.
916
- */
917
- isCleared() {
918
- return 0 !== this.expire && this.expire < (/* @__PURE__ */ new Date()).getTime();
919
- }
920
- /**
921
- * Convert the cookie to a Set-Cookie header string.
922
- */
923
- toString() {
924
- const from = Cookie.RESERVED_CHARS_FROM;
925
- const to = Cookie.RESERVED_CHARS_TO;
926
- const encodeName = (name) => this.isRaw() ? name : name.replaceAll(new RegExp(from.map((x) => `\\${x}`).join("|"), "g"), (m) => to[from.indexOf(m)]);
927
- const encodeValue = (val) => this.isRaw() ? val : encodeURIComponent(val);
928
- let str = `${encodeName(this.name)}=`;
929
- if (!this.value) str += `deleted; expires=${(/* @__PURE__ */ new Date(Date.now() - 31536001e3)).toUTCString()}; Max-Age=0`;
930
- else {
931
- str += encodeValue(this.value);
932
- if (this.expire !== 0) {
933
- const expiresAt = (/* @__PURE__ */ new Date(this.expire * 1e3)).toUTCString();
934
- str += `; expires=${expiresAt}; Max-Age=${this.getMaxAge()}`;
935
- }
936
- }
937
- if (this.path) str += `; path=${this.path}`;
938
- if (this.domain) str += `; domain=${this.domain}`;
939
- if (this.isSecure()) str += "; secure";
940
- if (this.isHttpOnly()) str += "; httponly";
941
- if (this.sameSite) str += `; samesite=${this.sameSite}`;
942
- if (this.partitioned) str += "; partitioned";
943
- return str;
944
- }
945
- /**
946
- * @param bool $default The default value of the "secure" flag when it is set to null
947
- */
948
- setSecureDefault(defaultValue) {
949
- this.secureDefault = defaultValue;
950
- }
951
- };
952
- //#endregion
953
- //#region src/Utilities/ResponseHeaderBag.ts
954
- /**
955
- * ResponseHeaderBag is a container for Response HTTP headers.
956
- * for Node/H3 environments.
957
- */
958
- var ResponseHeaderBag = class ResponseHeaderBag extends HeaderBag {
959
- static COOKIES_FLAT = "flat";
960
- static COOKIES_ARRAY = "array";
961
- static DISPOSITION_ATTACHMENT = "attachment";
962
- static DISPOSITION_INLINE = "inline";
963
- computedCacheControl = {};
964
- cookies = {};
965
- headerNames = {};
966
- constructor(event) {
967
- super(Object.fromEntries(event.res.headers.entries()));
968
- if (!this.headers["cache-control"]) this.set("Cache-Control", "");
969
- if (!this.headers["date"]) this.initDate();
970
- }
971
- /**
972
- * Returns the headers with original capitalizations.
973
- */
974
- allPreserveCase() {
975
- const headers = {};
976
- for (const [name, value] of Object.entries(this.all())) {
977
- const originalName = this.headerNames[name] ?? name;
978
- headers[originalName] = value;
979
- }
980
- return headers;
981
- }
982
- allPreserveCaseWithoutCookies() {
983
- const headers = this.allPreserveCase();
984
- if (this.headerNames["set-cookie"]) delete headers[this.headerNames["set-cookie"]];
985
- return headers;
986
- }
987
- replace(headers = {}) {
988
- this.headerNames = {};
989
- super.replace(headers);
990
- if (!this.headers["cache-control"]) this.set("Cache-Control", "");
991
- if (!this.headers["date"]) this.initDate();
992
- }
993
- all(key) {
994
- const headers = super.all();
995
- if (key) {
996
- const normalized = key.toLowerCase();
997
- if (normalized === "set-cookie") return this.getCookies().map(String);
998
- return headers[normalized] ?? [];
999
- }
1000
- const cookies = this.getCookies().map(String);
1001
- if (cookies.length > 0) headers["set-cookie"] = cookies;
1002
- return headers;
1003
- }
1004
- set(key, values, replace = true) {
1005
- const uniqueKey = key.toLowerCase();
1006
- if (uniqueKey === "set-cookie") {
1007
- if (replace) this.cookies = {};
1008
- for (const cookie of Array.isArray(values) ? values : [values]) if (cookie) this.setCookie(Cookie.fromString(cookie));
1009
- this.headerNames[uniqueKey] = key;
1010
- return;
1011
- }
1012
- this.headerNames[uniqueKey] = key;
1013
- super.set(key, values, replace);
1014
- if ([
1015
- "cache-control",
1016
- "etag",
1017
- "last-modified",
1018
- "expires"
1019
- ].includes(uniqueKey) && this.computeCacheControlValue() !== "") {
1020
- const computed = this.computeCacheControlValue();
1021
- this.headers["cache-control"] = [computed];
1022
- this.headerNames["cache-control"] = "Cache-Control";
1023
- this.computedCacheControl = this.parseCacheControl(computed);
1024
- }
1025
- }
1026
- remove(key) {
1027
- const uniqueKey = key.toLowerCase();
1028
- delete this.headerNames[uniqueKey];
1029
- if (uniqueKey === "set-cookie") {
1030
- this.cookies = {};
1031
- return;
1032
- }
1033
- super.remove(key);
1034
- if (uniqueKey === "cache-control") this.computedCacheControl = {};
1035
- if (uniqueKey === "date") this.initDate();
1036
- }
1037
- hasCacheControlDirective(key) {
1038
- return key in this.computedCacheControl;
1039
- }
1040
- getCacheControlDirective(key) {
1041
- return this.computedCacheControl[key] ?? null;
1042
- }
1043
- setCookie(cookie) {
1044
- const domain = cookie.getDomain() ?? "";
1045
- const path = cookie.getPath() ?? "/";
1046
- this.cookies[domain] ??= {};
1047
- this.cookies[domain][path] ??= {};
1048
- this.cookies[domain][path][cookie.getName()] = cookie;
1049
- this.headerNames["set-cookie"] = "Set-Cookie";
1050
- }
1051
- removeCookie(name, path = "/", domain = null) {
1052
- const d = domain ?? "";
1053
- delete this.cookies[d]?.[path]?.[name];
1054
- if (this.cookies[d] && Object.keys(this.cookies[d][path] ?? {}).length === 0) {
1055
- delete this.cookies[d][path];
1056
- if (Object.keys(this.cookies[d]).length === 0) delete this.cookies[d];
1057
- }
1058
- if (Object.keys(this.cookies).length === 0) delete this.headerNames["set-cookie"];
1059
- }
1060
- /**
1061
- * @throws {Error} if format is invalid
1062
- */
1063
- getCookies(format = ResponseHeaderBag.COOKIES_FLAT) {
1064
- if (![ResponseHeaderBag.COOKIES_FLAT, ResponseHeaderBag.COOKIES_ARRAY].includes(format)) throw new Error(`Format "${format}" invalid (${ResponseHeaderBag.COOKIES_FLAT}, ${ResponseHeaderBag.COOKIES_ARRAY}).`);
1065
- if (format === ResponseHeaderBag.COOKIES_ARRAY) return this.cookies;
1066
- const flattened = [];
1067
- for (const domain of Object.values(this.cookies)) for (const path of Object.values(domain)) for (const cookie of Object.values(path)) flattened.push(cookie);
1068
- return flattened;
1069
- }
1070
- clearCookie(name, path = "/", domain = null, secure = false, httpOnly = true, sameSite, partitioned = false) {
1071
- this.setCookie(new Cookie(name, null, 1, path, domain, secure, httpOnly, false, sameSite, partitioned));
1072
- }
1073
- makeDisposition(disposition, filename, fallback = "") {
1074
- return HeaderUtility.makeDisposition(disposition, filename, fallback);
1075
- }
1076
- computeCacheControlValue() {
1077
- if (Object.keys(this.cacheControl).length === 0) {
1078
- if (this.has("Last-Modified") || this.has("Expires")) return "private, must-revalidate";
1079
- return "no-cache, private";
1080
- }
1081
- const header = this.getCacheControlHeader();
1082
- if (this.cacheControl["public"] || this.cacheControl["private"]) return header;
1083
- if (!this.cacheControl["s-maxage"]) return `${header}, private`;
1084
- return header;
1085
- }
1086
- initDate() {
1087
- const now = (/* @__PURE__ */ new Date()).toUTCString();
1088
- this.set("Date", now);
1089
- }
1090
- };
1091
- //#endregion
1092
- //#region src/Utilities/HttpResponse.ts
1093
- var HttpResponse = class HttpResponse extends _h3ravel_contracts.IHttpResponse {
1094
- event;
1095
- statusCode = 200;
1096
- headers;
1097
- content;
1098
- version;
1099
- statusText;
1100
- charset;
1101
- /**
1102
- * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
1103
- */
1104
- HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = _h3ravel_foundation.HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES;
1105
- /**
1106
- * The exception that triggered the error response (if applicable).
1107
- */
1108
- exception;
1109
- /**
1110
- * Tracks headers already sent in informational responses.
1111
- */
1112
- sentHeaders = {};
1113
- /**
1114
- * Status codes translation table.
1115
- *
1116
- * The list of codes is complete according to the
1117
- * @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry
1118
- * (last updated 2021-10-01).
1119
- *
1120
- * Unless otherwise noted, the status code is defined in RFC2616.
1121
- */
1122
- static statusTexts = _h3ravel_foundation.statusTexts;
1123
- constructor(event) {
1124
- super();
1125
- this.event = event;
1126
- this.headers = new ResponseHeaderBag(this.event);
1127
- this.setContent();
1128
- this.setProtocolVersion("1.0");
1129
- }
1130
- /**
1131
- * Set HTTP status code.
1132
- */
1133
- setStatusCode(code, text) {
1134
- this.statusCode = code;
1135
- this.event.res.status = code;
1136
- if (this.isInvalid()) throw new _h3ravel_support.InvalidArgumentException(`The HTTP status code "${code}" is not valid.`);
1137
- if (!text) {
1138
- this.statusText = HttpResponse.statusTexts[code] ?? "unknown status";
1139
- this.event.res.statusText = this.statusText;
1140
- return this;
1141
- }
1142
- this.statusText = text;
1143
- this.event.res.statusText = this.statusText;
1144
- return this;
1145
- }
1146
- /**
1147
- * Retrieves the status code for the current web response.
1148
- */
1149
- getStatusCode() {
1150
- return this.statusCode;
1151
- }
1152
- /**
1153
- * Sets the response charset.
1154
- */
1155
- setCharset(charset) {
1156
- this.charset = charset;
1157
- return this;
1158
- }
1159
- /**
1160
- * Retrieves the response charset.
1161
- */
1162
- getCharset() {
1163
- return this.charset;
1164
- }
1165
- /**
1166
- * Returns true if the response may safely be kept in a shared (surrogate) cache.
1167
- *
1168
- * Responses marked "private" with an explicit Cache-Control directive are
1169
- * considered uncacheable.
1170
- *
1171
- * Responses with neither a freshness lifetime (Expires, max-age) nor cache
1172
- * validator (Last-Modified, ETag) are considered uncacheable because there is
1173
- * no way to tell when or how to remove them from the cache.
1174
- *
1175
- * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
1176
- * for example "status codes that are defined as cacheable by default [...]
1177
- * can be reused by a cache with heuristic expiration unless otherwise indicated"
1178
- * (https://tools.ietf.org/html/rfc7231#section-6.1)
1179
- *
1180
- * @final
1181
- */
1182
- isCacheable() {
1183
- if (![
1184
- 200,
1185
- 203,
1186
- 300,
1187
- 301,
1188
- 302,
1189
- 404,
1190
- 410
1191
- ].includes(this.statusCode)) return false;
1192
- if (this.headers.hasCacheControlDirective("no-store") || this.headers.getCacheControlDirective("private")) return false;
1193
- return this.isValidateable() || this.isFresh();
1194
- }
1195
- /**
1196
- * Returns true if the response is "fresh".
1197
- *
1198
- * Fresh responses may be served from cache without any interaction with the
1199
- * origin. A response is considered fresh when it includes a Cache-Control/max-age
1200
- * indicator or Expires header and the calculated age is less than the freshness lifetime.
1201
- */
1202
- isFresh() {
1203
- return Number(this.getTtl()) > 0;
1204
- }
1205
- /**
1206
- * Returns true if the response includes headers that can be used to validate
1207
- * the response with the origin server using a conditional GET request.
1208
- */
1209
- isValidateable() {
1210
- return this.headers.has("Last-Modified") || this.headers.has("ETag");
1211
- }
1212
- /**
1213
- * Sets the response content.
1214
- */
1215
- setContent(content) {
1216
- this.content = content ?? "";
1217
- return this;
1218
- }
1219
- /**
1220
- * Gets the current response content.
1221
- */
1222
- getContent() {
1223
- return this.content;
1224
- }
1225
- /**
1226
- * Set a header.
1227
- */
1228
- setHeader(name, value) {
1229
- this.headers.set(name, value);
1230
- return this;
1231
- }
1232
- /**
1233
- * Sets the HTTP protocol version (1.0 or 1.1).
1234
- */
1235
- setProtocolVersion(version) {
1236
- this.version = version;
1237
- return this;
1238
- }
1239
- /**
1240
- * Gets the HTTP protocol version.
1241
- */
1242
- getProtocolVersion() {
1243
- return this.version;
1244
- }
1245
- /**
1246
- * Marks the response as "private".
1247
- *
1248
- * It makes the response ineligible for serving other clients.
1249
- */
1250
- setPrivate() {
1251
- this.headers.removeCacheControlDirective("public");
1252
- this.headers.addCacheControlDirective("private");
1253
- return this;
1254
- }
1255
- /**
1256
- * Marks the response as "public".
1257
- *
1258
- * It makes the response eligible for serving other clients.
1259
- */
1260
- setPublic() {
1261
- this.headers.addCacheControlDirective("public");
1262
- this.headers.removeCacheControlDirective("private");
1263
- return this;
1264
- }
1265
- /**
1266
- * Returns the Date header as a DateTime instance.
1267
- * @throws {RuntimeException} When the header is not parseable
1268
- */
1269
- getDate() {
1270
- return this.headers.getDate("Date");
1271
- }
1272
- /**
1273
- * Returns the age of the response in seconds.
1274
- *
1275
- * @final
1276
- */
1277
- getAge() {
1278
- const age = this.headers.get("Age");
1279
- if (age) return Number(age);
1280
- return Math.max(_h3ravel_support.DateTime.now().unix() - this.getDate().unix(), 0);
1281
- }
1282
- /**
1283
- * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
1284
- */
1285
- expire() {
1286
- if (this.isFresh()) {
1287
- this.headers.set("Age", String(this.getMaxAge()));
1288
- this.headers.remove("Expires");
1289
- }
1290
- return this;
1291
- }
1292
- /**
1293
- * Returns the value of the Expires header as a DateTime instance.
1294
- *
1295
- * @final
1296
- */
1297
- getExpires() {
1298
- try {
1299
- return new _h3ravel_support.DateTime(this.headers.getDate("Expires"));
1300
- } catch {
1301
- return new _h3ravel_support.DateTime(_h3ravel_support.DateTime.now().subtract(2, "days"));
1302
- }
1303
- }
1304
- /**
1305
- * Returns the number of seconds after the time specified in the response's Date
1306
- * header when the response should no longer be considered fresh.
1307
- *
1308
- * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
1309
- * back on an expires header. It returns null when no maximum age can be established.
1310
- */
1311
- getMaxAge() {
1312
- if (this.headers.hasCacheControlDirective("s-maxage")) return Number(this.headers.getCacheControlDirective("s-maxage"));
1313
- if (this.headers.hasCacheControlDirective("max-age")) return Number(this.headers.getCacheControlDirective("max-age"));
1314
- const expires = this.getExpires();
1315
- if (expires) {
1316
- const maxAge = Number(expires.unix() - this.getDate().unix());
1317
- return Math.max(maxAge, 0);
1318
- }
1319
- }
1320
- /**
1321
- * Sets the number of seconds after which the response should no longer be considered fresh.
1322
- *
1323
- * This method sets the Cache-Control max-age directive.
1324
- */
1325
- setMaxAge(value) {
1326
- this.headers.addCacheControlDirective("max-age", String(value));
1327
- return this;
1328
- }
1329
- /**
1330
- * Sets the number of seconds after which the response should no longer be returned by shared caches when backend is down.
1331
- *
1332
- * This method sets the Cache-Control stale-if-error directive.
1333
- */
1334
- setStaleIfError(value) {
1335
- this.headers.addCacheControlDirective("stale-if-error", String(value));
1336
- return this;
1337
- }
1338
- /**
1339
- * Sets the number of seconds after which the response should no longer return stale content by shared caches.
1340
- *
1341
- * This method sets the Cache-Control stale-while-revalidate directive.
1342
- */
1343
- setStaleWhileRevalidate(value) {
1344
- this.headers.addCacheControlDirective("stale-while-revalidate", String(value));
1345
- return this;
1346
- }
1347
- /**
1348
- * Returns the response's time-to-live in seconds.
1349
- *
1350
- * It returns null when no freshness information is present in the response.
1351
- *
1352
- * When the response's TTL is 0, the response may not be served from cache without first
1353
- * revalidating with the origin.
1354
- *
1355
- * @final
1356
- */
1357
- getTtl() {
1358
- const maxAge = Number(this.getMaxAge());
1359
- return null !== maxAge ? Math.max(maxAge - this.getAge(), 0) : void 0;
1360
- }
1361
- /**
1362
- * Sets the response's time-to-live for shared caches in seconds.
1363
- *
1364
- * This method adjusts the Cache-Control/s-maxage directive.
1365
- */
1366
- setTtl(seconds) {
1367
- this.setSharedMaxAge(this.getAge() + seconds);
1368
- return this;
1369
- }
1370
- /**
1371
- * Sets the response's time-to-live for private/client caches in seconds.
1372
- *
1373
- * This method adjusts the Cache-Control/max-age directive.
1374
- */
1375
- setClientTtl(seconds) {
1376
- this.setMaxAge(this.getAge() + seconds);
1377
- return this;
1378
- }
1379
- /**
1380
- * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
1381
- *
1382
- * This method sets the Cache-Control s-maxage directive.
1383
- */
1384
- setSharedMaxAge(value) {
1385
- this.setPublic();
1386
- this.headers.addCacheControlDirective("s-maxage", String(value));
1387
- return this;
1388
- }
1389
- /**
1390
- * Returns the Last-Modified HTTP header as a DateTime instance.
1391
- *
1392
- * @throws \RuntimeException When the HTTP header is not parseable
1393
- *
1394
- * @final
1395
- */
1396
- getLastModified() {
1397
- return this.headers.getDate("Last-Modified");
1398
- }
1399
- /**
1400
- * Sets the Last-Modified HTTP header with a DateTime instance.
1401
- *
1402
- * Passing null as value will remove the header.
1403
- *
1404
- * @return $this
1405
- *
1406
- * @final
1407
- */
1408
- setLastModified(date) {
1409
- if (!date) {
1410
- this.headers.remove("Last-Modified");
1411
- return this;
1412
- }
1413
- date = new _h3ravel_support.DateTime(date).setTimezone("UTC");
1414
- this.headers.set("Last-Modified", date.format("ddd, DD MMM YYYY HH:mm:ss") + " GMT");
1415
- return this;
1416
- }
1417
- /**
1418
- * Returns the literal value of the ETag HTTP header.
1419
- */
1420
- getEtag() {
1421
- return this.headers.get("ETag");
1422
- }
1423
- /**
1424
- * Sets the ETag value.
1425
- *
1426
- * @param etag The ETag unique identifier or null to remove the header
1427
- * @param weak Whether you want a weak ETag or not
1428
- */
1429
- setEtag(etag, weak = false) {
1430
- if (!etag) this.headers.remove("Etag");
1431
- else {
1432
- if (!etag.startsWith("\"")) etag = "\"" + etag + "\"";
1433
- this.headers.set("ETag", (true === weak ? "W/" : "") + etag);
1434
- }
1435
- return this;
1436
- }
1437
- /**
1438
- * Sets the response's cache headers (validation and/or expiration).
1439
- *
1440
- * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag.
1441
- *
1442
- * @throws {InvalidArgumentException}
1443
- */
1444
- setCache(options) {
1445
- const invalidKeys = Object.keys(options).filter((key) => !(key in _h3ravel_foundation.HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES));
1446
- if (invalidKeys.length > 0) throw new _h3ravel_support.InvalidArgumentException(`Response does not support the following options: "${invalidKeys.join("\", \"")}"`);
1447
- if (options.etag) this.setEtag(options.etag);
1448
- if (options.last_modified) this.setLastModified(options.last_modified);
1449
- if (options.max_age) this.setMaxAge(options.max_age);
1450
- if (options.s_maxage) this.setSharedMaxAge(options.s_maxage);
1451
- if (options.stale_while_revalidate) this.setStaleWhileRevalidate(options.stale_while_revalidate);
1452
- if (options.stale_if_error) this.setStaleIfError(options.stale_if_error);
1453
- for (const [directive, hasValue] of Object.entries(_h3ravel_foundation.HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES)) if (!hasValue && directive in options) {
1454
- const token = directive.replace(/_/g, "-");
1455
- if (options[directive]) this.headers.addCacheControlDirective(token);
1456
- else this.headers.removeCacheControlDirective(token);
1457
- }
1458
- if (options.public !== void 0) if (options.public) this.setPublic();
1459
- else this.setPrivate();
1460
- if (options.private !== void 0) if (options.private) this.setPrivate();
1461
- else this.setPublic();
1462
- return this;
1463
- }
1464
- /**
1465
- * Modifies the response so that it conforms to the rules defined for a 304 status code.
1466
- *
1467
- * This sets the status, removes the body, and discards any headers
1468
- * that MUST NOT be included in 304 responses.
1469
- * @see https://tools.ietf.org/html/rfc2616#section-10.3.5
1470
- */
1471
- setNotModified() {
1472
- this.setStatusCode(304);
1473
- this.setContent();
1474
- for (const header of [
1475
- "Allow",
1476
- "Content-Encoding",
1477
- "Content-Language",
1478
- "Content-Length",
1479
- "Content-MD5",
1480
- "Content-Type",
1481
- "Last-Modified"
1482
- ]) this.headers.remove(header);
1483
- return this;
1484
- }
1485
- /**
1486
- * Add an array of headers to the response.
1487
- *
1488
- */
1489
- withHeaders(headers) {
1490
- if (headers instanceof HeaderBag) headers = headers.all();
1491
- for (const [key, value] of Object.entries(headers)) this.headers.set(key, value);
1492
- return this;
1493
- }
1494
- /**
1495
- * Set the exception to attach to the response.
1496
- */
1497
- withException(e) {
1498
- this.exception = e;
1499
- return this;
1500
- }
1501
- /**
1502
- * Throws the response in a HttpResponseException instance.
1503
- *
1504
- * @throws {HttpResponseException}
1505
- */
1506
- throwResponse() {
1507
- throw new HttpResponseException(this);
1508
- }
1509
- /**
1510
- * Is response invalid?
1511
- *
1512
- * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
1513
- */
1514
- isInvalid() {
1515
- return this.statusCode < 100 || this.statusCode >= 600;
1516
- }
1517
- /**
1518
- * Is response informative?
1519
- */
1520
- isInformational() {
1521
- return this.statusCode >= 100 && this.statusCode < 200;
1522
- }
1523
- /**
1524
- * Is response successful?
1525
- */
1526
- isSuccessful() {
1527
- return this.statusCode >= 200 && this.statusCode < 300;
1528
- }
1529
- /**
1530
- * Is the response a redirect?
1531
- */
1532
- isRedirection() {
1533
- return this.statusCode >= 300 && this.statusCode < 400;
1534
- }
1535
- /**
1536
- * Is there a client error?
1537
- */
1538
- isClientError() {
1539
- return this.statusCode >= 400 && this.statusCode < 500;
1540
- }
1541
- /**
1542
- * Was there a server side error?
1543
- */
1544
- isServerError() {
1545
- return this.statusCode >= 500 && this.statusCode < 600;
1546
- }
1547
- /**
1548
- * Is the response OK?
1549
- */
1550
- isOk() {
1551
- return 200 === this.statusCode;
1552
- }
1553
- /**
1554
- * Is the response forbidden?
1555
- */
1556
- isForbidden() {
1557
- return 403 === this.statusCode;
1558
- }
1559
- /**
1560
- * Is the response a not found error?
1561
- */
1562
- isNotFound() {
1563
- return 404 === this.statusCode;
1564
- }
1565
- /**
1566
- * Is the response a redirect of some form?
1567
- */
1568
- isRedirect(location) {
1569
- if (![
1570
- 201,
1571
- 301,
1572
- 302,
1573
- 303,
1574
- 307,
1575
- 308
1576
- ].includes(this.statusCode)) return false;
1577
- if (!location) return true;
1578
- return location === this.headers.get("Location");
1579
- }
1580
- /**
1581
- * Is the response empty?
1582
- */
1583
- isEmpty() {
1584
- return [204, 304].includes(this.statusCode);
1585
- }
1586
- /**
1587
- * Apply headers before sending response.
1588
- */
1589
- sendHeaders(statusCode) {
1590
- statusCode ??= this.statusCode;
1591
- const informational = statusCode >= 100 && statusCode < 200;
1592
- for (const [name, values] of Object.entries(this.headers.allPreserveCaseWithoutCookies())) {
1593
- const previousValues = this.sentHeaders[name] ?? null;
1594
- if (previousValues && JSON.stringify(previousValues) === JSON.stringify(values)) continue;
1595
- const replace = name.localeCompare("Content-Type", void 0, { sensitivity: "accent" }) === 0;
1596
- if (previousValues && previousValues.some((v) => !values.includes(v))) this.event.res.headers.delete(name);
1597
- const newValues = !previousValues ? values : values.filter((v) => !previousValues.includes(v));
1598
- for (const value of newValues) if (replace) this.event.res.headers.set(name, value);
1599
- else this.event.res.headers.append(name, value);
1600
- if (informational) this.sentHeaders[name] = values;
1601
- }
1602
- for (const cookie of this.headers.getCookies()) this.event.res.headers.append("Set-Cookie", cookie.toString());
1603
- if (informational) return this;
1604
- this.setStatusCode(statusCode, this.statusText);
1605
- return this;
1606
- }
1607
- /**
1608
- * Prepares the Response before it is sent to the client.
1609
- *
1610
- * This method tweaks the Response to ensure that it is
1611
- * compliant with RFC 2616. Most of the changes are based on
1612
- * the Request that is "associated" with this Response.
1613
- **/
1614
- prepare(request) {
1615
- const isInformational = this.isInformational();
1616
- const isEmpty = this.isEmpty();
1617
- if (isInformational || isEmpty) {
1618
- this.setContent();
1619
- this.headers.remove("Content-Type");
1620
- this.headers.remove("Content-Length");
1621
- return this;
1622
- }
1623
- if (!this.headers.has("Content-Type")) {
1624
- const format = request.getRequestFormat();
1625
- if (format) {
1626
- const mimeType = request.getMimeType(format);
1627
- if (mimeType) this.headers.set("Content-Type", mimeType);
1628
- }
1629
- }
1630
- const charset = this.charset || "UTF-8";
1631
- this.charset ??= charset;
1632
- const currentType = this.headers.get("Content-Type") || "";
1633
- if (!this.headers.has("Content-Type")) this.headers.set("Content-Type", `text/html; charset=${charset}`);
1634
- else if (currentType.toLowerCase().startsWith("text/") && !/charset=/i.test(currentType)) this.headers.set("Content-Type", `${currentType}; charset=${charset}`);
1635
- if (this.headers.has("Transfer-Encoding")) this.headers.remove("Content-Length");
1636
- if (request.isMethod("HEAD")) {
1637
- const length = this.headers.get("Content-Length");
1638
- this.setContent(void 0);
1639
- if (length) this.headers.set("Content-Length", length);
1640
- }
1641
- if ((request._server?.get("SERVER_PROTOCOL") || "HTTP/1.1") !== "HTTP/1.0") this.setProtocolVersion("1.1");
1642
- if (this.getProtocolVersion() === "1.0" && (this.headers.get("Cache-Control") || "").includes("no-cache")) {
1643
- this.headers.set("pragma", "no-cache");
1644
- this.headers.set("expires", "-1");
1645
- }
1646
- this.ensureIEOverSSLCompatibility(request);
1647
- if (request.isSecure()) for (const cookie of this.headers.getCookies()) cookie.setSecureDefault(true);
1648
- return this;
1649
- }
1650
- /**
1651
- * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
1652
- *
1653
- * @see http://support.microsoft.com/kb/323308
1654
- */
1655
- ensureIEOverSSLCompatibility(request) {
1656
- const contentDisposition = this.headers.get("Content-Disposition") || "";
1657
- const userAgent = request.headers.get("user-agent") || "";
1658
- if (contentDisposition.toLowerCase().includes("attachment") && /MSIE (.*?);/i.test(userAgent) && request.headers.get("x-forwarded-proto") === "https") {
1659
- const match = userAgent.match(/MSIE (.*?);/i);
1660
- if (match) {
1661
- if (parseInt(match[1], 10) < 9) this.headers.remove("Cache-Control");
1662
- }
1663
- }
1664
- }
1665
- };
1666
- //#endregion
1667
- //#region src/Utilities/Responsable.ts
1668
- var Responsable = class extends _h3ravel_contracts.IResponsable {
1669
- toResponse(request) {
1670
- return new Response(request.app, this.body, this.status, Object.fromEntries(this.headers.entries()));
1671
- }
1672
- HTTPResponse() {
1673
- return super.constructor;
1674
- }
1675
- };
1676
- //#endregion
1677
- //#region src/Response.ts
1678
- var Response = class Response extends HttpResponse {
1679
- app;
1680
- static codes = _h3ravel_foundation.ResponseCodes;
1681
- initializationData = {};
1682
- /**
1683
- * The current Http Context
1684
- */
1685
- context;
1686
- constructor(app, event, status = 200, headers = {}) {
1687
- const hasHeaders = Object.entries(headers).length > 0;
1688
- const content = !(event instanceof h3.H3Event) ? event : "";
1689
- event = event instanceof h3.H3Event ? event : app.getHttpContext("event");
1690
- super(event);
1691
- this.app = app;
1692
- if (content || status !== 200 || hasHeaders) {
1693
- this.setContent(content).setStatusCode(status);
1694
- if (hasHeaders) this.withHeaders(headers);
1695
- }
1696
- this.initializationData = {
1697
- app,
1698
- event,
1699
- status,
1700
- headers,
1701
- content
1702
- };
1703
- }
1704
- /**
1705
- * Sends content for the current web response.
1706
- */
1707
- sendContent(type, parse) {
1708
- if (!type) type = _h3ravel_support.Str.detectContentType(this.content);
1709
- return this[type].call(this, this.content, parse);
1710
- }
1711
- /**
1712
- * Sends content for the current web response.
1713
- */
1714
- send(type) {
1715
- return this.sendContent(type, true);
1716
- }
1717
- async view(viewPath, data, parse) {
1718
- const base = this.html(await this.app.make("edge").render(viewPath, data), parse);
1719
- return new Responsable(base.body, base);
1720
- }
1721
- async viewTemplate(content, data, parse) {
1722
- return this.html(await this.app.make("edge").renderRaw(content, data), parse);
1723
- }
1724
- html(content, parse) {
1725
- const base = this.httpResponse("text/html", content ?? this.content, parse);
1726
- if (base instanceof Response) return new Responsable(base.content, {
1727
- status: base.statusCode,
1728
- statusText: base.statusText,
1729
- headers: base.headers
1730
- });
1731
- return new Responsable(base.body, base);
1732
- }
1733
- json(data, parse) {
1734
- const content = data ?? this.content;
1735
- return this.httpResponse("application/json", typeof content !== "string" ? JSON.stringify(content) : content, parse);
1736
- }
1737
- text(content, parse) {
1738
- return this.httpResponse("text/plain", content ?? this.content, parse);
1739
- }
1740
- xml(data, parse) {
1741
- return this.httpResponse("application/xml", data ?? this.content, parse);
1742
- }
1743
- httpResponse(contentType, data, parse) {
1744
- if (parse) {
1745
- this.sendHeaders();
1746
- return new Responsable(data ?? this.content, {
1747
- status: this.statusCode,
1748
- statusText: this.statusText,
1749
- headers: { "content-type": `${contentType}; charset=${this.charset}` }
1750
- });
1751
- }
1752
- if (this.content?.trim()?.length <= 0) this.content = data ?? "";
1753
- this.setStatusCode(this.statusCode, this.statusText);
1754
- this.setHeader("content-type", `${contentType}; charset=${this.charset}`);
1755
- return this;
1756
- }
1757
- /**
1758
- * Redirect to another URL.
1759
- */
1760
- redirect(location, status = 302, statusText) {
1761
- return this.setStatusCode(status, statusText || (status === 301 ? "Moved Permanently" : "Found")).setContent(`<html><head><meta http-equiv="refresh" content="0; url=${location.replace(/"/g, "%22")}" /></head></html>`).withHeaders({
1762
- "content-type": "text/html; charset=utf-8",
1763
- location
1764
- });
1765
- }
1766
- /**
1767
- * Dump the response.
1768
- */
1769
- dump() {
1770
- dump(this.headers.all(), { charset: this.charset }, { version: this.version }, { statusText: this.statusText }, { statusCode: this.statusCode });
1771
- return this;
1772
- }
1773
- getEvent(key) {
1774
- return (0, _h3ravel_support.safeDot)(this.event, key);
1775
- }
1776
- /**
1777
- * Reset the response class to it's defautl
1778
- */
1779
- reset() {
1780
- return this;
1781
- }
1782
- };
1783
- //#endregion
1784
- //#region src/JsonResponse.ts
1785
- /**
1786
- * Response represents an HTTP response in JSON format.
1787
- *
1788
- * Note that this class does not force the returned JSON content to be an
1789
- * object. It is however recommended that you do return an object as it
1790
- * protects yourself against XSSI and JSON-JavaScript Hijacking.
1791
- *
1792
- * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside
1793
- */
1794
- var JsonResponse = class JsonResponse extends Response {
1795
- data;
1796
- callback;
1797
- /**
1798
- * @param bool $json If the data is already a JSON string
1799
- */
1800
- constructor(app, data, status = 200, headers = {}, json = false) {
1801
- super(app, "", status, headers);
1802
- if (json && typeof data !== "string" && typeof data !== "number" && typeof data.toString === "undefined") throw new TypeError(`"${this.constructor.name}": If \`json\` is set to true, argument \`data\` must be a string or object implementing toString(), "${typeof data}" given.`);
1803
- data ??= {};
1804
- if (json) this.setJson(data);
1805
- else this.setData(data);
1806
- }
1807
- /**
1808
- * Sets the JSONP callback.
1809
- *
1810
- * @param callback The JSONP callback or null to use none
1811
- *
1812
- * @throws {InvalidArgumentException} When the callback name is not valid
1813
- */
1814
- setCallback(callback) {
1815
- if (typeof callback !== "undefined") {
1816
- const pattern = /^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\u200C\u200D]*(?:\[(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\d+)\])*?$/u;
1817
- const reserved = [
1818
- "break",
1819
- "do",
1820
- "instanceof",
1821
- "typeof",
1822
- "case",
1823
- "else",
1824
- "new",
1825
- "var",
1826
- "catch",
1827
- "finally",
1828
- "return",
1829
- "void",
1830
- "continue",
1831
- "for",
1832
- "switch",
1833
- "while",
1834
- "debugger",
1835
- "function",
1836
- "this",
1837
- "with",
1838
- "default",
1839
- "if",
1840
- "throw",
1841
- "delete",
1842
- "in",
1843
- "try",
1844
- "class",
1845
- "enum",
1846
- "extends",
1847
- "super",
1848
- "const",
1849
- "export",
1850
- "import",
1851
- "implements",
1852
- "let",
1853
- "private",
1854
- "public",
1855
- "yield",
1856
- "interface",
1857
- "package",
1858
- "protected",
1859
- "static",
1860
- "null",
1861
- "true",
1862
- "false"
1863
- ];
1864
- const parts = callback.split(".");
1865
- for (const part of parts) if (!pattern.test(part) || reserved.includes(part)) throw new _h3ravel_support.InvalidArgumentException("The callback name is not valid.");
1866
- }
1867
- this.callback = callback;
1868
- return this.update();
1869
- }
1870
- /**
1871
- * Factory method for chainability.
1872
- *
1873
- * @example
1874
- *
1875
- * return JsonResponse.fromJsonString('{"key": "value"}').setSharedMaxAge(300);
1876
- *
1877
- * @param data The JSON response string
1878
- * @param status The response status code (200 "OK" by default)
1879
- * @param headers An array of response headers
1880
- */
1881
- static fromJsonString(app, data, status = 200, headers = {}) {
1882
- return new JsonResponse(app, data, status, headers, true);
1883
- }
1884
- /**
1885
- * Sets a raw string containing a JSON document to be sent.
1886
- *
1887
- * @param json
1888
- * @returns
1889
- */
1890
- setJson(json) {
1891
- this.data = json;
1892
- return this.update();
1893
- }
1894
- /**
1895
- * Sets the data to be sent as JSON.
1896
- *
1897
- * @param data
1898
- * @returns
1899
- */
1900
- setData(data = {}) {
1901
- let content;
1902
- try {
1903
- if (data.toJson === "undefined") content = JSON.stringify(data.toJson());
1904
- else if (data.toArray === "undefined") content = JSON.stringify(data.toArray());
1905
- else content = JSON.stringify(data);
1906
- } catch (e) {
1907
- if (e instanceof Error && e.message.startsWith("Failed calling ")) throw e.getPrevious() || e;
1908
- throw e;
1909
- }
1910
- return this.setJson(content);
1911
- }
1912
- /**
1913
- * Get the json_decoded data from the response.
1914
- *
1915
- * @param assoc
1916
- */
1917
- getData() {
1918
- return JSON.parse(String(this.data));
1919
- }
1920
- /**
1921
- * Sets the JSONP callback.
1922
- *
1923
- * @param callback
1924
- */
1925
- withCallback(callback) {
1926
- return this.setCallback(callback);
1927
- }
1928
- /**
1929
- * Updates the content and headers according to the JSON data and callback.
1930
- */
1931
- update() {
1932
- if (typeof this.callback !== "undefined") {
1933
- this.headers.set("Content-Type", "text/javascript");
1934
- return this.setContent(`/**/${this.callback}(${this.data});`);
1935
- }
1936
- if (!this.headers.has("Content-Type") || "text/javascript" === this.headers.get("Content-Type")) this.headers.set("Content-Type", "application/json");
1937
- return this.setContent(this.data);
1938
- }
1939
- };
1940
- //#endregion
1941
- //#region src/Middleware/TrustHosts.ts
1942
- var TrustHosts = class TrustHosts extends Middleware {
1943
- /**
1944
- * The trusted hosts that have been configured to always be trusted.
1945
- */
1946
- static alwaysTrust;
1947
- /**
1948
- * Indicates whether subdomains of the application URL should be trusted.
1949
- */
1950
- static subdomains;
1951
- /**
1952
- * Get the host patterns that should be trusted.
1953
- */
1954
- hosts() {
1955
- if (!TrustHosts.alwaysTrust) return [this.allSubdomainsOfApplicationUrl()];
1956
- let hosts;
1957
- switch (true) {
1958
- case Array.isArray(TrustHosts.alwaysTrust):
1959
- hosts = TrustHosts.alwaysTrust;
1960
- break;
1961
- case typeof TrustHosts.alwaysTrust === "function":
1962
- hosts = TrustHosts.alwaysTrust();
1963
- break;
1964
- default:
1965
- hosts = [];
1966
- break;
1967
- }
1968
- if (TrustHosts.subdomains) hosts.push(this.allSubdomainsOfApplicationUrl());
1969
- return hosts;
1970
- }
1971
- /**
1972
- * Handle the incoming request.
1973
- *
1974
- * @param request
1975
- * @param next
1976
- */
1977
- async handle(request, next) {
1978
- if (this.shouldSpecifyTrustedHosts()) Request.setTrustedHosts(this.hosts().filter((e) => typeof e !== "undefined"));
1979
- return next(request);
1980
- }
1981
- /**
1982
- * Specify the hosts that should always be trusted.
1983
- *
1984
- * @param hosts
1985
- * @param subdomains
1986
- */
1987
- static at(hosts, subdomains = true) {
1988
- TrustHosts.alwaysTrust = hosts;
1989
- TrustHosts.subdomains = subdomains;
1990
- }
1991
- /**
1992
- * Determine if the application should specify trusted hosts.
1993
- *
1994
- * @return bool
1995
- */
1996
- shouldSpecifyTrustedHosts() {
1997
- return !this.app?.environment("local") && !this.app?.runningUnitTests();
1998
- }
1999
- /**
2000
- * Get a regular expression matching the application URL and all of its subdomains.
2001
- */
2002
- allSubdomainsOfApplicationUrl() {
2003
- const appUrl = this.app?.make("config").get("app.url");
2004
- const host = new URL(appUrl).host;
2005
- if (host) return `^(.+\\.)?${host.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
2006
- }
2007
- /**
2008
- * Flush the state of the middleware.
2009
- *
2010
- * @return void
2011
- */
2012
- static flushState() {
2013
- TrustHosts.alwaysTrust = void 0;
2014
- TrustHosts.subdomains = void 0;
2015
- }
2016
- };
2017
- __decorate([
2018
- (0, _h3ravel_foundation.Injectable)(),
2019
- __decorateMetadata("design:type", Function),
2020
- __decorateMetadata("design:paramtypes", [typeof Request === "undefined" ? Object : Request, Function]),
2021
- __decorateMetadata("design:returntype", Promise)
2022
- ], TrustHosts.prototype, "handle", null);
2023
- //#endregion
2024
- //#region src/Providers/HttpServiceProvider.ts
2025
- /**
2026
- * Sets up HTTP kernel and request lifecycle.
2027
- *
2028
- * Register Request, Response, and Middleware classes.
2029
- * Configure global middleware stack.
2030
- * Boot HTTP kernel.
2031
- *
2032
- * Auto-Registered
2033
- */
2034
- var HttpServiceProvider = class {
2035
- app;
2036
- static priority = 998;
2037
- registeredCommands;
2038
- constructor(app) {
2039
- this.app = app;
2040
- }
2041
- register() {
2042
- /**
2043
- * Register Musket Commands
2044
- */
2045
- this.registeredCommands = [FireCommand];
2046
- this.app.alias([
2047
- [Request, "http.request"],
2048
- [_h3ravel_contracts.IRequest, "http.request"],
2049
- [Response, "http.response"],
2050
- [_h3ravel_contracts.IResponse, "http.response"],
2051
- [HttpContext, "http.context"],
2052
- [_h3ravel_contracts.IHttpContext, "http.context"]
2053
- ]);
2054
- }
2055
- boot() {}
2056
- };
2057
- //#endregion
2058
- //#region src/Utilities/ParamBag.ts
2059
- /**
2060
- * ParamBag is a container for key/value pairs
2061
- * for Node/H3 environments.
2062
- */
2063
- var ParamBag = class {
2064
- parameters;
2065
- event;
2066
- constructor(parameters = {}, event) {
2067
- this.parameters = parameters;
2068
- this.event = event;
2069
- this.parameters = { ...parameters };
2070
- }
2071
- /**
2072
- * Returns the parameters.
2073
- * @
2074
- * @param key The name of the parameter to return or undefined to get them all
2075
- *
2076
- * @throws BadRequestException if the value is not an array
2077
- */
2078
- all(key) {
2079
- if (!key) return { ...this.parameters };
2080
- const value = key ? this.parameters[key] : void 0;
2081
- if (value && typeof value !== "object") throw new BadRequestException(`Unexpected value for parameter "${key}": expected object, got ${typeof value}`);
2082
- return value || {};
2083
- }
2084
- get(key, defaultValue) {
2085
- return key in this.parameters ? this.parameters[key] : defaultValue;
2086
- }
2087
- set(key, value) {
2088
- this.parameters[key] = value;
2089
- }
2090
- /**
2091
- * Returns true if the parameter is defined.
2092
- *
2093
- * @param key
2094
- */
2095
- has(key) {
2096
- return Object.prototype.hasOwnProperty.call(this.parameters, key);
2097
- }
2098
- /**
2099
- * Removes a parameter.
2100
- *
2101
- * @param key
2102
- */
2103
- remove(key) {
2104
- delete this.parameters[key];
2105
- }
2106
- /**
2107
- *
2108
- * Returns the parameter as string.
2109
- *
2110
- * @param key
2111
- * @param defaultValue
2112
- * @throws UnexpectedValueException if the value cannot be converted to string
2113
- * @returns
2114
- */
2115
- getString(key, defaultValue = "") {
2116
- const value = this.get(key, defaultValue);
2117
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
2118
- throw new UnexpectedValueException(`Parameter "${key}" cannot be converted to string`);
2119
- }
2120
- /**
2121
- * Returns the parameter value converted to integer.
2122
- *
2123
- * @param key
2124
- * @param defaultValue
2125
- * @throws UnexpectedValueException if the value cannot be converted to integer
2126
- */
2127
- getInt(key, defaultValue = 0) {
2128
- const value = parseInt(this.get(key, defaultValue), 10);
2129
- if (isNaN(value)) throw new Error(`Parameter "${key}" is not an integer`);
2130
- return value;
2131
- }
2132
- /**
2133
- * Returns the parameter value converted to boolean.
2134
- *
2135
- * @param key
2136
- * @param defaultValue
2137
- * @throws UnexpectedValueException if the value cannot be converted to a boolean
2138
- */
2139
- getBoolean(key, defaultValue = false) {
2140
- const value = this.get(key, defaultValue);
2141
- if (typeof value === "boolean") return value;
2142
- if ([
2143
- "1",
2144
- "true",
2145
- "yes"
2146
- ].includes(String(value).toLowerCase())) return true;
2147
- if ([
2148
- "0",
2149
- "false",
2150
- "no"
2151
- ].includes(String(value).toLowerCase())) return false;
2152
- throw new Error(`Parameter "${key}" cannot be converted to boolean`);
2153
- }
2154
- /**
2155
- * Returns the alphabetic characters of the parameter value.
2156
- *
2157
- * @param key
2158
- * @param defaultValue
2159
- * @throws UnexpectedValueException if the value cannot be converted to string
2160
- */
2161
- getAlpha(key, defaultValue = "") {
2162
- return this.getString(key, defaultValue).replace(/[^a-z]/gi, "");
2163
- }
2164
- /**
2165
- * Returns the alphabetic characters and digits of the parameter value.
2166
- *
2167
- * @param key
2168
- * @param defaultValue
2169
- * @throws UnexpectedValueException if the value cannot be converted to string
2170
- */
2171
- getAlnum(key, defaultValue = "") {
2172
- return this.getString(key, defaultValue).replace(/[^a-z0-9]/gi, "");
2173
- }
2174
- /**
2175
- * Returns the digits of the parameter value.
2176
- *
2177
- * @param key
2178
- * @param defaultValue
2179
- * @throws UnexpectedValueException if the value cannot be converted to string
2180
- * @returns
2181
- **/
2182
- getDigits(key, defaultValue = "") {
2183
- return this.getString(key, defaultValue).replace(/\D/g, "");
2184
- }
2185
- /**
2186
- * Returns the parameter keys.
2187
- */
2188
- keys() {
2189
- return Object.keys(this.parameters);
2190
- }
2191
- /**
2192
- * Replaces the current parameters by a new set.
2193
- */
2194
- replace(parameters = {}) {
2195
- this.parameters = { ...parameters };
2196
- }
2197
- /**
2198
- * Adds parameters.
2199
- */
2200
- add(parameters = {}) {
2201
- this.parameters = {
2202
- ...this.parameters,
2203
- ...parameters
2204
- };
2205
- }
2206
- /**
2207
- * Returns the number of parameters.
2208
- */
2209
- count() {
2210
- return Object.keys(this.parameters).length;
2211
- }
2212
- /**
2213
- * Returns an iterator for parameters.
2214
- *
2215
- * @returns
2216
- */
2217
- [Symbol.iterator]() {
2218
- return Object.entries(this.parameters)[Symbol.iterator]();
2219
- }
2220
- };
2221
- //#endregion
2222
- //#region src/Utilities/InputBag.ts
2223
- /**
2224
- * InputBag is a container for user input values
2225
- * (e.g., query params, body, cookies)
2226
- * for Node/H3 environments.
2227
- */
2228
- var InputBag = class extends ParamBag {
2229
- constructor(inputs = {}, event) {
2230
- super(inputs, event);
2231
- this.add(inputs);
2232
- }
2233
- /**
2234
- * Returns a scalar input value by name.
2235
- *
2236
- * @param key
2237
- * @param defaultValue
2238
- * @throws BadRequestException if the input contains a non-scalar value
2239
- * @returns
2240
- */
2241
- get(key, defaultValue = null) {
2242
- if (defaultValue !== null && typeof defaultValue !== "string" && typeof defaultValue !== "number" && typeof defaultValue !== "boolean") throw new TypeError(`Expected a scalar value as 2nd argument to get(), got ${typeof defaultValue}`);
2243
- const value = _h3ravel_support.Obj.get(this.parameters, key, defaultValue);
2244
- if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") throw new BadRequestException(`Input value "${key}" contains a non-scalar value.`);
2245
- return value;
2246
- }
2247
- /**
2248
- * Replaces all current input values.
2249
- *
2250
- * @param inputs
2251
- * @returns
2252
- */
2253
- replace(inputs = {}) {
2254
- this.parameters = {};
2255
- this.add(inputs);
2256
- }
2257
- /**
2258
- * Adds multiple input values.
2259
- *
2260
- * @param inputs
2261
- * @returns
2262
- */
2263
- add(inputs = {}) {
2264
- Object.entries(inputs).forEach(([key, value]) => this.set(key, value));
2265
- }
2266
- /**
2267
- * Sets an input by name.
2268
- *
2269
- * @param key
2270
- * @param value
2271
- * @throws TypeError if value is not scalar or array
2272
- * @returns
2273
- */
2274
- set(key, value) {
2275
- if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && !Array.isArray(value) && typeof value !== "object") throw new TypeError(`Expected scalar, array, object, or null as value for set(), got ${typeof value}`);
2276
- this.parameters[key] = value;
2277
- }
2278
- /**
2279
- * Returns true if a key exists.
2280
- *
2281
- * @param key
2282
- * @returns
2283
- */
2284
- has(key) {
2285
- return Object.prototype.hasOwnProperty.call(this.parameters, key);
2286
- }
2287
- /**
2288
- * Returns all parameters.
2289
- *
2290
- * @returns
2291
- */
2292
- all() {
2293
- return { ...this.parameters };
2294
- }
2295
- /**
2296
- * Converts a parameter value to string.
2297
- *
2298
- * @param key
2299
- * @param defaultValue
2300
- * @throws BadRequestException if input contains a non-scalar value
2301
- * @returns
2302
- */
2303
- getString(key, defaultValue = "") {
2304
- const value = this.get(key, defaultValue);
2305
- return String(value ?? "");
2306
- }
2307
- /**
2308
- * Filters input value with a predicate.
2309
- * Mimics PHP’s filter_var() in spirit, but simpler.
2310
- *
2311
- * @param key
2312
- * @param defaultValue
2313
- * @param filterFn
2314
- * @throws BadRequestException if validation fails
2315
- * @returns
2316
- */
2317
- filter(key, defaultValue = null, filterFn) {
2318
- const value = this.has(key) ? this.parameters[key] : defaultValue;
2319
- if (Array.isArray(value)) throw new BadRequestException(`Input value "${key}" contains an array, but array filtering not allowed.`);
2320
- if (filterFn && !filterFn(value)) throw new BadRequestException(`Input value "${key}" is invalid and did not pass filter.`);
2321
- return value;
2322
- }
2323
- /**
2324
- * Returns an enum value by key.
2325
- *
2326
- * @param key
2327
- * @param EnumClass
2328
- * @param defaultValue
2329
- * @throws BadRequestException if conversion fails
2330
- * @returns
2331
- */
2332
- getEnum(key, EnumClass, defaultValue = null) {
2333
- const value = this.get(key, defaultValue);
2334
- if (value === null) return defaultValue;
2335
- if (!Object.values(EnumClass).includes(value)) throw new BadRequestException(`Input "${key}" cannot be converted to enum.`);
2336
- return value;
2337
- }
2338
- /**
2339
- * Removes a key.
2340
- *
2341
- * @param key
2342
- */
2343
- remove(key) {
2344
- delete this.parameters[key];
2345
- }
2346
- /**
2347
- * Returns all keys.
2348
- *
2349
- * @returns
2350
- */
2351
- keys() {
2352
- return Object.keys(this.parameters);
2353
- }
2354
- /**
2355
- * Returns number of parameters.
2356
- *
2357
- * @returns
2358
- */
2359
- count() {
2360
- return this.keys().length;
2361
- }
2362
- };
2363
- //#endregion
2364
- //#region src/Utilities/FileBag.ts
2365
- /**
2366
- * FileBag is a container for uploaded files
2367
- * for Node/H3 environments.
2368
- */
2369
- var FileBag = class extends ParamBag {
2370
- parameters = {};
2371
- constructor(parameters = {}, event) {
2372
- super(parameters, event);
2373
- this.replace(parameters);
2374
- }
2375
- /**
2376
- * Replace all stored files.
2377
- */
2378
- replace(files = {}) {
2379
- this.parameters = {};
2380
- this.add(files);
2381
- }
2382
- /**
2383
- * Set a file or array of files.
2384
- */
2385
- set(key, value) {
2386
- if (Array.isArray(value)) this.parameters[key] = value.map((v) => v ? this.convertFileInformation(v) : null).filter(Boolean);
2387
- else if (value) this.parameters[key] = this.convertFileInformation(value);
2388
- else this.parameters[key] = null;
2389
- }
2390
- /**
2391
- * Add multiple files.
2392
- */
2393
- add(files = {}) {
2394
- for (const [key, file] of Object.entries(files)) this.set(key, file);
2395
- }
2396
- /**
2397
- * Get all stored files.
2398
- */
2399
- all() {
2400
- return this.parameters;
2401
- }
2402
- /**
2403
- * Normalize file input into UploadedFile instances.
2404
- */
2405
- convertFileInformation(file) {
2406
- if (!file) return null;
2407
- if (file instanceof UploadedFile) return file;
2408
- if (file instanceof File) return UploadedFile.createFromBase(file);
2409
- throw new TypeError("Invalid file input — expected File or UploadedFile instance.");
2410
- }
2411
- };
2412
- //#endregion
2413
- //#region src/Utilities/ServerBag.ts
2414
- /**
2415
- * ServerBag — a simplified version of Symfony's ServerBag
2416
- * for Node/H3 environments.
2417
- *
2418
- * Responsible for extracting and normalizing HTTP headers
2419
- * from the incoming request.
2420
- */
2421
- var ServerBag = class ServerBag extends ParamBag {
2422
- static serverData = {};
2423
- constructor(parameters = {}, event) {
2424
- super({}, event);
2425
- this.add(Object.fromEntries(Object.entries(parameters).map(([k, v]) => [k.toLowerCase(), v])));
2426
- this.add(Object.fromEntries(Object.entries(ServerBag.initialize(event, this.getHeaders())).map(([k, v]) => [_h3ravel_support.Str.slugify(k, "-", { "_": "-" }), v])));
2427
- this.add(ServerBag.initialize(event, this.getHeaders()));
2428
- }
2429
- static initialize(event, headers) {
2430
- const req = event.req;
2431
- const url = new URL(req.url ?? "/");
2432
- const host = headers.host;
2433
- const method = req.method ?? "GET";
2434
- const protocol = (0, h3.getRequestProtocol)(event);
2435
- const isHttps = protocol === "https" || !!event.req.headers.get("x-forwarded-proto")?.includes("https");
2436
- this.serverData.SERVER_PROTOCOL = protocol;
2437
- this.serverData.REQUEST_METHOD = method;
2438
- this.serverData.REQUEST_URI = url.href;
2439
- this.serverData.PATH_INFO = url.pathname;
2440
- this.serverData.QUERY_STRING = url.search;
2441
- this.serverData.SERVER_NAME = host;
2442
- this.serverData.SERVER_PORT = url.port;
2443
- this.serverData.REMOTE_ADDR = void 0;
2444
- this.serverData.REMOTE_PORT = void 0;
2445
- this.serverData.HTTP_HOST = headers.HOST ?? headers.HTTP_HOST ?? host;
2446
- this.serverData.HTTP_USER_AGENT = headers.USER_AGENT ?? headers.HTTP_USER_AGENT ?? "";
2447
- this.serverData.HTTP_ACCEPT = headers.ACCEPT ?? "";
2448
- this.serverData.HTTP_ACCEPT_LANGUAGE = headers.ACCEPT_LANGUAGE ?? headers.HTTP_ACCEPT_LANGUAGE ?? "";
2449
- this.serverData.HTTP_ACCEPT_ENCODING = headers.ACCEPT_ENCODING ?? headers.HTTP_ACCEPT_ENCODING ?? "";
2450
- this.serverData.HTTP_REFERER = headers.REFERER ?? headers.HTTP_REFERER ?? "";
2451
- this.serverData.HTTPS = isHttps ? "on" : "off";
2452
- return this.serverData;
2453
- }
2454
- /**
2455
- * Returns all request headers, normalized to uppercase with underscores.
2456
- * Example: content-type → CONTENT_TYPE
2457
- */
2458
- getHeaders() {
2459
- const headers = {};
2460
- for (const [key, value] of Object.entries(this.parameters)) {
2461
- if (value === void 0 || value === "") continue;
2462
- switch (key) {
2463
- case "accept":
2464
- case "content-type":
2465
- case "content-length":
2466
- case "content-md5":
2467
- case "authorization":
2468
- headers[key.toUpperCase().replace(/-/g, "_")] = value;
2469
- break;
2470
- default: headers[`HTTP_${key.toUpperCase().replace(/-/g, "_")}`] = value;
2471
- }
2472
- }
2473
- if (headers["HTTP_AUTHORIZATION"] || headers["AUTHORIZATION"]) {
2474
- const auth = headers["HTTP_AUTHORIZATION"] || headers["AUTHORIZATION"] || "";
2475
- if (auth.toLowerCase().startsWith("basic ")) {
2476
- const [user, pass] = Buffer.from(auth.slice(6), "base64").toString().split(":", 2);
2477
- headers["AUTH_TYPE"] = "Basic";
2478
- headers["AUTH_USER"] = user;
2479
- headers["AUTH_PASS"] = pass;
2480
- } else if (auth.toLowerCase().startsWith("bearer ")) {
2481
- headers["AUTH_TYPE"] = "Bearer";
2482
- headers["AUTH_TOKEN"] = auth.slice(7);
2483
- } else if (auth.toLowerCase().startsWith("digest ")) {
2484
- headers["AUTH_TYPE"] = "Digest";
2485
- headers["AUTH_DIGEST"] = auth;
2486
- }
2487
- }
2488
- return headers;
2489
- }
2490
- /**
2491
- * Returns a specific header by name, case-insensitive.
2492
- */
2493
- get(name) {
2494
- return this.parameters[name.toLowerCase()] || this.parameters[name];
2495
- }
2496
- /**
2497
- * Returns true if a header exists.
2498
- */
2499
- has(name) {
2500
- return name.toLowerCase() in this.parameters || name in this.parameters;
2501
- }
2502
- };
2503
- //#endregion
2504
- //#region src/Utilities/IpUtils.ts
2505
- /**
2506
- * Http utility functions for IP handling.
2507
- */
2508
- var IpUtils = class {
2509
- static PRIVATE_SUBNETS = [
2510
- "127.0.0.0/8",
2511
- "10.0.0.0/8",
2512
- "192.168.0.0/16",
2513
- "172.16.0.0/12",
2514
- "169.254.0.0/16",
2515
- "0.0.0.0/8",
2516
- "240.0.0.0/4",
2517
- "::1/128",
2518
- "fc00::/7",
2519
- "fe80::/10",
2520
- "::ffff:0:0/96",
2521
- "::/128"
2522
- ];
2523
- static checkedIps = {};
2524
- constructor() {}
2525
- /**
2526
- * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
2527
- *
2528
- * @param requestIp
2529
- * @param ips List of IPs or subnets (can be a string if only a single one)
2530
- */
2531
- static checkIp(requestIp, ips) {
2532
- if (ips && !Array.isArray(ips)) ips = [ips];
2533
- const method = requestIp?.includes(":") ? this.checkIp6 : this.checkIp4;
2534
- for (const ip of ips ?? []) if (method.call(this, requestIp ?? "", ip)) return true;
2535
- return false;
2536
- }
2537
- /**
2538
- * Compares two IPv4 addresses or checks if one belongs to a CIDR subnet.
2539
- *
2540
- * @param requestIp
2541
- * @param ip IPv4 address or subnet in CIDR notation
2542
- *
2543
- * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
2544
- */
2545
- static checkIp4(requestIp, ip) {
2546
- const cacheKey = `${requestIp}-${ip}-v4`;
2547
- const cached = this.getCacheResult(cacheKey);
2548
- if (cached !== null) return cached;
2549
- if (!this.isIPv4(requestIp)) return this.setCacheResult(cacheKey, false);
2550
- let address;
2551
- let netmask;
2552
- if (ip.includes("/")) {
2553
- const parts = ip.split("/", 2);
2554
- address = parts[0];
2555
- netmask = parseInt(parts[1], 10);
2556
- if (netmask === 0) return this.setCacheResult(cacheKey, this.isIPv4(address));
2557
- if (netmask < 0 || netmask > 32) return this.setCacheResult(cacheKey, false);
2558
- } else {
2559
- address = ip;
2560
- netmask = 32;
2561
- }
2562
- const addrLong = this.ipv4ToLong(address);
2563
- const reqLong = this.ipv4ToLong(requestIp);
2564
- if (addrLong === null || reqLong === null) return this.setCacheResult(cacheKey, false);
2565
- const mask = netmask === 0 ? 0 : -1 << 32 - netmask >>> 0;
2566
- const result = (addrLong & mask) === (reqLong & mask);
2567
- return this.setCacheResult(cacheKey, result);
2568
- }
2569
- /**
2570
- * Compares two IPv6 addresses or checks if one belongs to a CIDR subnet.
2571
- *
2572
- * @see https://github.com/dsp/v6tools
2573
- *
2574
- * @param requestIp
2575
- * @param ip IPv6 address or subnet in CIDR notation
2576
- *
2577
- * @throws {RuntimeException} When IPV6 support is not enabled
2578
- */
2579
- static checkIp6(requestIp, ip) {
2580
- const cacheKey = `${requestIp}-${ip}-v6`;
2581
- const cached = this.getCacheResult(cacheKey);
2582
- if (cached !== null) return cached;
2583
- if (!this.isIPv6(requestIp)) return this.setCacheResult(cacheKey, false);
2584
- let address;
2585
- let netmask;
2586
- if (ip.includes("/")) {
2587
- const parts = ip.split("/", 2);
2588
- address = parts[0];
2589
- netmask = parseInt(parts[1], 10);
2590
- if (!this.isIPv6(address)) return this.setCacheResult(cacheKey, false);
2591
- if (netmask < 1 || netmask > 128) return this.setCacheResult(cacheKey, false);
2592
- } else {
2593
- address = ip;
2594
- netmask = 128;
2595
- }
2596
- const addrBytes = this.inetPton6(address);
2597
- const reqBytes = this.inetPton6(requestIp);
2598
- if (!addrBytes || !reqBytes) return this.setCacheResult(cacheKey, false);
2599
- const bytesToCheck = Math.ceil(netmask / 8);
2600
- for (let i = 0; i < bytesToCheck; i++) {
2601
- const left = netmask - i * 8;
2602
- const mask = 255 << 8 - (left >= 8 ? 8 : left);
2603
- if ((addrBytes[i] & mask) !== (reqBytes[i] & mask)) return this.setCacheResult(cacheKey, false);
2604
- }
2605
- return this.setCacheResult(cacheKey, true);
2606
- }
2607
- /**
2608
- * Anonymizes an IPv4/IPv6 by zeroing out trailing bytes.
2609
- *
2610
- * @param ip
2611
- * @param v4Bytes
2612
- * @param v6Bytes
2613
- */
2614
- static anonymize(ip, v4Bytes = 1, v6Bytes = 8) {
2615
- if (v4Bytes < 0 || v6Bytes < 0) throw new _h3ravel_support.RuntimeException("Cannot anonymize less than 0 bytes.");
2616
- if (v4Bytes > 4 || v6Bytes > 16) throw new _h3ravel_support.RuntimeException("Cannot anonymize more than 4 bytes for IPv4 and 16 bytes for IPv6.");
2617
- if (ip.includes("%")) ip = ip.split("%")[0];
2618
- let wrappedIPv6 = false;
2619
- if (ip.startsWith("[") && ip.endsWith("]")) {
2620
- wrappedIPv6 = true;
2621
- ip = ip.slice(1, -1);
2622
- }
2623
- const packed = this.inetPton(ip);
2624
- if (!packed) throw new _h3ravel_support.RuntimeException("Invalid IP address");
2625
- let mask;
2626
- if (packed.length === 4) {
2627
- mask = new Uint8Array(4);
2628
- mask.fill(255, 0, 4 - v4Bytes);
2629
- mask.fill(0, 4 - v4Bytes);
2630
- } else {
2631
- mask = new Uint8Array(16);
2632
- mask.fill(255, 0, 16 - v6Bytes);
2633
- mask.fill(0, 16 - v6Bytes);
2634
- }
2635
- const anon = new Uint8Array(packed.length);
2636
- for (let i = 0; i < packed.length; i++) anon[i] = packed[i] & mask[i];
2637
- const result = this.inetNtop(anon);
2638
- return wrappedIPv6 ? `[${result}]` : result;
2639
- }
2640
- /**
2641
- * Checks if IP is within private subnets.
2642
- */
2643
- static isPrivateIp(requestIp) {
2644
- return this.checkIp(requestIp, this.PRIVATE_SUBNETS);
2645
- }
2646
- static isIPv4(ip) {
2647
- return /^(\d{1,3}\.){3}\d{1,3}$/.test(ip);
2648
- }
2649
- static isIPv6(ip) {
2650
- return ip.includes(":");
2651
- }
2652
- static ipv4ToLong(ip) {
2653
- const parts = ip.split(".").map((n) => parseInt(n, 10));
2654
- if (parts.length !== 4 || parts.some((n) => n < 0 || n > 255)) return null;
2655
- return (parts[0] << 24 >>> 0) + (parts[1] << 16 >>> 0) + (parts[2] << 8 >>> 0) + parts[3] >>> 0;
2656
- }
2657
- static inetPton(ip) {
2658
- try {
2659
- return this.isIPv4(ip) ? this.inetPton4(ip) : this.inetPton6(ip);
2660
- } catch {
2661
- return null;
2662
- }
2663
- }
2664
- static inetPton4(ip) {
2665
- return new Uint8Array(ip.split(".").map((n) => parseInt(n, 10)));
2666
- }
2667
- static inetPton6(ip) {
2668
- const buf = new Uint8Array(16);
2669
- try {
2670
- const parts = ip.split("::");
2671
- const left = parts[0] ? parts[0].split(":") : [];
2672
- const right = parts[1] ? parts[1].split(":") : [];
2673
- const fill = 8 - (left.length + right.length);
2674
- const full = [
2675
- ...left,
2676
- ...Array(fill).fill("0"),
2677
- ...right
2678
- ].map((p) => parseInt(p || "0", 16));
2679
- for (let i = 0; i < 8; i++) {
2680
- buf[i * 2] = full[i] >> 8 & 255;
2681
- buf[i * 2 + 1] = full[i] & 255;
2682
- }
2683
- return buf;
2684
- } catch {
2685
- return null;
2686
- }
2687
- }
2688
- static inetNtop(buf) {
2689
- if (buf.length === 4) return Array.from(buf).join(".");
2690
- const words = [];
2691
- for (let i = 0; i < 16; i += 2) words.push((buf[i] << 8 | buf[i + 1]).toString(16));
2692
- return words.join(":").replace(/(^|:)0(:0)*:0(:|$)/, "::");
2693
- }
2694
- static getCacheResult(key) {
2695
- if (key in this.checkedIps) {
2696
- const val = this.checkedIps[key];
2697
- delete this.checkedIps[key];
2698
- this.checkedIps[key] = val;
2699
- return val;
2700
- }
2701
- return null;
2702
- }
2703
- static setCacheResult(key, result) {
2704
- if (Object.keys(this.checkedIps).length > 1e3) {
2705
- const entries = Object.entries(this.checkedIps).slice(500);
2706
- this.checkedIps = Object.fromEntries(entries);
2707
- }
2708
- this.checkedIps[key] = result;
2709
- return result;
2710
- }
2711
- };
2712
- //#endregion
2713
- //#region src/Utilities/HttpRequest.ts
2714
- var HttpRequest = class HttpRequest {
2715
- event;
2716
- app;
2717
- static HEADER_FORWARDED = 1;
2718
- static HEADER_X_FORWARDED_FOR = 2;
2719
- static HEADER_X_FORWARDED_HOST = 4;
2720
- static HEADER_X_FORWARDED_PROTO = 8;
2721
- static HEADER_X_FORWARDED_PORT = 16;
2722
- static HEADER_X_FORWARDED_PREFIX = 32;
2723
- static HEADER_X_FORWARDED_AWS_ELB = 26;
2724
- static HEADER_X_FORWARDED_TRAEFIK = 62;
2725
- static METHOD_HEAD = "HEAD";
2726
- static METHOD_GET = "GET";
2727
- static METHOD_POST = "POST";
2728
- static METHOD_PUT = "PUT";
2729
- static METHOD_PATCH = "PATCH";
2730
- static METHOD_DELETE = "DELETE";
2731
- static METHOD_PURGE = "PURGE";
2732
- static METHOD_OPTIONS = "OPTIONS";
2733
- static METHOD_TRACE = "TRACE";
2734
- static METHOD_CONNECT = "CONNECT";
2735
- /**
2736
- * Names for headers that can be trusted when
2737
- * using trusted proxies.
2738
- *
2739
- * The FORWARDED header is the standard as of rfc7239.
2740
- *
2741
- * The other headers are non-standard, but widely used
2742
- * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
2743
- */
2744
- TRUSTED_HEADERS = {
2745
- [HttpRequest.HEADER_FORWARDED]: "FORWARDED",
2746
- [HttpRequest.HEADER_X_FORWARDED_FOR]: "X_FORWARDED_FOR",
2747
- [HttpRequest.HEADER_X_FORWARDED_HOST]: "X_FORWARDED_HOST",
2748
- [HttpRequest.HEADER_X_FORWARDED_PROTO]: "X_FORWARDED_PROTO",
2749
- [HttpRequest.HEADER_X_FORWARDED_PORT]: "X_FORWARDED_PORT",
2750
- [HttpRequest.HEADER_X_FORWARDED_PREFIX]: "X_FORWARDED_PREFIX"
2751
- };
2752
- FORWARDED_PARAMS = {
2753
- [HttpRequest.HEADER_X_FORWARDED_FOR]: "for",
2754
- [HttpRequest.HEADER_X_FORWARDED_HOST]: "host",
2755
- [HttpRequest.HEADER_X_FORWARDED_PROTO]: "proto",
2756
- [HttpRequest.HEADER_X_FORWARDED_PORT]: "host"
2757
- };
2758
- #uri;
2759
- /**
2760
- * Parsed request body
2761
- */
2762
- body;
2763
- #method = void 0;
2764
- #isHostValid = true;
2765
- #isIisRewrite = false;
2766
- format;
2767
- basePath;
2768
- baseUrl;
2769
- requestUri;
2770
- pathInfo;
2771
- formData;
2772
- preferredFormat;
2773
- isForwardedValid = true;
2774
- static trustedHosts = [];
2775
- static trustedHeaderSet = -1;
2776
- static trustedHostPatterns = [];
2777
- /**
2778
- * Gets route parameters.
2779
- * @returns An object containing route parameters.
2780
- */
2781
- params;
2782
- /**
2783
- * Request body parameters (POST).
2784
- *
2785
- * @see getPayload() for portability between content types
2786
- */
2787
- request;
2788
- /**
2789
- * Uploaded files (FILES).
2790
- */
2791
- files;
2792
- /**
2793
- * Query string parameters (GET).
2794
- */
2795
- _query;
2796
- /**
2797
- * Server and execution environment parameters
2798
- */
2799
- _server;
2800
- /**
2801
- * Cookies
2802
- */
2803
- cookies;
2804
- /**
2805
- * The current Http Context
2806
- */
2807
- context;
2808
- /**
2809
- * The request attributes (parameters parsed from the PATH_INFO, ...).
2810
- */
2811
- attributes;
2812
- /**
2813
- * Gets the request headers.
2814
- * @returns An object containing request headers.
2815
- */
2816
- headers;
2817
- content = void 0;
2818
- static formats = void 0;
2819
- static trustedProxies = [];
2820
- static httpMethodParameterOverride = false;
2821
- sessionManager;
2822
- sessionManagerClass;
2823
- /**
2824
- * List of Acceptable Content Types
2825
- */
2826
- acceptableContentTypes = [];
2827
- trustedValuesCache = {};
2828
- constructor(event, app) {
2829
- this.event = event;
2830
- this.app = app;
2831
- }
2832
- /**
2833
- * Sets the parameters for this request.
2834
- *
2835
- * This method also re-initializes all properties.
2836
- *
2837
- * @param attributes
2838
- * @param cookies The COOKIE parameters
2839
- * @param files The FILES parameters
2840
- * @param server The SERVER parameters
2841
- * @param content The raw body data
2842
- */
2843
- initialize() {
2844
- this.buildRequirements();
2845
- }
2846
- buildRequirements() {
2847
- this.params = (0, h3.getRouterParams)(this.event);
2848
- this.request = new InputBag(this.formData ? this.formData.input() : {}, this.event);
2849
- this._query = new InputBag((0, h3.getQuery)(this.event), this.event);
2850
- this.attributes = new ParamBag((0, h3.getRouterParams)(this.event), this.event);
2851
- this.cookies = new InputBag((0, h3.parseCookies)(this.event), this.event);
2852
- this.files = new FileBag(this.formData ? this.formData.files() : {}, this.event);
2853
- this._server = new ServerBag(Object.fromEntries(this.event.req.headers.entries()), this.event);
2854
- this.headers = new HeaderBag(this._server.getHeaders());
2855
- this.acceptableContentTypes = [];
2856
- this.pathInfo = void 0;
2857
- this.requestUri = void 0;
2858
- this.baseUrl = void 0;
2859
- this.basePath = void 0;
2860
- this.#method = void 0;
2861
- this.format = void 0;
2862
- }
2863
- /**
2864
- * Gets a list of content types acceptable by the client browser in preferable order.
2865
- * @returns {string[]}
2866
- */
2867
- getAcceptableContentTypes() {
2868
- if (this.acceptableContentTypes.length > 0) return this.acceptableContentTypes;
2869
- const accept = this.getHeader("accept");
2870
- if (!accept) return [];
2871
- const types = accept.split(",").map((type) => type.trim()).map((type) => type.split(";")[0]).filter(Boolean);
2872
- return this.acceptableContentTypes = types;
2873
- }
2874
- /**
2875
- * Get a URI instance for the request.
2876
- */
2877
- getUriInstance() {
2878
- return this.#uri;
2879
- }
2880
- /**
2881
- * Returns the requested URI (path and query string).
2882
- *
2883
- * @return {string} The raw URI (i.e. not URI decoded)
2884
- */
2885
- getRequestUri() {
2886
- return this.requestUri ??= this.prepareRequestUri();
2887
- }
2888
- /**
2889
- * Gets the scheme and HTTP host.
2890
- *
2891
- * If the URL was called with basic authentication, the user
2892
- * and the password are not added to the generated string.
2893
- */
2894
- getSchemeAndHttpHost() {
2895
- return this.getScheme() + "://" + this.getHttpHost();
2896
- }
2897
- /**
2898
- * Returns the HTTP host being requested.
2899
- *
2900
- * The port name will be appended to the host if it's non-standard.
2901
- */
2902
- getHttpHost() {
2903
- const scheme = this.getScheme();
2904
- const port = this.getPort();
2905
- if ("http" === scheme && 80 == port || "https" === scheme && 443 == port) return this.getHost();
2906
- return this.getHost() + ":" + port;
2907
- }
2908
- /**
2909
- * Returns the root path from which this request is executed.
2910
- *
2911
- * @returns {string} The raw path (i.e. not urldecoded)
2912
- */
2913
- getBasePath() {
2914
- return this.basePath ??= this.prepareBasePath();
2915
- }
2916
- /**
2917
- * Returns the root URL from which this request is executed.
2918
- *
2919
- * The base URL never ends with a /.
2920
- *
2921
- * This is similar to getBasePath(), except that it also includes the
2922
- * script filename (e.g. index.php) if one exists.
2923
- *
2924
- * @return string The raw URL (i.e. not urldecoded)
2925
- */
2926
- getBaseUrl() {
2927
- let trustedPrefix = "";
2928
- let trustedPrefixValues;
2929
- if (this.isFromTrustedProxy() && (trustedPrefixValues = this.getTrustedValues(HttpRequest.HEADER_X_FORWARDED_PREFIX))) trustedPrefix = _h3ravel_support.Str.rtrim(trustedPrefixValues[0], "/");
2930
- return trustedPrefix + this.getBaseUrlReal();
2931
- }
2932
- /**
2933
- * Returns the real base URL received by the webserver from which this request is executed.
2934
- * The URL does not include trusted reverse proxy prefix.
2935
- *
2936
- * @return string The raw URL (i.e. not urldecoded)
2937
- */
2938
- getBaseUrlReal() {
2939
- return this.baseUrl ??= this.prepareBaseUrl();
2940
- }
2941
- /**
2942
- * Gets the request's scheme.
2943
- */
2944
- getScheme() {
2945
- return this.isSecure() ? "https" : "http";
2946
- }
2947
- /**
2948
- * Prepares the base URL.
2949
- */
2950
- prepareBaseUrl() {
2951
- const requestUri = this.getRequestUri() ?? "";
2952
- const baseUrl = "/" + node_path.default.basename(__filename);
2953
- const normalizedRequestUri = requestUri.startsWith("/") ? requestUri : "/" + requestUri;
2954
- if (normalizedRequestUri.startsWith(baseUrl)) return baseUrl;
2955
- const dirBase = node_path.default.dirname(baseUrl);
2956
- if (normalizedRequestUri.startsWith(dirBase)) return dirBase.replace(/[/\\]+$/, "");
2957
- return "";
2958
- }
2959
- /**
2960
- * Prepares the Request URI.
2961
- */
2962
- prepareRequestUri() {
2963
- let requestUri = "";
2964
- const unencodedUrl = this._server.get("x-original-url") ?? "";
2965
- if (this.isIisRewrite() && unencodedUrl) {
2966
- requestUri = unencodedUrl;
2967
- this._server.remove("x-original-url");
2968
- } else if (this._server.has("REQUEST_URI")) {
2969
- requestUri = this._server.get("REQUEST_URI") ?? "";
2970
- if (requestUri && requestUri[0] === "/") {
2971
- const hashPos = requestUri.indexOf("#");
2972
- if (hashPos !== -1) requestUri = requestUri.substring(0, hashPos);
2973
- } else try {
2974
- const urlObj = new URL(requestUri);
2975
- requestUri = urlObj.pathname;
2976
- if (urlObj.search) requestUri += urlObj.search;
2977
- } catch {}
2978
- } else requestUri = this.getRequestUri() ?? "/";
2979
- this._server.set("REQUEST_URI", requestUri);
2980
- return requestUri;
2981
- }
2982
- /**
2983
- * Prepares the base path.
2984
- */
2985
- prepareBasePath() {
2986
- const baseUrl = this.getBaseUrl();
2987
- if (!baseUrl) return "";
2988
- const scriptFilename = this._server.get("SCRIPT_FILENAME") ?? "";
2989
- const filename = node_path.default.basename(scriptFilename);
2990
- let basePath;
2991
- if (node_path.default.basename(baseUrl) === filename) basePath = node_path.default.dirname(baseUrl);
2992
- else basePath = baseUrl;
2993
- basePath = basePath.replace(/\\/g, "/");
2994
- return basePath.replace(/\/+$/, "");
2995
- }
2996
- /**
2997
- * Prepares the path info.
2998
- */
2999
- preparePathInfo() {
3000
- let requestUri = this.getRequestUri();
3001
- if (!requestUri) return "/";
3002
- const qPos = requestUri.indexOf("?");
3003
- if (qPos !== -1) requestUri = requestUri.substring(0, qPos);
3004
- if (requestUri && requestUri[0] !== "/") requestUri = "/" + requestUri;
3005
- const baseUrl = this.getBaseUrlReal();
3006
- if (baseUrl == null) return requestUri;
3007
- let pathInfo = requestUri.substring(baseUrl.length);
3008
- if (!pathInfo || pathInfo[0] !== "/") pathInfo = "/" + pathInfo;
3009
- return pathInfo;
3010
- }
3011
- /**
3012
- * Returns the port on which the request is made.
3013
- *
3014
- * This method can read the client port from the "X-Forwarded-Port" header
3015
- * when trusted proxies were set via "setTrustedProxies()".
3016
- *
3017
- * The "X-Forwarded-Port" header must contain the client port.
3018
- *
3019
- * @return int|string|null Can be a string if fetched from the server bag
3020
- */
3021
- getPort() {
3022
- let pos;
3023
- let host;
3024
- if (this.isFromTrustedProxy() && (host = this.getTrustedValues(HttpRequest.HEADER_X_FORWARDED_PORT))) host = host[0];
3025
- else if (this.isFromTrustedProxy() && (host = this.getTrustedValues(HttpRequest.HEADER_X_FORWARDED_HOST))) host = host[0];
3026
- else if (!(host = this.headers.get("HOST"))) return this._server.get("SERVER_PORT");
3027
- if (host[0] === "[") pos = host.lastIndexOf(":", host.lastIndexOf("]"));
3028
- else pos = host.lastIndexOf(":");
3029
- if (pos !== -1) {
3030
- const portStr = typeof host === "string" ? host.substring(pos + 1) : host.at(0)?.substring(pos + 1);
3031
- if (portStr) return parseInt(portStr, 10);
3032
- }
3033
- return "https" === this.getScheme() ? 443 : 80;
3034
- }
3035
- getHost() {
3036
- let host;
3037
- if (this.isFromTrustedProxy() && (host = this.getTrustedValues(HttpRequest.HEADER_X_FORWARDED_HOST)?.[0])) {} else if (!(host = this.headers.get("HOST"))) host = this._server.get("SERVER_NAME") ?? this._server.get("SERVER_ADDR") ?? process.env.SERVER_NAME ?? "";
3038
- host = (host ?? "").trim().replace(/:\d+$/, "").toLowerCase();
3039
- if (host && !HttpRequest.isHostValid(host)) {
3040
- if (!this.#isHostValid) return "";
3041
- this.#isHostValid = false;
3042
- throw new SuspiciousOperationException(`Invalid Host "${host}".`);
3043
- }
3044
- const ctor = this.constructor;
3045
- if (ctor.trustedHostPatterns.length > 0) {
3046
- if (ctor.trustedHosts.includes(host)) return host;
3047
- for (const pattern of ctor.trustedHostPatterns) if (pattern.test(host)) {
3048
- ctor.trustedHosts.push(host);
3049
- return host;
3050
- }
3051
- if (!this.#isHostValid) return "";
3052
- this.#isHostValid = false;
3053
- throw new SuspiciousOperationException(`Untrusted Host "${host}".`);
3054
- }
3055
- return host;
3056
- }
3057
- /**
3058
- * Checks whether the request is secure or not.
3059
- *
3060
- * This method can read the client protocol from the "X-Forwarded-Proto" header
3061
- * when trusted proxies were set via "setTrustedProxies()".
3062
- *
3063
- * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
3064
- */
3065
- isSecure() {
3066
- const proto = this.getTrustedValues(HttpRequest.HEADER_X_FORWARDED_PROTO);
3067
- if (this.isFromTrustedProxy() && proto) return [
3068
- "https",
3069
- "on",
3070
- "ssl",
3071
- "1"
3072
- ].includes(proto[0]?.toLowerCase());
3073
- const https = this._server.get("HTTPS");
3074
- return !!https && "off" !== https.toLowerCase();
3075
- }
3076
- /**
3077
- * Is this IIS with UrlRewriteModule?
3078
- *
3079
- * This method consumes, caches and removed the IIS_WasUrlRewritten env var,
3080
- * so we don't inherit it to sub-requests.
3081
- */
3082
- isIisRewrite() {
3083
- try {
3084
- if (1 === this._server.getInt("IIS_WasUrlRewritten")) {
3085
- this.#isIisRewrite = true;
3086
- this._server.remove("IIS_WasUrlRewritten");
3087
- }
3088
- } catch {}
3089
- return this.#isIisRewrite;
3090
- }
3091
- /**
3092
- * Returns the value of the requested header.
3093
- */
3094
- getHeader(name) {
3095
- return this.headers.get(name);
3096
- }
3097
- /**
3098
- * Checks if the request method is of specified type.
3099
- *
3100
- * @param method Uppercase request method (GET, POST etc)
3101
- */
3102
- isMethod(method) {
3103
- return this.getMethod() === method.toUpperCase();
3104
- }
3105
- /**
3106
- * Checks whether or not the method is safe.
3107
- *
3108
- * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
3109
- */
3110
- isMethodSafe() {
3111
- return [
3112
- "GET",
3113
- "HEAD",
3114
- "OPTIONS",
3115
- "TRACE"
3116
- ].includes(this.getMethod());
3117
- }
3118
- /**
3119
- * Checks whether or not the method is idempotent.
3120
- */
3121
- isMethodIdempotent() {
3122
- return [
3123
- "HEAD",
3124
- "GET",
3125
- "PUT",
3126
- "DELETE",
3127
- "TRACE",
3128
- "OPTIONS",
3129
- "PURGE"
3130
- ].includes(this.getMethod());
3131
- }
3132
- /**
3133
- * Checks whether the method is cacheable or not.
3134
- *
3135
- * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
3136
- */
3137
- isMethodCacheable() {
3138
- return ["GET", "HEAD"].includes(this.getMethod());
3139
- }
3140
- /**
3141
- * Returns true if the request is an XMLHttpRequest (AJAX).
3142
- */
3143
- isXmlHttpRequest() {
3144
- return "XMLHttpRequest" === this.getHeader("X-Requested-With");
3145
- }
3146
- /**
3147
- * See https://url.spec.whatwg.org/.
3148
- */
3149
- static isHostValid(host) {
3150
- /**
3151
- * Validate IPv6: [::1] or similar
3152
- */
3153
- if (host[0] === "[") {
3154
- if (host[host.length - 1] === "]") {
3155
- const inside = host.substring(1, host.length - 1);
3156
- return _h3ravel_support.Str.validateIp(inside, "ipv6");
3157
- }
3158
- return false;
3159
- }
3160
- /**
3161
- * Validate IPv4: ends with .123 or .123.
3162
- */
3163
- if (/\.[0-9]+\.?$/.test(host)) return _h3ravel_support.Str.validateIp(host, "ipv4");
3164
- /**
3165
- * fallback: remove valid chars and check if anything remains
3166
- */
3167
- return "" === host.replace(/[-a-zA-Z0-9_]+\.?/g, "");
3168
- }
3169
- /**
3170
- * Initializes HTTP request formats.
3171
- */
3172
- static initializeFormats() {
3173
- this.formats = {
3174
- html: ["text/html", "application/xhtml+xml"],
3175
- txt: ["text/plain"],
3176
- js: [
3177
- "application/javascript",
3178
- "application/x-javascript",
3179
- "text/javascript"
3180
- ],
3181
- css: ["text/css"],
3182
- json: ["application/json", "application/x-json"],
3183
- jsonld: ["application/ld+json"],
3184
- xml: [
3185
- "text/xml",
3186
- "application/xml",
3187
- "application/x-xml"
3188
- ],
3189
- rdf: ["application/rdf+xml"],
3190
- atom: ["application/atom+xml"],
3191
- rss: ["application/rss+xml"],
3192
- form: ["application/x-www-form-urlencoded", "multipart/form-data"]
3193
- };
3194
- }
3195
- /**
3196
- * Gets the request "intended" method.
3197
- *
3198
- * If the X-HTTP-Method-Override header is set, and if the method is a POST,
3199
- * then it is used to determine the "real" intended HTTP method.
3200
- *
3201
- * The _method request parameter can also be used to determine the HTTP method,
3202
- * but only if enableHttpMethodParameterOverride() has been called.
3203
- *
3204
- * The method is always an uppercased string.
3205
- *
3206
- * @see getRealMethod()
3207
- */
3208
- getMethod() {
3209
- if (this.#method) return this.#method;
3210
- this.#method = this.getRealMethod();
3211
- if ("POST" !== this.#method) return this.#method;
3212
- let method = this.event.req.headers.get("X-HTTP-METHOD-OVERRIDE");
3213
- if (!method && HttpRequest.httpMethodParameterOverride) method = this.request.get("_method", this._query.get("_method", "POST"));
3214
- if (typeof method !== "string") return this.#method;
3215
- method = method.toUpperCase();
3216
- if ([
3217
- "GET",
3218
- "HEAD",
3219
- "POST",
3220
- "PUT",
3221
- "DELETE",
3222
- "CONNECT",
3223
- "OPTIONS",
3224
- "PATCH",
3225
- "PURGE",
3226
- "TRACE"
3227
- ].includes(method)) {
3228
- this.#method = method;
3229
- return this.#method;
3230
- }
3231
- if (!/^[A-Z]+$/.test(method)) throw new SuspiciousOperationException("Invalid HTTP method override.");
3232
- this.#method = method;
3233
- return this.#method;
3234
- }
3235
- /**
3236
- * Gets the preferred format for the response by inspecting, in the following order:
3237
- * * the request format set using setRequestFormat;
3238
- * * the values of the Accept HTTP header.
3239
- *
3240
- * Note that if you use this method, you should send the "Vary: Accept" header
3241
- * in the response to prevent any issues with intermediary HTTP caches.
3242
- */
3243
- getPreferredFormat(defaultValue = "html") {
3244
- const preferredFormat = this.getRequestFormat();
3245
- if (!this.preferredFormat && !!preferredFormat) this.preferredFormat = preferredFormat;
3246
- if (this.preferredFormat ?? null) return this.preferredFormat;
3247
- for (const mimeType of this.getAcceptableContentTypes()) {
3248
- this.preferredFormat = this.getFormat(mimeType);
3249
- if (this.preferredFormat) return this.preferredFormat;
3250
- }
3251
- return defaultValue;
3252
- }
3253
- /**
3254
- * Gets the format associated with the mime type.
3255
- */
3256
- getFormat(mimeType) {
3257
- const pos = mimeType.indexOf(";");
3258
- let canonicalMimeType = null;
3259
- if (mimeType && pos > -1) canonicalMimeType = mimeType.slice(0, pos).trim();
3260
- if (!HttpRequest.formats) HttpRequest.initializeFormats();
3261
- let exactFormat = null;
3262
- let canonicalFormat = null;
3263
- for (const [format, mimeTypes] of Object.entries(HttpRequest.formats ?? {})) {
3264
- if (mimeTypes.includes(mimeType)) exactFormat = format;
3265
- if (null !== canonicalMimeType && mimeTypes.includes(canonicalMimeType)) canonicalFormat = format;
3266
- }
3267
- if (exactFormat ?? canonicalFormat) return exactFormat ?? canonicalFormat;
3268
- }
3269
- /**
3270
- * Gets the request format.
3271
- *
3272
- * Here is the process to determine the format:
3273
- *
3274
- * * format defined by the user (with setRequestFormat())
3275
- * * _format request attribute
3276
- * * $default
3277
- *
3278
- * @see getPreferredFormat
3279
- */
3280
- getRequestFormat(defaultValue = "html") {
3281
- this.format ??= this.attributes.get("_format");
3282
- return this.format ?? defaultValue;
3283
- }
3284
- /**
3285
- * Sets the request format.
3286
- */
3287
- setRequestFormat(format) {
3288
- this.format = format;
3289
- }
3290
- /**
3291
- * Gets the "real" request method.
3292
- *
3293
- * @see getMethod()
3294
- */
3295
- getRealMethod() {
3296
- return this.event.req.method.toUpperCase();
3297
- }
3298
- /**
3299
- * Gets the mime type associated with the format.
3300
- */
3301
- getMimeType(format) {
3302
- if (!HttpRequest.formats) HttpRequest.initializeFormats();
3303
- return HttpRequest.formats?.[format] ? HttpRequest.formats[format][0] : void 0;
3304
- }
3305
- /**
3306
- * Gets the mime types associated with the format.
3307
- */
3308
- static getMimeTypes(format) {
3309
- if (!HttpRequest.formats) HttpRequest.initializeFormats();
3310
- return HttpRequest.formats?.[format] ?? [];
3311
- }
3312
- /**
3313
- * Gets the list of trusted proxies.
3314
- */
3315
- static getTrustedProxies() {
3316
- return this.trustedProxies;
3317
- }
3318
- /**
3319
- * Returns the request body content.
3320
- *
3321
- * @param asStream If true, returns a ReadableStream instead of the parsed string
3322
- * @return {string | ReadableStream | Promise<string | ReadableStream>}
3323
- */
3324
- getContent(asStream = false) {
3325
- let content = this.body;
3326
- if (content !== void 0 && content !== null) {
3327
- if (asStream) {
3328
- if (content instanceof ReadableStream) return content;
3329
- const encoder = new TextEncoder();
3330
- return new ReadableStream({ start(controller) {
3331
- controller.enqueue(encoder.encode(String(content)));
3332
- controller.close();
3333
- } });
3334
- }
3335
- if (typeof content === "string") return content;
3336
- }
3337
- if (asStream) return this.content;
3338
- content = this.content;
3339
- this.body = content;
3340
- return content;
3341
- }
3342
- /**
3343
- * Gets a "parameter" value from any bag.
3344
- *
3345
- * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
3346
- * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
3347
- * public property instead (attributes, query, request).
3348
- *
3349
- * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
3350
- *
3351
- * @internal use explicit input sources instead
3352
- */
3353
- get(key, defaultValue) {
3354
- const result = this.attributes.get(key, this);
3355
- if (this !== result) return result;
3356
- if (this._query.has(key)) return this._query.all()[key];
3357
- if (this.request.has(key)) return this.request.all()[key];
3358
- return defaultValue;
3359
- }
3360
- /**
3361
- * Indicates whether this request originated from a trusted proxy.
3362
- *
3363
- * This can be useful to determine whether or not to trust the
3364
- * contents of a proxy-specific header.
3365
- */
3366
- isFromTrustedProxy() {
3367
- return !HttpRequest.trustedProxies?.length && IpUtils.checkIp(this._server.get("REMOTE_ADDR"), HttpRequest.trustedProxies);
3368
- }
3369
- /**
3370
- * This method is rather heavy because it splits and merges headers, and it's called by many other methods such as
3371
- * getPort(), isSecure(), getHost(), getClientIps(), this.() etc. Thus, we try to cache the results for
3372
- * best performance.
3373
- */
3374
- getTrustedValues(type, ip) {
3375
- const trustedHeaders = this.TRUSTED_HEADERS;
3376
- const trustedHeaderSet = HttpRequest.trustedHeaderSet;
3377
- const cacheKey = type + "\0" + (trustedHeaderSet & type ? this.headers.get(trustedHeaders[type]) ?? "" : "") + "\0" + (ip ?? "") + "\0" + (this.headers.get(trustedHeaders[HttpRequest.HEADER_FORWARDED]) ?? "");
3378
- if (this.trustedValuesCache[cacheKey]) return this.trustedValuesCache[cacheKey];
3379
- let clientValues = [];
3380
- let forwardedValues = [];
3381
- if (trustedHeaderSet & type && this.headers.has(trustedHeaders[type])) {
3382
- const headerValue = this.headers.get(trustedHeaders[type]);
3383
- for (const v of headerValue.split(",")) {
3384
- const value = (type === HttpRequest.HEADER_X_FORWARDED_PORT ? "0.0.0.0:" : "") + v.trim();
3385
- clientValues.push(value);
3386
- }
3387
- }
3388
- if (trustedHeaderSet & HttpRequest.HEADER_FORWARDED && this.FORWARDED_PARAMS[type] && this.headers.has(trustedHeaders[HttpRequest.HEADER_FORWARDED])) {
3389
- const forwarded = this.headers.get(trustedHeaders[HttpRequest.HEADER_FORWARDED]);
3390
- const parts = HeaderUtility.split(forwarded, ",;=");
3391
- const param = this.FORWARDED_PARAMS[type];
3392
- for (const subParts of parts) {
3393
- let v = HeaderUtility.combine(subParts)[param];
3394
- if (typeof v === "boolean") v = "0";
3395
- if (v == null) continue;
3396
- if (type === HttpRequest.HEADER_X_FORWARDED_PORT) {
3397
- if (v.endsWith("]") || !(v = v.substring(v.lastIndexOf(":")))) v = this.isSecure() ? ":443" : ":80";
3398
- v = "0.0.0.0" + v;
3399
- }
3400
- forwardedValues.push(v);
3401
- }
3402
- }
3403
- if (ip != null) {
3404
- clientValues = this.normalizeAndFilterClientIps(clientValues, ip);
3405
- forwardedValues = this.normalizeAndFilterClientIps(forwardedValues, ip);
3406
- }
3407
- if (JSON.stringify(forwardedValues) === JSON.stringify(clientValues) || clientValues.length === 0) {
3408
- this.trustedValuesCache[cacheKey] = forwardedValues;
3409
- return forwardedValues;
3410
- }
3411
- if (forwardedValues.length === 0) {
3412
- this.trustedValuesCache[cacheKey] = clientValues;
3413
- return clientValues;
3414
- }
3415
- if (!this.isForwardedValid) {
3416
- const fallback = ip != null ? ["0.0.0.0", ip] : [];
3417
- this.trustedValuesCache[cacheKey] = fallback;
3418
- return fallback;
3419
- }
3420
- this.isForwardedValid = false;
3421
- throw new ConflictingHeadersException(`The request has both a trusted "${trustedHeaders[HttpRequest.HEADER_FORWARDED]}" header and a trusted "${trustedHeaders[type]}" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.`);
3422
- }
3423
- /**
3424
- *
3425
- * @param clientIps
3426
- * @param ip
3427
- * @returns
3428
- */
3429
- normalizeAndFilterClientIps(clientIps, ip) {
3430
- if (!clientIps || clientIps.length === 0) return [];
3431
- clientIps = [...clientIps, ip];
3432
- let firstTrustedIp = null;
3433
- for (let i = 0; i < clientIps.length; i++) {
3434
- let clientIp = clientIps[i];
3435
- if (clientIp.includes(".")) {
3436
- const colonIndex = clientIp.indexOf(":");
3437
- if (colonIndex > -1) {
3438
- clientIp = clientIp.substring(0, colonIndex);
3439
- clientIps[i] = clientIp;
3440
- }
3441
- } else if (clientIp.startsWith("[")) {
3442
- const endBracketIndex = clientIp.indexOf("]", 1);
3443
- if (endBracketIndex > -1) {
3444
- clientIp = clientIp.substring(1, endBracketIndex);
3445
- clientIps[i] = clientIp;
3446
- }
3447
- }
3448
- if (_h3ravel_support.Str.validateIp(clientIp)) {
3449
- clientIps.splice(i, 1);
3450
- i--;
3451
- continue;
3452
- }
3453
- if (IpUtils.checkIp(clientIp, HttpRequest.trustedProxies)) {
3454
- clientIps.splice(i, 1);
3455
- i--;
3456
- firstTrustedIp ??= clientIp;
3457
- }
3458
- }
3459
- return clientIps.length > 0 ? clientIps.reverse() : firstTrustedIp ? [firstTrustedIp] : [];
3460
- }
3461
- /**
3462
- * Sets a list of trusted host patterns.
3463
- *
3464
- * You should only list the hosts you manage using regexes.
3465
- *
3466
- * @param hostPatterns
3467
- */
3468
- static setTrustedHosts(hostPatterns) {
3469
- this.trustedHostPatterns = hostPatterns.map((hostPattern) => new RegExp(hostPattern, "i"));
3470
- /**
3471
- * reset trusted hosts when patterns change
3472
- */
3473
- this.trustedHosts = [];
3474
- }
3475
- /**
3476
- * Returns the path being requested relative to the executed script.
3477
- *
3478
- * The path info always starts with a /.
3479
- *
3480
- * @return {string} The raw path (i.e. not urldecoded)
3481
- */
3482
- getPathInfo() {
3483
- return this.pathInfo ??= this.preparePathInfo();
3484
- }
3485
- /**
3486
- * Gets the list of trusted host patterns.
3487
- */
3488
- static getTrustedHosts() {
3489
- return this.trustedHostPatterns;
3490
- }
3491
- /**
3492
- * Enables support for the _method request parameter to determine the intended HTTP method.
3493
- *
3494
- * Be warned that enabling this feature might lead to CSRF issues in your code.
3495
- * Check that you are using CSRF tokens when required.
3496
- * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
3497
- * and used to send a "PUT" or "DELETE" request via the _method request parameter.
3498
- * If these methods are not protected against CSRF, this presents a possible vulnerability.
3499
- *
3500
- * The HTTP method can only be overridden when the real HTTP method is POST.
3501
- */
3502
- static enableHttpMethodParameterOverride() {
3503
- this.httpMethodParameterOverride = true;
3504
- }
3505
- /**
3506
- * Checks whether support for the _method request parameter is enabled.
3507
- */
3508
- static getHttpMethodParameterOverride() {
3509
- return this.httpMethodParameterOverride;
3510
- }
3511
- };
3512
- //#endregion
3513
- //#region src/Request.ts
3514
- var Request = class Request extends HttpRequest {
3515
- /**
3516
- * The decoded JSON content for the request.
3517
- */
3518
- #json;
3519
- /**
3520
- * All of the converted files for the request.
3521
- */
3522
- convertedFiles;
3523
- /**
3524
- * The route resolver callback.
3525
- */
3526
- routeResolver;
3527
- /**
3528
- * The user resolver callback.
3529
- */
3530
- userResolver;
3531
- constructor(event, app) {
3532
- if (Request.httpMethodParameterOverride) HttpRequest.enableHttpMethodParameterOverride();
3533
- super(event, app);
3534
- }
3535
- /**
3536
- * Factory method to create a Request instance from an H3Event.
3537
- */
3538
- static async create(event, app) {
3539
- const instance = new Request(event, app);
3540
- await instance.setBody();
3541
- instance.initialize();
3542
- return instance;
3543
- }
3544
- /**
3545
- * Factory method to create a syncronous Request instance from an H3Event.
3546
- */
3547
- static createSync(event, app) {
3548
- const instance = new Request(event, app);
3549
- instance.content = event.req.body;
3550
- instance.body = instance.content;
3551
- instance.buildRequirements();
3552
- return instance;
3553
- }
3554
- async setBody() {
3555
- const type = this.event.req.headers.get("content-type") || "";
3556
- if (this.body) return;
3557
- if (type.includes("application/json")) {
3558
- this.body = await this.event.req.json().catch(() => ({}));
3559
- this.content = this.body;
3560
- } else if (type.includes("form-data") || type.includes("x-www-form-urlencoded")) {
3561
- this.formData = new FormRequest(await this.event.req.formData());
3562
- this.body = this.formData.all();
3563
- this.content = JSON.stringify(this.formData.input());
3564
- } else if (type.startsWith("text/")) {
3565
- this.body = await this.event.req.text();
3566
- this.content = this.body;
3567
- } else {
3568
- const content = this.event.req.body;
3569
- this.content = content;
3570
- if (content instanceof ReadableStream) {
3571
- const reader = content.getReader();
3572
- const chunks = [];
3573
- let done = false;
3574
- while (!done) {
3575
- const { value, done: isDone } = await reader.read();
3576
- if (value) chunks.push(value);
3577
- done = isDone;
3578
- }
3579
- this.body = new TextDecoder().decode(new Uint8Array(chunks.flatMap((chunk) => Array.from(chunk))));
3580
- } else this.body = content;
3581
- }
3582
- }
3583
- /**
3584
- * Validate the incoming request data
3585
- *
3586
- * @param data
3587
- * @param rules
3588
- * @param messages
3589
- */
3590
- async validate(rules, messages = {}) {
3591
- const { Validator } = await import("@h3ravel/validation");
3592
- return await new Validator(this.all(), rules, messages).validate();
3593
- }
3594
- /**
3595
- * Retrieve all data from the instance (query + body).
3596
- */
3597
- all(keys) {
3598
- const input = _h3ravel_support.Obj.deepMerge({}, this.input(), this.allFiles());
3599
- if (!keys) return input;
3600
- const results = {};
3601
- const list = Array.isArray(keys) ? keys : [keys];
3602
- for (const key of list) (0, _h3ravel_support.data_set)(results, key, _h3ravel_support.Obj.get(input, key));
3603
- return results;
3604
- }
3605
- /**
3606
- * Retrieve an input item from the request.
3607
- *
3608
- * @param key
3609
- * @param defaultValue
3610
- * @returns
3611
- */
3612
- input(key, defaultValue) {
3613
- const source = {
3614
- ...this.getInputSource().all(),
3615
- ...this._query.all()
3616
- };
3617
- return key ? (0, _h3ravel_support.data_get)(source, key, defaultValue) : _h3ravel_support.Arr.except(source, ["_method"]);
3618
- }
3619
- file(key, defaultValue, expectArray) {
3620
- const files = (0, _h3ravel_support.data_get)(this.allFiles(), key, defaultValue);
3621
- if (!files) return defaultValue;
3622
- if (Array.isArray(files)) return expectArray ? files : files[0];
3623
- return files;
3624
- }
3625
- /**
3626
- * Get the user making the request.
3627
- *
3628
- * @param guard
3629
- */
3630
- user(guard) {
3631
- return Reflect.apply(this.getUserResolver(), this, [guard]);
3632
- }
3633
- route(param, defaultParam) {
3634
- const route = Reflect.apply(this.getRouteResolver(), this, []);
3635
- if (typeof route === "undefined" || !param) return route;
3636
- return route.parameter(param, defaultParam);
3637
- }
3638
- /**
3639
- * Determine if the uploaded data contains a file.
3640
- *
3641
- * @param key
3642
- * @return boolean
3643
- */
3644
- hasFile(key) {
3645
- let files = this.file(key, void 0, true);
3646
- if (!Array.isArray(files)) files = [files];
3647
- return files.some((e) => this.isValidFile(e));
3648
- }
3649
- /**
3650
- * Check that the given file is a valid file instance.
3651
- *
3652
- * @param file
3653
- * @return boolean
3654
- */
3655
- isValidFile(file) {
3656
- return file.content instanceof File && file.size > 0;
3657
- }
3658
- /**
3659
- * Get an object with all the files on the request.
3660
- */
3661
- allFiles() {
3662
- if (this.convertedFiles) return this.convertedFiles;
3663
- const entries = Object.entries(this.files.all()).filter((e) => e[1] != null);
3664
- const files = Object.fromEntries(entries);
3665
- this.convertedFiles = this.convertUploadedFiles(files);
3666
- return this.convertedFiles;
3667
- }
3668
- /**
3669
- * Extract and convert uploaded files from FormData.
3670
- */
3671
- convertUploadedFiles(files) {
3672
- if (!this.formData) return files;
3673
- for (const [key, value] of Object.entries(this.formData.files())) {
3674
- if (!(value instanceof File)) continue;
3675
- if (key.endsWith("[]")) {
3676
- const normalizedKey = key.slice(0, -2);
3677
- if (!files[normalizedKey]) files[normalizedKey] = [];
3678
- files[normalizedKey].push(UploadedFile.createFromBase(value));
3679
- } else files[key] = UploadedFile.createFromBase(value);
3680
- }
3681
- return files;
3682
- }
3683
- /**
3684
- * Get the current decoded path info for the request.
3685
- */
3686
- decodedPath() {
3687
- try {
3688
- return decodeURIComponent(this.path());
3689
- } catch {
3690
- return this.path();
3691
- }
3692
- }
3693
- /**
3694
- * Determine if the data contains a given key.
3695
- *
3696
- * @param keys
3697
- * @returns
3698
- */
3699
- has(keys) {
3700
- return _h3ravel_support.Obj.has(this.all(), keys);
3701
- }
3702
- /**
3703
- * Determine if the instance is missing a given key.
3704
- */
3705
- missing(key) {
3706
- const keys = Array.isArray(key) ? key : [key];
3707
- return !this.has(keys);
3708
- }
3709
- /**
3710
- * Get a subset containing the provided keys with values from the instance data.
3711
- *
3712
- * @param keys
3713
- * @returns
3714
- */
3715
- only(keys) {
3716
- const data = Object.entries(this.all()).filter(([key]) => keys.includes(key));
3717
- return Object.fromEntries(data);
3718
- }
3719
- /**
3720
- * Determine if the request is over HTTPS.
3721
- */
3722
- secure() {
3723
- return this.isSecure();
3724
- }
3725
- /**
3726
- * Get all of the data except for a specified array of items.
3727
- *
3728
- * @param keys
3729
- * @returns
3730
- */
3731
- except(keys) {
3732
- const data = Object.entries(this.all()).filter(([key]) => !keys.includes(key));
3733
- return Object.fromEntries(data);
3734
- }
3735
- /**
3736
- * Merges new input data into the current request's input source.
3737
- *
3738
- * @param input - An object containing key-value pairs to merge.
3739
- * @returns this - For fluent chaining.
3740
- */
3741
- merge(input) {
3742
- const source = this.getInputSource();
3743
- for (const [key, value] of Object.entries(input)) source.set(key, value);
3744
- return this;
3745
- }
3746
- /**
3747
- * Merge new input into the request's input, but only when that key is missing from the request.
3748
- *
3749
- * @param input
3750
- */
3751
- mergeIfMissing(input) {
3752
- return this.merge(Object.fromEntries(Object.entries(input).filter(([key]) => this.missing(key))));
3753
- }
3754
- /**
3755
- * Get the keys for all of the input and files.
3756
- */
3757
- keys() {
3758
- return [...Object.keys(this.input()), ...this.files.keys()];
3759
- }
3760
- /**
3761
- * Get an instance of the current session manager
3762
- *
3763
- * @param key
3764
- * @param defaultValue
3765
- * @returns a global instance of the current session manager.
3766
- */
3767
- session(key, defaultValue) {
3768
- this.sessionManager ??= this.app.make("session");
3769
- if (typeof key === "string") return this.sessionManager.get(key, defaultValue);
3770
- else if (typeof key === "object") {
3771
- for (const [k, val] of Object.entries(key)) this.sessionManager.put(k, val);
3772
- return;
3773
- }
3774
- return this.sessionManager;
3775
- }
3776
- /**
3777
- * Get the host name.
3778
- */
3779
- host() {
3780
- return this.getHost();
3781
- }
3782
- /**
3783
- * Get the HTTP host being requested.
3784
- */
3785
- httpHost() {
3786
- return this.getHttpHost();
3787
- }
3788
- /**
3789
- * Get the scheme and HTTP host.
3790
- */
3791
- schemeAndHttpHost() {
3792
- return this.getSchemeAndHttpHost();
3793
- }
3794
- /**
3795
- * Determine if the request is sending JSON.
3796
- *
3797
- * @return bool
3798
- */
3799
- isJson() {
3800
- return _h3ravel_support.Str.contains(this.getHeader("CONTENT_TYPE") ?? "", ["/json", "+json"]);
3801
- }
3802
- /**
3803
- * Determine if the current request probably expects a JSON response.
3804
- *
3805
- * @returns
3806
- */
3807
- expectsJson() {
3808
- return _h3ravel_support.Str.contains(this.getHeader("Accept") ?? "", "application/json");
3809
- }
3810
- /**
3811
- * Determine if the current request is asking for JSON.
3812
- *
3813
- * @returns
3814
- */
3815
- wantsJson() {
3816
- const acceptable = this.getAcceptableContentTypes();
3817
- return !!acceptable[0] && _h3ravel_support.Str.contains(acceptable[0].toLowerCase(), ["/json", "+json"]);
3818
- }
3819
- /**
3820
- * Determine if the request is the result of a PJAX call.
3821
- *
3822
- * @return bool
3823
- */
3824
- pjax() {
3825
- return this.headers.get("X-PJAX") == true;
3826
- }
3827
- /**
3828
- * Returns true if the request is an XMLHttpRequest (AJAX).
3829
- *
3830
- * @alias isXmlHttpRequest()
3831
- * @returns {boolean}
3832
- */
3833
- ajax() {
3834
- return this.isXmlHttpRequest();
3835
- }
3836
- /**
3837
- * Get the client IP address.
3838
- */
3839
- ip() {
3840
- return (0, h3.getRequestIP)(this.event);
3841
- }
3842
- async old(key, defaultValue) {
3843
- const payload = await this.session().get("_old", {});
3844
- if (key) return (0, _h3ravel_support.safeDot)(payload, key) || defaultValue;
3845
- return payload;
3846
- }
3847
- /**
3848
- * Get a URI instance for the request.
3849
- */
3850
- uri() {
3851
- return Reflect.apply(this.app.getUriResolver(), this, []).of(this.fullUrl(), this.app);
3852
- }
3853
- /**
3854
- * Get the root URL for the application.
3855
- *
3856
- * @return string
3857
- */
3858
- root() {
3859
- return _h3ravel_support.Str.rtrim(this.getSchemeAndHttpHost() + this.getBaseUrl(), "/");
3860
- }
3861
- /**
3862
- * Get the URL (no query string) for the request.
3863
- *
3864
- * @return string
3865
- */
3866
- url() {
3867
- return _h3ravel_support.Str.rtrim(this.uri().toString().replace(/\?.*/, ""), "/");
3868
- }
3869
- /**
3870
- * Get the full URL for the request.
3871
- */
3872
- fullUrl() {
3873
- return this.event.req.url;
3874
- }
3875
- /**
3876
- * Get the current path info for the request.
3877
- */
3878
- path() {
3879
- const pattern = (this.getPathInfo() ?? "").replace(/^\/+|\/+$/g, "");
3880
- return pattern === "" ? "/" : pattern;
3881
- }
3882
- /**
3883
- * Return the Request instance.
3884
- */
3885
- instance() {
3886
- return this;
3887
- }
3888
- /**
3889
- * Get the request method.
3890
- */
3891
- method() {
3892
- return this.getMethod();
3893
- }
3894
- /**
3895
- * Get the JSON payload for the request.
3896
- *
3897
- * @param key
3898
- * @param defaultValue
3899
- * @return {InputBag}
3900
- */
3901
- json(key, defaultValue) {
3902
- if (!this.#json) {
3903
- let json = this.getContent();
3904
- if (typeof json == "string") json = JSON.parse(json || "{}");
3905
- this.#json = new InputBag(json, this.event);
3906
- }
3907
- if (!key) return this.#json;
3908
- return _h3ravel_support.Obj.get(this.#json.all(), key, defaultValue);
3909
- }
3910
- /**
3911
- * Get the user resolver callback.
3912
- */
3913
- getUserResolver() {
3914
- return this.userResolver ?? (() => void 0);
3915
- }
3916
- /**
3917
- * Set the user resolver callback.
3918
- *
3919
- * @param callback
3920
- */
3921
- setUserResolver(callback) {
3922
- this.userResolver = callback;
3923
- return this;
3924
- }
3925
- /**
3926
- * Get the route resolver callback.
3927
- */
3928
- getRouteResolver() {
3929
- return this.routeResolver ?? (() => void 0);
3930
- }
3931
- /**
3932
- * Set the route resolver callback.
3933
- *
3934
- * @param callback
3935
- */
3936
- setRouteResolver(callback) {
3937
- this.routeResolver = callback;
3938
- return this;
3939
- }
3940
- /**
3941
- * Get the bearer token from the request headers.
3942
- */
3943
- bearerToken() {
3944
- let header = this.header("Authorization", "");
3945
- const position = header.toLowerCase().lastIndexOf("bearer ");
3946
- if (position !== -1) {
3947
- header = header.slice(position + 7);
3948
- const commaIndex = header.indexOf(",");
3949
- return commaIndex !== -1 ? header.slice(0, commaIndex) : header;
3950
- }
3951
- }
3952
- /**
3953
- * Retrieve data from the instance.
3954
- *
3955
- * @param key
3956
- * @param defaultValue
3957
- */
3958
- data(key, defaultValue) {
3959
- return this.input(key, defaultValue);
3960
- }
3961
- /**
3962
- * Retrieve a request payload item from the request.
3963
- *
3964
- * @param key
3965
- * @param default
3966
- */
3967
- post(key, defaultValue) {
3968
- return this.retrieveItem("request", key, defaultValue);
3969
- }
3970
- /**
3971
- * Determine if a header is set on the request.
3972
- *
3973
- * @param key
3974
- */
3975
- hasHeader(key) {
3976
- return this.header(key) != null;
3977
- }
3978
- /**
3979
- * Retrieve a header from the request.
3980
- *
3981
- * @param key
3982
- * @param default
3983
- */
3984
- header(key, defaultValue) {
3985
- return this.retrieveItem("headers", key, defaultValue);
3986
- }
3987
- /**
3988
- * Determine if a cookie is set on the request.
3989
- *
3990
- * @param string $key
3991
- */
3992
- hasCookie(key) {
3993
- return this.cookie(key) != null;
3994
- }
3995
- /**
3996
- * Retrieve a cookie from the request.
3997
- *
3998
- * @param key
3999
- * @param default
4000
- */
4001
- cookie(key, defaultValue) {
4002
- return this.retrieveItem("cookies", key, defaultValue);
4003
- }
4004
- /**
4005
- * Retrieve a query string item from the request.
4006
- *
4007
- * @param key
4008
- * @param default
4009
- */
4010
- query(key, defaultValue) {
4011
- return this.retrieveItem("_query", key, defaultValue);
4012
- }
4013
- /**
4014
- * Retrieve a server variable from the request.
4015
- *
4016
- * @param key
4017
- * @param default
4018
- */
4019
- server(key, defaultValue) {
4020
- return this.retrieveItem("_server", key, defaultValue);
4021
- }
4022
- /**
4023
- * Get the input source for the request.
4024
- *
4025
- * @return {InputBag}
4026
- */
4027
- getInputSource() {
4028
- if (this.isJson()) return this.json();
4029
- return ["GET", "HEAD"].includes(this.getRealMethod()) ? this._query : this.request;
4030
- }
4031
- /**
4032
- * Retrieve a parameter item from a given source.
4033
- *
4034
- * @param source
4035
- * @param key
4036
- * @param defaultValue
4037
- */
4038
- retrieveItem(source, key, defaultValue) {
4039
- if (key == null) return this[source].all();
4040
- if (this[source] instanceof InputBag) return this[source].all()[key] ?? defaultValue;
4041
- return this[source].get(key, defaultValue);
4042
- }
4043
- /**
4044
- * Dump the items.
4045
- *
4046
- * @param keys
4047
- */
4048
- dump(...keys) {
4049
- if (keys.length > 0) this.only(keys).then(dump);
4050
- else this.all().then(dump);
4051
- return this;
4052
- }
4053
- getEvent(key) {
4054
- return (0, _h3ravel_support.safeDot)(this.event, key);
4055
- }
4056
- };
4057
- //#endregion
4058
- //#region src/Resources/JsonResource.ts
4059
- /**
4060
- * Class to render API resource
4061
- */
4062
- var JsonResource = class {
4063
- event;
4064
- /**
4065
- * The request instance
4066
- */
4067
- request;
4068
- /**
4069
- * The response instance
4070
- */
4071
- response;
4072
- /**
4073
- * The data to send to the client
4074
- */
4075
- resource;
4076
- /**
4077
- * The final response data object
4078
- */
4079
- body = { data: {} };
4080
- /**
4081
- * Flag to track if response should be sent automatically
4082
- */
4083
- shouldSend = false;
4084
- /**
4085
- * Flag to track if response has been sent
4086
- */
4087
- responseSent = false;
4088
- /**
4089
- * @param req The request instance
4090
- * @param res The response instance
4091
- * @param rsc The data to send to the client
4092
- */
4093
- constructor(event, rsc) {
4094
- this.event = event;
4095
- this.request = event.req;
4096
- this.response = event.res;
4097
- this.resource = rsc;
4098
- for (const key of Object.keys(rsc)) if (!(key in this)) Object.defineProperty(this, key, {
4099
- enumerable: true,
4100
- configurable: true,
4101
- get: () => this.resource[key],
4102
- set: (value) => {
4103
- this.resource[key] = value;
4104
- }
4105
- });
4106
- }
4107
- /**
4108
- * Return the data in the expected format
4109
- *
4110
- * @returns
4111
- */
4112
- data() {
4113
- return this.resource;
4114
- }
4115
- /**
4116
- * Build the response object
4117
- * @returns this
4118
- */
4119
- json() {
4120
- this.shouldSend = true;
4121
- this.response.status = 200;
4122
- const resource = this.data();
4123
- let data = Array.isArray(resource) ? [...resource] : { ...resource };
4124
- if (typeof data.data !== "undefined") data = data.data;
4125
- if (!Array.isArray(resource)) delete data.pagination;
4126
- this.body = { data };
4127
- if (!Array.isArray(resource) && resource.pagination) {
4128
- const meta = this.body.meta ?? {};
4129
- meta.pagination = resource.pagination;
4130
- this.body.meta = meta;
4131
- }
4132
- if (this.resource.pagination && !this.body.meta?.pagination) {
4133
- const meta = this.body.meta ?? {};
4134
- meta.pagination = this.resource.pagination;
4135
- this.body.meta = meta;
4136
- }
4137
- return this;
4138
- }
4139
- /**
4140
- * Add context data to the response object
4141
- * @param data Context data
4142
- * @returns this
4143
- */
4144
- additional(data) {
4145
- this.shouldSend = true;
4146
- delete data.data;
4147
- delete data.pagination;
4148
- this.body = {
4149
- ...this.body,
4150
- ...data
4151
- };
4152
- return this;
4153
- }
4154
- /**
4155
- * Send the output to the client
4156
- * @returns this
4157
- */
4158
- send() {
4159
- this.shouldSend = false;
4160
- if (!this.responseSent) this.#send();
4161
- return this;
4162
- }
4163
- /**
4164
- * Set the status code for this response
4165
- * @param code Status code
4166
- * @returns this
4167
- */
4168
- status(code) {
4169
- this.response.status = code;
4170
- return this;
4171
- }
4172
- /**
4173
- * Private method to send the response
4174
- */
4175
- #send() {
4176
- if (!this.responseSent) this.responseSent = true;
4177
- }
4178
- /**
4179
- * Check if send should be triggered automatically
4180
- */
4181
- checkSend() {
4182
- if (this.shouldSend && !this.responseSent) this.#send();
4183
- }
4184
- };
4185
- //#endregion
4186
- //#region src/Resources/ApiResource.ts
4187
- function ApiResource(instance) {
4188
- return new Proxy(instance, { get(target, prop, receiver) {
4189
- const value = Reflect.get(target, prop, receiver);
4190
- if (typeof value === "function") {
4191
- if (prop === "json" || prop === "additional") return (...args) => {
4192
- const result = value.apply(target, args);
4193
- setImmediate(() => target["checkSend"]());
4194
- return result;
4195
- };
4196
- else if (prop === "send") return (...args) => {
4197
- target["shouldSend"] = false;
4198
- return value.apply(target, args);
4199
- };
4200
- }
4201
- return value;
4202
- } });
4203
- }
4204
- //#endregion
4205
- exports.ApiResource = ApiResource;
4206
- exports.BadRequestException = BadRequestException;
4207
- exports.ConflictingHeadersException = ConflictingHeadersException;
4208
- exports.Cookie = Cookie;
4209
- exports.FileBag = FileBag;
4210
- exports.FireCommand = FireCommand;
4211
- exports.FlashDataMiddleware = FlashDataMiddleware;
4212
- exports.FormRequest = FormRequest;
4213
- exports.HeaderBag = HeaderBag;
4214
- exports.HeaderUtility = HeaderUtility;
4215
- exports.HttpContext = HttpContext;
4216
- exports.HttpRequest = HttpRequest;
4217
- exports.HttpResponse = HttpResponse;
4218
- exports.HttpResponseException = HttpResponseException;
4219
- exports.HttpServiceProvider = HttpServiceProvider;
4220
- exports.InputBag = InputBag;
4221
- exports.IpUtils = IpUtils;
4222
- exports.JsonResource = JsonResource;
4223
- exports.JsonResponse = JsonResponse;
4224
- exports.LogRequests = LogRequests;
4225
- Object.defineProperty(exports, "Middleware", {
4226
- enumerable: true,
4227
- get: function() {
4228
- return Middleware;
4229
- }
4230
- });
4231
- exports.ParamBag = ParamBag;
4232
- exports.Request = Request;
4233
- exports.Responsable = Responsable;
4234
- exports.Response = Response;
4235
- exports.ResponseHeaderBag = ResponseHeaderBag;
4236
- exports.ServerBag = ServerBag;
4237
- exports.SuspiciousOperationException = SuspiciousOperationException;
4238
- exports.TrustHosts = TrustHosts;
4239
- exports.UnexpectedValueException = UnexpectedValueException;
4240
- exports.UploadedFile = UploadedFile;