total5 0.0.1 → 0.0.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/controller.js ADDED
@@ -0,0 +1,1640 @@
1
+ // Total.js Controller
2
+ // The MIT License
3
+ // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
4
+
5
+ 'use strict';
6
+
7
+ const REG_FILETMP = /\//g;
8
+ const REG_RANGE = /bytes=/;
9
+ const REG_ROBOT = /search|agent|bot|crawler|spider/i;
10
+ const REG_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Tablet/i;
11
+ const REG_ENCODINGCLEANER = /[;\s]charset=utf-8/g;
12
+
13
+ const CHECK_DATA = { POST: 1, PUT: 1, PATCH: 1, DELETE: 1 };
14
+ const CHECK_COMPRESSION = { 'text/plain': true, 'text/javascript': true, 'text/css': true, 'text/jsx': true, 'application/javascript': true, 'application/x-javascript': true, 'application/json': true, 'application/xml': true, 'text/xml': true, 'image/svg+xml': true, 'text/x-markdown': true, 'text/html': true, 'application/x-ipynb+json': true, 'application/x-ijsnb+json': true };
15
+ const CHECK_CHARSET = { 'text/plain': true, 'text/javascript': true, 'text/css': true, 'text/jsx': true, 'application/javascript': true, 'application/x-javascript': true, 'application/json': true, 'text/xml': true, 'text/x-markdown': true, 'text/html': true, 'application/x-ijsnb+json': true, 'application/x-ipynb+json': true };
16
+ const CHECK_NOCACHE = { zip: 1, rar: 1 };
17
+ const CHECK_MIN = /(\.|-|@)min/i;
18
+
19
+ const GZIP_FILE = { memLevel: 9 };
20
+ const GZIP_STREAM = { memLevel: 1 };
21
+
22
+ const NOCACHE = 'private, no-cache, no-store, max-age=0';
23
+
24
+ function Controller(req, res) {
25
+
26
+ var ctrl = this;
27
+
28
+ req.controller = ctrl;
29
+
30
+ ctrl.timeout = F.config.$httptimeout;
31
+ ctrl.req = req;
32
+ ctrl.res = res;
33
+ ctrl.method = ctrl.req.method;
34
+ ctrl.route = null;
35
+ ctrl.uri = F.TUtils.parseURI2(req.url);
36
+ ctrl.isfile = ctrl.uri.file;
37
+ ctrl.language = '';
38
+ ctrl.headers = req.headers;
39
+ ctrl.ext = ctrl.uri.ext;
40
+ ctrl.split = ctrl.uri.split;
41
+ ctrl.split2 = [];
42
+ ctrl.url = ctrl.uri.key;
43
+ ctrl.released = false;
44
+ ctrl.downloaded = false;
45
+ ctrl.protocol = req.connection.encrypted || (req.headers['x-forwarded-protocol'] || req.headers['x-forwarded-proto']) === 'https' ? 'https' : 'http';
46
+
47
+ for (let path of ctrl.split)
48
+ ctrl.split2.push(path.toLowerCase());
49
+
50
+ ctrl.params = {};
51
+ ctrl.query = ctrl.uri.search.parseEncoded();
52
+ ctrl.files = [];
53
+ ctrl.body = {};
54
+
55
+ if (ctrl.isfile)
56
+ F.stats.performance.file++;
57
+ else
58
+ F.stats.performance.request++;
59
+
60
+ // ctrl.payload = null;
61
+ // ctrl.payloadsize = 0;
62
+ // ctrl.user = null;
63
+ ctrl.datatype = ''; // json|xml|multipart|urlencoded
64
+
65
+ ctrl.response = {
66
+ status: 200,
67
+ cache: global.DEBUG != true,
68
+ minify: true,
69
+ // minifyjson: false
70
+ // encrypt: false
71
+ headers: {}
72
+ };
73
+
74
+ if (CHECK_DATA[ctrl.method]) {
75
+
76
+ if (ctrl.isfile) {
77
+ ctrl.destroyed = true;
78
+ ctrl.req.destroy();
79
+ return;
80
+ }
81
+
82
+ let type = ctrl.headers['content-type'] || '';
83
+ let index = type.indexOf(';', 10);
84
+
85
+ if (index != -1)
86
+ type = type.substring(0, index);
87
+
88
+ switch (type) {
89
+ case 'application/json':
90
+ case 'text/json':
91
+ ctrl.datatype = 'json';
92
+ break;
93
+ case 'application/x-www-form-urlencoded':
94
+ ctrl.datatype = 'urlencoded';
95
+ break;
96
+ case 'multipart/form-data':
97
+ ctrl.datatype = 'multipart';
98
+ break;
99
+ case 'application/xml':
100
+ case 'text/xml':
101
+ ctrl.datatype = 'xml';
102
+ break;
103
+ case 'text/html':
104
+ case 'text/plain':
105
+ ctrl.datatype = 'text';
106
+ break;
107
+ default:
108
+ ctrl.datatype = 'binary';
109
+ break;
110
+ }
111
+ }
112
+ }
113
+
114
+ Controller.prototype = {
115
+
116
+ get mobile() {
117
+ let ua = this.headers['user-agent'];
118
+ return ua ? REG_MOBILE.test(ua) : false;
119
+ },
120
+
121
+ get robot() {
122
+ let ua = this.headers['user-agent'];
123
+ return ua ? REG_ROBOT.test(ua) : false;
124
+ },
125
+
126
+ get xhr() {
127
+ return this.headers['x-requested-with'] === 'XMLHttpRequest';
128
+ },
129
+
130
+ get extension() {
131
+ return this.ext;
132
+ },
133
+
134
+ get ua() {
135
+ if (this.$ua != null)
136
+ return this.$ua;
137
+ let ua = this.headers['user-agent'];
138
+ this.$ua = ua ? ua.parseUA() : '';
139
+ return this.$ua;
140
+ },
141
+
142
+ get ip() {
143
+
144
+ if (this.$ip != null)
145
+ return this.$ip;
146
+
147
+ // x-forwarded-for: client, proxy1, proxy2, ...
148
+ let proxy = this.headers['x-forwarded-for'];
149
+ if (proxy)
150
+ this.$ip = proxy.split(',', 1)[0] || this.req.connection.remoteAddress;
151
+ else if (!this.$ip)
152
+ this.$ip = this.req.connection.remoteAddress;
153
+
154
+ return this.$ip;
155
+ },
156
+
157
+ get referrer() {
158
+ return this.headers.referer;
159
+ },
160
+
161
+ get host() {
162
+ return this.headers.host;
163
+ }
164
+
165
+ };
166
+
167
+ Controller.prototype.callback = function(err, value) {
168
+
169
+ var ctrl = this;
170
+
171
+ if (arguments.length == 0) {
172
+ return function(err, response) {
173
+ if (err)
174
+ ctrl.invalid(err);
175
+ else
176
+ ctrl.json(response);
177
+ };
178
+ }
179
+
180
+ if (err)
181
+ ctrl.invalid(err);
182
+ else {
183
+ if (value === undefined)
184
+ ctrl.success();
185
+ else
186
+ ctrl.json(value);
187
+ }
188
+
189
+ };
190
+
191
+ Controller.prototype.csrf = function() {
192
+ return F.def.onCSRFcreate(this);
193
+ };
194
+
195
+ Controller.prototype.redirect = function(value, permanent) {
196
+ var ctrl = this;
197
+
198
+ if (ctrl.destroyed)
199
+ return;
200
+
201
+ ctrl.response.headers.Location = value;
202
+ ctrl.response.status = permanent ? 301 : 302;
203
+ ctrl.flush();
204
+ F.stats.response.redirect++;
205
+ };
206
+
207
+ Controller.prototype.html = function(value) {
208
+ var ctrl = this;
209
+
210
+ if (ctrl.destroyed)
211
+ return;
212
+
213
+ ctrl.response.headers['content-type'] = 'text/html';
214
+
215
+ if (value != null)
216
+ ctrl.response.value = ctrl.response.minify && F.config.$minifyhtml ? F.TMinificators.html(value) : value;
217
+
218
+ ctrl.flush();
219
+ F.stats.response.html++;
220
+ };
221
+
222
+ Controller.prototype.xml = function(value) {
223
+ var ctrl = this;
224
+
225
+ if (ctrl.destroyed)
226
+ return;
227
+
228
+ if (value != null)
229
+ ctrl.response.value = value;
230
+
231
+ ctrl.response.headers['content-type'] = 'text/xml';
232
+ ctrl.flush();
233
+
234
+ F.stats.response.xml++;
235
+ };
236
+
237
+ Controller.prototype.text = Controller.prototype.plain = function(value) {
238
+ var ctrl = this;
239
+
240
+ if (ctrl.destroyed)
241
+ return;
242
+
243
+ ctrl.response.headers['content-type'] = 'text/plain';
244
+
245
+ if (value != null)
246
+ ctrl.response.value = value;
247
+
248
+ ctrl.flush();
249
+ F.stats.response.text++;
250
+ };
251
+
252
+ Controller.prototype.respond = function(value, a, b) {
253
+ var ctrl = this;
254
+ var output = ctrl.response.output;
255
+ switch (output) {
256
+ case 'html':
257
+ case 'plain':
258
+ case 'text':
259
+ case 'xml':
260
+ case 'json':
261
+ case 'redirect':
262
+ case 'jsonstring':
263
+ case 'empty':
264
+ case 'file':
265
+ ctrl[output](value, a, b);
266
+ break;
267
+ default:
268
+ ctrl.json(value, a, b);
269
+ break;
270
+ }
271
+ return ctrl;
272
+ };
273
+
274
+ Controller.prototype.json = function(value, beautify, replacer) {
275
+ var ctrl = this;
276
+
277
+ if (ctrl.destroyed)
278
+ return;
279
+
280
+ var response = ctrl.response;
281
+ response.headers['content-type'] = 'application/json';
282
+ response.headers['cache-control'] = NOCACHE;
283
+ response.headers.vary = 'Accept-Encoding, Last-Modified, User-Agent';
284
+ response.headers.expires = '-1';
285
+ response.value = JSON.stringify(value, beautify ? '\t' : null, replacer);
286
+ ctrl.flush();
287
+ F.stats.response.json++;
288
+ };
289
+
290
+ Controller.prototype.jsonstring = function(value) {
291
+ var ctrl = this;
292
+
293
+ if (ctrl.destroyed)
294
+ return;
295
+
296
+ var response = ctrl.response;
297
+ response.headers['content-type'] = 'application/json';
298
+ response.headers['cache-control'] = NOCACHE;
299
+ response.headers.vary = 'Accept-Encoding, Last-Modified, User-Agent';
300
+ response.headers.expires = '-1';
301
+ response.value = value;
302
+ response.type = 'json';
303
+ ctrl.flush();
304
+ F.stats.response.json++;
305
+ };
306
+
307
+ Controller.prototype.empty = function() {
308
+ var ctrl = this;
309
+
310
+ if (ctrl.destroyed)
311
+ return;
312
+
313
+ ctrl.response.status = 204;
314
+ ctrl.flush();
315
+ F.stats.response.empty++;
316
+ };
317
+
318
+ Controller.prototype.invalid = function(value) {
319
+
320
+ var ctrl = this;
321
+
322
+ if (ctrl.destroyed)
323
+ return;
324
+
325
+ var response = ctrl.response;
326
+ var err;
327
+
328
+ if (value instanceof F.TBuilders.ErrorBuilder) {
329
+ err = value;
330
+ } else {
331
+ err = new F.TBuilders.ErrorBuilder();
332
+ err.push(value);
333
+ }
334
+
335
+ response.headers['content-type'] = 'application/json';
336
+ response.headers['cache-control'] = NOCACHE;
337
+ response.headers.vary = 'Accept-Encoding, Last-Modified, User-Agent';
338
+ response.value = JSON.stringify(err.output(ctrl.language));
339
+ response.status = err.status === 408 ? 503 : err.status;
340
+ ctrl.flush();
341
+
342
+ var key = 'error' + err.status;
343
+
344
+ if (F.stats.response[key] != null)
345
+ F.stats.response[key]++;
346
+ };
347
+
348
+ Controller.prototype.flush = function() {
349
+
350
+ var ctrl = this;
351
+
352
+ if (ctrl.destroyed)
353
+ return;
354
+
355
+ let accept = ctrl.headers['accept-encoding'];
356
+ let response = ctrl.response;
357
+ let buffer = response.value ? response.value instanceof Buffer ? response.value : Buffer.from(response.value, 'utf8') : null;
358
+ let type = response.headers['content-type'];
359
+
360
+ if (F.config.$xpoweredby)
361
+ response.headers['x-powered-by'] = F.config.$xpoweredby;
362
+
363
+ // GZIP compression
364
+ if (F.config.$httpcompress && buffer && accept && buffer.length > 256 && accept.indexOf('gzip') !== -1) {
365
+ if (CHECK_COMPRESSION[type]) {
366
+
367
+ if (CHECK_CHARSET[type])
368
+ response.headers['content-type'] += '; charset=utf-8';
369
+
370
+ F.Zlib.gzip(buffer, function(err, buffer) {
371
+ if (err) {
372
+ ctrl.fallback(400, err.toString());
373
+ } else {
374
+ response.headers['content-encoding'] = 'gzip';
375
+ ctrl.res.writeHead(response.status, response.headers);
376
+ ctrl.res.end(buffer, 'utf8');
377
+ F.stats.performance.upload += buffer.length / 1024 / 1024;
378
+ ctrl.free();
379
+ }
380
+ });
381
+ return;
382
+ }
383
+ }
384
+
385
+ if (CHECK_CHARSET[type])
386
+ response.headers['content-type'] += '; charset=utf-8';
387
+
388
+ try {
389
+ ctrl.res.writeHead(response.status, response.headers);
390
+ ctrl.res.end(buffer);
391
+ } finally {
392
+ ctrl.free();
393
+ }
394
+ };
395
+
396
+ Controller.prototype.fallback = function(code, err) {
397
+ var ctrl = this;
398
+
399
+ if (ctrl.destroyed)
400
+ return;
401
+
402
+ let key = code + '';
403
+ var route = F.routes.fallback[key];
404
+ if (route) {
405
+ ctrl.route = route;
406
+ ctrl.route.action(ctrl);
407
+ } else {
408
+
409
+ var view;
410
+
411
+ if (ctrl.xhr) {
412
+ if (code === 999)
413
+ code = 503;
414
+ ctrl.invalid(code);
415
+ return;
416
+ }
417
+
418
+ // Paused
419
+ if (code === 999) {
420
+ view = F.temporary.views.$pause;
421
+ ctrl.response.status = 503;
422
+ if (!view) {
423
+ F.temporary.views.$pause = view = new F.TViewEngine.View();
424
+ view.compiled = F.TViewEngine.compile('$pause', F.Fs.readFileSync(F.Path.join(F.config.$total5, 'pause.html'), 'utf8'), false);
425
+ }
426
+ view.model = F.paused;
427
+ } else {
428
+ ctrl.response.status = code === 408 ? 503 : code;
429
+ view = F.temporary.views.$error;
430
+ if (!view) {
431
+ F.temporary.views.$error = view = new F.TViewEngine.View();
432
+ view.compiled = F.TViewEngine.compile('$error', F.Fs.readFileSync(F.Path.join(F.config.$total5, 'error.html'), 'utf8'), false);
433
+ }
434
+ view.model = { code: code, status: F.TUtils.httpstatus(code), error: err ? (DEBUG ? err.toString() : '') : '' };
435
+ }
436
+
437
+ ctrl.html(view.compiled(view));
438
+ }
439
+ };
440
+
441
+ Controller.prototype.layout = function(name) {
442
+ var ctrl = this;
443
+ ctrl.response.layout = name;
444
+ };
445
+
446
+ Controller.prototype.view = function(name, model) {
447
+
448
+ var ctrl = this;
449
+
450
+ if (ctrl.destroyed)
451
+ return;
452
+
453
+ var view = new F.TViewEngine.View(ctrl);
454
+ ctrl.response.layout && view.layout(ctrl.response.layout);
455
+ setImmediate(renderview, view, name, model);
456
+ return view;
457
+ };
458
+
459
+ function renderview(view, name, model) {
460
+ view.controller.html(view.render(name, model));
461
+ }
462
+
463
+ Controller.prototype.file = function(path, download) {
464
+
465
+ var ctrl = this;
466
+
467
+ if (ctrl.destroyed)
468
+ return;
469
+
470
+ var response = ctrl.response;
471
+
472
+ if (download) {
473
+ if (typeof(download) !== 'string')
474
+ download = F.TUtils.getName(path);
475
+ response.headers['content-disposition'] = 'attachment; filename*=utf-8\'\'' + encodeURIComponent(download);
476
+ }
477
+
478
+ var ext = F.TUtils.getExtension(path);
479
+
480
+ if (ext === 'js') {
481
+ if (response.minify) {
482
+ response.minify = F.config.$minifyjs;
483
+ if (response.minify)
484
+ response.minify = !CHECK_MIN.test(path);
485
+ }
486
+ } else if (ext === 'css') {
487
+ if (response.minify) {
488
+ response.minify = F.config.$minifycss;
489
+ if (response.minify)
490
+ response.minify = !CHECK_MIN.test(path);
491
+ }
492
+ } else if (ext === 'html') {
493
+ if (response.minify) {
494
+ response.minify = F.config.$minifyhtml;
495
+ if (response.minify)
496
+ response.minify = !CHECK_MIN.test(path);
497
+ }
498
+ }
499
+
500
+ if (response.minify) {
501
+ switch (ext) {
502
+ case 'js':
503
+ send_js(ctrl, path);
504
+ break;
505
+ case 'css':
506
+ send_css(ctrl, path);
507
+ break;
508
+ case 'html':
509
+ send_html(ctrl, path);
510
+ break;
511
+ default:
512
+ send_file(ctrl, path, ext);
513
+ break;
514
+ }
515
+ } else
516
+ send_file(ctrl, path, ext);
517
+
518
+ F.stats.response.file++;
519
+ };
520
+
521
+ Controller.prototype.stream = function(type, stream, download) {
522
+
523
+ var ctrl = this;
524
+
525
+ if (ctrl.destroyed)
526
+ return;
527
+
528
+ var response = ctrl.response;
529
+
530
+ if (download && typeof(download) === 'string')
531
+ response.headers['content-disposition'] = 'attachment; filename*=utf-8\'\'' + encodeURIComponent(download);
532
+
533
+ var accept = ctrl.headers['accept-encoding'];
534
+ var compress = F.config.$httpcompress && accept && CHECK_COMPRESSION[type] && accept.indexOf('gzip') !== -1;
535
+
536
+ if (response.headers.expires)
537
+ delete response.headers.expires;
538
+
539
+ response.headers.etag = '858' + F.config.$httpetag;
540
+
541
+ if (CHECK_CHARSET[type])
542
+ type += '; charset=utf-8';
543
+
544
+ response.headers['content-type'] = type || 'application/octet-stream';
545
+
546
+ if (compress)
547
+ response.headers['content-encoding'] = 'gzip';
548
+
549
+ ctrl.res.writeHead(response.status, response.headers);
550
+
551
+ if (compress)
552
+ stream.pipe(F.Zlib.createGzip(GZIP_STREAM)).pipe(ctrl.res);
553
+ else
554
+ stream.pipe(ctrl.res);
555
+
556
+ F.stats.response.stream++;
557
+ };
558
+
559
+ Controller.prototype.filefs = function(name, id, download, checkmeta) {
560
+
561
+ var ctrl = this;
562
+
563
+ if (ctrl.destroyed)
564
+ return;
565
+
566
+ var opt = {};
567
+
568
+ opt.id = id;
569
+ opt.download = download;
570
+ opt.check = checkmeta;
571
+
572
+ F.filestorage(name).http(ctrl, opt);
573
+ };
574
+
575
+ Controller.prototype.binary = function(buffer, type, download) {
576
+
577
+ var ctrl = this;
578
+
579
+ if (ctrl.destroyed)
580
+ return;
581
+
582
+ var response = ctrl.response;
583
+
584
+ response.headers['content-type'] = type;
585
+ response.type = 'binary';
586
+
587
+ if (typeof(download) === 'string')
588
+ response.headers['content-disposition'] = 'attachment; filename*=utf-8\'\'' + encodeURIComponent(download);
589
+
590
+ response.value = buffer;
591
+ ctrl.flush();
592
+
593
+ F.stats.response.binary++;
594
+ };
595
+
596
+ Controller.prototype.proxy = function(opt) {
597
+
598
+ var ctrl = this;
599
+
600
+ if (ctrl.destroyed)
601
+ return;
602
+
603
+ if (typeof(opt) === 'string')
604
+ opt = { url: opt };
605
+
606
+ if (!opt.headers)
607
+ opt.headers = {};
608
+
609
+ if (!opt.method)
610
+ opt.method = ctrl.method;
611
+
612
+ opt.resolve = true;
613
+ opt.encoding = 'binary';
614
+ opt.body = ctrl.payload;
615
+
616
+ var tmp;
617
+
618
+ if (opt.url.indexOf('?') === -1) {
619
+ tmp = F.TUtils.toURLEncode(ctrl.query);
620
+ if (tmp)
621
+ opt.url += '?' + tmp;
622
+ }
623
+
624
+ for (let key in ctrl.headers) {
625
+ switch (key) {
626
+ case 'x-forwarded-for':
627
+ case 'x-forwarded-protocol':
628
+ case 'x-forwarded-proto':
629
+ case 'x-nginx-proxy':
630
+ case 'connection':
631
+ case 'host':
632
+ case 'accept-encoding':
633
+ break;
634
+ default:
635
+ opt.headers[key] = ctrl.headers[key];
636
+ break;
637
+ }
638
+ }
639
+
640
+ if (!opt.timeout)
641
+ opt.timeout = 10000;
642
+
643
+ var prepare = opt.callback;
644
+
645
+ opt.callback = function(err, response) {
646
+
647
+ prepare && prepare(err, response);
648
+
649
+ if (err) {
650
+ ctrl.invalid(err);
651
+ return;
652
+ }
653
+
654
+ ctrl.response.status = response.status;
655
+ ctrl.binary(response.body, (response.headers['content-type'] || 'text/plain').replace(REG_ENCODINGCLEANER, ''));
656
+ };
657
+
658
+ REQUEST(opt);
659
+
660
+ };
661
+
662
+ Controller.prototype.successful = function(callback) {
663
+ var ctrl = this;
664
+ return function(err, a, b, c) {
665
+ if (err)
666
+ ctrl.invalid(err);
667
+ else
668
+ callback.call(ctrl, a, b, c);
669
+ };
670
+ };
671
+
672
+ Controller.prototype.done = function(arg) {
673
+ var ctrl = this;
674
+ return function(err, response) {
675
+ if (err)
676
+ ctrl.invalid(err);
677
+ else
678
+ ctrl.json(DEF.onSuccess(arg === true ? response : arg));
679
+ };
680
+ };
681
+
682
+ Controller.prototype.transform = function(name, value, callback) {
683
+ return F.transform(name, value, callback, this);
684
+ };
685
+
686
+ Controller.prototype.success = function(value) {
687
+ F.TUtils.success.value = value;
688
+ this.json(F.TUtils.success);
689
+ };
690
+
691
+ Controller.prototype.clear = function() {
692
+
693
+ var ctrl = this;
694
+
695
+ if (ctrl.files.length) {
696
+ let remove = [];
697
+ for (var file of ctrl.files) {
698
+ if (file.removable)
699
+ remove.push(file.path);
700
+ }
701
+ F.path.unlink(remove);
702
+ ctrl.files.length = 0;
703
+ }
704
+
705
+ };
706
+
707
+ Controller.prototype.cookie = function(name, value, expires, options) {
708
+
709
+ var ctrl = this;
710
+ var arr;
711
+
712
+ if (value === undefined) {
713
+
714
+ if (ctrl.cookies)
715
+ return F.TUtils.decodeURIComponent(ctrl.cookies[name] || '');
716
+
717
+ var cookie = ctrl.headers.cookie;
718
+ if (!cookie) {
719
+ ctrl.cookies = F.EMPTYOBJECT;
720
+ return '';
721
+ }
722
+
723
+ ctrl.cookies = {};
724
+
725
+ arr = cookie.split(';');
726
+ for (let i = 0; i < arr.length; i++) {
727
+ let line = arr[i].trim();
728
+ let index = line.indexOf('=');
729
+ if (index !== -1)
730
+ ctrl.cookies[line.substring(0, index)] = line.substring(index + 1);
731
+ }
732
+
733
+ return name ? F.TUtils.decodeURIComponent(ctrl.cookies[name] || '') : '';
734
+ }
735
+
736
+ var cookiename = name + '=';
737
+ var builder = [cookiename + value];
738
+ var type = typeof(expires);
739
+
740
+ if (expires && type === 'object') {
741
+ options = expires;
742
+ expires = options.expires || options.expire || null;
743
+ }
744
+
745
+ if (type === 'string')
746
+ expires = expires.parseDateExpiration();
747
+
748
+ if (!options)
749
+ options = {};
750
+
751
+ if (!options.path)
752
+ options.path = '/';
753
+
754
+ expires && builder.push('Expires=' + expires.toUTCString());
755
+ options.domain && builder.push('Domain=' + options.domain);
756
+ options.path && builder.push('Path=' + options.path);
757
+
758
+ if (options.secure == true || (options.secure == null && F.config.$cookiesecure))
759
+ builder.push('Secure');
760
+
761
+ if (options.httpOnly || options.httponly || options.HttpOnly)
762
+ builder.push('HttpOnly');
763
+
764
+ var same = options.security || options.samesite || F.config.$cookiesamesite;
765
+
766
+ switch (same) {
767
+ case 1:
768
+ same = 'Lax';
769
+ break;
770
+ case 2:
771
+ same = 'Strict';
772
+ break;
773
+ }
774
+
775
+ builder.push('SameSite=' + same);
776
+
777
+ arr = ctrl.response.headers['set-cookie'] || [];
778
+
779
+ // Cookie, already, can be in array, resulting in duplicate 'set-cookie' header
780
+ if (arr.length) {
781
+ var l = cookiename.length;
782
+ for (let i = 0; i < arr.length; i++) {
783
+ if (arr[i].substring(0, l) === cookiename) {
784
+ arr.splice(i, 1);
785
+ break;
786
+ }
787
+ }
788
+ }
789
+
790
+ arr.push(builder.join('; '));
791
+ ctrl.response.headers['set-cookie'] = arr;
792
+
793
+ return ctrl;
794
+ };
795
+
796
+ Controller.prototype.custom = function() {
797
+ this.destroyed = true;
798
+ };
799
+
800
+ Controller.prototype.autoclear = function(value) {
801
+ this.preventclearfiles = value === false;
802
+ };
803
+
804
+ Controller.prototype.resume = function() {
805
+
806
+ var ctrl = this;
807
+
808
+ if (ctrl.isfile) {
809
+
810
+ var path = ctrl.uri.key;
811
+ if (path[1] === '_') {
812
+
813
+ let tmp = path.substring(1);
814
+ let index = tmp.indexOf('/', 1);
815
+ if (index === -1) {
816
+ ctrl.fallback(404);
817
+ return;
818
+ }
819
+
820
+ path = F.path.root('plugins/' + tmp.substring(1, index) + '/public/' + tmp.substring(index + 1));
821
+ } else
822
+ path = F.path.public(path.substring(1));
823
+
824
+ switch (ctrl.ext) {
825
+ case 'js':
826
+ if (CHECK_MIN.test(path))
827
+ send_file(ctrl, path, ctrl.ext);
828
+ else
829
+ send_js(ctrl, path);
830
+ break;
831
+ case 'css':
832
+ if (CHECK_MIN.test(path))
833
+ send_file(ctrl, path, ctrl.ext);
834
+ else
835
+ send_css(ctrl, path);
836
+ break;
837
+ case 'html':
838
+ if (CHECK_MIN.test(path))
839
+ send_file(ctrl, path, ctrl.ext);
840
+ else
841
+ send_html(ctrl, path);
842
+ break;
843
+ default:
844
+ send_file(ctrl, path, ctrl.ext);
845
+ break;
846
+ }
847
+
848
+ } else
849
+ ctrl.fallback(404);
850
+ };
851
+
852
+ Controller.prototype.free = function() {
853
+
854
+ var ctrl = this;
855
+
856
+ if (ctrl.released)
857
+ return;
858
+
859
+ ctrl.released = true;
860
+ ctrl.destroyed = true;
861
+ ctrl.payload = null;
862
+
863
+ // Potential problem
864
+ // ctrl.body = null;
865
+ // ctrl.params = null;
866
+ // ctrl.query = null;
867
+
868
+ if (ctrl.preventclearfiles != true)
869
+ ctrl.clear();
870
+
871
+ // Clear resources
872
+ ctrl.req.controller = null;
873
+
874
+ };
875
+
876
+ Controller.prototype.hostname = function(path) {
877
+ var ctrl = this;
878
+ return ctrl.protocol + '://' + ctrl.headers.host + (path ? path : '');
879
+ };
880
+
881
+ Controller.prototype.$route = function() {
882
+
883
+ var ctrl = this;
884
+ if (ctrl.isfile) {
885
+
886
+ if (F.routes.files.length) {
887
+ let route = F.TRouting.lookupfile(ctrl);
888
+ if (route) {
889
+ ctrl.route = route;
890
+ if (route.middleware.length)
891
+ middleware(ctrl);
892
+ else
893
+ route.action(ctrl);
894
+ return;
895
+ }
896
+ }
897
+
898
+ if (F.config.$httpfiles[ctrl.ext])
899
+ ctrl.resume();
900
+ else
901
+ ctrl.fallback(404);
902
+
903
+ return;
904
+ }
905
+
906
+ let route = F.TRouting.lookup(ctrl);
907
+ if (route) {
908
+
909
+ ctrl.route = route;
910
+
911
+ // process data
912
+ // call action
913
+
914
+ if (ctrl.datatype === 'multipart') {
915
+ multipart(ctrl);
916
+ } else if (ctrl.datatype) {
917
+
918
+ ctrl.payload = [];
919
+ ctrl.payloadsize = 0;
920
+ ctrl.toolarge = false;
921
+ ctrl.downloaded = false;
922
+
923
+ ctrl.req.on('data', function(chunk) {
924
+ ctrl.payloadsize += chunk.length;
925
+ if (ctrl.payloadsize > ctrl.route.size) {
926
+ if (!ctrl.toolarge) {
927
+ ctrl.toolarge = true;
928
+ delete ctrl.payload;
929
+ }
930
+ } else
931
+ ctrl.payload.push(chunk);
932
+ });
933
+
934
+ ctrl.req.on('abort', () => ctrl.free());
935
+ ctrl.req.on('end', function() {
936
+
937
+ ctrl.downloaded = true;
938
+
939
+ if (ctrl.toolarge) {
940
+ ctrl.fallback(431);
941
+ return;
942
+ }
943
+
944
+ ctrl.payload = Buffer.concat(ctrl.payload);
945
+ F.stats.performance.download += ctrl.payload.length / 1024 / 1024;
946
+
947
+ switch (ctrl.datatype) {
948
+ case 'json':
949
+ ctrl.body = F.def.parsers.json(ctrl.payload.toString('utf8'));
950
+ break;
951
+ case 'urlencoded':
952
+ ctrl.body = F.def.parsers.urlencoded(ctrl.payload.toString('utf8'));
953
+ break;
954
+ }
955
+
956
+ authorize(ctrl);
957
+ });
958
+
959
+ } else
960
+ authorize(ctrl);
961
+
962
+ } else
963
+ ctrl.fallback(404);
964
+
965
+ };
966
+
967
+ function readfile(filename, callback) {
968
+ F.stats.performance.open++;
969
+ F.Fs.lstat(filename, function(err, stats) {
970
+
971
+ if (err) {
972
+ callback(err);
973
+ return;
974
+ }
975
+
976
+ F.stats.performance.open++;
977
+ F.Fs.readFile(filename, 'utf8', function(err, text) {
978
+ if (err) {
979
+ callback(err);
980
+ } else {
981
+ let obj = {};
982
+ obj.date = stats.mtime.toUTCString();
983
+ obj.body = text.ROOT();
984
+ callback(null, obj);
985
+ }
986
+ });
987
+ });
988
+ }
989
+
990
+ Controller.prototype.notmodified = function(date) {
991
+ var ctrl = this;
992
+ if (ctrl.headers['if-modified-since'] === date) {
993
+ ctrl.response.status = 304;
994
+ ctrl.response.headers['cache-control'] = 'public, must-revalidate, max-age=' + F.config.$httpmaxage; // 5 min.
995
+ ctrl.response.headers['last-modified'] = date;
996
+ ctrl.flush();
997
+ F.stats.response.notmodified++;
998
+ return true;
999
+ }
1000
+ };
1001
+
1002
+ Controller.prototype.httpcache = function(date) {
1003
+ var ctrl = this;
1004
+
1005
+ if (date instanceof Date)
1006
+ date = date.toUTCString();
1007
+
1008
+ if (!ctrl.response.headers.expires)
1009
+ ctrl.response.headers.expires = F.config.$httpexpire;
1010
+
1011
+ if (!ctrl.response.headers['cache-control'])
1012
+ ctrl.response.headers['cache-control'] = 'public, must-revalidate, max-age=' + F.config.$httpmaxage; // 5 minute cache for revalidate (304)
1013
+
1014
+ ctrl.response.headers['last-modified'] = date;
1015
+ ctrl.response.headers.etag = '858' + F.config.$httpetag;
1016
+ };
1017
+
1018
+ function multipart(ctrl) {
1019
+
1020
+ var type = ctrl.headers['content-type'];
1021
+ var index = type.indexOf('boundary=');
1022
+ if (index === -1) {
1023
+ ctrl.fallback(400);
1024
+ return;
1025
+ }
1026
+
1027
+ var end = type.length;
1028
+
1029
+ for (var i = (index + 10); i < end; i++) {
1030
+ if (type[i] === ';' || type[i] === ' ') {
1031
+ end = i;
1032
+ break;
1033
+ }
1034
+ }
1035
+
1036
+ var boundary = type.substring(index + 9, end);
1037
+ var parser = F.TUtils.multipartparser(boundary, ctrl.req, function(err, meta) {
1038
+
1039
+ F.stats.performance.download += meta.size / 1024 / 1024;
1040
+
1041
+ for (var i = 0; i < meta.files.length; i++) {
1042
+
1043
+ var item = meta.files[i];
1044
+ var file = new HttpFile(item);
1045
+
1046
+ // IE9 sends absolute filename
1047
+ var index = file.filename.lastIndexOf('\\');
1048
+
1049
+ // For Unix like senders
1050
+ if (index === -1)
1051
+ index = file.filename.lastIndexOf('/');
1052
+
1053
+ if (index !== -1)
1054
+ file.filename = file.filename.substring(index + 1);
1055
+
1056
+ file.ext = F.TUtils.getExtension(file.filename);
1057
+ ctrl.files.push(file);
1058
+ }
1059
+
1060
+ // Error
1061
+ if (err) {
1062
+ ctrl.clear();
1063
+ switch (err[0][0]) {
1064
+ case '4':
1065
+ case '5':
1066
+ case '6':
1067
+ ctrl.fallback(431, err[0]);
1068
+ break;
1069
+ default:
1070
+ ctrl.fallback(400, err[0]);
1071
+ break;
1072
+ }
1073
+ } else {
1074
+ ctrl.body = meta.body;
1075
+ authorize(ctrl);
1076
+ }
1077
+ });
1078
+
1079
+ parser.skipcheck = !F.config.$httpchecktypes;
1080
+ parser.limits.total = ctrl.route.size;
1081
+ }
1082
+
1083
+ function authorize(ctrl) {
1084
+ if (F.def.onAuthorize) {
1085
+ var opt = new F.TBuilders.Options(ctrl);
1086
+ opt.TYPE = 'auth'; // important
1087
+ opt.query = ctrl.query;
1088
+ opt.next = opt.callback;
1089
+ opt.$callback = function(err, user) {
1090
+ let auth = user ? 1 : 2;
1091
+ ctrl.user = user;
1092
+ if (ctrl.route.auth === auth) {
1093
+ execute(ctrl);
1094
+ } else {
1095
+ ctrl.route = F.TRouting.lookup(ctrl, auth);
1096
+ if (ctrl.route)
1097
+ execute(ctrl);
1098
+ else
1099
+ ctrl.fallback(401);
1100
+ }
1101
+ };
1102
+ F.def.onAuthorize(opt);
1103
+ } else
1104
+ execute(ctrl);
1105
+ }
1106
+
1107
+ function execute(ctrl, skipmiddleware) {
1108
+
1109
+ if (ctrl.route.timeout)
1110
+ ctrl.timeout = ctrl.route.timeout;
1111
+
1112
+ for (let param of ctrl.route.params) {
1113
+ let value = ctrl.split[param.index];
1114
+ ctrl.params[param.name] = value;
1115
+ }
1116
+
1117
+ if (!ctrl.language && F.def.onLocalize) {
1118
+ ctrl.language = F.def.onLocalize(ctrl);
1119
+ ctrl.uri.cache += ctrl.language;
1120
+ }
1121
+
1122
+ if (!skipmiddleware && ctrl.route.middleware.length) {
1123
+ middleware(ctrl);
1124
+ } else {
1125
+
1126
+ if (ctrl.route.api) {
1127
+ let body = ctrl.body;
1128
+ if (body && typeof(body) === 'object' && body.schema && typeof(body.schema) === 'string') {
1129
+
1130
+ let index = body.schema.indexOf('?');
1131
+ let query = null;
1132
+
1133
+ if (index !== -1) {
1134
+ query = body.schema.substring(index + 1);
1135
+ body.schema = body.schema.substring(0, index);
1136
+ }
1137
+
1138
+ let schema = body.schema.split('/');
1139
+ let endpoint = ctrl.route.api[schema[0]];
1140
+ if (endpoint) {
1141
+
1142
+ if ((endpoint.auth === 1 && ctrl.user == null) || (endpoint.auth === 2 && ctrl.user)) {
1143
+ ctrl.fallback(401);
1144
+ return;
1145
+ }
1146
+
1147
+ let params = {};
1148
+ if (endpoint.params) {
1149
+ let err = null;
1150
+ for (let m of endpoint.params) {
1151
+ params[m.name] = schema[m.index] || '';
1152
+ if (!params[m.name]) {
1153
+ if (!err)
1154
+ err = new F.TBuilders.ErrorBuilder();
1155
+ err.push2('params.' + m.name);
1156
+ }
1157
+ }
1158
+ if (err) {
1159
+ ctrl.invalid(err);
1160
+ return;
1161
+ }
1162
+ }
1163
+
1164
+ body = body.data;
1165
+
1166
+ if (!body || typeof(body) === 'object') {
1167
+
1168
+ if (endpoint.timeout)
1169
+ ctrl.timeout = endpoint.timeout;
1170
+
1171
+ ctrl.params = params;
1172
+ ctrl.query = query ? query.parseEncoded() : {};
1173
+ let action = endpoint.action;
1174
+ if (action) {
1175
+ ctrl.body = body || {};
1176
+ action(ctrl);
1177
+ } else
1178
+ F.action(endpoint.actions, body || {}, ctrl).autorespond();
1179
+ return;
1180
+ }
1181
+ }
1182
+
1183
+ ctrl.fallback(400, 'Invalid data');
1184
+ }
1185
+ } else {
1186
+ if (ctrl.route.actions) {
1187
+ F.action(ctrl.route.actions, ctrl.body, ctrl).autorespond();
1188
+ } else {
1189
+ if (ctrl.route.view) {
1190
+ ctrl.view(ctrl.route.view);
1191
+ return;
1192
+ }
1193
+ let action = ctrl.route.action;
1194
+ if (!action)
1195
+ action = auto_view;
1196
+ action(ctrl);
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+
1202
+ function auto_view(ctrl) {
1203
+ ctrl.view(ctrl.split[0] || 'index');
1204
+ }
1205
+
1206
+ function send_html(ctrl, path) {
1207
+
1208
+ if (!ctrl.language && F.def.onLocalize) {
1209
+ ctrl.language = F.def.onLocalize(ctrl);
1210
+ ctrl.uri.cache += ctrl.language;
1211
+ }
1212
+
1213
+ if (F.temporary.notfound[ctrl.uri.cache]) {
1214
+ ctrl.fallback(404);
1215
+ return;
1216
+ }
1217
+
1218
+ let filename = F.temporary.minified[ctrl.uri.cache];
1219
+ if (filename) {
1220
+ send_file(ctrl, filename, 'html');
1221
+ return;
1222
+ }
1223
+
1224
+ readfile(path, function(err, output) {
1225
+
1226
+ if (err) {
1227
+
1228
+ if (!DEBUG)
1229
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1230
+
1231
+ ctrl.fallback(404);
1232
+ return;
1233
+ }
1234
+
1235
+ output.body = F.translate(ctrl.language, output.body);
1236
+
1237
+ if (ctrl.response.minify && F.config.$minifyhtml)
1238
+ output.body = F.TMinificators.html(output.body);
1239
+
1240
+ if (DEBUG) {
1241
+ ctrl.response.headers['cache-control'] = NOCACHE;
1242
+ ctrl.response.headers['last-modified'] = output.date;
1243
+ ctrl.response.headers['content-type'] = 'text/html';
1244
+ ctrl.response.value = output.body;
1245
+ ctrl.flush();
1246
+ } else {
1247
+ let filename = F.path.tmp(F.clusterid + ctrl.uri.cache.substring(1).replace(REG_FILETMP, '-') + '-min.html');
1248
+ F.Fs.writeFile(filename, output.body, function(err) {
1249
+ if (err) {
1250
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1251
+ ctrl.fallback(404, err.toString());
1252
+ } else {
1253
+ F.temporary.minified[ctrl.uri.cache] = filename;
1254
+ send_file(ctrl, filename, 'html');
1255
+ }
1256
+ });
1257
+ }
1258
+ });
1259
+ }
1260
+
1261
+ function send_css(ctrl, path) {
1262
+
1263
+ if (F.temporary.notfound[ctrl.uri.cache]) {
1264
+ ctrl.fallback(404);
1265
+ return;
1266
+ }
1267
+
1268
+ let filename = F.temporary.minified[ctrl.uri.cache];
1269
+ if (filename) {
1270
+ send_file(ctrl, filename, 'css');
1271
+ return;
1272
+ }
1273
+
1274
+ readfile(path, function(err, output) {
1275
+
1276
+ if (err) {
1277
+
1278
+ if (!DEBUG)
1279
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1280
+
1281
+ ctrl.fallback(404);
1282
+ return;
1283
+ }
1284
+
1285
+ if (ctrl.response.minify && F.config.$minifycss)
1286
+ output.body = F.TMinificators.css(output.body);
1287
+
1288
+ if (DEBUG) {
1289
+ ctrl.response.headers['cache-control'] = NOCACHE;
1290
+ ctrl.response.headers['last-modified'] = output.date;
1291
+ ctrl.response.headers['content-type'] = 'text/css';
1292
+ ctrl.response.value = output.body;
1293
+ ctrl.flush();
1294
+ } else {
1295
+ let filename = F.path.tmp(F.clusterid + ctrl.uri.cache.substring(1).replace(REG_FILETMP, '-') + '-min.css');
1296
+ F.Fs.writeFile(filename, output.body, function(err) {
1297
+ if (err) {
1298
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1299
+ ctrl.fallback(404, err.toString());
1300
+ } else {
1301
+ F.temporary.minified[ctrl.uri.cache] = filename;
1302
+ send_file(ctrl, filename, 'css');
1303
+ }
1304
+ });
1305
+ }
1306
+ });
1307
+ }
1308
+
1309
+ function send_js(ctrl, path) {
1310
+
1311
+ if (F.temporary.notfound[ctrl.uri.cache]) {
1312
+ ctrl.fallback(404);
1313
+ return;
1314
+ }
1315
+
1316
+ let filename = F.temporary.minified[ctrl.uri.cache];
1317
+ if (filename) {
1318
+ send_file(ctrl, filename, 'js');
1319
+ return;
1320
+ }
1321
+
1322
+ readfile(path, function(err, output) {
1323
+
1324
+ if (err) {
1325
+
1326
+ if (!DEBUG)
1327
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1328
+
1329
+ ctrl.fallback(404);
1330
+ return;
1331
+ }
1332
+
1333
+ if (ctrl.response.minify && F.config.$minifyjs)
1334
+ output.body = F.TMinificators.js(output.body);
1335
+
1336
+ if (DEBUG) {
1337
+ ctrl.response.headers['cache-control'] = NOCACHE;
1338
+ ctrl.response.headers['last-modified'] = output.date;
1339
+ ctrl.response.headers['content-type'] = 'text/javascript';
1340
+ ctrl.response.value = output.body;
1341
+ ctrl.flush();
1342
+ } else {
1343
+ let filename = F.path.tmp(F.clusterid + ctrl.uri.cache.substring(1).replace(REG_FILETMP, '-') + '-min.js');
1344
+ F.Fs.writeFile(filename, output.body, function(err) {
1345
+ if (err) {
1346
+ F.temporary.notfound[ctrl.uri.cache] = 1;
1347
+ ctrl.fallback(404, err.toString());
1348
+ } else {
1349
+ F.temporary.minified[ctrl.uri.cache] = filename;
1350
+ send_file(ctrl, filename, 'js');
1351
+ }
1352
+ });
1353
+ }
1354
+ });
1355
+ }
1356
+
1357
+ function send_file(ctrl, path, ext) {
1358
+
1359
+ // Check the file existence
1360
+ if (F.temporary.notfound[ctrl.uri.cache]) {
1361
+ ctrl.fallback(404);
1362
+ return;
1363
+ }
1364
+
1365
+ var cache = F.temporary.tmp[ctrl.uri.cache];
1366
+
1367
+ // HTTP Cache
1368
+ if (ctrl.response.cache && cache && ctrl.notmodified(cache.date))
1369
+ return;
1370
+
1371
+ var accept = ctrl.headers['accept-encoding'];
1372
+ var type = F.TUtils.getContentType(ext);
1373
+ var compress = F.config.$httpcompress && accept && CHECK_COMPRESSION[type] && accept.indexOf('gzip') !== -1;
1374
+ var range = ctrl.headers.range;
1375
+ var httpcache = ctrl.response.cache && !CHECK_NOCACHE[ext] && F.config.$httpexpire;
1376
+
1377
+ var loadstats = function(err, stats, cache) {
1378
+
1379
+ if (err) {
1380
+
1381
+ if (!DEBUG && ctrl.response.cache)
1382
+ F.temporary.notfound[ctrl.uri.cache] = true;
1383
+
1384
+ ctrl.fallback(404);
1385
+ return;
1386
+ }
1387
+
1388
+ if (httpcache) {
1389
+ if (!ctrl.response.headers.expires)
1390
+ ctrl.response.headers.expires = F.config.$httpexpire;
1391
+ if (!ctrl.response.headers['cache-control'])
1392
+ ctrl.response.headers['cache-control'] = 'public, must-revalidate, max-age=' + F.config.$httpmaxage; // 5 minute cache for revalidate (304)
1393
+ } else if (ctrl.response.headers.expires)
1394
+ delete ctrl.response.headers.expires;
1395
+
1396
+ if (!httpcache)
1397
+ ctrl.response.headers['cache-control'] = NOCACHE;
1398
+
1399
+ if (!cache)
1400
+ cache = { date: stats.mtime.toUTCString(), size: stats.size };
1401
+
1402
+ ctrl.response.headers['last-modified'] = cache.date;
1403
+ ctrl.response.headers.etag = '858' + F.config.$httpetag;
1404
+
1405
+ var type = F.TUtils.contentTypes[ext] || F.TUtils.contentTypes.bin;
1406
+
1407
+ if (CHECK_CHARSET[type])
1408
+ type += '; charset=utf-8';
1409
+
1410
+ ctrl.response.headers['content-type'] = type;
1411
+ F.temporary.tmp[ctrl.uri.cache] = cache;
1412
+
1413
+ F.stats.performance.open++;
1414
+
1415
+ var reader;
1416
+
1417
+ if (range) {
1418
+
1419
+ let size = range.replace(REG_RANGE, '').split('-');
1420
+ let beg = +size[0] || 0;
1421
+ let end = +size[1] || 0;
1422
+
1423
+ if (end <= 0)
1424
+ end = beg + (1024 * F.config.$httprangebuffer); // 5 MB
1425
+
1426
+ if (beg > end) {
1427
+ beg = 0;
1428
+ end = cache.size - 1;
1429
+ }
1430
+
1431
+ if (end > cache.size)
1432
+ end = cache.size - 1;
1433
+
1434
+ ctrl.response.headers['content-length'] = (end - beg) + 1;
1435
+ ctrl.response.headers['content-range'] = 'bytes ' + beg + '-' + end + '/' + cache.size;
1436
+ ctrl.res.writeHead(206, ctrl.response.headers);
1437
+ reader = F.Fs.createReadStream(path, { start: beg, end: end });
1438
+ reader.pipe(ctrl.res);
1439
+ F.stats.response.streaming++;
1440
+
1441
+ } else {
1442
+
1443
+ reader = F.Fs.createReadStream(path);
1444
+
1445
+ if (compress)
1446
+ ctrl.response.headers['content-encoding'] = 'gzip';
1447
+
1448
+ ctrl.res.writeHead(ctrl.response.status, ctrl.response.headers);
1449
+
1450
+ if (compress)
1451
+ reader.pipe(F.Zlib.createGzip(GZIP_FILE)).pipe(ctrl.res);
1452
+ else
1453
+ reader.pipe(ctrl.res);
1454
+
1455
+ F.stats.response.file++;
1456
+ }
1457
+ };
1458
+
1459
+ if (cache) {
1460
+ loadstats(null, null, cache);
1461
+ } else {
1462
+ F.stats.performance.open++;
1463
+ F.Fs.lstat(path, loadstats);
1464
+ }
1465
+ }
1466
+
1467
+ function middleware(ctrl) {
1468
+ var run = function(index) {
1469
+ let name = ctrl.route.middleware[index];
1470
+ if (name) {
1471
+ let fn = F.routes.middleware[name];
1472
+ if (fn)
1473
+ fn(ctrl, () => run(index + 1));
1474
+ else
1475
+ run(index + 1);
1476
+ } else
1477
+ execute(ctrl, true);
1478
+ };
1479
+ run(0);
1480
+ }
1481
+
1482
+ function HttpFile(meta) {
1483
+ var self = this;
1484
+ self.path = meta.path;
1485
+ self.name = meta.name;
1486
+ self.filename = meta.filename;
1487
+ self.size = meta.size || 0;
1488
+ self.width = meta.width || 0;
1489
+ self.height = meta.height || 0;
1490
+ self.type = meta.type;
1491
+ self.removable = true;
1492
+ }
1493
+
1494
+ HttpFile.prototype = {
1495
+ get extension() {
1496
+ return this.ext;
1497
+ },
1498
+ get isImage() {
1499
+ return this.type.indexOf('image/') !== -1;
1500
+ },
1501
+ get isVideo() {
1502
+ return this.type.indexOf('video/') !== -1;
1503
+ },
1504
+ get isAudio() {
1505
+ return this.type.indexOf('audio/') !== -1;
1506
+ }
1507
+ };
1508
+
1509
+ HttpFile.prototype.rename = HttpFile.prototype.move = function(filename, callback) {
1510
+ var self = this;
1511
+ if (callback)
1512
+ return self.$move(filename, callback);
1513
+ else
1514
+ return new Promise((resolve, reject) => self.$move(filename, err => err ? reject(err) : resolve()));
1515
+ };
1516
+
1517
+ HttpFile.prototype.$move = function(filename, callback) {
1518
+ var self = this;
1519
+ F.stats.performance.open++;
1520
+ F.Fs.rename(self.path, filename, function(err) {
1521
+ if (err && err.code === 'EXDEV') {
1522
+ F.stats.performance.open++;
1523
+ self.copy(filename, function(err){
1524
+
1525
+ F.stats.performance.open++;
1526
+ F.path.unlink(self.path, NOOP);
1527
+
1528
+ if (!err) {
1529
+ self.path = filename;
1530
+ self.removable = false;
1531
+ }
1532
+
1533
+ callback && callback(err);
1534
+ });
1535
+ } else {
1536
+ if (!err) {
1537
+ self.path = filename;
1538
+ self.removable = false;
1539
+ }
1540
+ callback && callback(err);
1541
+ }
1542
+ });
1543
+ return self;
1544
+ };
1545
+
1546
+ HttpFile.prototype.copy = function(filename, callback) {
1547
+ var self = this;
1548
+ if (callback)
1549
+ return self.$copy(filename, callback);
1550
+ else
1551
+ return new Promise((resolve, reject) => self._copy(filename, err => err ? reject(err) : resolve()));
1552
+ };
1553
+
1554
+ HttpFile.prototype.$copy = function(filename, callback) {
1555
+
1556
+ var self = this;
1557
+
1558
+ if (!callback) {
1559
+ F.stats.performance.open++;
1560
+ F.Fs.createReadStream(self.path).pipe(F.Fs.createWriteStream(filename));
1561
+ return;
1562
+ }
1563
+
1564
+ F.stats.performance.open++;
1565
+ var reader = F.Fs.createReadStream(self.path);
1566
+ var writer = F.Fs.createWriteStream(filename);
1567
+
1568
+ reader.on('close', callback);
1569
+ reader.pipe(writer);
1570
+
1571
+ return self;
1572
+ };
1573
+
1574
+ HttpFile.prototype.read = function(callback) {
1575
+ var self = this;
1576
+ if (callback)
1577
+ return self.$read(callback);
1578
+ else
1579
+ return new Promise((resolve, reject) => self.$read((err, res) => err ? reject(err) : resolve(res)));
1580
+ };
1581
+
1582
+ HttpFile.prototype.$read = function(callback) {
1583
+ var self = this;
1584
+ F.stats.performance.open++;
1585
+ F.Fs.readFile(self.path, callback);
1586
+ return self;
1587
+ };
1588
+
1589
+ HttpFile.prototype.md5 = function(callback) {
1590
+ var self = this;
1591
+ if (callback)
1592
+ return self.$md5(callback);
1593
+ else
1594
+ return new Promise((resolve, reject) => self.$md5((err, res) => err ? reject(err) : resolve(res)));
1595
+ };
1596
+
1597
+ HttpFile.prototype.$md5 = function(callback) {
1598
+
1599
+ var self = this;
1600
+ var md5 = F.Crypto.createHash('md5');
1601
+ var stream = F.Fs.createReadStream(self.path);
1602
+ F.stats.performance.open++;
1603
+
1604
+ stream.on('data', buffer => md5.update(buffer));
1605
+ stream.on('error', function(error) {
1606
+ if (callback) {
1607
+ callback(error, null);
1608
+ callback = null;
1609
+ }
1610
+ });
1611
+
1612
+ F.cleanup(stream, function() {
1613
+ if (callback) {
1614
+ callback(null, md5.digest('hex'));
1615
+ callback = null;
1616
+ }
1617
+ });
1618
+
1619
+ return self;
1620
+ };
1621
+
1622
+ HttpFile.prototype.stream = function(opt) {
1623
+ F.stats.performance.open++;
1624
+ return F.Fs.createReadStream(this.path, opt);
1625
+ };
1626
+
1627
+ HttpFile.prototype.pipe = function(stream, opt) {
1628
+ F.stats.performance.open++;
1629
+ return F.Fs.createReadStream(this.path, opt).pipe(stream, opt);
1630
+ };
1631
+
1632
+ HttpFile.prototype.image = function(shell) {
1633
+ return F.TImages.load(this.path, shell, this.width, this.height);
1634
+ };
1635
+
1636
+ HttpFile.prototype.fs = function(storage, fileid, callback, custom, expire) {
1637
+ return F.filestorage(storage).save(fileid, this.filename, this.path, callback, custom, expire);
1638
+ };
1639
+
1640
+ exports.Controller = Controller;