total5 0.0.1-1

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