mastercontroller 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +4 -1
- package/MasterAction.js +137 -23
- package/MasterActionFilters.js +197 -92
- package/MasterControl.js +265 -44
- package/MasterHtml.js +226 -143
- package/MasterPipeline.js +1 -1
- package/MasterRequest.js +202 -24
- package/MasterSocket.js +6 -1
- package/MasterTools.js +428 -13
- package/README.md +2364 -309
- package/SECURITY-FIXES-v1.3.2.md +614 -0
- package/docs/SECURITY-AUDIT-ACTION-SYSTEM.md +1374 -0
- package/docs/SECURITY-AUDIT-HTTPS.md +1056 -0
- package/docs/SECURITY-QUICKSTART.md +375 -0
- package/docs/timeout-and-error-handling.md +8 -6
- package/package.json +1 -1
- package/security/SecurityEnforcement.js +241 -0
- package/security/SessionSecurity.js +100 -2
- package/test/security/filters.test.js +276 -0
- package/test/security/https.test.js +214 -0
- package/test/security/path-traversal.test.js +222 -0
- package/test/security/xss.test.js +190 -0
- package/MasterSession.js +0 -208
- package/docs/server-setup-hostname-binding.md +0 -24
- package/docs/server-setup-http.md +0 -32
- package/docs/server-setup-https-credentials.md +0 -32
- package/docs/server-setup-https-env-tls-sni.md +0 -62
- package/docs/server-setup-nginx-reverse-proxy.md +0 -46
package/MasterHtml.js
CHANGED
|
@@ -18,14 +18,32 @@ const { sanitizeTemplateHTML, sanitizeUserHTML, escapeHTML } = require('./securi
|
|
|
18
18
|
class html {
|
|
19
19
|
|
|
20
20
|
javaScriptSerializer(name, obj){
|
|
21
|
+
// SECURITY: Escape closing script tags and dangerous characters
|
|
22
|
+
const jsonStr = JSON.stringify(obj)
|
|
23
|
+
.replace(/</g, '\\u003c')
|
|
24
|
+
.replace(/>/g, '\\u003e')
|
|
25
|
+
.replace(/&/g, '\\u0026')
|
|
26
|
+
.replace(/\u2028/g, '\\u2028')
|
|
27
|
+
.replace(/\u2029/g, '\\u2029');
|
|
28
|
+
|
|
21
29
|
return `<script type="text/javascript">
|
|
22
|
-
${name} = ${
|
|
30
|
+
${escapeHTML(name)} = ${jsonStr}
|
|
23
31
|
</script>`;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
// render partial views
|
|
27
35
|
renderPartial(path, data){
|
|
28
36
|
try {
|
|
37
|
+
// SECURITY: Validate path to prevent traversal attacks
|
|
38
|
+
if (!path || path.includes('..') || path.includes('~') || path.startsWith('/')) {
|
|
39
|
+
logger.warn({
|
|
40
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
41
|
+
message: 'Path traversal attempt blocked in renderPartial',
|
|
42
|
+
path: path
|
|
43
|
+
});
|
|
44
|
+
return '<!-- Invalid path -->';
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
var partialViewUrl = `/app/views/${path}`;
|
|
30
48
|
var fullPath = master.router.currentRoute.root + partialViewUrl;
|
|
31
49
|
|
|
@@ -64,6 +82,16 @@ class html {
|
|
|
64
82
|
|
|
65
83
|
// render all your link tags styles given the folder location
|
|
66
84
|
renderStyles(folderName, typeArray){
|
|
85
|
+
// SECURITY: Validate folder name to prevent path traversal
|
|
86
|
+
if (folderName && (folderName.includes('..') || folderName.includes('~') || folderName.startsWith('/'))) {
|
|
87
|
+
logger.warn({
|
|
88
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
89
|
+
message: 'Path traversal attempt blocked in renderStyles',
|
|
90
|
+
folderName: folderName
|
|
91
|
+
});
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
67
95
|
var styles = [];
|
|
68
96
|
var styleFolder = `/app/assets/stylesheets/`;
|
|
69
97
|
var rootLocation = master.router.currentRoute.root;
|
|
@@ -108,6 +136,15 @@ class html {
|
|
|
108
136
|
|
|
109
137
|
// renders all scripts in main folder or folder location inside of javascript also its type specific if you provide type
|
|
110
138
|
renderScripts(folderName, typeArray){
|
|
139
|
+
// SECURITY: Validate folder name to prevent path traversal
|
|
140
|
+
if (folderName && (folderName.includes('..') || folderName.includes('~') || folderName.startsWith('/'))) {
|
|
141
|
+
logger.warn({
|
|
142
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
143
|
+
message: 'Path traversal attempt blocked in renderScripts',
|
|
144
|
+
folderName: folderName
|
|
145
|
+
});
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
111
148
|
|
|
112
149
|
var scripts = [];
|
|
113
150
|
var jsFolder =`/app/assets/javascripts/`;
|
|
@@ -193,38 +230,46 @@ class html {
|
|
|
193
230
|
|
|
194
231
|
// return link tag
|
|
195
232
|
linkTo(name, location){
|
|
196
|
-
|
|
233
|
+
const safeName = escapeHTML(String(name));
|
|
234
|
+
const safeLocation = escapeHTML(String(location));
|
|
235
|
+
return `<a href="${safeLocation}">${safeName}</a>`;
|
|
197
236
|
}
|
|
198
237
|
|
|
199
238
|
// return image tag
|
|
200
239
|
imgTag(alt, location){
|
|
201
|
-
|
|
240
|
+
const safeAlt = escapeHTML(String(alt));
|
|
241
|
+
const safeLocation = escapeHTML(String(location));
|
|
242
|
+
return `<img src="${safeLocation}" alt="${safeAlt}">`;
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
// return text are tag
|
|
205
246
|
textAreaTag(name, message, obj){
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
for (var key in obj) {
|
|
209
|
-
if (obj.hasOwnProperty(key)) {
|
|
210
|
-
textArea = textArea + " " + key + "=" + "'" + obj[key] + "'";
|
|
211
|
-
}
|
|
212
|
-
};
|
|
247
|
+
const safeName = escapeHTML(String(name));
|
|
248
|
+
const safeMessage = escapeHTML(String(message));
|
|
213
249
|
|
|
214
|
-
textArea =
|
|
250
|
+
let textArea = `<textarea name="${safeName}"`;
|
|
251
|
+
|
|
252
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
253
|
+
const safeKey = escapeHTML(String(key));
|
|
254
|
+
const safeValue = escapeHTML(String(value));
|
|
255
|
+
textArea += ` ${safeKey}="${safeValue}"`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
textArea += `>${safeMessage}</textarea>`;
|
|
215
259
|
|
|
216
260
|
return textArea;
|
|
217
261
|
}
|
|
218
262
|
|
|
219
263
|
// form element builder starter
|
|
220
264
|
formTag(location, obj){
|
|
221
|
-
|
|
265
|
+
const safeLocation = escapeHTML(String(location));
|
|
266
|
+
let form = `<form action="${safeLocation}"`;
|
|
222
267
|
|
|
223
|
-
for (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
}
|
|
268
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
269
|
+
const safeKey = escapeHTML(String(key));
|
|
270
|
+
const safeValue = escapeHTML(String(value));
|
|
271
|
+
form += ` ${safeKey}="${safeValue}"`;
|
|
272
|
+
}
|
|
228
273
|
|
|
229
274
|
return form + ">";
|
|
230
275
|
}
|
|
@@ -235,90 +280,102 @@ class html {
|
|
|
235
280
|
}
|
|
236
281
|
// return text tag
|
|
237
282
|
passwordFieldTag(name, obj){
|
|
238
|
-
|
|
283
|
+
const safeName = escapeHTML(String(name));
|
|
284
|
+
let passwordField = `<input type="password" name="${safeName}"`;
|
|
239
285
|
|
|
240
|
-
for (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
286
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
287
|
+
const safeKey = escapeHTML(String(key));
|
|
288
|
+
const safeValue = escapeHTML(String(value));
|
|
289
|
+
passwordField += ` ${safeKey}="${safeValue}"`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
passwordField += '/>';
|
|
246
293
|
|
|
247
294
|
return passwordField;
|
|
248
295
|
}
|
|
249
|
-
|
|
296
|
+
|
|
250
297
|
// return password field tag
|
|
251
298
|
textFieldTag(name, obj){
|
|
252
|
-
|
|
299
|
+
const safeName = escapeHTML(String(name));
|
|
300
|
+
let textField = `<input type="text" name="${safeName}"`;
|
|
253
301
|
|
|
254
|
-
for (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
302
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
303
|
+
const safeKey = escapeHTML(String(key));
|
|
304
|
+
const safeValue = escapeHTML(String(value));
|
|
305
|
+
textField += ` ${safeKey}="${safeValue}"`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
textField += '/>';
|
|
260
309
|
return textField;
|
|
261
310
|
};
|
|
262
311
|
|
|
263
312
|
// hidden field tag
|
|
264
313
|
hiddenFieldTag(name, value, obj){
|
|
265
|
-
|
|
266
|
-
|
|
314
|
+
const safeName = escapeHTML(String(name));
|
|
315
|
+
const safeValue = escapeHTML(String(value));
|
|
316
|
+
|
|
317
|
+
let hiddenField = `<input type="hidden" name="${safeName}" value="${safeValue}"`;
|
|
318
|
+
|
|
319
|
+
for (const [key, val] of Object.entries(obj || {})) {
|
|
320
|
+
const safeKey = escapeHTML(String(key));
|
|
321
|
+
const safeVal = escapeHTML(String(val));
|
|
322
|
+
hiddenField += ` ${safeKey}="${safeVal}"`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
hiddenField += '/>';
|
|
267
326
|
|
|
268
|
-
for (var key in obj) {
|
|
269
|
-
if (obj.hasOwnProperty(key)) {
|
|
270
|
-
hiddenField = hiddenField + " " + key + "=" + "'" + obj[key] + "'";
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
hiddenField = hiddenField + '/>';
|
|
274
|
-
|
|
275
327
|
return hiddenField;
|
|
276
328
|
|
|
277
329
|
}
|
|
278
330
|
|
|
279
331
|
// subit tag
|
|
280
332
|
submitButton(name, obj){
|
|
281
|
-
|
|
282
|
-
var submitButton = "<button type='submit' name='" + name + "'";
|
|
333
|
+
const safeName = escapeHTML(String(name));
|
|
283
334
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
335
|
+
let submitButton = `<button type="submit" name="${safeName}"`;
|
|
336
|
+
|
|
337
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
338
|
+
const safeKey = escapeHTML(String(key));
|
|
339
|
+
const safeValue = escapeHTML(String(value));
|
|
340
|
+
submitButton += ` ${safeKey}="${safeValue}"`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
submitButton += `>${safeName}</button>`;
|
|
289
344
|
|
|
290
|
-
submitButton = submitButton + ">" + name +'</button>';
|
|
291
|
-
|
|
292
345
|
return submitButton;
|
|
293
346
|
|
|
294
347
|
}
|
|
295
348
|
|
|
296
349
|
// search tag
|
|
297
350
|
searchField(name, obj){
|
|
298
|
-
|
|
299
|
-
|
|
351
|
+
const safeName = escapeHTML(String(name));
|
|
352
|
+
|
|
353
|
+
let searchField = `<input type="search" name="${safeName}"`;
|
|
354
|
+
|
|
355
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
356
|
+
const safeKey = escapeHTML(String(key));
|
|
357
|
+
const safeValue = escapeHTML(String(value));
|
|
358
|
+
searchField += ` ${safeKey}="${safeValue}"`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
searchField += '/>';
|
|
300
362
|
|
|
301
|
-
for (var key in obj) {
|
|
302
|
-
if (obj.hasOwnProperty(key)) {
|
|
303
|
-
searchField = searchField + " " + key + "=" + "'" + obj[key] + "'";
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
searchField = searchField + '/>';
|
|
307
|
-
|
|
308
363
|
return searchField;
|
|
309
364
|
}
|
|
310
365
|
|
|
311
366
|
// telephone field tag
|
|
312
367
|
telephoneField(name, obj){
|
|
368
|
+
const safeName = escapeHTML(String(name));
|
|
313
369
|
|
|
314
|
-
|
|
370
|
+
let telephoneField = `<input type="tel" name="${safeName}"`;
|
|
315
371
|
|
|
316
|
-
for (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
372
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
373
|
+
const safeKey = escapeHTML(String(key));
|
|
374
|
+
const safeValue = escapeHTML(String(value));
|
|
375
|
+
telephoneField += ` ${safeKey}="${safeValue}"`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
telephoneField += '/>';
|
|
322
379
|
|
|
323
380
|
return telephoneField;
|
|
324
381
|
|
|
@@ -326,75 +383,85 @@ class html {
|
|
|
326
383
|
|
|
327
384
|
// date field tag
|
|
328
385
|
dateField(name, obj){
|
|
386
|
+
const safeName = escapeHTML(String(name));
|
|
329
387
|
|
|
330
|
-
|
|
388
|
+
let dateField = `<input type="date" name="${safeName}"`;
|
|
331
389
|
|
|
332
|
-
for (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
390
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
391
|
+
const safeKey = escapeHTML(String(key));
|
|
392
|
+
const safeValue = escapeHTML(String(value));
|
|
393
|
+
dateField += ` ${safeKey}="${safeValue}"`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
dateField += '/>';
|
|
338
397
|
|
|
339
398
|
return dateField;
|
|
340
399
|
}
|
|
341
400
|
|
|
342
401
|
// date time local field tag
|
|
343
402
|
datetimeLocalField(name, obj){
|
|
403
|
+
const safeName = escapeHTML(String(name));
|
|
344
404
|
|
|
345
|
-
|
|
405
|
+
let datetimeLocalField = `<input type="datetime-local" name="${safeName}"`;
|
|
346
406
|
|
|
347
|
-
for (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
407
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
408
|
+
const safeKey = escapeHTML(String(key));
|
|
409
|
+
const safeValue = escapeHTML(String(value));
|
|
410
|
+
datetimeLocalField += ` ${safeKey}="${safeValue}"`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
datetimeLocalField += '/>';
|
|
353
414
|
|
|
354
415
|
return datetimeLocalField;
|
|
355
416
|
}
|
|
356
417
|
|
|
357
418
|
// date month field tag
|
|
358
419
|
monthField(name, obj){
|
|
420
|
+
const safeName = escapeHTML(String(name));
|
|
359
421
|
|
|
360
|
-
|
|
422
|
+
let monthField = `<input type="month" name="${safeName}"`;
|
|
361
423
|
|
|
362
|
-
for (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
424
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
425
|
+
const safeKey = escapeHTML(String(key));
|
|
426
|
+
const safeValue = escapeHTML(String(value));
|
|
427
|
+
monthField += ` ${safeKey}="${safeValue}"`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
monthField += '/>';
|
|
368
431
|
|
|
369
432
|
return monthField;
|
|
370
433
|
}
|
|
371
434
|
|
|
372
435
|
// date week field tag
|
|
373
436
|
weekField(name, obj){
|
|
437
|
+
const safeName = escapeHTML(String(name));
|
|
374
438
|
|
|
375
|
-
|
|
439
|
+
let weekField = `<input type="week" name="${safeName}"`;
|
|
440
|
+
|
|
441
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
442
|
+
const safeKey = escapeHTML(String(key));
|
|
443
|
+
const safeValue = escapeHTML(String(value));
|
|
444
|
+
weekField += ` ${safeKey}="${safeValue}"`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
weekField += '/>';
|
|
376
448
|
|
|
377
|
-
for (var key in obj) {
|
|
378
|
-
if (obj.hasOwnProperty(key)) {
|
|
379
|
-
weekField = weekField + " " + key + "=" + "'" + obj[key] + "'";
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
weekField = weekField + '/>';
|
|
383
|
-
|
|
384
449
|
return weekField;
|
|
385
450
|
}
|
|
386
451
|
|
|
387
452
|
// date url field tag
|
|
388
453
|
urlField(name, obj){
|
|
389
|
-
|
|
390
|
-
var urlField = "<input type='url' name='" + name + "' ";
|
|
454
|
+
const safeName = escapeHTML(String(name));
|
|
391
455
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
456
|
+
let urlField = `<input type="url" name="${safeName}"`;
|
|
457
|
+
|
|
458
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
459
|
+
const safeKey = escapeHTML(String(key));
|
|
460
|
+
const safeValue = escapeHTML(String(value));
|
|
461
|
+
urlField += ` ${safeKey}="${safeValue}"`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
urlField += '/>';
|
|
398
465
|
|
|
399
466
|
return urlField;
|
|
400
467
|
}
|
|
@@ -402,76 +469,92 @@ class html {
|
|
|
402
469
|
|
|
403
470
|
// date email field tag
|
|
404
471
|
emailField(name, obj){
|
|
405
|
-
|
|
406
|
-
var emailField = "<input type='email' name='" + name + "' ";
|
|
472
|
+
const safeName = escapeHTML(String(name));
|
|
407
473
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
474
|
+
let emailField = `<input type="email" name="${safeName}"`;
|
|
475
|
+
|
|
476
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
477
|
+
const safeKey = escapeHTML(String(key));
|
|
478
|
+
const safeValue = escapeHTML(String(value));
|
|
479
|
+
emailField += ` ${safeKey}="${safeValue}"`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
emailField += '/>';
|
|
414
483
|
|
|
415
484
|
return emailField;
|
|
416
485
|
}
|
|
417
486
|
|
|
418
487
|
// date color field tag
|
|
419
488
|
colorField(name, color, obj){
|
|
420
|
-
|
|
421
|
-
|
|
489
|
+
const safeName = escapeHTML(String(name));
|
|
490
|
+
const safeColor = escapeHTML(String(color));
|
|
422
491
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
492
|
+
let colorField = `<input type="color" name="${safeName}" value="${safeColor}"`;
|
|
493
|
+
|
|
494
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
495
|
+
const safeKey = escapeHTML(String(key));
|
|
496
|
+
const safeValue = escapeHTML(String(value));
|
|
497
|
+
colorField += ` ${safeKey}="${safeValue}"`;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
colorField += '/>';
|
|
429
501
|
|
|
430
502
|
return colorField;
|
|
431
503
|
}
|
|
432
504
|
|
|
433
505
|
// date time field tag
|
|
434
506
|
timeField(name, obj){
|
|
435
|
-
|
|
436
|
-
var timeField = "<input type='time' name='" + name + "' ";
|
|
507
|
+
const safeName = escapeHTML(String(name));
|
|
437
508
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
509
|
+
let timeField = `<input type="time" name="${safeName}"`;
|
|
510
|
+
|
|
511
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
512
|
+
const safeKey = escapeHTML(String(key));
|
|
513
|
+
const safeValue = escapeHTML(String(value));
|
|
514
|
+
timeField += ` ${safeKey}="${safeValue}"`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
timeField += '/>';
|
|
444
518
|
|
|
445
519
|
return timeField;
|
|
446
520
|
}
|
|
447
521
|
|
|
448
522
|
// date number field tag
|
|
449
523
|
numberField(name, min, max, step, obj){
|
|
450
|
-
|
|
451
|
-
|
|
524
|
+
const safeName = escapeHTML(String(name));
|
|
525
|
+
const safeMin = escapeHTML(String(min));
|
|
526
|
+
const safeMax = escapeHTML(String(max));
|
|
527
|
+
const safeStep = escapeHTML(String(step));
|
|
452
528
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
529
|
+
let numberField = `<input type="number" name="${safeName}" min="${safeMin}" max="${safeMax}" step="${safeStep}"`;
|
|
530
|
+
|
|
531
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
532
|
+
const safeKey = escapeHTML(String(key));
|
|
533
|
+
const safeValue = escapeHTML(String(value));
|
|
534
|
+
numberField += ` ${safeKey}="${safeValue}"`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
numberField += '/>';
|
|
459
538
|
|
|
460
539
|
return numberField;
|
|
461
540
|
}
|
|
462
541
|
|
|
463
542
|
// date range field tag
|
|
464
543
|
rangeField(name, min, max, obj){
|
|
544
|
+
const safeName = escapeHTML(String(name));
|
|
545
|
+
const safeMin = escapeHTML(String(min));
|
|
546
|
+
const safeMax = escapeHTML(String(max));
|
|
465
547
|
|
|
466
|
-
|
|
548
|
+
let rangeField = `<input type="range" name="${safeName}" min="${safeMin}" max="${safeMax}"`;
|
|
549
|
+
|
|
550
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
551
|
+
const safeKey = escapeHTML(String(key));
|
|
552
|
+
const safeValue = escapeHTML(String(value));
|
|
553
|
+
rangeField += ` ${safeKey}="${safeValue}"`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
rangeField += '/>';
|
|
467
557
|
|
|
468
|
-
for (var key in obj) {
|
|
469
|
-
if (obj.hasOwnProperty(key)) {
|
|
470
|
-
rangeField = rangeField + " " + key + "=" + "'" + obj[key] + "'";
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
rangeField = rangeField + '/>';
|
|
474
|
-
|
|
475
558
|
return rangeField;
|
|
476
559
|
}
|
|
477
560
|
|
package/MasterPipeline.js
CHANGED
|
@@ -44,7 +44,7 @@ class MasterPipeline {
|
|
|
44
44
|
/**
|
|
45
45
|
* Run: Add terminal middleware that ends the pipeline
|
|
46
46
|
*
|
|
47
|
-
* Terminal middleware signature: async (ctx) => {
|
|
47
|
+
* Terminal middleware signature: async (ctx) => { ... send response ... }
|
|
48
48
|
* - Does NOT call next()
|
|
49
49
|
* - Must send response
|
|
50
50
|
*
|