@h3ravel/http 11.4.3 → 11.5.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/app.globals.d.ts +10 -0
- package/dist/index.cjs +1329 -30
- package/dist/index.d.cts +825 -20
- package/dist/index.d.ts +824 -19
- package/dist/index.js +1318 -30
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { i as __toESM, r as __require, t as __commonJS } from "./chunk-BLWcukCW.js";
|
|
2
|
+
import { writeFile } from "fs/promises";
|
|
3
|
+
import { Arr, Obj, Str, data_get, data_set, safeDot } from "@h3ravel/support";
|
|
2
4
|
import { Command } from "@h3ravel/musket";
|
|
3
5
|
import { HttpContext, Logger } from "@h3ravel/shared";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -16,9 +18,697 @@ import { appendFileSync, createReadStream, createWriteStream, readFileSync, stat
|
|
|
16
18
|
import { finished } from "node:stream/promises";
|
|
17
19
|
import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
|
|
18
20
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
19
|
-
import { H3, getQuery, getRouterParams, html,
|
|
20
|
-
import { safeDot } from "@h3ravel/support";
|
|
21
|
+
import { H3, getQuery, getRequestIP, getRequestURL, getRouterParams, html, parseCookies, redirect, serve } from "h3";
|
|
21
22
|
|
|
23
|
+
//#region src/Exceptions/BadRequestException.ts
|
|
24
|
+
var BadRequestException = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "BadRequestException";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/Exceptions/UnexpectedValueException.ts
|
|
33
|
+
var UnexpectedValueException = class extends Error {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "UnexpectedValueException";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/Bags/ParamBag.ts
|
|
42
|
+
/**
|
|
43
|
+
* ParamBag is a container for key/value pairs
|
|
44
|
+
* for Node/H3 environments.
|
|
45
|
+
*/
|
|
46
|
+
var ParamBag = class {
|
|
47
|
+
constructor(parameters = {}, event) {
|
|
48
|
+
this.parameters = parameters;
|
|
49
|
+
this.event = event;
|
|
50
|
+
this.parameters = { ...parameters };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the parameters.
|
|
54
|
+
* @
|
|
55
|
+
* @param key The name of the parameter to return or null to get them all
|
|
56
|
+
*
|
|
57
|
+
* @throws BadRequestException if the value is not an array
|
|
58
|
+
*/
|
|
59
|
+
all(key) {
|
|
60
|
+
if (key === null) return { ...this.parameters };
|
|
61
|
+
const value = key ? this.parameters[key] : void 0;
|
|
62
|
+
if (value && typeof value !== "object") throw new BadRequestException(`Unexpected value for parameter "${key}": expected object, got ${typeof value}`);
|
|
63
|
+
return value || {};
|
|
64
|
+
}
|
|
65
|
+
get(key, defaultValue) {
|
|
66
|
+
return key in this.parameters ? this.parameters[key] : defaultValue;
|
|
67
|
+
}
|
|
68
|
+
set(key, value) {
|
|
69
|
+
this.parameters[key] = value;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns true if the parameter is defined.
|
|
73
|
+
*
|
|
74
|
+
* @param key
|
|
75
|
+
*/
|
|
76
|
+
has(key) {
|
|
77
|
+
return Object.prototype.hasOwnProperty.call(this.parameters, key);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Removes a parameter.
|
|
81
|
+
*
|
|
82
|
+
* @param key
|
|
83
|
+
*/
|
|
84
|
+
remove(key) {
|
|
85
|
+
delete this.parameters[key];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* Returns the parameter as string.
|
|
90
|
+
*
|
|
91
|
+
* @param key
|
|
92
|
+
* @param defaultValue
|
|
93
|
+
* @throws UnexpectedValueException if the value cannot be converted to string
|
|
94
|
+
* @returns
|
|
95
|
+
*/
|
|
96
|
+
getString(key, defaultValue = "") {
|
|
97
|
+
const value = this.get(key, defaultValue);
|
|
98
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
|
|
99
|
+
throw new UnexpectedValueException(`Parameter "${key}" cannot be converted to string`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the parameter value converted to integer.
|
|
103
|
+
*
|
|
104
|
+
* @param key
|
|
105
|
+
* @param defaultValue
|
|
106
|
+
* @throws UnexpectedValueException if the value cannot be converted to integer
|
|
107
|
+
*/
|
|
108
|
+
getInt(key, defaultValue = 0) {
|
|
109
|
+
const value = parseInt(this.get(key, defaultValue), 10);
|
|
110
|
+
if (isNaN(value)) throw new Error(`Parameter "${key}" is not an integer`);
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns the parameter value converted to boolean.
|
|
115
|
+
*
|
|
116
|
+
* @param key
|
|
117
|
+
* @param defaultValue
|
|
118
|
+
* @throws UnexpectedValueException if the value cannot be converted to a boolean
|
|
119
|
+
*/
|
|
120
|
+
getBoolean(key, defaultValue = false) {
|
|
121
|
+
const value = this.get(key, defaultValue);
|
|
122
|
+
if (typeof value === "boolean") return value;
|
|
123
|
+
if ([
|
|
124
|
+
"1",
|
|
125
|
+
"true",
|
|
126
|
+
"yes"
|
|
127
|
+
].includes(String(value).toLowerCase())) return true;
|
|
128
|
+
if ([
|
|
129
|
+
"0",
|
|
130
|
+
"false",
|
|
131
|
+
"no"
|
|
132
|
+
].includes(String(value).toLowerCase())) return false;
|
|
133
|
+
throw new Error(`Parameter "${key}" cannot be converted to boolean`);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Returns the alphabetic characters of the parameter value.
|
|
137
|
+
*
|
|
138
|
+
* @param key
|
|
139
|
+
* @param defaultValue
|
|
140
|
+
* @throws UnexpectedValueException if the value cannot be converted to string
|
|
141
|
+
*/
|
|
142
|
+
getAlpha(key, defaultValue = "") {
|
|
143
|
+
return this.getString(key, defaultValue).replace(/[^a-z]/gi, "");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns the alphabetic characters and digits of the parameter value.
|
|
147
|
+
*
|
|
148
|
+
* @param key
|
|
149
|
+
* @param defaultValue
|
|
150
|
+
* @throws UnexpectedValueException if the value cannot be converted to string
|
|
151
|
+
*/
|
|
152
|
+
getAlnum(key, defaultValue = "") {
|
|
153
|
+
return this.getString(key, defaultValue).replace(/[^a-z0-9]/gi, "");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Returns the digits of the parameter value.
|
|
157
|
+
*
|
|
158
|
+
* @param key
|
|
159
|
+
* @param defaultValue
|
|
160
|
+
* @throws UnexpectedValueException if the value cannot be converted to string
|
|
161
|
+
* @returns
|
|
162
|
+
**/
|
|
163
|
+
getDigits(key, defaultValue = "") {
|
|
164
|
+
return this.getString(key, defaultValue).replace(/\D/g, "");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Returns the parameter keys.
|
|
168
|
+
*/
|
|
169
|
+
keys() {
|
|
170
|
+
return Object.keys(this.parameters);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Replaces the current parameters by a new set.
|
|
174
|
+
*/
|
|
175
|
+
replace(parameters = {}) {
|
|
176
|
+
this.parameters = { ...parameters };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Adds parameters.
|
|
180
|
+
*/
|
|
181
|
+
add(parameters = {}) {
|
|
182
|
+
this.parameters = {
|
|
183
|
+
...this.parameters,
|
|
184
|
+
...parameters
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Returns the number of parameters.
|
|
189
|
+
*/
|
|
190
|
+
count() {
|
|
191
|
+
return Object.keys(this.parameters).length;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Returns an iterator for parameters.
|
|
195
|
+
*
|
|
196
|
+
* @returns
|
|
197
|
+
*/
|
|
198
|
+
[Symbol.iterator]() {
|
|
199
|
+
return Object.entries(this.parameters)[Symbol.iterator]();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/UploadedFile.ts
|
|
205
|
+
var UploadedFile = class UploadedFile {
|
|
206
|
+
constructor(originalName, mimeType, size, content) {
|
|
207
|
+
this.originalName = originalName;
|
|
208
|
+
this.mimeType = mimeType;
|
|
209
|
+
this.size = size;
|
|
210
|
+
this.content = content;
|
|
211
|
+
}
|
|
212
|
+
static createFromBase(file) {
|
|
213
|
+
return new UploadedFile(file.name, file.type, file.size, file);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Save to disk (Node environment only)
|
|
217
|
+
*/
|
|
218
|
+
async moveTo(destination) {
|
|
219
|
+
await writeFile(destination, Buffer.from(await this.content.arrayBuffer()));
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/Bags/FileBag.ts
|
|
225
|
+
/**
|
|
226
|
+
* FileBag is a container for uploaded files
|
|
227
|
+
* for Node/H3 environments.
|
|
228
|
+
*/
|
|
229
|
+
var FileBag = class extends ParamBag {
|
|
230
|
+
parameters = {};
|
|
231
|
+
constructor(parameters = {}, event) {
|
|
232
|
+
super(parameters, event);
|
|
233
|
+
this.replace(parameters);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Replace all stored files.
|
|
237
|
+
*/
|
|
238
|
+
replace(files = {}) {
|
|
239
|
+
this.parameters = {};
|
|
240
|
+
this.add(files);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Set a file or array of files.
|
|
244
|
+
*/
|
|
245
|
+
set(key, value) {
|
|
246
|
+
if (Array.isArray(value)) this.parameters[key] = value.map((v) => v ? this.convertFileInformation(v) : null).filter(Boolean);
|
|
247
|
+
else if (value) this.parameters[key] = this.convertFileInformation(value);
|
|
248
|
+
else this.parameters[key] = null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Add multiple files.
|
|
252
|
+
*/
|
|
253
|
+
add(files = {}) {
|
|
254
|
+
for (const [key, file] of Object.entries(files)) this.set(key, file);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get all stored files.
|
|
258
|
+
*/
|
|
259
|
+
all() {
|
|
260
|
+
return this.parameters;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Normalize file input into UploadedFile instances.
|
|
264
|
+
*/
|
|
265
|
+
convertFileInformation(file) {
|
|
266
|
+
if (!file) return null;
|
|
267
|
+
if (file instanceof UploadedFile) return file;
|
|
268
|
+
if (file instanceof File) return UploadedFile.createFromBase(file);
|
|
269
|
+
throw new TypeError("Invalid file input — expected File or UploadedFile instance.");
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/Bags/HeaderBag.ts
|
|
275
|
+
/**
|
|
276
|
+
* HeaderBag — A container for HTTP headers
|
|
277
|
+
* for Node/H3 environments.
|
|
278
|
+
*/
|
|
279
|
+
var HeaderBag = class HeaderBag {
|
|
280
|
+
static UPPER = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
281
|
+
static LOWER = "-abcdefghijklmnopqrstuvwxyz";
|
|
282
|
+
headers = {};
|
|
283
|
+
cacheControl = {};
|
|
284
|
+
constructor(headers = {}) {
|
|
285
|
+
for (const [key, values] of Object.entries(headers)) this.set(key, values);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns all headers as string (for debugging / toString)
|
|
289
|
+
*
|
|
290
|
+
* @returns
|
|
291
|
+
*/
|
|
292
|
+
toString() {
|
|
293
|
+
const headers = this.all();
|
|
294
|
+
if (!Object.keys(headers).length) return "";
|
|
295
|
+
const sortedKeys = Object.keys(headers).sort();
|
|
296
|
+
const max = Math.max(...sortedKeys.map((k) => k.length)) + 1;
|
|
297
|
+
let content = "";
|
|
298
|
+
for (const name of sortedKeys) {
|
|
299
|
+
const values = headers[name] ?? [];
|
|
300
|
+
const displayName = name.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("-");
|
|
301
|
+
for (const value of values) content += `${displayName + ":"}`.padEnd(max + 1, " ") + `${value ?? ""}\r\n`;
|
|
302
|
+
}
|
|
303
|
+
return content;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Returns all headers or specific header list
|
|
307
|
+
*
|
|
308
|
+
* @param key
|
|
309
|
+
* @returns
|
|
310
|
+
*/
|
|
311
|
+
all(key) {
|
|
312
|
+
if (key !== void 0) return this.headers[this.normalizeKey(key)] ?? [];
|
|
313
|
+
return this.headers;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Returns header keys
|
|
317
|
+
*
|
|
318
|
+
* @returns
|
|
319
|
+
*/
|
|
320
|
+
keys() {
|
|
321
|
+
return Object.keys(this.headers);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Replace all headers with new set
|
|
325
|
+
*
|
|
326
|
+
* @param headers
|
|
327
|
+
*/
|
|
328
|
+
replace(headers = {}) {
|
|
329
|
+
this.headers = {};
|
|
330
|
+
this.add(headers);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Add multiple headers
|
|
334
|
+
*
|
|
335
|
+
* @param headers
|
|
336
|
+
*/
|
|
337
|
+
add(headers) {
|
|
338
|
+
for (const [key, values] of Object.entries(headers)) this.set(key, values);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Returns first header value by name or default
|
|
342
|
+
*
|
|
343
|
+
* @param key
|
|
344
|
+
* @param defaultValue
|
|
345
|
+
* @returns
|
|
346
|
+
*/
|
|
347
|
+
get(key, defaultValue = null) {
|
|
348
|
+
const headers = this.all(key);
|
|
349
|
+
if (!headers.length) return defaultValue;
|
|
350
|
+
return headers[0];
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Sets a header by name.
|
|
354
|
+
*
|
|
355
|
+
* @param replace Whether to replace existing values (default true)
|
|
356
|
+
*/
|
|
357
|
+
set(key, values, replace = true) {
|
|
358
|
+
const normalized = this.normalizeKey(key);
|
|
359
|
+
if (Array.isArray(values)) {
|
|
360
|
+
const valList = values.map((v) => v === void 0 ? null : v);
|
|
361
|
+
if (replace || !this.headers[normalized]) this.headers[normalized] = valList;
|
|
362
|
+
else this.headers[normalized].push(...valList);
|
|
363
|
+
} else {
|
|
364
|
+
const val = values === void 0 ? null : values;
|
|
365
|
+
if (replace || !this.headers[normalized]) this.headers[normalized] = [val];
|
|
366
|
+
else this.headers[normalized].push(val);
|
|
367
|
+
}
|
|
368
|
+
if (normalized === "cache-control") this.cacheControl = this.parseCacheControl((this.headers[normalized] ?? []).join(", "));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Returns true if header exists
|
|
372
|
+
*
|
|
373
|
+
* @param key
|
|
374
|
+
* @returns
|
|
375
|
+
*/
|
|
376
|
+
has(key) {
|
|
377
|
+
return Object.prototype.hasOwnProperty.call(this.headers, this.normalizeKey(key));
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Returns true if header contains value
|
|
381
|
+
*
|
|
382
|
+
* @param key
|
|
383
|
+
* @param value
|
|
384
|
+
* @returns
|
|
385
|
+
*/
|
|
386
|
+
contains(key, value) {
|
|
387
|
+
return this.all(key).includes(value);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Removes a header
|
|
391
|
+
*
|
|
392
|
+
* @param key
|
|
393
|
+
*/
|
|
394
|
+
remove(key) {
|
|
395
|
+
const normalized = this.normalizeKey(key);
|
|
396
|
+
delete this.headers[normalized];
|
|
397
|
+
if (normalized === "cache-control") this.cacheControl = {};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Returns parsed date from header
|
|
401
|
+
*
|
|
402
|
+
* @param key
|
|
403
|
+
* @param defaultValue
|
|
404
|
+
* @returns
|
|
405
|
+
*/
|
|
406
|
+
getDate(key, defaultValue = null) {
|
|
407
|
+
const value = this.get(key);
|
|
408
|
+
if (!value) return defaultValue ? new Date(defaultValue) : null;
|
|
409
|
+
const parsed = new Date(value);
|
|
410
|
+
if (isNaN(parsed.getTime())) throw new Error(`The "${key}" HTTP header is not parseable (${value}).`);
|
|
411
|
+
return parsed;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Adds a Cache-Control directive
|
|
415
|
+
*
|
|
416
|
+
* @param key
|
|
417
|
+
* @param value
|
|
418
|
+
*/
|
|
419
|
+
addCacheControlDirective(key, value = true) {
|
|
420
|
+
this.cacheControl[key] = value;
|
|
421
|
+
this.set("Cache-Control", this.getCacheControlHeader());
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Returns true if Cache-Control directive is defined
|
|
425
|
+
*
|
|
426
|
+
* @param key
|
|
427
|
+
* @returns
|
|
428
|
+
*/
|
|
429
|
+
hasCacheControlDirective(key) {
|
|
430
|
+
return Object.prototype.hasOwnProperty.call(this.cacheControl, key);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Returns a Cache-Control directive value by name
|
|
434
|
+
*
|
|
435
|
+
* @param key
|
|
436
|
+
* @returns
|
|
437
|
+
*/
|
|
438
|
+
getCacheControlDirective(key) {
|
|
439
|
+
return this.cacheControl[key] ?? null;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Removes a Cache-Control directive
|
|
443
|
+
*
|
|
444
|
+
* @param key
|
|
445
|
+
* @returns
|
|
446
|
+
*/
|
|
447
|
+
removeCacheControlDirective(key) {
|
|
448
|
+
delete this.cacheControl[key];
|
|
449
|
+
this.set("Cache-Control", this.getCacheControlHeader());
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Number of headers
|
|
453
|
+
*
|
|
454
|
+
* @param key
|
|
455
|
+
* @returns
|
|
456
|
+
*/
|
|
457
|
+
count() {
|
|
458
|
+
return Object.keys(this.headers).length;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Normalize header name to lowercase with hyphens
|
|
462
|
+
*
|
|
463
|
+
* @param key
|
|
464
|
+
* @returns
|
|
465
|
+
*/
|
|
466
|
+
normalizeKey(key) {
|
|
467
|
+
return key.replace(/[A-Z_]/g, (ch) => {
|
|
468
|
+
const idx = HeaderBag.UPPER.indexOf(ch);
|
|
469
|
+
return idx === -1 ? ch : HeaderBag.LOWER[idx];
|
|
470
|
+
}).toLowerCase();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Generates Cache-Control header string
|
|
474
|
+
*
|
|
475
|
+
* @param header
|
|
476
|
+
* @returns
|
|
477
|
+
*/
|
|
478
|
+
getCacheControlHeader() {
|
|
479
|
+
return Object.entries(this.cacheControl).sort(([a$1], [b]) => a$1.localeCompare(b)).map(([k, v]) => v === true ? k : v === false ? "" : `${k}=${v}`).filter(Boolean).join(", ");
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Parses Cache-Control header
|
|
483
|
+
*
|
|
484
|
+
* @param header
|
|
485
|
+
* @returns
|
|
486
|
+
*/
|
|
487
|
+
parseCacheControl(header) {
|
|
488
|
+
const directives = {};
|
|
489
|
+
const parts = header.split(",").map((p) => p.trim()).filter(Boolean);
|
|
490
|
+
for (const part of parts) {
|
|
491
|
+
const [key, val] = part.split("=", 2);
|
|
492
|
+
directives[key.trim()] = val !== void 0 ? val.trim() : true;
|
|
493
|
+
}
|
|
494
|
+
return directives;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Iterator support
|
|
498
|
+
* @returns
|
|
499
|
+
*/
|
|
500
|
+
[Symbol.iterator]() {
|
|
501
|
+
return Object.entries(this.headers)[Symbol.iterator]();
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/Bags/InputBag.ts
|
|
507
|
+
/**
|
|
508
|
+
* InputBag is a container for user input values
|
|
509
|
+
* (e.g., query params, body, cookies)
|
|
510
|
+
* for Node/H3 environments.
|
|
511
|
+
*/
|
|
512
|
+
var InputBag = class extends ParamBag {
|
|
513
|
+
constructor(inputs = {}, event) {
|
|
514
|
+
super(inputs, event);
|
|
515
|
+
this.add(inputs);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Returns a scalar input value by name.
|
|
519
|
+
*
|
|
520
|
+
* @param key
|
|
521
|
+
* @param defaultValue
|
|
522
|
+
* @throws BadRequestException if the input contains a non-scalar value
|
|
523
|
+
* @returns
|
|
524
|
+
*/
|
|
525
|
+
get(key, defaultValue = null) {
|
|
526
|
+
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}`);
|
|
527
|
+
const value = Obj.get(this.parameters, key, defaultValue);
|
|
528
|
+
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") throw new BadRequestException(`Input value "${key}" contains a non-scalar value.`);
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Replaces all current input values.
|
|
533
|
+
*
|
|
534
|
+
* @param inputs
|
|
535
|
+
* @returns
|
|
536
|
+
*/
|
|
537
|
+
replace(inputs = {}) {
|
|
538
|
+
this.parameters = {};
|
|
539
|
+
this.add(inputs);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Adds multiple input values.
|
|
543
|
+
*
|
|
544
|
+
* @param inputs
|
|
545
|
+
* @returns
|
|
546
|
+
*/
|
|
547
|
+
add(inputs = {}) {
|
|
548
|
+
Object.entries(inputs).forEach(([key, value]) => this.set(key, value));
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Sets an input by name.
|
|
552
|
+
*
|
|
553
|
+
* @param key
|
|
554
|
+
* @param value
|
|
555
|
+
* @throws TypeError if value is not scalar or array
|
|
556
|
+
* @returns
|
|
557
|
+
*/
|
|
558
|
+
set(key, value) {
|
|
559
|
+
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}`);
|
|
560
|
+
this.parameters[key] = value;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Returns true if a key exists.
|
|
564
|
+
*
|
|
565
|
+
* @param key
|
|
566
|
+
* @returns
|
|
567
|
+
*/
|
|
568
|
+
has(key) {
|
|
569
|
+
return Object.prototype.hasOwnProperty.call(this.parameters, key);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Returns all parameters.
|
|
573
|
+
*
|
|
574
|
+
* @returns
|
|
575
|
+
*/
|
|
576
|
+
all() {
|
|
577
|
+
return { ...this.parameters };
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Converts a parameter value to string.
|
|
581
|
+
*
|
|
582
|
+
* @param key
|
|
583
|
+
* @param defaultValue
|
|
584
|
+
* @throws BadRequestException if input contains a non-scalar value
|
|
585
|
+
* @returns
|
|
586
|
+
*/
|
|
587
|
+
getString(key, defaultValue = "") {
|
|
588
|
+
const value = this.get(key, defaultValue);
|
|
589
|
+
return String(value ?? "");
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Filters input value with a predicate.
|
|
593
|
+
* Mimics PHP’s filter_var() in spirit, but simpler.
|
|
594
|
+
*
|
|
595
|
+
* @param key
|
|
596
|
+
* @param defaultValue
|
|
597
|
+
* @param filterFn
|
|
598
|
+
* @throws BadRequestException if validation fails
|
|
599
|
+
* @returns
|
|
600
|
+
*/
|
|
601
|
+
filter(key, defaultValue = null, filterFn) {
|
|
602
|
+
const value = this.has(key) ? this.parameters[key] : defaultValue;
|
|
603
|
+
if (Array.isArray(value)) throw new BadRequestException(`Input value "${key}" contains an array, but array filtering not allowed.`);
|
|
604
|
+
if (filterFn && !filterFn(value)) throw new BadRequestException(`Input value "${key}" is invalid and did not pass filter.`);
|
|
605
|
+
return value;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Returns an enum value by key.
|
|
609
|
+
*
|
|
610
|
+
* @param key
|
|
611
|
+
* @param EnumClass
|
|
612
|
+
* @param defaultValue
|
|
613
|
+
* @throws BadRequestException if conversion fails
|
|
614
|
+
* @returns
|
|
615
|
+
*/
|
|
616
|
+
getEnum(key, EnumClass, defaultValue = null) {
|
|
617
|
+
const value = this.get(key, defaultValue);
|
|
618
|
+
if (value === null) return defaultValue;
|
|
619
|
+
if (!Object.values(EnumClass).includes(value)) throw new BadRequestException(`Input "${key}" cannot be converted to enum.`);
|
|
620
|
+
return value;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Removes a key.
|
|
624
|
+
*
|
|
625
|
+
* @param key
|
|
626
|
+
*/
|
|
627
|
+
remove(key) {
|
|
628
|
+
delete this.parameters[key];
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Returns all keys.
|
|
632
|
+
*
|
|
633
|
+
* @returns
|
|
634
|
+
*/
|
|
635
|
+
keys() {
|
|
636
|
+
return Object.keys(this.parameters);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Returns number of parameters.
|
|
640
|
+
*
|
|
641
|
+
* @returns
|
|
642
|
+
*/
|
|
643
|
+
count() {
|
|
644
|
+
return this.keys().length;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region src/Bags/ServerBag.ts
|
|
650
|
+
/**
|
|
651
|
+
* ServerBag — a simplified version of Symfony's ServerBag
|
|
652
|
+
* for Node/H3 environments.
|
|
653
|
+
*
|
|
654
|
+
* Responsible for extracting and normalizing HTTP headers
|
|
655
|
+
* from the incoming request.
|
|
656
|
+
*/
|
|
657
|
+
var ServerBag = class extends ParamBag {
|
|
658
|
+
constructor(parameters = {}, event) {
|
|
659
|
+
super(Object.fromEntries(Object.entries(parameters).map(([k, v]) => [k.toLowerCase(), v])), event);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Returns all request headers, normalized to uppercase with underscores.
|
|
663
|
+
* Example: content-type → CONTENT_TYPE
|
|
664
|
+
*/
|
|
665
|
+
getHeaders() {
|
|
666
|
+
const headers = {};
|
|
667
|
+
for (const [key, value] of Object.entries(this.parameters)) {
|
|
668
|
+
if (value === void 0 || value === "") continue;
|
|
669
|
+
switch (key) {
|
|
670
|
+
case "accept":
|
|
671
|
+
case "content-type":
|
|
672
|
+
case "content-length":
|
|
673
|
+
case "content-md5":
|
|
674
|
+
case "authorization":
|
|
675
|
+
headers[key.toUpperCase().replace(/-/g, "_")] = value;
|
|
676
|
+
break;
|
|
677
|
+
default: headers[`HTTP_${key.toUpperCase().replace(/-/g, "_")}`] = value;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (headers["HTTP_AUTHORIZATION"] || headers["AUTHORIZATION"]) {
|
|
681
|
+
const auth = headers["HTTP_AUTHORIZATION"] || headers["AUTHORIZATION"] || "";
|
|
682
|
+
if (auth.toLowerCase().startsWith("basic ")) {
|
|
683
|
+
const [user, pass] = Buffer.from(auth.slice(6), "base64").toString().split(":", 2);
|
|
684
|
+
headers["AUTH_TYPE"] = "Basic";
|
|
685
|
+
headers["AUTH_USER"] = user;
|
|
686
|
+
headers["AUTH_PASS"] = pass;
|
|
687
|
+
} else if (auth.toLowerCase().startsWith("bearer ")) {
|
|
688
|
+
headers["AUTH_TYPE"] = "Bearer";
|
|
689
|
+
headers["AUTH_TOKEN"] = auth.slice(7);
|
|
690
|
+
} else if (auth.toLowerCase().startsWith("digest ")) {
|
|
691
|
+
headers["AUTH_TYPE"] = "Digest";
|
|
692
|
+
headers["AUTH_DIGEST"] = auth;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return headers;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Returns a specific header by name, case-insensitive.
|
|
699
|
+
*/
|
|
700
|
+
get(name) {
|
|
701
|
+
return this.parameters[name.toLowerCase()];
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Returns true if a header exists.
|
|
705
|
+
*/
|
|
706
|
+
has(name) {
|
|
707
|
+
return name.toLowerCase() in this.parameters;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
//#endregion
|
|
22
712
|
//#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
|
|
23
713
|
function isPlainObject(value) {
|
|
24
714
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -10227,8 +10917,8 @@ var require_graceful_fs = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm
|
|
|
10227
10917
|
}
|
|
10228
10918
|
}
|
|
10229
10919
|
var fs$writeFile = fs$9.writeFile;
|
|
10230
|
-
fs$9.writeFile = writeFile;
|
|
10231
|
-
function writeFile(path$12, data, options, cb) {
|
|
10920
|
+
fs$9.writeFile = writeFile$1;
|
|
10921
|
+
function writeFile$1(path$12, data, options, cb) {
|
|
10232
10922
|
if (typeof options === "function") cb = options, options = null;
|
|
10233
10923
|
return go$writeFile(path$12, data, options, cb);
|
|
10234
10924
|
function go$writeFile(path$13, data$1, options$1, cb$1, startTime) {
|
|
@@ -18039,7 +18729,7 @@ var require_dumper = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/js-y
|
|
|
18039
18729
|
}
|
|
18040
18730
|
}
|
|
18041
18731
|
}
|
|
18042
|
-
function dump(input, options) {
|
|
18732
|
+
function dump$1(input, options) {
|
|
18043
18733
|
options = options || {};
|
|
18044
18734
|
var state = new State(options);
|
|
18045
18735
|
if (!state.noRefs) getDuplicateReferences(input, state);
|
|
@@ -18047,9 +18737,9 @@ var require_dumper = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/js-y
|
|
|
18047
18737
|
return "";
|
|
18048
18738
|
}
|
|
18049
18739
|
function safeDump(input, options) {
|
|
18050
|
-
return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
|
|
18740
|
+
return dump$1(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
|
|
18051
18741
|
}
|
|
18052
|
-
module.exports.dump = dump;
|
|
18742
|
+
module.exports.dump = dump$1;
|
|
18053
18743
|
module.exports.safeDump = safeDump;
|
|
18054
18744
|
}) });
|
|
18055
18745
|
|
|
@@ -18260,6 +18950,68 @@ var FireCommand = class extends Command {
|
|
|
18260
18950
|
}
|
|
18261
18951
|
};
|
|
18262
18952
|
|
|
18953
|
+
//#endregion
|
|
18954
|
+
//#region src/Exceptions/SuspiciousOperationException.ts
|
|
18955
|
+
var SuspiciousOperationException = class extends Error {
|
|
18956
|
+
constructor(message) {
|
|
18957
|
+
super(message);
|
|
18958
|
+
this.name = "SuspiciousOperationException";
|
|
18959
|
+
}
|
|
18960
|
+
};
|
|
18961
|
+
|
|
18962
|
+
//#endregion
|
|
18963
|
+
//#region src/FormRequest.ts
|
|
18964
|
+
var FormRequest = class {
|
|
18965
|
+
dataset;
|
|
18966
|
+
constructor(data) {
|
|
18967
|
+
this.initialize(data);
|
|
18968
|
+
}
|
|
18969
|
+
/**
|
|
18970
|
+
* Initialize the data
|
|
18971
|
+
* @param data
|
|
18972
|
+
*/
|
|
18973
|
+
initialize(data) {
|
|
18974
|
+
this.dataset = {
|
|
18975
|
+
files: {},
|
|
18976
|
+
input: {}
|
|
18977
|
+
};
|
|
18978
|
+
for (const [rawKey, value] of data.entries()) {
|
|
18979
|
+
const key = rawKey.endsWith("[]") ? rawKey.slice(0, -2) : rawKey;
|
|
18980
|
+
if (value instanceof File) {
|
|
18981
|
+
const uploaded = value instanceof UploadedFile ? value : UploadedFile.createFromBase(value);
|
|
18982
|
+
if (this.dataset.files[key]) {
|
|
18983
|
+
const existing = this.dataset.files[key];
|
|
18984
|
+
if (Array.isArray(existing)) existing.push(uploaded);
|
|
18985
|
+
else this.dataset.files[key] = [existing, uploaded];
|
|
18986
|
+
} else this.dataset.files[key] = uploaded;
|
|
18987
|
+
} else if (this.dataset.input[key]) {
|
|
18988
|
+
const existing = this.dataset.input[key];
|
|
18989
|
+
if (Array.isArray(existing)) existing.push(value);
|
|
18990
|
+
else this.dataset.input[key] = [existing, value];
|
|
18991
|
+
} else this.dataset.input[key] = value;
|
|
18992
|
+
}
|
|
18993
|
+
}
|
|
18994
|
+
/**
|
|
18995
|
+
* Get all uploaded files
|
|
18996
|
+
*/
|
|
18997
|
+
files() {
|
|
18998
|
+
return this.dataset.files;
|
|
18999
|
+
}
|
|
19000
|
+
/**
|
|
19001
|
+
* Get all input fields
|
|
19002
|
+
*/
|
|
19003
|
+
input() {
|
|
19004
|
+
return this.dataset.input;
|
|
19005
|
+
}
|
|
19006
|
+
/**
|
|
19007
|
+
* Get combined input and files
|
|
19008
|
+
* File entries take precedence if names overlap.
|
|
19009
|
+
*/
|
|
19010
|
+
all() {
|
|
19011
|
+
return Object.assign({}, this.dataset.input, this.dataset.files);
|
|
19012
|
+
}
|
|
19013
|
+
};
|
|
19014
|
+
|
|
18263
19015
|
//#endregion
|
|
18264
19016
|
//#region src/Middleware.ts
|
|
18265
19017
|
var Middleware = class {};
|
|
@@ -18306,50 +19058,586 @@ var HttpServiceProvider = class {
|
|
|
18306
19058
|
|
|
18307
19059
|
//#endregion
|
|
18308
19060
|
//#region src/Request.ts
|
|
18309
|
-
var Request = class {
|
|
19061
|
+
var Request = class Request {
|
|
19062
|
+
/**
|
|
19063
|
+
* Parsed request body
|
|
19064
|
+
*/
|
|
19065
|
+
body;
|
|
19066
|
+
/**
|
|
19067
|
+
* The decoded JSON content for the request.
|
|
19068
|
+
*/
|
|
19069
|
+
#json;
|
|
19070
|
+
#uri;
|
|
19071
|
+
#method = void 0;
|
|
18310
19072
|
/**
|
|
18311
19073
|
* Gets route parameters.
|
|
18312
19074
|
* @returns An object containing route parameters.
|
|
18313
19075
|
*/
|
|
18314
19076
|
params;
|
|
18315
19077
|
/**
|
|
18316
|
-
*
|
|
18317
|
-
|
|
19078
|
+
* All of the converted files for the request.
|
|
19079
|
+
*/
|
|
19080
|
+
convertedFiles;
|
|
19081
|
+
/**
|
|
19082
|
+
* Form data from incoming request.
|
|
19083
|
+
* @returns The FormRequest object.
|
|
19084
|
+
*/
|
|
19085
|
+
formData;
|
|
19086
|
+
/**
|
|
19087
|
+
* Request body parameters (POST).
|
|
19088
|
+
*
|
|
19089
|
+
* @see getPayload() for portability between content types
|
|
19090
|
+
*/
|
|
19091
|
+
request;
|
|
19092
|
+
/**
|
|
19093
|
+
* Query string parameters (GET).
|
|
18318
19094
|
*/
|
|
18319
19095
|
query;
|
|
18320
19096
|
/**
|
|
19097
|
+
* Uploaded files (FILES).
|
|
19098
|
+
*/
|
|
19099
|
+
files;
|
|
19100
|
+
/**
|
|
19101
|
+
* Server and execution environment parameters
|
|
19102
|
+
*/
|
|
19103
|
+
server;
|
|
19104
|
+
/**
|
|
19105
|
+
* Cookies
|
|
19106
|
+
*/
|
|
19107
|
+
cookies;
|
|
19108
|
+
/**
|
|
19109
|
+
* The request attributes (parameters parsed from the PATH_INFO, ...).
|
|
19110
|
+
*/
|
|
19111
|
+
attributes;
|
|
19112
|
+
/**
|
|
18321
19113
|
* Gets the request headers.
|
|
18322
19114
|
* @returns An object containing request headers.
|
|
18323
19115
|
*/
|
|
18324
19116
|
headers;
|
|
19117
|
+
content = void 0;
|
|
19118
|
+
static formats = void 0;
|
|
19119
|
+
static httpMethodParameterOverride = false;
|
|
18325
19120
|
/**
|
|
18326
|
-
*
|
|
19121
|
+
* List of Acceptable Content Types
|
|
18327
19122
|
*/
|
|
18328
|
-
|
|
19123
|
+
acceptableContentTypes = [];
|
|
18329
19124
|
constructor(event, app) {
|
|
18330
|
-
this.app = app;
|
|
18331
19125
|
this.event = event;
|
|
18332
|
-
this.
|
|
19126
|
+
this.app = app;
|
|
19127
|
+
}
|
|
19128
|
+
/**
|
|
19129
|
+
* Factory method to create a Request instance from an H3Event.
|
|
19130
|
+
*/
|
|
19131
|
+
static async create(event, app) {
|
|
19132
|
+
const instance = new Request(event, app);
|
|
19133
|
+
await instance.setBody();
|
|
19134
|
+
await instance.initialize();
|
|
19135
|
+
globalThis.request = () => instance;
|
|
19136
|
+
return instance;
|
|
19137
|
+
}
|
|
19138
|
+
/**
|
|
19139
|
+
* Sets the parameters for this request.
|
|
19140
|
+
*
|
|
19141
|
+
* This method also re-initializes all properties.
|
|
19142
|
+
*
|
|
19143
|
+
* @param attributes
|
|
19144
|
+
* @param cookies The COOKIE parameters
|
|
19145
|
+
* @param files The FILES parameters
|
|
19146
|
+
* @param server The SERVER parameters
|
|
19147
|
+
* @param content The raw body data
|
|
19148
|
+
*/
|
|
19149
|
+
async initialize() {
|
|
18333
19150
|
this.params = getRouterParams(this.event);
|
|
18334
|
-
this.
|
|
19151
|
+
this.request = new InputBag(this.formData ? this.formData.input() : {}, this.event);
|
|
19152
|
+
this.query = new InputBag(getQuery(this.event), this.event);
|
|
19153
|
+
this.attributes = new ParamBag(getRouterParams(this.event), this.event);
|
|
19154
|
+
this.cookies = new InputBag(parseCookies(this.event), this.event);
|
|
19155
|
+
this.files = new FileBag(this.formData ? this.formData.files() : {}, this.event);
|
|
19156
|
+
this.server = new ServerBag(Object.fromEntries(this.event.req.headers.entries()), this.event);
|
|
19157
|
+
this.headers = new HeaderBag(this.server.getHeaders());
|
|
19158
|
+
this.acceptableContentTypes = [];
|
|
19159
|
+
this.#method = void 0;
|
|
19160
|
+
this.#uri = (await import(String("@h3ravel/url"))).Url.of(getRequestURL(this.event).toString(), this.app);
|
|
19161
|
+
}
|
|
19162
|
+
async setBody() {
|
|
19163
|
+
const type = this.event.req.headers.get("content-type") || "";
|
|
19164
|
+
if (this.body) return;
|
|
19165
|
+
if (type.includes("application/json")) {
|
|
19166
|
+
this.body = await this.event.req.json().catch(() => ({}));
|
|
19167
|
+
this.content = this.body;
|
|
19168
|
+
} else if (type.includes("form-data") || type.includes("x-www-form-urlencoded")) {
|
|
19169
|
+
this.formData = new FormRequest(await this.event.req.formData());
|
|
19170
|
+
this.body = this.formData.all();
|
|
19171
|
+
this.content = JSON.stringify(this.formData.input());
|
|
19172
|
+
} else if (type.startsWith("text/")) {
|
|
19173
|
+
this.body = await this.event.req.text();
|
|
19174
|
+
this.content = this.body;
|
|
19175
|
+
} else {
|
|
19176
|
+
const content = this.event.req.body;
|
|
19177
|
+
this.content = content;
|
|
19178
|
+
if (content instanceof ReadableStream) {
|
|
19179
|
+
const reader = content.getReader();
|
|
19180
|
+
const chunks = [];
|
|
19181
|
+
let done = false;
|
|
19182
|
+
while (!done) {
|
|
19183
|
+
const { value, done: isDone } = await reader.read();
|
|
19184
|
+
if (value) chunks.push(value);
|
|
19185
|
+
done = isDone;
|
|
19186
|
+
}
|
|
19187
|
+
this.body = new TextDecoder().decode(new Uint8Array(chunks.flatMap((chunk) => Array.from(chunk))));
|
|
19188
|
+
} else this.body = content;
|
|
19189
|
+
}
|
|
19190
|
+
}
|
|
19191
|
+
/**
|
|
19192
|
+
* Retrieve all data from the instance (query + body).
|
|
19193
|
+
*/
|
|
19194
|
+
all(keys) {
|
|
19195
|
+
const input = Obj.deepMerge({}, this.input(), this.allFiles());
|
|
19196
|
+
if (!keys) return input;
|
|
19197
|
+
const results = {};
|
|
19198
|
+
const list = Array.isArray(keys) ? keys : [keys];
|
|
19199
|
+
for (const key of list) data_set(results, key, Obj.get(input, key));
|
|
19200
|
+
return results;
|
|
18335
19201
|
}
|
|
18336
19202
|
/**
|
|
18337
|
-
*
|
|
19203
|
+
* Retrieve an input item from the request.
|
|
19204
|
+
*
|
|
19205
|
+
* @param key
|
|
19206
|
+
* @param defaultValue
|
|
19207
|
+
* @returns
|
|
18338
19208
|
*/
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
...
|
|
18342
|
-
...
|
|
19209
|
+
input(key, defaultValue) {
|
|
19210
|
+
const source = {
|
|
19211
|
+
...this.getInputSource().all(),
|
|
19212
|
+
...this.query.all()
|
|
18343
19213
|
};
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
19214
|
+
return key ? data_get(source, key, defaultValue) : Arr.except(source, ["_method"]);
|
|
19215
|
+
}
|
|
19216
|
+
/**
|
|
19217
|
+
* Retrieve a file from the request.
|
|
19218
|
+
*
|
|
19219
|
+
* @param key
|
|
19220
|
+
* @param defaultValue
|
|
19221
|
+
* @returns
|
|
19222
|
+
*/
|
|
19223
|
+
file(key, defaultValue) {
|
|
19224
|
+
const source = this.allFiles();
|
|
19225
|
+
return key ? data_get(source, key, defaultValue) : source;
|
|
19226
|
+
}
|
|
19227
|
+
/**
|
|
19228
|
+
* Get an array of all of the files on the request.
|
|
19229
|
+
*/
|
|
19230
|
+
allFiles() {
|
|
19231
|
+
if (this.convertedFiles) return this.convertedFiles;
|
|
19232
|
+
const entries = Object.entries(this.files.all()).filter((e) => e[1] != null);
|
|
19233
|
+
const files = Object.fromEntries(entries);
|
|
19234
|
+
this.convertedFiles = this.convertUploadedFiles(files);
|
|
19235
|
+
return this.convertedFiles;
|
|
19236
|
+
}
|
|
19237
|
+
/**
|
|
19238
|
+
* Extract and convert uploaded files from FormData.
|
|
19239
|
+
*/
|
|
19240
|
+
convertUploadedFiles(files) {
|
|
19241
|
+
if (!this.formData) return files;
|
|
19242
|
+
for (const [key, value] of Object.entries(this.formData.files())) {
|
|
19243
|
+
if (!(value instanceof File)) continue;
|
|
19244
|
+
if (key.endsWith("[]")) {
|
|
19245
|
+
const normalizedKey = key.slice(0, -2);
|
|
19246
|
+
if (!files[normalizedKey]) files[normalizedKey] = [];
|
|
19247
|
+
files[normalizedKey].push(UploadedFile.createFromBase(value));
|
|
19248
|
+
} else files[key] = UploadedFile.createFromBase(value);
|
|
19249
|
+
}
|
|
19250
|
+
return files;
|
|
19251
|
+
}
|
|
19252
|
+
/**
|
|
19253
|
+
* Determine if the data contains a given key.
|
|
19254
|
+
*
|
|
19255
|
+
* @param keys
|
|
19256
|
+
* @returns
|
|
19257
|
+
*/
|
|
19258
|
+
has(keys) {
|
|
19259
|
+
return Obj.has(this.all(), keys);
|
|
19260
|
+
}
|
|
19261
|
+
/**
|
|
19262
|
+
* Determine if the instance is missing a given key.
|
|
19263
|
+
*/
|
|
19264
|
+
missing(key) {
|
|
19265
|
+
const keys = Array.isArray(key) ? key : [key];
|
|
19266
|
+
return !this.has(keys);
|
|
19267
|
+
}
|
|
19268
|
+
/**
|
|
19269
|
+
* Get a subset containing the provided keys with values from the instance data.
|
|
19270
|
+
*
|
|
19271
|
+
* @param keys
|
|
19272
|
+
* @returns
|
|
19273
|
+
*/
|
|
19274
|
+
only(keys) {
|
|
19275
|
+
const data = Object.entries(this.all()).filter(([key]) => keys.includes(key));
|
|
19276
|
+
return Object.fromEntries(data);
|
|
19277
|
+
}
|
|
19278
|
+
/**
|
|
19279
|
+
* Get all of the data except for a specified array of items.
|
|
19280
|
+
*
|
|
19281
|
+
* @param keys
|
|
19282
|
+
* @returns
|
|
19283
|
+
*/
|
|
19284
|
+
except(keys) {
|
|
19285
|
+
const data = Object.entries(this.all()).filter(([key]) => !keys.includes(key));
|
|
19286
|
+
return Object.fromEntries(data);
|
|
18347
19287
|
}
|
|
18348
19288
|
/**
|
|
18349
|
-
*
|
|
19289
|
+
* Merges new input data into the current request's input source.
|
|
19290
|
+
*
|
|
19291
|
+
* @param input - An object containing key-value pairs to merge.
|
|
19292
|
+
* @returns this - For fluent chaining.
|
|
18350
19293
|
*/
|
|
18351
|
-
|
|
18352
|
-
|
|
19294
|
+
merge(input) {
|
|
19295
|
+
const source = this.getInputSource();
|
|
19296
|
+
for (const [key, value] of Object.entries(input)) source.set(key, value);
|
|
19297
|
+
return this;
|
|
19298
|
+
}
|
|
19299
|
+
/**
|
|
19300
|
+
* Merge new input into the request's input, but only when that key is missing from the request.
|
|
19301
|
+
*
|
|
19302
|
+
* @param input
|
|
19303
|
+
*/
|
|
19304
|
+
mergeIfMissing(input) {
|
|
19305
|
+
return this.merge(Object.fromEntries(Object.entries(input).filter(([key]) => this.missing(key))));
|
|
19306
|
+
}
|
|
19307
|
+
/**
|
|
19308
|
+
* Get the keys for all of the input and files.
|
|
19309
|
+
*/
|
|
19310
|
+
keys() {
|
|
19311
|
+
return [...Object.keys(this.input()), ...this.files.keys()];
|
|
19312
|
+
}
|
|
19313
|
+
/**
|
|
19314
|
+
* Determine if the request is sending JSON.
|
|
19315
|
+
*
|
|
19316
|
+
* @return bool
|
|
19317
|
+
*/
|
|
19318
|
+
isJson() {
|
|
19319
|
+
return Str.contains(this.getHeader("CONTENT_TYPE") ?? "", ["/json", "+json"]);
|
|
19320
|
+
}
|
|
19321
|
+
/**
|
|
19322
|
+
* Determine if the current request probably expects a JSON response.
|
|
19323
|
+
*
|
|
19324
|
+
* @returns
|
|
19325
|
+
*/
|
|
19326
|
+
expectsJson() {
|
|
19327
|
+
return Str.contains(this.getHeader("Accept") ?? "", "application/json");
|
|
19328
|
+
}
|
|
19329
|
+
/**
|
|
19330
|
+
* Determine if the current request is asking for JSON.
|
|
19331
|
+
*
|
|
19332
|
+
* @returns
|
|
19333
|
+
*/
|
|
19334
|
+
wantsJson() {
|
|
19335
|
+
const acceptable = this.getAcceptableContentTypes();
|
|
19336
|
+
return !!acceptable[0] && Str.contains(acceptable[0].toLowerCase(), ["/json", "+json"]);
|
|
19337
|
+
}
|
|
19338
|
+
/**
|
|
19339
|
+
* Gets a list of content types acceptable by the client browser in preferable order.
|
|
19340
|
+
* @returns {string[]}
|
|
19341
|
+
*/
|
|
19342
|
+
getAcceptableContentTypes() {
|
|
19343
|
+
if (this.acceptableContentTypes.length > 0) return this.acceptableContentTypes;
|
|
19344
|
+
const accept = this.getHeader("accept");
|
|
19345
|
+
if (!accept) return [];
|
|
19346
|
+
return this.acceptableContentTypes = accept.split(",").map((type) => type.trim()).map((type) => type.split(";")[0]).filter(Boolean);
|
|
19347
|
+
}
|
|
19348
|
+
/**
|
|
19349
|
+
* Determine if the request is the result of a PJAX call.
|
|
19350
|
+
*
|
|
19351
|
+
* @return bool
|
|
19352
|
+
*/
|
|
19353
|
+
pjax() {
|
|
19354
|
+
return this.headers.get("X-PJAX") == true;
|
|
19355
|
+
}
|
|
19356
|
+
/**
|
|
19357
|
+
* Returns true if the request is an XMLHttpRequest (AJAX).
|
|
19358
|
+
*
|
|
19359
|
+
* @alias isXmlHttpRequest()
|
|
19360
|
+
* @returns {boolean}
|
|
19361
|
+
*/
|
|
19362
|
+
ajax() {
|
|
19363
|
+
return this.isXmlHttpRequest();
|
|
19364
|
+
}
|
|
19365
|
+
/**
|
|
19366
|
+
* Returns true if the request is an XMLHttpRequest (AJAX).
|
|
19367
|
+
*/
|
|
19368
|
+
isXmlHttpRequest() {
|
|
19369
|
+
return "XMLHttpRequest" === this.getHeader("X-Requested-With");
|
|
19370
|
+
}
|
|
19371
|
+
/**
|
|
19372
|
+
* Returns the value of the requested header.
|
|
19373
|
+
*/
|
|
19374
|
+
getHeader(name) {
|
|
19375
|
+
return this.headers.get(name);
|
|
19376
|
+
}
|
|
19377
|
+
/**
|
|
19378
|
+
* Checks if the request method is of specified type.
|
|
19379
|
+
*
|
|
19380
|
+
* @param method Uppercase request method (GET, POST etc)
|
|
19381
|
+
*/
|
|
19382
|
+
isMethod(method) {
|
|
19383
|
+
return this.getMethod() === method.toUpperCase();
|
|
19384
|
+
}
|
|
19385
|
+
/**
|
|
19386
|
+
* Checks whether or not the method is safe.
|
|
19387
|
+
*
|
|
19388
|
+
* @see https://tools.ietf.org/html/rfc7231#section-4.2.1
|
|
19389
|
+
*/
|
|
19390
|
+
isMethodSafe() {
|
|
19391
|
+
return [
|
|
19392
|
+
"GET",
|
|
19393
|
+
"HEAD",
|
|
19394
|
+
"OPTIONS",
|
|
19395
|
+
"TRACE"
|
|
19396
|
+
].includes(this.getMethod());
|
|
19397
|
+
}
|
|
19398
|
+
/**
|
|
19399
|
+
* Checks whether or not the method is idempotent.
|
|
19400
|
+
*/
|
|
19401
|
+
isMethodIdempotent() {
|
|
19402
|
+
return [
|
|
19403
|
+
"HEAD",
|
|
19404
|
+
"GET",
|
|
19405
|
+
"PUT",
|
|
19406
|
+
"DELETE",
|
|
19407
|
+
"TRACE",
|
|
19408
|
+
"OPTIONS",
|
|
19409
|
+
"PURGE"
|
|
19410
|
+
].includes(this.getMethod());
|
|
19411
|
+
}
|
|
19412
|
+
/**
|
|
19413
|
+
* Checks whether the method is cacheable or not.
|
|
19414
|
+
*
|
|
19415
|
+
* @see https://tools.ietf.org/html/rfc7231#section-4.2.3
|
|
19416
|
+
*/
|
|
19417
|
+
isMethodCacheable() {
|
|
19418
|
+
return ["GET", "HEAD"].includes(this.getMethod());
|
|
19419
|
+
}
|
|
19420
|
+
/**
|
|
19421
|
+
* Initializes HTTP request formats.
|
|
19422
|
+
*/
|
|
19423
|
+
static initializeFormats() {
|
|
19424
|
+
this.formats = {
|
|
19425
|
+
html: ["text/html", "application/xhtml+xml"],
|
|
19426
|
+
txt: ["text/plain"],
|
|
19427
|
+
js: [
|
|
19428
|
+
"application/javascript",
|
|
19429
|
+
"application/x-javascript",
|
|
19430
|
+
"text/javascript"
|
|
19431
|
+
],
|
|
19432
|
+
css: ["text/css"],
|
|
19433
|
+
json: ["application/json", "application/x-json"],
|
|
19434
|
+
jsonld: ["application/ld+json"],
|
|
19435
|
+
xml: [
|
|
19436
|
+
"text/xml",
|
|
19437
|
+
"application/xml",
|
|
19438
|
+
"application/x-xml"
|
|
19439
|
+
],
|
|
19440
|
+
rdf: ["application/rdf+xml"],
|
|
19441
|
+
atom: ["application/atom+xml"],
|
|
19442
|
+
rss: ["application/rss+xml"],
|
|
19443
|
+
form: ["application/x-www-form-urlencoded", "multipart/form-data"]
|
|
19444
|
+
};
|
|
19445
|
+
}
|
|
19446
|
+
/**
|
|
19447
|
+
* Gets the request "intended" method.
|
|
19448
|
+
*
|
|
19449
|
+
* If the X-HTTP-Method-Override header is set, and if the method is a POST,
|
|
19450
|
+
* then it is used to determine the "real" intended HTTP method.
|
|
19451
|
+
*
|
|
19452
|
+
* The _method request parameter can also be used to determine the HTTP method,
|
|
19453
|
+
* but only if enableHttpMethodParameterOverride() has been called.
|
|
19454
|
+
*
|
|
19455
|
+
* The method is always an uppercased string.
|
|
19456
|
+
*
|
|
19457
|
+
* @see getRealMethod()
|
|
19458
|
+
*/
|
|
19459
|
+
getMethod() {
|
|
19460
|
+
if (this.#method) return this.#method;
|
|
19461
|
+
this.#method = this.getRealMethod();
|
|
19462
|
+
if ("POST" !== this.#method) return this.#method;
|
|
19463
|
+
let method = this.event.req.headers.get("X-HTTP-METHOD-OVERRIDE");
|
|
19464
|
+
if (!method && Request.httpMethodParameterOverride) method = this.request.get("_method", this.query.get("_method", "POST"));
|
|
19465
|
+
if (typeof method !== "string") return this.#method;
|
|
19466
|
+
method = method.toUpperCase();
|
|
19467
|
+
if ([
|
|
19468
|
+
"GET",
|
|
19469
|
+
"HEAD",
|
|
19470
|
+
"POST",
|
|
19471
|
+
"PUT",
|
|
19472
|
+
"DELETE",
|
|
19473
|
+
"CONNECT",
|
|
19474
|
+
"OPTIONS",
|
|
19475
|
+
"PATCH",
|
|
19476
|
+
"PURGE",
|
|
19477
|
+
"TRACE"
|
|
19478
|
+
].includes(method)) {
|
|
19479
|
+
this.#method = method;
|
|
19480
|
+
return this.#method;
|
|
19481
|
+
}
|
|
19482
|
+
if (!/^[A-Z]+$/.test(method)) throw new SuspiciousOperationException("Invalid HTTP method override.");
|
|
19483
|
+
this.#method = method;
|
|
19484
|
+
return this.#method;
|
|
19485
|
+
}
|
|
19486
|
+
/**
|
|
19487
|
+
* Gets the "real" request method.
|
|
19488
|
+
*
|
|
19489
|
+
* @see getMethod()
|
|
19490
|
+
*/
|
|
19491
|
+
getRealMethod() {
|
|
19492
|
+
return this.event.req.method.toUpperCase();
|
|
19493
|
+
}
|
|
19494
|
+
/**
|
|
19495
|
+
* Get the client IP address.
|
|
19496
|
+
*/
|
|
19497
|
+
ip() {
|
|
19498
|
+
return getRequestIP(this.event);
|
|
19499
|
+
}
|
|
19500
|
+
/**
|
|
19501
|
+
* Get a URI instance for the request.
|
|
19502
|
+
*/
|
|
19503
|
+
uri() {
|
|
19504
|
+
return this.#uri;
|
|
19505
|
+
}
|
|
19506
|
+
/**
|
|
19507
|
+
* Get the full URL for the request.
|
|
19508
|
+
*/
|
|
19509
|
+
fullUrl() {
|
|
19510
|
+
return this.event.req.url;
|
|
19511
|
+
}
|
|
19512
|
+
/**
|
|
19513
|
+
* Return the Request instance.
|
|
19514
|
+
*/
|
|
19515
|
+
instance() {
|
|
19516
|
+
return this;
|
|
19517
|
+
}
|
|
19518
|
+
/**
|
|
19519
|
+
* Get the request method.
|
|
19520
|
+
*/
|
|
19521
|
+
method() {
|
|
19522
|
+
return this.getMethod();
|
|
19523
|
+
}
|
|
19524
|
+
/**
|
|
19525
|
+
* Get the JSON payload for the request.
|
|
19526
|
+
*
|
|
19527
|
+
* @param key
|
|
19528
|
+
* @param defaultValue
|
|
19529
|
+
* @return {InputBag}
|
|
19530
|
+
*/
|
|
19531
|
+
json(key, defaultValue) {
|
|
19532
|
+
if (!this.#json) {
|
|
19533
|
+
let json = this.getContent();
|
|
19534
|
+
if (typeof json == "string") json = JSON.parse(json || "{}");
|
|
19535
|
+
this.#json = new InputBag(json, this.event);
|
|
19536
|
+
}
|
|
19537
|
+
if (!key) return this.#json;
|
|
19538
|
+
return Obj.get(this.#json.all(), key, defaultValue);
|
|
19539
|
+
}
|
|
19540
|
+
/**
|
|
19541
|
+
* Get the input source for the request.
|
|
19542
|
+
*
|
|
19543
|
+
* @return {InputBag}
|
|
19544
|
+
*/
|
|
19545
|
+
getInputSource() {
|
|
19546
|
+
if (this.isJson()) return this.json();
|
|
19547
|
+
return ["GET", "HEAD"].includes(this.getRealMethod()) ? this.query : this.request;
|
|
19548
|
+
}
|
|
19549
|
+
/**
|
|
19550
|
+
* Returns the request body content.
|
|
19551
|
+
*
|
|
19552
|
+
* @param asStream If true, returns a ReadableStream instead of the parsed string
|
|
19553
|
+
* @return {string | ReadableStream | Promise<string | ReadableStream>}
|
|
19554
|
+
*/
|
|
19555
|
+
getContent(asStream = false) {
|
|
19556
|
+
let content = this.body;
|
|
19557
|
+
if (content !== void 0 && content !== null) {
|
|
19558
|
+
if (asStream) {
|
|
19559
|
+
if (content instanceof ReadableStream) return content;
|
|
19560
|
+
const encoder = new TextEncoder();
|
|
19561
|
+
return new ReadableStream({ start(controller) {
|
|
19562
|
+
controller.enqueue(encoder.encode(String(content)));
|
|
19563
|
+
controller.close();
|
|
19564
|
+
} });
|
|
19565
|
+
}
|
|
19566
|
+
if (typeof content === "string") return content;
|
|
19567
|
+
}
|
|
19568
|
+
if (asStream) return this.content;
|
|
19569
|
+
content = this.content;
|
|
19570
|
+
this.body = content;
|
|
19571
|
+
return content;
|
|
19572
|
+
}
|
|
19573
|
+
/**
|
|
19574
|
+
* Determine if the uploaded data contains a file.
|
|
19575
|
+
*
|
|
19576
|
+
* @param key
|
|
19577
|
+
* @return boolean
|
|
19578
|
+
*/
|
|
19579
|
+
hasFile(key) {
|
|
19580
|
+
let files = this.file(key);
|
|
19581
|
+
if (!Array.isArray(files)) files = [files];
|
|
19582
|
+
return files.some((e) => this.isValidFile(e));
|
|
19583
|
+
}
|
|
19584
|
+
/**
|
|
19585
|
+
* Check that the given file is a valid file instance.
|
|
19586
|
+
*
|
|
19587
|
+
* @param file
|
|
19588
|
+
* @return boolean
|
|
19589
|
+
*/
|
|
19590
|
+
isValidFile(file) {
|
|
19591
|
+
return file.content instanceof File && file.size > 0;
|
|
19592
|
+
}
|
|
19593
|
+
/**
|
|
19594
|
+
* Gets a "parameter" value from any bag.
|
|
19595
|
+
*
|
|
19596
|
+
* This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
|
|
19597
|
+
* flexibility in controllers, it is better to explicitly get request parameters from the appropriate
|
|
19598
|
+
* public property instead (attributes, query, request).
|
|
19599
|
+
*
|
|
19600
|
+
* Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
|
|
19601
|
+
*
|
|
19602
|
+
* @internal use explicit input sources instead
|
|
19603
|
+
*/
|
|
19604
|
+
get(key, defaultValue) {
|
|
19605
|
+
const result = this.attributes.get(key, this);
|
|
19606
|
+
if (this !== result) return result;
|
|
19607
|
+
if (this.query.has(key)) return this.query.all()[key];
|
|
19608
|
+
if (this.request.has(key)) return this.request.all()[key];
|
|
19609
|
+
return defaultValue;
|
|
19610
|
+
}
|
|
19611
|
+
/**
|
|
19612
|
+
* Enables support for the _method request parameter to determine the intended HTTP method.
|
|
19613
|
+
*
|
|
19614
|
+
* Be warned that enabling this feature might lead to CSRF issues in your code.
|
|
19615
|
+
* Check that you are using CSRF tokens when required.
|
|
19616
|
+
* If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
|
|
19617
|
+
* and used to send a "PUT" or "DELETE" request via the _method request parameter.
|
|
19618
|
+
* If these methods are not protected against CSRF, this presents a possible vulnerability.
|
|
19619
|
+
*
|
|
19620
|
+
* The HTTP method can only be overridden when the real HTTP method is POST.
|
|
19621
|
+
*/
|
|
19622
|
+
static enableHttpMethodParameterOverride() {
|
|
19623
|
+
this.httpMethodParameterOverride = true;
|
|
19624
|
+
}
|
|
19625
|
+
/**
|
|
19626
|
+
* Checks whether support for the _method request parameter is enabled.
|
|
19627
|
+
*/
|
|
19628
|
+
static getHttpMethodParameterOverride() {
|
|
19629
|
+
return this.httpMethodParameterOverride;
|
|
19630
|
+
}
|
|
19631
|
+
/**
|
|
19632
|
+
* Dump the items.
|
|
19633
|
+
*
|
|
19634
|
+
* @param keys
|
|
19635
|
+
* @return this
|
|
19636
|
+
*/
|
|
19637
|
+
dump(...keys) {
|
|
19638
|
+
if (keys.length > 0) this.only(keys).then(dump);
|
|
19639
|
+
else this.all().then(dump);
|
|
19640
|
+
return this;
|
|
18353
19641
|
}
|
|
18354
19642
|
getEvent(key) {
|
|
18355
19643
|
return safeDot(this.event, key);
|
|
@@ -18534,7 +19822,7 @@ var Response = class {
|
|
|
18534
19822
|
}
|
|
18535
19823
|
html(content) {
|
|
18536
19824
|
this.applyHeaders();
|
|
18537
|
-
return html(
|
|
19825
|
+
return html(content);
|
|
18538
19826
|
}
|
|
18539
19827
|
/**
|
|
18540
19828
|
* Send a JSON response.
|
|
@@ -18555,9 +19843,9 @@ var Response = class {
|
|
|
18555
19843
|
/**
|
|
18556
19844
|
* Redirect to another URL.
|
|
18557
19845
|
*/
|
|
18558
|
-
redirect(
|
|
19846
|
+
redirect(location, status = 302, statusText) {
|
|
18559
19847
|
this.setStatusCode(status);
|
|
18560
|
-
return redirect(
|
|
19848
|
+
return redirect(location, this.statusCode, statusText);
|
|
18561
19849
|
}
|
|
18562
19850
|
/**
|
|
18563
19851
|
* Apply headers before sending response.
|
|
@@ -18573,4 +19861,4 @@ var Response = class {
|
|
|
18573
19861
|
};
|
|
18574
19862
|
|
|
18575
19863
|
//#endregion
|
|
18576
|
-
export { ApiResource, FireCommand, HttpContext, HttpServiceProvider, JsonResource, LogRequests, Middleware, Request, Response };
|
|
19864
|
+
export { ApiResource, BadRequestException, FileBag, FireCommand, FormRequest, HeaderBag, HttpContext, HttpServiceProvider, InputBag, JsonResource, LogRequests, Middleware, ParamBag, Request, Response, ServerBag, SuspiciousOperationException, UnexpectedValueException, UploadedFile };
|