lieko-express 0.0.7 → 0.0.9
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/lib/cors.js +100 -0
- package/lib/schema.js +418 -0
- package/lib/static.js +247 -0
- package/lieko-express.js +213 -804
- package/package.json +4 -3
package/lieko-express.js
CHANGED
|
@@ -3,410 +3,15 @@ const net = require("net");
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class Schema {
|
|
17
|
-
constructor(rules) {
|
|
18
|
-
this.rules = rules;
|
|
19
|
-
this.fields = rules;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
validate(data) {
|
|
23
|
-
const errors = [];
|
|
24
|
-
for (const [field, validators] of Object.entries(this.rules)) {
|
|
25
|
-
const value = data[field];
|
|
26
|
-
for (const validator of validators) {
|
|
27
|
-
const error = validator(value, field, data);
|
|
28
|
-
if (error) {
|
|
29
|
-
errors.push(error);
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (errors.length > 0) throw new ValidationError(errors);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const validators = {
|
|
40
|
-
required: (message = 'Field is required') => {
|
|
41
|
-
return (value, field) => {
|
|
42
|
-
if (value === undefined || value === null || value === '') {
|
|
43
|
-
return { field, message, type: 'required' };
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
requiredTrue: (message = 'Field must be true') => {
|
|
50
|
-
return (value, field) => {
|
|
51
|
-
const normalized = value === true || value === 'true' || value === '1' || value === 1;
|
|
52
|
-
if (!normalized) {
|
|
53
|
-
return { field, message, type: 'requiredTrue' };
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
optional: () => {
|
|
60
|
-
return () => null;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
string: (message = 'Field must be a string') => {
|
|
64
|
-
return (value, field) => {
|
|
65
|
-
if (value !== undefined && typeof value !== 'string') {
|
|
66
|
-
return { field, message, type: 'string' };
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
number: (message = 'Field must be a number') => {
|
|
73
|
-
return (value, field) => {
|
|
74
|
-
if (value !== undefined && typeof value !== 'number') {
|
|
75
|
-
return { field, message, type: 'number' };
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
boolean: (message = 'Field must be a boolean') => {
|
|
82
|
-
return (value, field) => {
|
|
83
|
-
if (value === undefined || value === null || value === '') return null;
|
|
84
|
-
|
|
85
|
-
const validTrue = ['true', true, 1, '1'];
|
|
86
|
-
const validFalse = ['false', false, 0, '0'];
|
|
87
|
-
|
|
88
|
-
const isValid = validTrue.includes(value) || validFalse.includes(value);
|
|
89
|
-
|
|
90
|
-
if (!isValid) {
|
|
91
|
-
return { field, message, type: 'boolean' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return null;
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
integer: (message = 'Field must be an integer') => {
|
|
99
|
-
return (value, field) => {
|
|
100
|
-
if (value !== undefined && !Number.isInteger(value)) {
|
|
101
|
-
return { field, message, type: 'integer' };
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
positive: (message = 'Field must be positive') => {
|
|
108
|
-
return (value, field) => {
|
|
109
|
-
if (value !== undefined && value <= 0) {
|
|
110
|
-
return { field, message, type: 'positive' };
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
negative: (message = 'Field must be negative') => {
|
|
117
|
-
return (value, field) => {
|
|
118
|
-
if (value !== undefined && value >= 0) {
|
|
119
|
-
return { field, message, type: 'negative' };
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
email: (message = 'Invalid email format') => {
|
|
126
|
-
return (value, field) => {
|
|
127
|
-
if (value !== undefined && value !== null) {
|
|
128
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
129
|
-
if (!emailRegex.test(value)) {
|
|
130
|
-
return { field, message, type: 'email' };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
};
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
min: (minValue, message) => {
|
|
138
|
-
return (value, field) => {
|
|
139
|
-
if (value !== undefined && value !== null) {
|
|
140
|
-
if (typeof value === 'string' && value.length < minValue) {
|
|
141
|
-
return {
|
|
142
|
-
field,
|
|
143
|
-
message: message || `Field must be at least ${minValue} characters`,
|
|
144
|
-
type: 'min'
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
if (typeof value === 'number' && value < minValue) {
|
|
148
|
-
return {
|
|
149
|
-
field,
|
|
150
|
-
message: message || `Field must be at least ${minValue}`,
|
|
151
|
-
type: 'min'
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
max: (maxValue, message) => {
|
|
160
|
-
return (value, field) => {
|
|
161
|
-
if (value !== undefined && value !== null) {
|
|
162
|
-
if (typeof value === 'string' && value.length > maxValue) {
|
|
163
|
-
return {
|
|
164
|
-
field,
|
|
165
|
-
message: message || `Field must be at most ${maxValue} characters`,
|
|
166
|
-
type: 'max'
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (typeof value === 'number' && value > maxValue) {
|
|
170
|
-
return {
|
|
171
|
-
field,
|
|
172
|
-
message: message || `Field must be at most ${maxValue}`,
|
|
173
|
-
type: 'max'
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
length: (n, message) => {
|
|
182
|
-
return (value, field) => {
|
|
183
|
-
if (typeof value === 'string' && value.length !== n) {
|
|
184
|
-
return {
|
|
185
|
-
field,
|
|
186
|
-
message: message || `Field must be exactly ${n} characters`,
|
|
187
|
-
type: 'length'
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
};
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
minLength: (minLength, message) => {
|
|
195
|
-
return (value, field) => {
|
|
196
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
197
|
-
if (value.length < minLength) {
|
|
198
|
-
return {
|
|
199
|
-
field,
|
|
200
|
-
message: message || `Field must be at least ${minLength} characters`,
|
|
201
|
-
type: 'minLength'
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
};
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
maxLength: (maxLength, message) => {
|
|
210
|
-
return (value, field) => {
|
|
211
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
212
|
-
if (value.length > maxLength) {
|
|
213
|
-
return {
|
|
214
|
-
field,
|
|
215
|
-
message: message || `Field must be at most ${maxLength} characters`,
|
|
216
|
-
type: 'maxLength'
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
};
|
|
222
|
-
},
|
|
6
|
+
const {
|
|
7
|
+
Schema,
|
|
8
|
+
ValidationError,
|
|
9
|
+
validators,
|
|
10
|
+
validate,
|
|
11
|
+
validatePartial
|
|
12
|
+
} = require('./lib/schema');
|
|
223
13
|
|
|
224
|
-
|
|
225
|
-
return (value, field) => {
|
|
226
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
227
|
-
if (!regex.test(value)) {
|
|
228
|
-
return { field, message, type: 'pattern' };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
oneOf: (allowedValues, message) => {
|
|
236
|
-
return (value, field) => {
|
|
237
|
-
if (value !== undefined && value !== null) {
|
|
238
|
-
if (!allowedValues.includes(value)) {
|
|
239
|
-
return {
|
|
240
|
-
field,
|
|
241
|
-
message: message || `Field must be one of: ${allowedValues.join(', ')}`,
|
|
242
|
-
type: 'oneOf'
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return null;
|
|
247
|
-
};
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
notOneOf: (values, message) => {
|
|
251
|
-
return (value, field) => {
|
|
252
|
-
if (values.includes(value)) {
|
|
253
|
-
return {
|
|
254
|
-
field,
|
|
255
|
-
message: message || `Field cannot be one of: ${values.join(', ')}`,
|
|
256
|
-
type: 'notOneOf'
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
return null;
|
|
260
|
-
};
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
custom: (validatorFn, message = 'Validation failed') => {
|
|
264
|
-
return (value, field, data) => {
|
|
265
|
-
const isValid = validatorFn(value, data);
|
|
266
|
-
if (!isValid) {
|
|
267
|
-
return { field, message, type: 'custom' };
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
};
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
equal: (expectedValue, message) => {
|
|
274
|
-
return (value, field) => {
|
|
275
|
-
if (value !== expectedValue) {
|
|
276
|
-
return {
|
|
277
|
-
field,
|
|
278
|
-
message: message || `Field must be equal to ${expectedValue}`,
|
|
279
|
-
type: 'equal'
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
return null;
|
|
283
|
-
};
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
mustBeTrue: (message = 'This field must be accepted') => {
|
|
287
|
-
return (value, field) => {
|
|
288
|
-
const normalized = value === true || value === 'true' || value === '1' || value === 1;
|
|
289
|
-
if (!normalized) {
|
|
290
|
-
return { field, message, type: 'mustBeTrue' };
|
|
291
|
-
}
|
|
292
|
-
return null;
|
|
293
|
-
};
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
mustBeFalse: (message = 'This field must be declined') => {
|
|
297
|
-
return (value, field) => {
|
|
298
|
-
const normalized = value === false || value === 'false' || value === '0' || value === 0;
|
|
299
|
-
if (!normalized) {
|
|
300
|
-
return { field, message, type: 'mustBeFalse' };
|
|
301
|
-
}
|
|
302
|
-
return null;
|
|
303
|
-
};
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
date: (message = 'Invalid date') => {
|
|
307
|
-
return (value, field) => {
|
|
308
|
-
if (!value) return null;
|
|
309
|
-
const date = new Date(value);
|
|
310
|
-
if (isNaN(date.getTime())) {
|
|
311
|
-
return { field, message, type: 'date' };
|
|
312
|
-
}
|
|
313
|
-
return null;
|
|
314
|
-
};
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
before: (limit, message) => {
|
|
318
|
-
return (value, field) => {
|
|
319
|
-
if (!value) return null;
|
|
320
|
-
const d1 = new Date(value);
|
|
321
|
-
const d2 = new Date(limit);
|
|
322
|
-
if (isNaN(d1) || d1 >= d2) {
|
|
323
|
-
return {
|
|
324
|
-
field,
|
|
325
|
-
message: message || `Date must be before ${limit}`,
|
|
326
|
-
type: 'before'
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
};
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
after: (limit, message) => {
|
|
334
|
-
return (value, field) => {
|
|
335
|
-
if (!value) return null;
|
|
336
|
-
const d1 = new Date(value);
|
|
337
|
-
const d2 = new Date(limit);
|
|
338
|
-
if (isNaN(d1) || d1 <= d2) {
|
|
339
|
-
return {
|
|
340
|
-
field,
|
|
341
|
-
message: message || `Date must be after ${limit}`,
|
|
342
|
-
type: 'after'
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
return null;
|
|
346
|
-
};
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
startsWith: (prefix, message) => {
|
|
350
|
-
return (value, field) => {
|
|
351
|
-
if (typeof value === 'string' && !value.startsWith(prefix)) {
|
|
352
|
-
return {
|
|
353
|
-
field,
|
|
354
|
-
message: message || `Field must start with "${prefix}"`,
|
|
355
|
-
type: 'startsWith'
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
return null;
|
|
359
|
-
};
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
endsWith: (suffix, message) => {
|
|
363
|
-
return (value, field) => {
|
|
364
|
-
if (typeof value === 'string' && !value.endsWith(suffix)) {
|
|
365
|
-
return {
|
|
366
|
-
field,
|
|
367
|
-
message: message || `Field must end with "${suffix}"`,
|
|
368
|
-
type: 'endsWith'
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
function validate(schema) {
|
|
377
|
-
return (req, res, next) => {
|
|
378
|
-
try {
|
|
379
|
-
schema.validate(req.body);
|
|
380
|
-
next();
|
|
381
|
-
} catch (error) {
|
|
382
|
-
if (error instanceof ValidationError) {
|
|
383
|
-
return res.status(400).json({
|
|
384
|
-
success: false,
|
|
385
|
-
message: 'Validation failed',
|
|
386
|
-
errors: error.errors
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
throw error;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function validatePartial(schema) {
|
|
395
|
-
const partial = {};
|
|
396
|
-
|
|
397
|
-
for (const field in schema.fields) {
|
|
398
|
-
const rules = schema.fields[field];
|
|
399
|
-
|
|
400
|
-
const filtered = rules.filter(v =>
|
|
401
|
-
v.name !== 'required' &&
|
|
402
|
-
v.name !== 'requiredTrue' &&
|
|
403
|
-
v.name !== 'mustBeTrue'
|
|
404
|
-
);
|
|
405
|
-
partial[field] = [validators.optional(), ...filtered];
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return new Schema(partial);
|
|
409
|
-
}
|
|
14
|
+
process.env.UV_THREADPOOL_SIZE = require('os').availableParallelism();
|
|
410
15
|
|
|
411
16
|
class LiekoExpress {
|
|
412
17
|
constructor() {
|
|
@@ -424,10 +29,11 @@ class LiekoExpress {
|
|
|
424
29
|
strictTrailingSlash: true,
|
|
425
30
|
allowTrailingSlash: true,
|
|
426
31
|
views: path.join(process.cwd(), "views"),
|
|
427
|
-
"view engine":
|
|
32
|
+
"view engine": "html"
|
|
428
33
|
};
|
|
429
34
|
|
|
430
35
|
this.engines = {};
|
|
36
|
+
this.engines['.html'] = this._defaultHtmlEngine.bind(this);
|
|
431
37
|
|
|
432
38
|
this.bodyParserOptions = {
|
|
433
39
|
json: {
|
|
@@ -458,97 +64,20 @@ class LiekoExpress {
|
|
|
458
64
|
}
|
|
459
65
|
|
|
460
66
|
cors(options = {}) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
...options
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
_matchOrigin(origin, allowedOrigin) {
|
|
469
|
-
if (!origin || !allowedOrigin) return false;
|
|
470
|
-
|
|
471
|
-
if (Array.isArray(allowedOrigin)) {
|
|
472
|
-
return allowedOrigin.some(o => this._matchOrigin(origin, o));
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (allowedOrigin === "*") return true;
|
|
476
|
-
|
|
477
|
-
// Wildcard https://*.example.com
|
|
478
|
-
if (allowedOrigin.includes("*")) {
|
|
479
|
-
const regex = new RegExp("^" + allowedOrigin
|
|
480
|
-
.replace(/\./g, "\\.")
|
|
481
|
-
.replace(/\*/g, ".*") + "$");
|
|
482
|
-
|
|
483
|
-
return regex.test(origin);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
return origin === allowedOrigin;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
_applyCors(req, res, opts) {
|
|
490
|
-
if (!opts || !opts.enabled) return;
|
|
491
|
-
|
|
492
|
-
const requestOrigin = req.headers.origin || "";
|
|
493
|
-
|
|
494
|
-
let finalOrigin = "*";
|
|
495
|
-
|
|
496
|
-
if (opts.strictOrigin && requestOrigin) {
|
|
497
|
-
const allowed = this._matchOrigin(requestOrigin, opts.origin);
|
|
498
|
-
|
|
499
|
-
if (!allowed) {
|
|
500
|
-
res.statusCode = 403;
|
|
501
|
-
return res.end(JSON.stringify({
|
|
502
|
-
success: false,
|
|
503
|
-
error: "Origin Forbidden",
|
|
504
|
-
message: `Origin "${requestOrigin}" is not allowed`
|
|
505
|
-
}));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (opts.origin === "*") {
|
|
510
|
-
finalOrigin = "*";
|
|
511
|
-
} else if (Array.isArray(opts.origin)) {
|
|
512
|
-
const match = opts.origin.find(o => this._matchOrigin(requestOrigin, o));
|
|
513
|
-
finalOrigin = match || opts.origin[0];
|
|
514
|
-
} else {
|
|
515
|
-
finalOrigin = this._matchOrigin(requestOrigin, opts.origin)
|
|
516
|
-
? requestOrigin
|
|
517
|
-
: opts.origin;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
this._logCorsDebug(req, {
|
|
521
|
-
...opts,
|
|
522
|
-
origin: finalOrigin
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
res.setHeader("Access-Control-Allow-Origin", finalOrigin);
|
|
526
|
-
|
|
527
|
-
if (opts.credentials) {
|
|
528
|
-
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
67
|
+
if (options === false) {
|
|
68
|
+
this._corsMiddleware = null;
|
|
69
|
+
return this;
|
|
529
70
|
}
|
|
530
71
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
opts.exposedHeaders.join(", "));
|
|
534
|
-
}
|
|
72
|
+
const middleware = require('./lib/cors')(options);
|
|
73
|
+
this._corsMiddleware = middleware;
|
|
535
74
|
|
|
536
|
-
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
req.headers["access-control-request-private-network"] === "true"
|
|
540
|
-
) {
|
|
541
|
-
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
75
|
+
const stack = new Error().stack;
|
|
76
|
+
if (!stack.includes('at LiekoExpress.use')) {
|
|
77
|
+
this.use(middleware);
|
|
542
78
|
}
|
|
543
79
|
|
|
544
|
-
|
|
545
|
-
res.setHeader("Access-Control-Allow-Methods", opts.methods.join(", "));
|
|
546
|
-
res.setHeader("Access-Control-Allow-Headers", opts.headers.join(", "));
|
|
547
|
-
res.setHeader("Access-Control-Max-Age", opts.maxAge);
|
|
548
|
-
|
|
549
|
-
res.statusCode = 204;
|
|
550
|
-
return res.end();
|
|
551
|
-
}
|
|
80
|
+
return middleware;
|
|
552
81
|
}
|
|
553
82
|
|
|
554
83
|
debug(value = true) {
|
|
@@ -1075,248 +604,7 @@ ${cyan} (req, res, next) => {
|
|
|
1075
604
|
}
|
|
1076
605
|
|
|
1077
606
|
static(root, options = {}) {
|
|
1078
|
-
|
|
1079
|
-
maxAge: options.maxAge || 0,
|
|
1080
|
-
index: options.index !== undefined ? options.index : 'index.html',
|
|
1081
|
-
dotfiles: options.dotfiles || 'ignore',
|
|
1082
|
-
etag: options.etag !== undefined ? options.etag : true,
|
|
1083
|
-
extensions: options.extensions || false,
|
|
1084
|
-
fallthrough: options.fallthrough !== undefined ? options.fallthrough : true,
|
|
1085
|
-
immutable: options.immutable || false,
|
|
1086
|
-
lastModified: options.lastModified !== undefined ? options.lastModified : true,
|
|
1087
|
-
redirect: options.redirect !== undefined ? options.redirect : true,
|
|
1088
|
-
setHeaders: options.setHeaders || null,
|
|
1089
|
-
cacheControl: options.cacheControl !== undefined ? options.cacheControl : true
|
|
1090
|
-
};
|
|
1091
|
-
|
|
1092
|
-
const mimeTypes = {
|
|
1093
|
-
'.html': 'text/html; charset=utf-8',
|
|
1094
|
-
'.htm': 'text/html; charset=utf-8',
|
|
1095
|
-
'.css': 'text/css; charset=utf-8',
|
|
1096
|
-
'.js': 'application/javascript; charset=utf-8',
|
|
1097
|
-
'.mjs': 'application/javascript; charset=utf-8',
|
|
1098
|
-
'.json': 'application/json; charset=utf-8',
|
|
1099
|
-
'.xml': 'application/xml; charset=utf-8',
|
|
1100
|
-
'.txt': 'text/plain; charset=utf-8',
|
|
1101
|
-
'.md': 'text/markdown; charset=utf-8',
|
|
1102
|
-
'.jpg': 'image/jpeg',
|
|
1103
|
-
'.jpeg': 'image/jpeg',
|
|
1104
|
-
'.png': 'image/png',
|
|
1105
|
-
'.gif': 'image/gif',
|
|
1106
|
-
'.svg': 'image/svg+xml',
|
|
1107
|
-
'.webp': 'image/webp',
|
|
1108
|
-
'.ico': 'image/x-icon',
|
|
1109
|
-
'.bmp': 'image/bmp',
|
|
1110
|
-
'.tiff': 'image/tiff',
|
|
1111
|
-
'.tif': 'image/tiff',
|
|
1112
|
-
'.mp3': 'audio/mpeg',
|
|
1113
|
-
'.wav': 'audio/wav',
|
|
1114
|
-
'.ogg': 'audio/ogg',
|
|
1115
|
-
'.m4a': 'audio/mp4',
|
|
1116
|
-
'.aac': 'audio/aac',
|
|
1117
|
-
'.flac': 'audio/flac',
|
|
1118
|
-
'.mp4': 'video/mp4',
|
|
1119
|
-
'.webm': 'video/webm',
|
|
1120
|
-
'.ogv': 'video/ogg',
|
|
1121
|
-
'.avi': 'video/x-msvideo',
|
|
1122
|
-
'.mov': 'video/quicktime',
|
|
1123
|
-
'.wmv': 'video/x-ms-wmv',
|
|
1124
|
-
'.flv': 'video/x-flv',
|
|
1125
|
-
'.mkv': 'video/x-matroska',
|
|
1126
|
-
'.woff': 'font/woff',
|
|
1127
|
-
'.woff2': 'font/woff2',
|
|
1128
|
-
'.ttf': 'font/ttf',
|
|
1129
|
-
'.otf': 'font/otf',
|
|
1130
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
1131
|
-
'.zip': 'application/zip',
|
|
1132
|
-
'.rar': 'application/x-rar-compressed',
|
|
1133
|
-
'.tar': 'application/x-tar',
|
|
1134
|
-
'.gz': 'application/gzip',
|
|
1135
|
-
'.7z': 'application/x-7z-compressed',
|
|
1136
|
-
'.pdf': 'application/pdf',
|
|
1137
|
-
'.doc': 'application/msword',
|
|
1138
|
-
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
1139
|
-
'.xls': 'application/vnd.ms-excel',
|
|
1140
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
1141
|
-
'.ppt': 'application/vnd.ms-powerpoint',
|
|
1142
|
-
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
1143
|
-
'.wasm': 'application/wasm',
|
|
1144
|
-
'.csv': 'text/csv; charset=utf-8'
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
const getMimeType = (filePath) => {
|
|
1148
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
1149
|
-
return mimeTypes[ext] || 'application/octet-stream';
|
|
1150
|
-
};
|
|
1151
|
-
|
|
1152
|
-
const generateETag = (stats) => {
|
|
1153
|
-
const mtime = stats.mtime.getTime().toString(16);
|
|
1154
|
-
const size = stats.size.toString(16);
|
|
1155
|
-
return `W/"${size}-${mtime}"`;
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
return async (req, res, next) => {
|
|
1159
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1160
|
-
return next();
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
try {
|
|
1164
|
-
let pathname = req.url;
|
|
1165
|
-
const qIndex = pathname.indexOf('?');
|
|
1166
|
-
if (qIndex !== -1) {
|
|
1167
|
-
pathname = pathname.substring(0, qIndex);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
if (pathname === '') {
|
|
1171
|
-
pathname = '/';
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
try {
|
|
1175
|
-
pathname = decodeURIComponent(pathname);
|
|
1176
|
-
} catch (e) {
|
|
1177
|
-
if (opts.fallthrough) return next();
|
|
1178
|
-
return res.status(400).send('Bad Request');
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
let filePath = pathname === '/' ? root : path.join(root, pathname);
|
|
1182
|
-
|
|
1183
|
-
const resolvedPath = path.resolve(filePath);
|
|
1184
|
-
const resolvedRoot = path.resolve(root);
|
|
1185
|
-
|
|
1186
|
-
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
1187
|
-
if (opts.fallthrough) return next();
|
|
1188
|
-
return res.status(403).send('Forbidden');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
let stats;
|
|
1192
|
-
try {
|
|
1193
|
-
stats = await fs.promises.stat(filePath);
|
|
1194
|
-
} catch (err) {
|
|
1195
|
-
if (pathname === '/' && opts.index) {
|
|
1196
|
-
const indexes = Array.isArray(opts.index) ? opts.index : [opts.index];
|
|
1197
|
-
for (const indexFile of indexes) {
|
|
1198
|
-
const indexPath = path.join(root, indexFile);
|
|
1199
|
-
try {
|
|
1200
|
-
stats = await fs.promises.stat(indexPath);
|
|
1201
|
-
if (stats.isFile()) {
|
|
1202
|
-
filePath = indexPath;
|
|
1203
|
-
break;
|
|
1204
|
-
}
|
|
1205
|
-
} catch (e) {}
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
if (!stats && opts.extensions && Array.isArray(opts.extensions)) {
|
|
1210
|
-
let found = false;
|
|
1211
|
-
for (const ext of opts.extensions) {
|
|
1212
|
-
const testPath = filePath + (ext.startsWith('.') ? ext : '.' + ext);
|
|
1213
|
-
try {
|
|
1214
|
-
stats = await fs.promises.stat(testPath);
|
|
1215
|
-
filePath = testPath;
|
|
1216
|
-
found = true;
|
|
1217
|
-
break;
|
|
1218
|
-
} catch (e) {}
|
|
1219
|
-
}
|
|
1220
|
-
if (!found) return next();
|
|
1221
|
-
} else if (!stats) {
|
|
1222
|
-
return next();
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
if (stats.isDirectory()) {
|
|
1227
|
-
if (opts.redirect && !pathname.endsWith('/')) {
|
|
1228
|
-
const query = qIndex !== -1 ? req.url.substring(qIndex) : '';
|
|
1229
|
-
const redirectUrl = pathname + '/' + query;
|
|
1230
|
-
return res.redirect(redirectUrl, 301);
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
if (opts.index) {
|
|
1234
|
-
const indexes = Array.isArray(opts.index) ? opts.index : [opts.index];
|
|
1235
|
-
|
|
1236
|
-
for (const indexFile of indexes) {
|
|
1237
|
-
const indexPath = path.join(filePath, indexFile);
|
|
1238
|
-
try {
|
|
1239
|
-
const indexStats = await fs.promises.stat(indexPath);
|
|
1240
|
-
if (indexStats.isFile()) {
|
|
1241
|
-
filePath = indexPath;
|
|
1242
|
-
stats = indexStats;
|
|
1243
|
-
break;
|
|
1244
|
-
}
|
|
1245
|
-
} catch (e) {}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (stats.isDirectory()) {
|
|
1249
|
-
if (opts.fallthrough) return next();
|
|
1250
|
-
return res.status(404).send('Not Found');
|
|
1251
|
-
}
|
|
1252
|
-
} else {
|
|
1253
|
-
if (opts.fallthrough) return next();
|
|
1254
|
-
return res.status(404).send('Not Found');
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
if (opts.etag) {
|
|
1259
|
-
const etag = generateETag(stats);
|
|
1260
|
-
const ifNoneMatch = req.headers['if-none-match'];
|
|
1261
|
-
|
|
1262
|
-
if (ifNoneMatch === etag) {
|
|
1263
|
-
res.statusCode = 304;
|
|
1264
|
-
res.end();
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
res.setHeader('ETag', etag);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
if (opts.lastModified) {
|
|
1272
|
-
const lastModified = stats.mtime.toUTCString();
|
|
1273
|
-
const ifModifiedSince = req.headers['if-modified-since'];
|
|
1274
|
-
|
|
1275
|
-
if (ifModifiedSince === lastModified) {
|
|
1276
|
-
res.statusCode = 304;
|
|
1277
|
-
res.end();
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
res.setHeader('Last-Modified', lastModified);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
if (opts.cacheControl) {
|
|
1285
|
-
let cacheControl = 'public';
|
|
1286
|
-
|
|
1287
|
-
if (opts.maxAge > 0) {
|
|
1288
|
-
cacheControl += `, max-age=${opts.maxAge}`;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
if (opts.immutable) {
|
|
1292
|
-
cacheControl += ', immutable';
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
res.setHeader('Cache-Control', cacheControl);
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
const mimeType = getMimeType(filePath);
|
|
1299
|
-
res.setHeader('Content-Type', mimeType);
|
|
1300
|
-
res.setHeader('Content-Length', stats.size);
|
|
1301
|
-
|
|
1302
|
-
if (typeof opts.setHeaders === 'function') {
|
|
1303
|
-
opts.setHeaders(res, filePath, stats);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
if (req.method === 'HEAD') {
|
|
1307
|
-
return res.end();
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
const data = await fs.promises.readFile(filePath);
|
|
1311
|
-
res.end(data);
|
|
1312
|
-
return;
|
|
1313
|
-
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
console.error('Static middleware error:', error);
|
|
1316
|
-
if (opts.fallthrough) return next();
|
|
1317
|
-
res.status(500).send('Internal Server Error');
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
607
|
+
return require('./lib/static')(root, options);
|
|
1320
608
|
}
|
|
1321
609
|
|
|
1322
610
|
_mountRouter(basePath, router) {
|
|
@@ -1352,6 +640,10 @@ ${cyan} (req, res, next) => {
|
|
|
1352
640
|
}
|
|
1353
641
|
|
|
1354
642
|
const finalHandler = handlers[handlers.length - 1];
|
|
643
|
+
if (!finalHandler) {
|
|
644
|
+
throw new Error(`Route handler is undefined for ${method} ${path}`);
|
|
645
|
+
}
|
|
646
|
+
|
|
1355
647
|
const routeMiddlewares = handlers.slice(0, -1);
|
|
1356
648
|
|
|
1357
649
|
routeMiddlewares.forEach(mw => {
|
|
@@ -1363,7 +655,7 @@ ${cyan} (req, res, next) => {
|
|
|
1363
655
|
const paths = Array.isArray(path) ? path : [path];
|
|
1364
656
|
|
|
1365
657
|
paths.forEach(original => {
|
|
1366
|
-
let p = String(original).trim();
|
|
658
|
+
let p = String(original).trim();
|
|
1367
659
|
p = p.replace(/\/+/g, '/');
|
|
1368
660
|
|
|
1369
661
|
if (p !== '/' && p.endsWith('/')) {
|
|
@@ -1383,7 +675,7 @@ ${cyan} (req, res, next) => {
|
|
|
1383
675
|
path: p,
|
|
1384
676
|
originalPath: original,
|
|
1385
677
|
handler: finalHandler,
|
|
1386
|
-
handlerName: finalHandler.name || 'anonymous',
|
|
678
|
+
handlerName: (finalHandler && finalHandler.name) || 'anonymous',
|
|
1387
679
|
middlewares: routeMiddlewares,
|
|
1388
680
|
pattern: this._pathToRegex(p),
|
|
1389
681
|
allowTrailingSlash: this.settings.allowTrailingSlash ?? false,
|
|
@@ -1447,7 +739,6 @@ ${cyan} (req, res, next) => {
|
|
|
1447
739
|
}
|
|
1448
740
|
}
|
|
1449
741
|
}
|
|
1450
|
-
|
|
1451
742
|
return null;
|
|
1452
743
|
}
|
|
1453
744
|
|
|
@@ -1780,6 +1071,100 @@ ${cyan} (req, res, next) => {
|
|
|
1780
1071
|
|
|
1781
1072
|
req.originalUrl = req.url;
|
|
1782
1073
|
req.xhr = (req.headers['x-requested-with'] || '').toLowerCase() === 'xmlhttprequest';
|
|
1074
|
+
|
|
1075
|
+
req.get = (name) => {
|
|
1076
|
+
if (typeof name !== 'string') return undefined;
|
|
1077
|
+
const lower = name.toLowerCase();
|
|
1078
|
+
for (const key in req.headers) {
|
|
1079
|
+
if (key.toLowerCase() === lower) return req.headers[key];
|
|
1080
|
+
}
|
|
1081
|
+
return undefined;
|
|
1082
|
+
};
|
|
1083
|
+
req.header = req.get;
|
|
1084
|
+
|
|
1085
|
+
const parseAccept = (header) => {
|
|
1086
|
+
if (!header) return [];
|
|
1087
|
+
return header
|
|
1088
|
+
.split(',')
|
|
1089
|
+
.map(part => {
|
|
1090
|
+
const [type, ...rest] = part.trim().split(';');
|
|
1091
|
+
const q = rest
|
|
1092
|
+
.find(p => p.trim().startsWith('q='))
|
|
1093
|
+
?.split('=')[1];
|
|
1094
|
+
const quality = q ? parseFloat(q) : 1.0;
|
|
1095
|
+
return { type: type.trim().toLowerCase(), quality };
|
|
1096
|
+
})
|
|
1097
|
+
.filter(item => item.quality > 0)
|
|
1098
|
+
.sort((a, b) => b.quality - a.quality)
|
|
1099
|
+
.map(item => item.type);
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
const accepts = (types) => {
|
|
1103
|
+
if (!Array.isArray(types)) types = [types];
|
|
1104
|
+
const accepted = parseAccept(req.headers['accept']);
|
|
1105
|
+
|
|
1106
|
+
for (const type of types) {
|
|
1107
|
+
const t = type.toLowerCase();
|
|
1108
|
+
|
|
1109
|
+
if (accepted.includes(t)) return type;
|
|
1110
|
+
|
|
1111
|
+
if (accepted.some(a => {
|
|
1112
|
+
if (a === '*/*') return true;
|
|
1113
|
+
if (a.endsWith('/*')) {
|
|
1114
|
+
const prefix = a.slice(0, -1);
|
|
1115
|
+
return t.startsWith(prefix);
|
|
1116
|
+
}
|
|
1117
|
+
return false;
|
|
1118
|
+
})) {
|
|
1119
|
+
return type;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return false;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
req.accepts = function (types) {
|
|
1127
|
+
return accepts(types);
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
req.acceptsLanguages = function (langs) {
|
|
1131
|
+
if (!Array.isArray(langs)) langs = [langs];
|
|
1132
|
+
const accepted = parseAccept(req.headers['accept-language'] || '');
|
|
1133
|
+
for (const lang of langs) {
|
|
1134
|
+
const l = lang.toLowerCase();
|
|
1135
|
+
if (accepted.some(a => a === l || a.startsWith(l + '-'))) return lang;
|
|
1136
|
+
}
|
|
1137
|
+
return false;
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
req.acceptsEncodings = function (encodings) {
|
|
1141
|
+
if (!Array.isArray(encodings)) encodings = [encodings];
|
|
1142
|
+
const accepted = parseAccept(req.headers['accept-encoding'] || '');
|
|
1143
|
+
for (const enc of encodings) {
|
|
1144
|
+
if (accepted.includes(enc.toLowerCase())) return enc;
|
|
1145
|
+
}
|
|
1146
|
+
return false;
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
req.acceptsCharsets = function (charsets) {
|
|
1150
|
+
if (!Array.isArray(charsets)) charsets = [charsets];
|
|
1151
|
+
const accepted = parseAccept(req.headers['accept-charset'] || '');
|
|
1152
|
+
for (const charset of charsets) {
|
|
1153
|
+
if (accepted.includes(charset.toLowerCase())) return charset;
|
|
1154
|
+
}
|
|
1155
|
+
return false;
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
req.is = function (type) {
|
|
1159
|
+
const ct = (req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
|
|
1160
|
+
if (!type) return ct;
|
|
1161
|
+
const t = type.toLowerCase();
|
|
1162
|
+
if (t.includes('/')) return ct === t;
|
|
1163
|
+
if (t === 'json') return ct.includes('json');
|
|
1164
|
+
if (t === 'urlencoded') return ct.includes('x-www-form-urlencoded');
|
|
1165
|
+
if (t === 'multipart') return ct.includes('multipart');
|
|
1166
|
+
return false;
|
|
1167
|
+
};
|
|
1783
1168
|
}
|
|
1784
1169
|
|
|
1785
1170
|
_enhanceResponse(req, res) {
|
|
@@ -1858,39 +1243,46 @@ ${cyan} (req, res, next) => {
|
|
|
1858
1243
|
const locals = { ...res.locals, ...options };
|
|
1859
1244
|
let viewPath = view;
|
|
1860
1245
|
let ext = path.extname(view);
|
|
1246
|
+
console.log("EXT: ", ext)
|
|
1861
1247
|
|
|
1862
1248
|
if (!ext) {
|
|
1863
1249
|
ext = this.settings['view engine'];
|
|
1864
1250
|
if (!ext) {
|
|
1865
|
-
|
|
1251
|
+
ext = '.html';
|
|
1252
|
+
viewPath = view + ext;
|
|
1253
|
+
} else {
|
|
1254
|
+
if (!ext.startsWith('.')) ext = '.' + ext;
|
|
1255
|
+
viewPath = view + ext;
|
|
1866
1256
|
}
|
|
1867
|
-
if (!ext.startsWith('.')) ext = '.' + ext;
|
|
1868
|
-
viewPath = view + ext;
|
|
1869
1257
|
}
|
|
1870
1258
|
|
|
1871
1259
|
const viewsDir = this.settings.views || path.join(process.cwd(), 'views');
|
|
1872
1260
|
let fullPath = path.join(viewsDir, viewPath);
|
|
1873
|
-
|
|
1261
|
+
console.log(fullPath)
|
|
1874
1262
|
let fileExists = false;
|
|
1875
1263
|
try {
|
|
1876
1264
|
await fs.promises.access(fullPath);
|
|
1877
1265
|
fileExists = true;
|
|
1878
1266
|
} catch (err) {
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1267
|
+
console.log(err)
|
|
1268
|
+
const extensions = ['.html', '.ejs', '.pug', '.hbs'];
|
|
1269
|
+
for (const tryExt of extensions) {
|
|
1270
|
+
if (tryExt === ext) continue;
|
|
1271
|
+
const tryPath = fullPath.replace(new RegExp(ext.replace('.', '\\.') + '$'), tryExt);
|
|
1272
|
+
try {
|
|
1273
|
+
await fs.promises.access(tryPath);
|
|
1274
|
+
fullPath = tryPath;
|
|
1275
|
+
ext = tryExt;
|
|
1276
|
+
fileExists = true;
|
|
1277
|
+
break;
|
|
1278
|
+
} catch (err2) { }
|
|
1886
1279
|
}
|
|
1887
1280
|
}
|
|
1888
1281
|
|
|
1889
1282
|
if (!fileExists) {
|
|
1890
1283
|
const error = new Error(
|
|
1891
|
-
`View "${view}" not found
|
|
1892
|
-
|
|
1893
|
-
`- ${fullPath.replace(new RegExp(ext.replace('.', '\\.') + '$'), '.html')}`
|
|
1284
|
+
`View "${view}" not found in views directory "${viewsDir}".\n` +
|
|
1285
|
+
`Tried: ${fullPath}`
|
|
1894
1286
|
);
|
|
1895
1287
|
error.code = 'ENOENT';
|
|
1896
1288
|
if (callback) return callback(error);
|
|
@@ -1900,24 +1292,38 @@ ${cyan} (req, res, next) => {
|
|
|
1900
1292
|
const renderEngine = this.engines[ext];
|
|
1901
1293
|
|
|
1902
1294
|
if (!renderEngine) {
|
|
1903
|
-
throw new Error(
|
|
1295
|
+
throw new Error(
|
|
1296
|
+
`No engine registered for extension "${ext}".\n` +
|
|
1297
|
+
`Use app.engine("${ext}", renderFunction) to register one.`
|
|
1298
|
+
);
|
|
1904
1299
|
}
|
|
1905
1300
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (
|
|
1909
|
-
|
|
1910
|
-
|
|
1301
|
+
return new Promise((resolve, reject) => {
|
|
1302
|
+
renderEngine(fullPath, locals, (err, html) => {
|
|
1303
|
+
if (err) {
|
|
1304
|
+
if (callback) {
|
|
1305
|
+
callback(err);
|
|
1306
|
+
resolve();
|
|
1307
|
+
} else {
|
|
1308
|
+
reject(err);
|
|
1309
|
+
}
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1911
1312
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1313
|
+
if (callback) {
|
|
1314
|
+
callback(null, html);
|
|
1315
|
+
resolve();
|
|
1316
|
+
} else {
|
|
1317
|
+
/*
|
|
1318
|
+
HBS cause error header already sent here ??
|
|
1319
|
+
*/
|
|
1320
|
+
//res.statusCode = statusCode || 200;
|
|
1321
|
+
//res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1322
|
+
//responseSent = true;
|
|
1323
|
+
res.html(html);
|
|
1324
|
+
resolve();
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1921
1327
|
});
|
|
1922
1328
|
|
|
1923
1329
|
} catch (error) {
|
|
@@ -1929,17 +1335,15 @@ ${cyan} (req, res, next) => {
|
|
|
1929
1335
|
}
|
|
1930
1336
|
};
|
|
1931
1337
|
|
|
1932
|
-
|
|
1933
1338
|
res.json = (data) => {
|
|
1934
1339
|
if (responseSent) return res;
|
|
1935
1340
|
|
|
1936
1341
|
const json = JSON.stringify(data);
|
|
1937
1342
|
const length = Buffer.byteLength(json);
|
|
1938
1343
|
|
|
1939
|
-
res.writeHead(statusCode, buildHeaders('application/json; charset=utf-8', length));
|
|
1344
|
+
res.writeHead(statusCode || 200, buildHeaders('application/json; charset=utf-8', length));
|
|
1940
1345
|
|
|
1941
1346
|
responseSent = true;
|
|
1942
|
-
statusCode = 200;
|
|
1943
1347
|
return res.end(json);
|
|
1944
1348
|
};
|
|
1945
1349
|
|
|
@@ -1964,15 +1368,14 @@ ${cyan} (req, res, next) => {
|
|
|
1964
1368
|
|
|
1965
1369
|
const length = Buffer.byteLength(body);
|
|
1966
1370
|
|
|
1967
|
-
res.writeHead(statusCode, buildHeaders(contentType, length));
|
|
1371
|
+
res.writeHead(statusCode || 200, buildHeaders(contentType, length));
|
|
1968
1372
|
|
|
1969
1373
|
responseSent = true;
|
|
1970
|
-
statusCode = 200;
|
|
1971
1374
|
return res.end(body);
|
|
1972
1375
|
};
|
|
1973
1376
|
|
|
1974
|
-
res.html = function (html, status
|
|
1975
|
-
res.statusCode = status;
|
|
1377
|
+
res.html = function (html, status) {
|
|
1378
|
+
res.statusCode = status !== undefined ? status : (statusCode || 200);
|
|
1976
1379
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1977
1380
|
res.end(html);
|
|
1978
1381
|
};
|
|
@@ -2067,6 +1470,42 @@ ${cyan} (req, res, next) => {
|
|
|
2067
1470
|
};
|
|
2068
1471
|
}
|
|
2069
1472
|
|
|
1473
|
+
_defaultHtmlEngine(filePath, locals, callback) {
|
|
1474
|
+
fs.readFile(filePath, 'utf-8', (err, content) => {
|
|
1475
|
+
if (err) return callback(err);
|
|
1476
|
+
|
|
1477
|
+
let rendered = content;
|
|
1478
|
+
|
|
1479
|
+
Object.keys(locals).forEach(key => {
|
|
1480
|
+
if (locals[key] !== undefined && locals[key] !== null) {
|
|
1481
|
+
const safeRegex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
1482
|
+
const unsafeRegex = new RegExp(`{{{\\s*${key}\\s*}}}`, 'g');
|
|
1483
|
+
|
|
1484
|
+
if (safeRegex.test(rendered)) {
|
|
1485
|
+
const escaped = this._escapeHtml(String(locals[key]));
|
|
1486
|
+
rendered = rendered.replace(safeRegex, escaped);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
if (unsafeRegex.test(rendered)) {
|
|
1490
|
+
rendered = rendered.replace(unsafeRegex, String(locals[key]));
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
callback(null, rendered);
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
_escapeHtml(text) {
|
|
1500
|
+
if (typeof text !== 'string') return text;
|
|
1501
|
+
return text
|
|
1502
|
+
.replace(/&/g, '&')
|
|
1503
|
+
.replace(/</g, '<')
|
|
1504
|
+
.replace(/>/g, '>')
|
|
1505
|
+
.replace(/"/g, '"')
|
|
1506
|
+
.replace(/'/g, ''');
|
|
1507
|
+
}
|
|
1508
|
+
|
|
2070
1509
|
async _runMiddleware(handler, req, res) {
|
|
2071
1510
|
return new Promise((resolve, reject) => {
|
|
2072
1511
|
const next = (err) => err ? reject(err) : resolve();
|
|
@@ -2145,33 +1584,6 @@ ${cyan} (req, res, next) => {
|
|
|
2145
1584
|
console.log('\n' + logLines.join('\n') + '\n');
|
|
2146
1585
|
}
|
|
2147
1586
|
|
|
2148
|
-
_logCorsDebug(req, opts) {
|
|
2149
|
-
if (!opts.debug) return;
|
|
2150
|
-
|
|
2151
|
-
console.log("\n[CORS DEBUG]");
|
|
2152
|
-
console.log("Request:", req.method, req.url);
|
|
2153
|
-
console.log("Origin:", req.headers.origin || "none");
|
|
2154
|
-
|
|
2155
|
-
console.log("Applied CORS Policy:");
|
|
2156
|
-
console.log(" - Access-Control-Allow-Origin:", opts.origin);
|
|
2157
|
-
console.log(" - Access-Control-Allow-Methods:", opts.methods.join(", "));
|
|
2158
|
-
console.log(" - Access-Control-Allow-Headers:", opts.headers.join(", "));
|
|
2159
|
-
|
|
2160
|
-
if (opts.credentials) {
|
|
2161
|
-
console.log(" - Access-Control-Allow-Credentials: true");
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
if (opts.exposedHeaders?.length) {
|
|
2165
|
-
console.log(" - Access-Control-Expose-Headers:", opts.exposedHeaders.join(", "));
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
console.log(" - Max-Age:", opts.maxAge);
|
|
2169
|
-
|
|
2170
|
-
if (req.method === "OPTIONS") {
|
|
2171
|
-
console.log("Preflight request handled with status 204\n");
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
1587
|
listRoutes() {
|
|
2176
1588
|
const routeEntries = [];
|
|
2177
1589
|
|
|
@@ -2242,10 +1654,10 @@ ${cyan} (req, res, next) => {
|
|
|
2242
1654
|
console.log(` \x1b[36m${r.method.padEnd(7)}\x1b[0m \x1b[33m${pathStr}\x1b[0m \x1b[90m(mw: ${r.mw})\x1b[0m`);
|
|
2243
1655
|
}
|
|
2244
1656
|
}
|
|
1657
|
+
|
|
2245
1658
|
listen() {
|
|
2246
1659
|
const args = Array.from(arguments);
|
|
2247
1660
|
const server = createServer(this._handleRequest.bind(this));
|
|
2248
|
-
|
|
2249
1661
|
server.listen.apply(server, args);
|
|
2250
1662
|
this.server = server;
|
|
2251
1663
|
return server;
|
|
@@ -2262,13 +1674,10 @@ function Router() {
|
|
|
2262
1674
|
|
|
2263
1675
|
module.exports = Lieko;
|
|
2264
1676
|
module.exports.Router = Router;
|
|
1677
|
+
|
|
2265
1678
|
module.exports.Schema = Schema;
|
|
2266
|
-
module.exports.
|
|
1679
|
+
module.exports.createSchema = (...args) => new Schema(...args);
|
|
2267
1680
|
module.exports.validators = validators;
|
|
2268
1681
|
module.exports.validate = validate;
|
|
2269
1682
|
module.exports.validatePartial = validatePartial;
|
|
2270
1683
|
module.exports.ValidationError = ValidationError;
|
|
2271
|
-
module.exports.static = function (root, options) {
|
|
2272
|
-
const app = new LiekoExpress();
|
|
2273
|
-
return app.static(root, options);
|
|
2274
|
-
};
|