slower 1.1.26 → 2.0.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/lib/router.js DELETED
@@ -1,476 +0,0 @@
1
- const http = require('http');
2
- const https = require('https');
3
- const fs = require('fs');
4
- const { clone, noop, slugify, isSparseEqual, last, renderDynamicHTML, setSocketLocals, setSocketSecurityHeaders, setResponseAssessors, isPromise, UUID } = require('./utils');
5
- const mimetable = require('./mimetable.json');
6
-
7
- class Route {
8
- constructor (path, type, callback) {
9
- this.path = (path.startsWith('/') ? path : '/' + path);
10
- this.type = SlowerRouter.http_methods.includes(slugify(type).toUpperCase()) ? slugify(type).toUpperCase() : null;
11
- this.callback = (typeof callback == 'function' ? callback : noop);
12
- }
13
- }
14
-
15
- class SlowerRouter {
16
- static http_methods = [
17
- 'GET', 'POST', 'PUT',
18
- 'HEAD', 'DELETE', 'OPTIONS',
19
- 'TRACE', 'COPY', 'LOCK',
20
- 'MKCOL', 'MOVE', 'PURGE',
21
- 'PROPFIND', 'PROPPATCH', 'UNLOCK',
22
- 'REPORT', 'MKACTIVITY', 'CHECKOUT',
23
- 'MERGE', 'M-SEARCH', 'NOTIFY',
24
- 'SUBSCRIBE', 'UNSUBSCRIBE', 'PATCH',
25
- 'SEARCH', 'CONNECT'
26
- ];
27
-
28
- static mime_table = mimetable;
29
-
30
- constructor () {
31
- this.strictHeaders = false;
32
- this.routes = [];
33
- this.middleware = [noop];
34
- this.fallback = noop;
35
- this.allowedMethods = clone(SlowerRouter.http_methods);
36
- this.blockedMethodCallback = noop;
37
- this.tls = { use: false, key: null, cert: null };
38
- this.server = null;
39
- this.connectionpool = {};
40
- }
41
-
42
- enableStrictHeaders () {
43
- // This activates the responses with many security-related response headers.
44
- // It is not enabled by default, as some header are very restrictive
45
- this.strictHeaders = true;
46
- return this;
47
- }
48
-
49
- disableStrictHeaders () {
50
- // This deactivates the responses with many security-related response headers.
51
- // It is not enabled by default, as some headers are very restrictive
52
- this.strictHeaders = false;
53
- return this;
54
- }
55
-
56
- setSecureContext (key = null, cert = null) {
57
- // Activates TLS and HTTPS using a certificate and a key
58
- if (!key || !cert || !Buffer.isBuffer(key) || !Buffer.isBuffer(cert)) {
59
- throw new Error ("INVALID SECURE CONTEXT INFORMATION PROVIDED - CHECK KEY AND CERTIFICATE");
60
- }
61
- this.tls.use = true;
62
- this.tls.key = key;
63
- this.tls.cert = cert;
64
- return this;
65
- }
66
-
67
- /**
68
- * Defines a route for a determined request method
69
- * @category Router
70
- * @param {String} path The route that will be defined
71
- * @param {String} type The method to respond to
72
- * @param {Function} callback Callback to use when the chosen route is accessed
73
- * @returns {Object} An Route object
74
- * @example <caption> Defining a simple GET route:</caption>
75
- * setRoute('/', 'GET', (req, res) => {
76
- * console.log(res.url);
77
- * res.end('received');
78
- * });
79
- * // => <Route> { path:..., type:..., callback:... }
80
- */
81
- setRoute = function (path = '/', type = 'GET', callback) {
82
- let stat = new Route(path, type, callback);
83
- this.routes.push(stat);
84
- return this;
85
- }
86
-
87
- /**
88
- * Defines a middleware for all requests
89
- * @category Middleware
90
- * @param {Function} callback Callback to use when the requests are made
91
- * @returns {Array} The used middlewares list
92
- */
93
- setMiddleware = function (callback) {
94
- this.middleware.push((typeof callback == 'function' ? callback : noop));
95
- return this;
96
- }
97
-
98
- /**
99
- * Defines a route for a determined request method
100
- * @category Router
101
- * @param {String} path The route that will be defined
102
- * @param {String} file The file path that will be used to respond to the route
103
- * @param {String} mime The file's mime type
104
- * @param {Object} replacementData The replacement data map for the dynamic HTML rendering
105
- * @returns {Object} An Route object already configured
106
- */
107
- /**
108
- * Dynamic rendering example:
109
- * It's a template engine, to render HTML containing template spaces.
110
- * The charset for replacement is <{content}>
111
- * @since 1.2.5
112
- *
113
- * @param {String} html The HTML code
114
- * @param {Object} patterns The patterns to replace in the HTML code
115
- * @return {String} The HTML with the templates replaces
116
- *
117
- * @example <caption> Rendering: </caption>
118
- * var template = 'Hello, my name is <{name}>. I\\'m <{age}> years old.';
119
- * console.log(TemplateEngine(template, {
120
- * name: "Krasimir",
121
- * age: 29
122
- * }));
123
- */
124
- setDynamic = function (path, file = '', mime = '', replacementData = null) {
125
- let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8');
126
- if (file.includes('{%}')) { file = file.replace(/\{\%\}/gim, path.replace(/\//gim,'')); }
127
- let stat = new Route(path, 'GET', (req, res) => {
128
- let data, targetFile, extension, targetMime, targetEncoding;
129
- if (fs.existsSync(file) && fs.lstatSync(file).isDirectory()) {
130
- targetFile = file.replace(/\//gim, '\\');
131
- targetFile = ((targetFile.endsWith('\\')) ? targetFile : targetFile+'\\') + req.url.replace('/', '');
132
- } else {
133
- targetFile = ((file == '' || !file) ? req.url : file);
134
- }
135
- extension = last(targetFile.split('.'));
136
- targetMime = mime || (SlowerRouter.mime_table[extension]?.[0] || SlowerRouter.mime_table.default[0]);
137
- targetEncoding = encoding || (SlowerRouter.mime_table[extension]?.[1] || SlowerRouter.mime_table.default[1]);
138
- try {
139
- data = fs.readFileSync(targetFile, targetEncoding);
140
- } catch (err) {
141
- data = '';
142
- }
143
- try {
144
- if (replacementData) data = renderDynamicHTML(data, replacementData);
145
- } catch (err) {}
146
- res.writeHead(200, { 'Content-Type': targetMime });
147
- res.write(data);
148
- res.end();
149
- });
150
- this.routes.push(stat);
151
- return this;
152
- }
153
-
154
- // SERVES FILES FOR SPECIFIC PATHS. WILDCARS ALLOWED. BE CAREFUL - PATH TRAVERSAL MAY BE POSSIBLE
155
- // Use app.setStatic('/*', __dirname+'/') for serving all local files.
156
- // or use app.setStatic('/*', __dirname+'/public/') for serving files in a specific folder (public).
157
- // This is a one-liner for the setRoute function, when the route responds with a simple page or document
158
- /**
159
- * Defines a route for a determined request method
160
- * @category Router
161
- * @param {String} path The route that will be defined
162
- * @param {String} file The file path that will be used to respond to the route
163
- * @param {String} mime The file's mime type
164
- * @returns {Object} An Route object already configured
165
- * @example <caption> Defining a simple GET route:</caption>
166
- * setStatic('/login', __dirname+'/public/static/views/{%}.html', 'text/html');
167
- */
168
- setStatic = function (path, file = '', mime = '') {
169
- let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8');
170
- if (file.includes('{%}')) { file = file.replace(/\{\%\}/gim, path.replace(/\//gim,'')); }
171
- let stat = new Route(path, 'GET', (req, res) => {
172
- let data, targetFile, extension, targetMime, targetEncoding;
173
- if (fs.existsSync(file) && fs.lstatSync(file).isDirectory()) {
174
- targetFile = file.replace(/\//gim, '\\');
175
- targetFile = ((targetFile.endsWith('\\')) ? targetFile : targetFile+'\\') + req.url.replace('/', '');
176
- } else {
177
- targetFile = ((file == '' || !file) ? req.url : file);
178
- }
179
- extension = last(targetFile.split('.'));
180
- targetMime = mime || (SlowerRouter.mime_table[extension]?.[0] || SlowerRouter.mime_table.default[0]);
181
- targetEncoding = encoding || (SlowerRouter.mime_table[extension]?.[1] || SlowerRouter.mime_table.default[1]);
182
- try {
183
- data = fs.readFileSync(targetFile, targetEncoding);
184
- } catch (err) {
185
- data = '';
186
- }
187
- res.writeHead(200, { 'Content-Type': targetMime });
188
- res.write(data);
189
- res.end();
190
- });
191
- this.routes.push(stat);
192
- return this;
193
- }
194
-
195
- /**
196
- * Defines a default response for non-defined routes (custom treated 404 error)
197
- * @category Router
198
- * @param {String} callback The function to execute for unhandled routes
199
- * @returns {undefined}
200
- */
201
- setFallback = function (callback) { this.fallback = callback; return this; }
202
-
203
- // SERVES FILES FOR SPECIFIC PATHS. WILDCARS ALLOWED. BE CAREFUL - PATH TRAVERSAL MAY BE POSSIBLE
204
- // Use app.setStatic('/*', __dirname+'/') for serving all local files.
205
- // or use app.setStatic('/*', __dirname+'/public/') for serving files in a specific folder (public).
206
- // This is a one-liner for the setRoute function, when the route responds with a simple page or document
207
- /**
208
- * Defines a route for a determined request method
209
- * @category Router
210
- * @param {String} path The route that will be defined
211
- * @param {String} file The file path that will be used to respond to the route
212
- * @param {String} mime The file's mime type
213
- * @param {Object} replacementData The replacement data map for the dynamic HTML rendering
214
- * @returns {Object} An Route object already configured
215
- * @example <caption> Defining a simple GET route:</caption>
216
- * setStatic('/login', __dirname+'/public/static/views/login.html', 'text/html', 'utf8');
217
- */
218
- /**
219
- * Dynamic rendering example:
220
- * It's a template engine, to render HTML containing template spaces.
221
- * The charset for replacement is <{content}>
222
- * @since 1.2.5
223
- *
224
- * @param {String} html The HTML code
225
- * @param {Object} patterns The patterns to replace in the HTML code
226
- * @return {String} The HTML with the templates replaces
227
- *
228
- * @example <caption> Rendering: </caption>
229
- * var template = 'Hello, my name is <{name}>. I\\'m <{age}> years old.';
230
- * console.log(TemplateEngine(template, {
231
- * name: "Krasimir",
232
- * age: 29
233
- * }));
234
- */
235
- setFallbackFile = function (file = '', mime = '', replacementData = null) {
236
- this.fallback = function fb (req,res) {
237
- let encoding = (mime === SlowerRouter.mime_table['default'] ? undefined : 'utf-8');
238
- let data, targetFile, extension, targetMime, targetEncoding;
239
- if (fs.existsSync(file) && fs.lstatSync(file).isDirectory()) {
240
- targetFile = file.replace(/\//gim, '\\');
241
- targetFile = ((targetFile.endsWith('\\')) ? targetFile : targetFile+'\\') + req.url.replace('/', '');
242
- } else {
243
- targetFile = ((file == '' || !file) ? req.url : file);
244
- }
245
- extension = last(targetFile.split('.'));
246
- targetMime = mime || (SlowerRouter.mime_table[extension]?.[0] || SlowerRouter.mime_table.default[0]);
247
- targetEncoding = encoding || (SlowerRouter.mime_table[extension]?.[1] || SlowerRouter.mime_table.default[1]);
248
- try {
249
- data = fs.readFileSync(targetFile, targetEncoding);
250
- } catch (err) {
251
- data = '';
252
- }
253
- try {
254
- if (replacementData) data = renderDynamicHTML(data, replacementData);
255
- } catch (err) {}
256
- res.writeHead(200, { 'Content-Type': targetMime });
257
- res.write(data);
258
- res.end();
259
- }
260
- return this;
261
- }
262
-
263
- // Sets the methods that the application will respond to. The rest is simply discarded with empty responses.
264
- // To configure a deeper level of error handling, or to serve customized 'METHOD_NOT_ALLOWED' errors,
265
- // use 'setRoute' with params ('/*', {{method_name}}, (req,res) => { and serve the error file here });
266
- // INFO: Middlewares are still triggered when a blocked method request comes.
267
- /**
268
- * Defines a route for a determined request method
269
- * @category Router
270
- * @param {Array} methods The methods that are allowed by the application. Methods that do not conform with standards are ignored.
271
- * @returns {Object} The AllowedMethods Array object.
272
- * @example <caption> Allowing only GET and POST:</caption>
273
- * app.setAllowedMethods(['GET', 'POST']);
274
- */
275
- setAllowedMethods = function (methods = []) {
276
- this.allowedMethods = [];
277
- // If not specified, all methods are allowed
278
- if (methods.length == 0 || methods == '*') {
279
- return this.allowedMethods = JSON.parse(JSON.stringify(SlowerRouter.http_methods));
280
- }
281
- for (let i = 0; i < methods.length; i++) {
282
- if (!SlowerRouter.http_methods.includes(methods[i])) continue;
283
- this.allowedMethods.push(methods[i]);
284
- }
285
- // Default callback for blocked methods respond with code 200 and an empty response
286
- this.blockedMethodCallback = function (req,res) {
287
- res.writeHead(405); // sends the meme error 418 'i am a teapot'
288
- res.end();
289
- };
290
- return this;
291
- }
292
-
293
- /*
294
- Route Objects looks like:
295
-
296
- {
297
- type: "route",
298
- path: "/",
299
- method: "GET",
300
- handle: someFunction
301
- }
302
-
303
- {
304
- type: "middleware",
305
- handle: someFunction
306
- }
307
-
308
- {
309
- type: "fallback",
310
- handle: someFunction
311
- }
312
-
313
- {
314
- type: "static",
315
- path: "/style.css",
316
- file: "./www/assets/style.css"
317
- }
318
-
319
- {
320
- type: "dynamic",
321
- path: "/client.html",
322
- file: "./www/templates/client.html",
323
- data: {
324
- "CLIENTID": someRenderingFunction
325
- }
326
- }
327
-
328
- {
329
- type: "fallbackfile",
330
- file: "./www/pages/404.html"
331
- data: {
332
- "CUSTOM_ERROR_MESSAGE": () => { return err.message },
333
- "CUSTOM_ERROR_CODE": someErrorCodeFetchingFunction
334
- }
335
- }
336
-
337
- */
338
- // Converts a series of objectRoutes into actual internal routes
339
- parse = function (routeObjectList) {
340
- for (let entry of routeObjectList) {
341
- const { type, path, method, file, data, handle } = entry;
342
- switch (type) {
343
- case 'route': this.setRoute(path, method, handle); break;
344
- case 'middleware': this.setMiddleware(handle); break;
345
- case 'fallback': this.setFallback(handle); break;
346
- case 'static': this.setStatic(path, file, undefined); break;
347
- case 'dynamic': this.setDynamic(path, file, undefined, data); break;
348
- case 'fallbackfile': this.setFallbackFile(file, undefined, data); break;
349
- }
350
- }
351
- return this;
352
- }
353
-
354
- // Starts the server - listening at <host>:<port>
355
- /**
356
- * @category Router
357
- * @param {Number} port The port number the server will listen to.
358
- * @param {String} host The host's network interface address the server will listen into (use a falsy value or '0.0.0.0' for listening on all).
359
- * @returns {Promise<http.Server>} The server instance
360
- */
361
- start = async function (port = 8080, host = undefined) {
362
- let routes = this.routes;
363
- let middle = this.middleware;
364
- let fallback = this.fallback;
365
- let allowedMethods = this.allowedMethods;
366
- let blockedMethodCallback = this.blockedMethodCallback;
367
-
368
- if (this.tls.use && this.tls.cert && this.tls.key) {
369
- this.server = https.createServer({ key:this.tls.key, cert: this.tls.cert }, mainServerHandler);
370
-
371
- } else {
372
- this.server = http.createServer({}, mainServerHandler);
373
- }
374
-
375
- this.server.on('connection', (socket) => {
376
- const id = UUID();
377
- this.connectionpool[id] = socket;
378
- socket.on('close', () => delete this.connectionpool[id]);
379
- socket.on('error', () => delete this.connectionpool[id]);
380
- socket.on('timeout', () => { this.connectionpool[id].destroy(); delete this.connectionpool[id]; });
381
- });
382
-
383
- async function mainServerHandler (req, res) {
384
- try {
385
- // Sets local req.session object
386
- setSocketLocals(req);
387
- setResponseAssessors(res);
388
- // Sets security headers in req.headers
389
- // This is set before custom callbacks are applies
390
- // so it is possible to override all of the headers
391
- if (!!this.strictHeaders) setSocketSecurityHeaders(req);
392
- // Runs all middlewares
393
- for (let i = 0; i < middle.length; i++) {
394
- const rr = middle[i](req, res);
395
- if (isPromise(rr)) await rr;
396
- if (res.writableEnded) return; // if res.end() is caller early in a middleware
397
- }
398
- // Only respond to allowed methods with callbacks, else, use the default empty response.
399
- if (allowedMethods.includes(req.method)) {
400
- for (let i = 0; i < routes.length; i++) {
401
- let route = routes[i];
402
- if (
403
- route.type === req.method &&
404
- (
405
- route.path === req.url ||
406
- isSparseEqual(route.path, req.url)
407
- )
408
- ) {
409
- i = routes.length;
410
- const rr = route.callback(req, res);
411
- if (isPromise(rr)) await rr;
412
- // if (res.writableEnded) return; // if res.end() was called
413
- // else res.end(); // if res.end() was not called
414
- return;
415
- }
416
- }
417
- const rr = fallback(req,res);
418
- if (isPromise(rr)) await rr;
419
- if (!res.writableEnded) res.end();
420
- } else {
421
- blockedMethodCallback(req, res);
422
- if (!res.writableEnded) res.end();
423
- }
424
- } catch(err) {
425
- console.log(err);
426
- }
427
- }
428
-
429
- this.port = port;
430
- this.host = host;
431
- if (!host) host = undefined; // Turn falsy values into undefined, for default behaviour
432
- return new Promise((resolve, reject) => this.server.listen(port, host, () => resolve(this)));
433
- }
434
-
435
- /**
436
- *
437
- * @param {Object} options
438
- * @returns
439
- * Options include:
440
- * forceDisconnection: boolean (default: false) - If enabled, force sockets to disconnect after a 2 seconds timeout.
441
- * instantClose: boolean (default: false) - Only relevant if 'forceDisconnection' is enabled. If enabled, force sockets to disconnect immediately.
442
- * silent: boolean (default: true) - If set to false, show the disconnection status messages.
443
- */
444
- close = async function ({ forceDisconnection = false, instantClose = false, silent = true } = {forceDisconnection: false, instantClose: false, silent: true}) {
445
- return new Promise (async (resolve, reject) => {
446
- this.server.on('close', () => resolve());
447
- this.server.on('error', (err) => reject(err));
448
-
449
- // After this, no more sockets can connect, but the already connected ones, remain
450
- this.server.close();
451
- if (!silent) console.log('<> Shutting down server...');
452
-
453
- if (forceDisconnection) {
454
- // wait 1 second before continuing
455
- if (!instantClose) await new Promise ((resolve1, reject1) => setTimeout(resolve1, 1000));
456
-
457
- // If there are sockets still connected (waiting for timeout),
458
- // wait until all disconnected or force all to disconnect after 2 seconds
459
- if (!silent) {
460
- const activesockets = await new Promise((resolve2, reject2) => this.server.getConnections((err,count) => resolve2(count)));
461
- console.log(`<> Awaiting disconnection of active sockets [${activesockets}]`);
462
- }
463
-
464
- // wait 1 second and destroy the ones still connected
465
- if (!instantClose) await new Promise ((resolve1, reject1) => setTimeout(resolve1, 1000));
466
- for (let sock in this.connectionpool) {
467
- this.connectionpool[sock].destroy();
468
- }
469
- }
470
- });
471
- }
472
- }
473
-
474
- const Slower = function () { return new SlowerRouter(); }
475
-
476
- module.exports = Slower;
package/lib/utils.js DELETED
@@ -1,172 +0,0 @@
1
- const noop = function () {};
2
-
3
- const last = (array) => { return array[array.length-1]; }
4
-
5
- const slugify = (string, replacement = '-', replaceSpaces = true) => {
6
- return (string
7
- .replace(/<(?:.|\n)*?>/gm, '')
8
- .replace(/[!\"#$%&'\(\)\*\+,\/:;<=>\?\@\[\\\]\^`\{\|\}~]/g, '')
9
- .replace((replaceSpaces ? /(\s|\.)/g : /(\.)/g), replacement)
10
- .replace(/—/g, replacement)
11
- .replace(/-{2,}/g, replacement));
12
- }
13
-
14
- /**
15
- * Compares two strings in 'sparse' mode. Using the wildcards '{*}' and '{?}' to match strings.
16
- * Use '{*}' for any number of (any) characters, and {?}' for one (any) character.
17
- *
18
- * @since 1.2.7
19
- *
20
- * @param {String} str1 The first string to compare
21
- * @param {String} str2 The second string to compare
22
- * @return {Boolean} If the strings are sparsely equal or not
23
- * @example <caption> Comparing simple strings: </caption>
24
- * isSparseEqual("hello", "hello")
25
- * // => true
26
- * isSparseEqual("hello", "wello")
27
- * // => false
28
- * @example <caption> Comparing complex strings: </caption>
29
- * isSparseEqual("{?}ello", "hello")
30
- * // => true
31
- * isSparseEqual("h*", "hello")
32
- * // => true
33
- * isSparseEqual("h{*}e", "hello")
34
- * // => false
35
- * isSparseEqual("h{*}e", "helle")
36
- * // => true
37
- */
38
- const isSparseEqual = (str1 = '', str2 = '') => {
39
- const string1 = str1.replace(/{\?}/g, '.').replace(/{\*}/g, '.*');
40
- const string2 = str2.replace(/{\?}/g, '.').replace(/{\*}/g, '.*');
41
- const regex = new RegExp(`^${string1}$`);
42
- return regex.test(string2);
43
- }
44
-
45
- /**
46
- * It's a template engine, to render HTML containing template spaces.
47
- * The charset for replacement is {{content}}
48
- * @since 1.2.5
49
- *
50
- * @param {String} html The HTML code
51
- * @param {Object} patterns The patterns to replace in the HTML code
52
- * @return {String} The HTML with the templates replaces
53
- *
54
- * @example <caption> Rendering: </caption>
55
- * var template = 'Hello, my name is {{name}}. I\\'m {{age}} years old.';
56
- * console.log(TemplateEngine(template, {
57
- * name: "Krasimir",
58
- * age: 29
59
- * }));
60
- */
61
- const renderDynamicHTML = (html, patterns) => {
62
- let template = html;
63
- for (let item in patterns) {
64
- template = template.replace(
65
- new RegExp('{{'+item+'}}', 'gim'),
66
- patterns[item]
67
- );
68
- }
69
- return template;
70
- }
71
-
72
- const setSocketLocals = (reqSocket) => {
73
- reqSocket.session = {};
74
- reqSocket.session.port = reqSocket.socket.localPort;
75
- reqSocket.session.rport = reqSocket.socket.remotePort;
76
- reqSocket.session.host = (
77
- reqSocket.socket.localAddress.startsWith('::') ?
78
- reqSocket.socket.localAddress.substring(
79
- reqSocket.socket.localAddress.indexOf(':',2)+1
80
- ) : reqSocket.socket.localAddress);
81
- reqSocket.session.rhost = (
82
- reqSocket.socket.remoteAddress.startsWith('::') ?
83
- reqSocket.socket.remoteAddress.substring(
84
- reqSocket.socket.remoteAddress.indexOf(':',2)+1
85
- ) : reqSocket.socket.remoteAddress);
86
- return reqSocket;
87
- }
88
-
89
- const setResponseAssessors = (resSocket) => {
90
- resSocket.sendText = (data, code = 200) => {
91
- resSocket.writeHead(code, { 'Content-Type': 'text/plain' });
92
- resSocket.write(data);
93
- resSocket.end();
94
- };
95
- resSocket.sendJSON = (data, code = 200) => {
96
- resSocket.writeHead(code, { 'Content-Type': 'application/json' });
97
- resSocket.write(typeof data === 'string' ? data : JSON.stringify(data));
98
- resSocket.end();
99
- };
100
- resSocket.sendHTML = (data, code = 200) => {
101
- resSocket.writeHead(code, { 'Content-Type': 'text/html' });
102
- resSocket.write(data);
103
- resSocket.end();
104
- };
105
- }
106
-
107
- const setSocketSecurityHeaders = (req) => {
108
- // This should be set in a regular website:
109
- // Forces the use of HTTPS for a long time, including subDomains - and prevent MitM attacks
110
- // Does not work in servers that don't allow HTTPS (like this one)
111
- // req.setHeader('Strict-Transport-Security', ['max-age=31536000', 'includeSubDomains']); // Only works on HTTPS
112
-
113
- // This blocks requests with MIME type different from style/css and */script
114
- // TODO test this - probably gonna break the server, as MIMEs are not overriden here
115
- // req.setHeader('X-Content-Type-Options', 'nosniff');
116
-
117
- // This deines the main security policy - Usually, when using a website,
118
- // this should be highly customized, but, for simple sites, it can be leaved like that:
119
- req.setHeader('Content-Security-Policy', [
120
- 'default-src=none',
121
- 'script-src=self',
122
- 'connect-src=self',
123
- 'img-src=self',
124
- 'style-src=self',
125
- 'frame-ancestors=none',
126
- 'form-action=self',
127
- // 'upgrade-insecure-requests' // Only works on HTTPS
128
- ]);
129
- // Isolates the browsing context exclusively to same-origin documents.
130
- // Cross-origin documents are not loaded in the same browsing context.
131
- req.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
132
- // The HTTP Cross-Origin-Resource-Policy response header conveys a desire
133
- // that the browser blocks no-cors cross-origin/cross-site requests to the given resource.
134
- req.setHeader('Cross-Origin-Resource-Policy', 'same-site');
135
- // A new HTTP response header that instructs the browser
136
- // to prevent synchronous scripting access between same-site cross-origin pages.
137
- req.setHeader('Origin-Agent-Cluster', '?1');
138
- // Blocks information about the website when sending
139
- // local requests or redirections to other sites
140
- req.setHeader('Referrer-Policy', 'no-referrer');
141
- // Enabling this makes all URLs in a page (even the cross domain ones)
142
- // to be prefetched - This is dangerouns in terms of DNS queries
143
- req.setHeader('X-DNS-Prefetch-Control', 'off');
144
- // A legacy header, just for IE, and is highly dangerous
145
- // Not setting this as disabled can cause malicious
146
- // HTML+JS code to be loaded in the wrong context
147
- req.setHeader('X-Download-Options', 'noopen');
148
- // Blocks attempts to display the website as an IFrame
149
- // If another website tries to display this website as a frame,
150
- // this header will block it
151
- req.setHeader('X-Frame-Options', 'DENY');
152
- // Set the unnecessary XSS-Protection legacy header to disabled
153
- // This header increases the number of vulnerabilities, and is used only in IE
154
- req.setHeader('X-XSS-Protection', 0);
155
- // Remove X-Powered-By - This header allows attackers to
156
- // gather information about the application engine
157
- if (req.hasHeader('X-Powered-By')) req.removeHeader('X-Powered-By');
158
- }
159
-
160
- const toBool = [() => true, () => false];
161
-
162
- const clone = (object) => JSON.parse(JSON.stringify(object));
163
-
164
- const isPromise = obj => {
165
- return obj !== null &&
166
- (typeof obj === 'object' || typeof obj === 'function') &&
167
- typeof obj.then === 'function';
168
- }
169
-
170
- const UUID = () => '################################'.replace(new RegExp(`[#]`, 'gim'), () => Math.random().toString(16)[6]);
171
-
172
- module.exports = { clone, noop, slugify, isSparseEqual, toBool, last, renderDynamicHTML, setSocketLocals, setSocketSecurityHeaders, setResponseAssessors, isPromise, UUID };