nodester 0.0.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +125 -0
  3. package/docs/App.md +13 -0
  4. package/docs/Queries.md +61 -0
  5. package/docs/Readme.md +2 -0
  6. package/docs/Routing.md +34 -0
  7. package/examples/goal/index.js +23 -0
  8. package/examples/rest/index.js +25 -0
  9. package/examples/rest/node_modules/.package-lock.json +40 -0
  10. package/examples/rest/package-lock.json +72 -0
  11. package/examples/rest/package.json +14 -0
  12. package/lib/application/MiddlewareStack.js +125 -0
  13. package/lib/application/http/request.js +462 -0
  14. package/lib/application/http/response.js +1107 -0
  15. package/lib/application/http/utils.js +254 -0
  16. package/lib/application/index.js +292 -0
  17. package/lib/constants/ConstantsEnum.js +13 -0
  18. package/lib/constants/ResponseFormats.js +7 -0
  19. package/lib/controllers/Controller.js +474 -0
  20. package/lib/controllers/JWTController.js +240 -0
  21. package/lib/controllers/ServiceController.js +109 -0
  22. package/lib/controllers/WebController.js +75 -0
  23. package/lib/facades/Facade.js +388 -0
  24. package/lib/facades/FacadeParams.js +11 -0
  25. package/lib/facades/ServiceFacade.js +17 -0
  26. package/lib/facades/jwt.facade.js +273 -0
  27. package/lib/factories/errors/CustomError.js +22 -0
  28. package/lib/factories/errors/index.js +9 -0
  29. package/lib/factories/responses/api.js +90 -0
  30. package/lib/factories/responses/html.js +55 -0
  31. package/lib/logger/console.js +24 -0
  32. package/lib/models/DisabledRefreshToken.js +68 -0
  33. package/lib/models/Extractor.js +320 -0
  34. package/lib/models/define.js +62 -0
  35. package/lib/models/mixins.js +369 -0
  36. package/lib/policies/Role.js +77 -0
  37. package/lib/policies/RoleExtracting.js +97 -0
  38. package/lib/preprocessors/BodyPreprocessor.js +61 -0
  39. package/lib/preprocessors/IncludesPreprocessor.js +55 -0
  40. package/lib/preprocessors/QueryPreprocessor.js +64 -0
  41. package/lib/routers/Default/index.js +143 -0
  42. package/lib/routers/Default/layer.js +50 -0
  43. package/lib/routers/Main/index.js +10 -0
  44. package/lib/routers/Roles/index.js +81 -0
  45. package/lib/services/includes.service.js +79 -0
  46. package/lib/services/jwt.service.js +147 -0
  47. package/lib/tools/sql.tool.js +82 -0
  48. package/lib/utils/dates.util.js +23 -0
  49. package/lib/utils/forms.util.js +22 -0
  50. package/lib/utils/json.util.js +49 -0
  51. package/lib/utils/mappers/Routes/index.js +100 -0
  52. package/lib/utils/mappers/Routes/utils.js +20 -0
  53. package/lib/utils/modelAssociations.util.js +44 -0
  54. package/lib/utils/objects.util.js +69 -0
  55. package/lib/utils/params.util.js +19 -0
  56. package/lib/utils/path.util.js +26 -0
  57. package/lib/utils/queries.util.js +240 -0
  58. package/lib/utils/sanitizations.util.js +111 -0
  59. package/lib/utils/sql.util.js +78 -0
  60. package/lib/utils/strings.util.js +43 -0
  61. package/lib/utils/types.util.js +26 -0
  62. package/package.json +63 -0
  63. package/tests/index.test.js +35 -0
@@ -0,0 +1,1107 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ // Constants.
8
+ const charsetRegExp = /;\s*charset\s*=/;
9
+
10
+ // Utils:
11
+ const contentDisposition = require('content-disposition');
12
+ const createError = require('http-errors')
13
+ const encodeUrl = require('encodeurl');
14
+ const escapeHtml = require('escape-html');
15
+ const http = require('http');
16
+ const onFinished = require('on-finished');
17
+ const statuses = require('statuses')
18
+ const sign = require('cookie-signature').sign;
19
+ const cookie = require('cookie');
20
+ const send = require('send');
21
+ const mime = send.mime;
22
+ const vary = require('vary');
23
+ const {
24
+ normalizeType,
25
+ normalizeTypes,
26
+ setCharset
27
+ } = require('./utils');
28
+ const path = require('path');
29
+ const extname = path.extname;
30
+ const resolve = path.resolve;
31
+ const { isAbsolute } = require('../../utils/path.util');
32
+
33
+
34
+ const res = http.ServerResponse.prototype;
35
+ module.exports = res;
36
+
37
+ /**
38
+ * Set status `code`.
39
+ *
40
+ * @param {Number} code
41
+ * @return {ServerResponse}
42
+ *
43
+ * @public
44
+ */
45
+ res.status = function status(code) {
46
+ this.statusCode = code;
47
+ return this;
48
+ };
49
+
50
+
51
+ /**
52
+ * Set Link header field with the given `links`.
53
+ *
54
+ * Examples:
55
+ *
56
+ * res.links({
57
+ * next: 'http://api.example.com/users?page=2',
58
+ * last: 'http://api.example.com/users?page=5'
59
+ * });
60
+ *
61
+ * @param {Object} links
62
+ * @return {ServerResponse}
63
+ *
64
+ * @public
65
+ */
66
+ res.links = function(links){
67
+ const link = this.get('Link') || '';
68
+
69
+ if (link) {
70
+ link += ', ';
71
+ }
72
+
73
+ const linkRel = Object.keys(links)
74
+ .map((rel) => '<' + links[rel] + '>; rel="' + rel + '"')
75
+ .join(', ');
76
+ this.set('Link', link + linkRel);
77
+ };
78
+
79
+
80
+ /**
81
+ * Send a response.
82
+ *
83
+ * Examples:
84
+ *
85
+ * res.send(Buffer.from('wahoo'));
86
+ * res.send({ some: 'json' });
87
+ * res.send('<p>some html</p>');
88
+ *
89
+ * @param {string|number|boolean|object|Buffer} body
90
+ *
91
+ * @public
92
+ */
93
+ res.send = function send(body) {
94
+ const req = this.req;
95
+
96
+ let chunk = body;
97
+ let encoding;
98
+ let type;
99
+
100
+ // Settings.
101
+ const app = this.app;
102
+
103
+ switch (typeof chunk) {
104
+ // string defaulting to html
105
+ case 'string':
106
+ if (!this.get('Content-Type')) {
107
+ this.type('html');
108
+ }
109
+ break;
110
+ case 'boolean':
111
+ case 'number':
112
+ case 'object':
113
+ if (chunk === null) {
114
+ chunk = '';
115
+ }
116
+ else if (Buffer.isBuffer(chunk)) {
117
+ if (!this.get('Content-Type')) {
118
+ this.type('bin');
119
+ }
120
+ }
121
+ else {
122
+ return this.json(chunk);
123
+ }
124
+ break;
125
+ }
126
+
127
+ // write strings in utf-8
128
+ if (typeof chunk === 'string') {
129
+ encoding = 'utf8';
130
+ type = this.get('Content-Type');
131
+
132
+ // reflect this in content-type
133
+ if (typeof type === 'string') {
134
+ this.set('Content-Type', setCharset(type, 'utf-8'));
135
+ }
136
+ }
137
+
138
+ // determine if ETag should be generated
139
+ const etagFn = app.get('etag fn')
140
+ const generateETag = !this.get('ETag') && typeof etagFn === 'function'
141
+
142
+ // populate Content-Length
143
+ let bufferLength = undefined;
144
+ if (chunk !== undefined) {
145
+ if (Buffer.isBuffer(chunk)) {
146
+ // Get length of the Buffer.
147
+ bufferLength = chunk.length
148
+ }
149
+ else if (!generateETag && chunk.length < 1000) {
150
+ // Just calculate length when no ETag + small chunk.
151
+ bufferLength = Buffer.byteLength(chunk, encoding)
152
+ }
153
+ else {
154
+ // Convert chunk to Buffer and calculate:
155
+ chunk = Buffer.from(chunk, encoding)
156
+ encoding = undefined;
157
+ bufferLength = chunk.length
158
+ }
159
+
160
+ this.set('Content-Length', bufferLength);
161
+ }
162
+
163
+ // Populate ETag
164
+ let etag = undefined;
165
+ if (generateETag && bufferLength !== undefined) {
166
+ if ((etag = etagFn(chunk, encoding))) {
167
+ this.set('ETag', etag);
168
+ }
169
+ }
170
+
171
+ // freshness:
172
+ if (req.fresh) {
173
+ this.statusCode = 304;
174
+ }
175
+
176
+ // strip irrelevant headers
177
+ if (204 === this.statusCode || 304 === this.statusCode) {
178
+ this.removeHeader('Content-Type');
179
+ this.removeHeader('Content-Length');
180
+ this.removeHeader('Transfer-Encoding');
181
+ chunk = '';
182
+ }
183
+
184
+ // alter headers for 205
185
+ if (this.statusCode === 205) {
186
+ this.set('Content-Length', '0')
187
+ this.removeHeader('Transfer-Encoding')
188
+ chunk = ''
189
+ }
190
+
191
+ if (req.method === 'HEAD') {
192
+ // skip body for HEAD
193
+ this.end();
194
+ }
195
+ else {
196
+ // respond
197
+ this.end(chunk, encoding);
198
+ }
199
+
200
+ return this;
201
+ };
202
+
203
+
204
+ /**
205
+ * Send JSON response.
206
+ *
207
+ * Examples:
208
+ *
209
+ * res.json(null);
210
+ * res.json({ user: 'tj' });
211
+ *
212
+ * @param {string|number|boolean|object} obj
213
+ *
214
+ * @public
215
+ */
216
+ res.json = function json(obj) {
217
+ const app = this.app;
218
+
219
+ // Settings:
220
+ const escape = app.get('json escape')
221
+ const replacer = app.get('json replacer');
222
+ const spaces = app.get('json spaces');
223
+ const body = stringify(obj, replacer, spaces, escape)
224
+
225
+ // content-type
226
+ if (!this.get('Content-Type')) {
227
+ this.set('Content-Type', 'application/json');
228
+ }
229
+
230
+ return this.send(body);
231
+ };
232
+
233
+
234
+ /**
235
+ * Send JSON response with JSONP callback support.
236
+ *
237
+ * Examples:
238
+ *
239
+ * res.jsonp(null);
240
+ * res.jsonp({ user: 'tj' });
241
+ *
242
+ * @param {string|number|boolean|object} obj
243
+ *
244
+ * @public
245
+ */
246
+ res.jsonp = function jsonp(val) {
247
+ const app = this.app;
248
+ // Settings:
249
+ const escape = app.get('json escape')
250
+ const replacer = app.get('json replacer');
251
+ const spaces = app.get('json spaces');
252
+
253
+ let body = stringify(val, replacer, spaces, escape)
254
+ let callback = this.req.query[app.get('jsonp callback name')];
255
+
256
+ // content-type
257
+ if (!this.get('Content-Type')) {
258
+ this.set('X-Content-Type-Options', 'nosniff');
259
+ this.set('Content-Type', 'application/json');
260
+ }
261
+
262
+ // fixup callback
263
+ if (Array.isArray(callback)) {
264
+ callback = callback[0];
265
+ }
266
+
267
+ // jsonp
268
+ if (typeof callback === 'string' && callback.length !== 0) {
269
+ this.set('X-Content-Type-Options', 'nosniff');
270
+ this.set('Content-Type', 'text/javascript');
271
+
272
+ // restrict callback charset
273
+ callback = callback.replace(/[^\[\]\w$.]/g, '');
274
+
275
+ if (body === undefined) {
276
+ // empty argument.
277
+ body = '';
278
+ }
279
+ else if (typeof body === 'string') {
280
+ // replace chars not allowed in JavaScript that are in JSON.
281
+ body = body.replace(/\u2028/g, '\\u2028')
282
+ .replace(/\u2029/g, '\\u2029');
283
+ }
284
+
285
+ // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
286
+ // the typeof check is just to reduce client error noise
287
+ body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
288
+ }
289
+
290
+ return this.send(body);
291
+ };
292
+
293
+
294
+ /**
295
+ * Send given HTTP status code.
296
+ *
297
+ * Sets the response status to `statusCode` and the body of the
298
+ * response to the standard description from node's http.STATUS_CODES
299
+ * or the statusCode number if no description.
300
+ *
301
+ * Examples:
302
+ *
303
+ * res.sendStatus(200);
304
+ *
305
+ * @param {number} statusCode
306
+ *
307
+ * @public
308
+ */
309
+ res.sendStatus = function sendStatus(statusCode) {
310
+ const body = statuses.message[statusCode] || String(statusCode)
311
+
312
+ this.statusCode = statusCode;
313
+ this.type('txt');
314
+
315
+ return this.send(body);
316
+ };
317
+
318
+
319
+ /**
320
+ * Transfer the file at the given `path`.
321
+ *
322
+ * Automatically sets the _Content-Type_ response header field.
323
+ * The callback `callback(err)` is invoked when the transfer is complete
324
+ * or when an error occurs. Be sure to check `res.headersSent`
325
+ * if you wish to attempt responding, as the header and some data
326
+ * may have already been transferred.
327
+ *
328
+ * Options:
329
+ *
330
+ * - `maxAge` defaulting to 0 (can be string converted by `ms`)
331
+ * - `root` root directory for relative filenames
332
+ * - `headers` object of headers to serve with file
333
+ * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
334
+ *
335
+ * Other options are passed along to `send`.
336
+ *
337
+ * Examples:
338
+ *
339
+ * The following example illustrates how `res.sendFile()` may
340
+ * be used as an alternative for the `static()` middleware for
341
+ * dynamic situations. The code backing `res.sendFile()` is actually
342
+ * the same code, so HTTP cache support etc is identical.
343
+ *
344
+ * app.get('/user/:uid/photos/:file', function(req, res){
345
+ * var uid = req.params.uid
346
+ * , file = req.params.file;
347
+ *
348
+ * req.user.mayViewFilesFrom(uid, function(yes){
349
+ * if (yes) {
350
+ * res.sendFile('/uploads/' + uid + '/' + file);
351
+ * }
352
+ else {
353
+ * res.send(403, 'Sorry! you cant see that.');
354
+ * }
355
+ * });
356
+ * });
357
+ *
358
+ * @public
359
+ */
360
+ res.sendFile = function sendFile(path, options, callback) {
361
+ const req = this.req;
362
+ const res = this;
363
+ const next = req.next;
364
+
365
+ let opts = options || {};
366
+ let done = callback;
367
+
368
+ if (!path) {
369
+ throw new TypeError('path argument is required to res.sendFile');
370
+ }
371
+
372
+ if (typeof path !== 'string') {
373
+ throw new TypeError('path must be a string to res.sendFile')
374
+ }
375
+
376
+ // support function as second arg
377
+ if (typeof options === 'function') {
378
+ done = options;
379
+ opts = {};
380
+ }
381
+
382
+ if (!opts.root && !isAbsolute(path)) {
383
+ throw new TypeError('path must be absolute or specify root to res.sendFile');
384
+ }
385
+
386
+ // create file stream
387
+ const pathname = encodeURI(path);
388
+ const file = send(req, pathname, opts);
389
+
390
+ // transfer
391
+ sendfile(res, file, opts, function (err) {
392
+ if (done)
393
+ return done(err);
394
+
395
+ if (err && err.code === 'EISDIR')
396
+ return next();
397
+
398
+ // next() all but write errors
399
+ if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
400
+ next(err);
401
+ }
402
+ });
403
+ };
404
+
405
+
406
+ /**
407
+ * Transfer the file at the given `path`.
408
+ *
409
+ * Automatically sets the _Content-Type_ response header field.
410
+ * The callback `callback(err)` is invoked when the transfer is complete
411
+ * or when an error occurs. Be sure to check `res.headersSent`
412
+ * if you wish to attempt responding, as the header and some data
413
+ * may have already been transferred.
414
+ *
415
+ * Options:
416
+ *
417
+ * - `maxAge` defaulting to 0 (can be string converted by `ms`)
418
+ * - `root` root directory for relative filenames
419
+ * - `headers` object of headers to serve with file
420
+ * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
421
+ *
422
+ * Other options are passed along to `send`.
423
+ *
424
+ * Examples:
425
+ *
426
+ * The following example illustrates how `res.sendfile()` may
427
+ * be used as an alternative for the `static()` middleware for
428
+ * dynamic situations. The code backing `res.sendfile()` is actually
429
+ * the same code, so HTTP cache support etc is identical.
430
+ *
431
+ * app.get('/user/:uid/photos/:file', function(req, res){
432
+ * var uid = req.params.uid
433
+ * , file = req.params.file;
434
+ *
435
+ * req.user.mayViewFilesFrom(uid, function(yes){
436
+ * if (yes) {
437
+ * res.sendfile('/uploads/' + uid + '/' + file);
438
+ * }
439
+ else {
440
+ * res.send(403, 'Sorry! you cant see that.');
441
+ * }
442
+ * });
443
+ * });
444
+ *
445
+ * @public
446
+ */
447
+ res.sendfile = function (path, options, callback) {
448
+ const req = this.req;
449
+ const res = this;
450
+ const next = req.next;
451
+
452
+ let opts = options || {};
453
+ let done = callback;
454
+
455
+ // support function as second arg
456
+ if (typeof options === 'function') {
457
+ done = options;
458
+ opts = {};
459
+ }
460
+
461
+ // create file stream
462
+ const file = send(req, path, opts);
463
+
464
+ // transfer
465
+ sendfile(res, file, opts, function (err) {
466
+ if (done)
467
+ return done(err);
468
+
469
+ if (err && err.code === 'EISDIR')
470
+ return next();
471
+
472
+ // next() all but write errors
473
+ if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
474
+ next(err);
475
+ }
476
+ });
477
+ };
478
+
479
+
480
+ /**
481
+ * Transfer the file at the given `path` as an attachment.
482
+ *
483
+ * Optionally providing an alternate attachment `filename`,
484
+ * and optional callback `callback(err)`. The callback is invoked
485
+ * when the data transfer is complete, or when an error has
486
+ * occurred. Be sure to check `res.headersSent` if you plan to respond.
487
+ *
488
+ * Optionally providing an `options` object to use with `res.sendFile()`.
489
+ * This function will set the `Content-Disposition` header, overriding
490
+ * any `Content-Disposition` header passed as header options in order
491
+ * to set the attachment and filename.
492
+ *
493
+ * This method uses `res.sendFile()`.
494
+ *
495
+ * @public
496
+ */
497
+ res.download = function download (path, filename, options, callback) {
498
+ let done = callback;
499
+ let name = filename;
500
+ let opts = options || null
501
+
502
+ // support function as second or third arg
503
+ if (typeof filename === 'function') {
504
+ done = filename;
505
+ name = null;
506
+ opts = null
507
+ }
508
+ else if (typeof options === 'function') {
509
+ done = options
510
+ opts = null
511
+ }
512
+
513
+ // support optional filename, where options may be in it's place
514
+ if (typeof filename === 'object' &&
515
+ (typeof options === 'function' || options === undefined)) {
516
+ name = null
517
+ opts = filename
518
+ }
519
+
520
+ // set Content-Disposition when file is sent
521
+ const headers = {
522
+ 'Content-Disposition': contentDisposition(name || path)
523
+ };
524
+
525
+ // merge user-provided headers
526
+ if (opts && opts.headers) {
527
+ const keys = Object.keys(opts.headers)
528
+ for (let i = 0; i < keys.length; i++) {
529
+ const key = keys[i]
530
+ if (key.toLowerCase() !== 'content-disposition') {
531
+ headers[key] = opts.headers[key]
532
+ }
533
+ }
534
+ }
535
+
536
+ // merge user-provided options
537
+ opts = Object.create(opts)
538
+ opts.headers = headers
539
+
540
+ // Resolve the full path for sendFile
541
+ const fullPath = !opts.root ?
542
+ resolve(path)
543
+ :
544
+ path;
545
+
546
+ // send file
547
+ return this.sendFile(fullPath, opts, done)
548
+ };
549
+
550
+
551
+ /**
552
+ * Set _Content-Type_ response header with `type` through `mime.lookup()`
553
+ * when it does not contain "/", or set the Content-Type to `type` otherwise.
554
+ *
555
+ * Examples:
556
+ *
557
+ * res.type('.html');
558
+ * res.type('html');
559
+ * res.type('json');
560
+ * res.type('application/json');
561
+ * res.type('png');
562
+ *
563
+ * @param {String} type
564
+ * @return {ServerResponse} for chaining
565
+ *
566
+ * @public
567
+ */
568
+ res.contentType =
569
+ res.type = function contentType(type) {
570
+ const _contentType = type.indexOf('/') === -1 ?
571
+ mime.lookup(type)
572
+ :
573
+ type;
574
+
575
+ return this.set('Content-Type', _contentType);
576
+ };
577
+
578
+
579
+ /**
580
+ * Respond to the Acceptable formats using an `obj`
581
+ * of mime-type callbacks.
582
+ *
583
+ * This method uses `req.accepted`, an array of
584
+ * acceptable types ordered by their quality values.
585
+ * When "Accept" is not present the _first_ callback
586
+ * is invoked, otherwise the first match is used. When
587
+ * no match is performed the server responds with
588
+ * 406 "Not Acceptable".
589
+ *
590
+ * Content-Type is set for you, however if you choose
591
+ * you may alter this within the callback using `res.type()`
592
+ * or `res.set('Content-Type', ...)`.
593
+ *
594
+ * res.format({
595
+ * 'text/plain': function(){
596
+ * res.send('hey');
597
+ * },
598
+ *
599
+ * 'text/html': function(){
600
+ * res.send('<p>hey</p>');
601
+ * },
602
+ *
603
+ * 'application/json': function () {
604
+ * res.send({ message: 'hey' });
605
+ * }
606
+ * });
607
+ *
608
+ * In addition to canonicalized MIME types you may
609
+ * also use extnames mapped to these types:
610
+ *
611
+ * res.format({
612
+ * text: function(){
613
+ * res.send('hey');
614
+ * },
615
+ *
616
+ * html: function(){
617
+ * res.send('<p>hey</p>');
618
+ * },
619
+ *
620
+ * json: function(){
621
+ * res.send({ message: 'hey' });
622
+ * }
623
+ * });
624
+ *
625
+ * By default Express passes an `Error`
626
+ * with a `.status` of 406 to `next(err)`
627
+ * if a match is not made. If you provide
628
+ * a `.default` callback it will be invoked
629
+ * instead.
630
+ *
631
+ * @param {Object} obj
632
+ * @return {ServerResponse} for chaining
633
+ *
634
+ * @public
635
+ */
636
+ res.format = function(obj){
637
+ const req = this.req;
638
+ const next = req.next;
639
+
640
+ const keys = Object.keys(obj)
641
+ .filter((v) => v !== 'default');
642
+
643
+ const key = keys.length > 0 ?
644
+ req.accepts(keys)
645
+ :
646
+ false;
647
+
648
+ this.vary('Accept');
649
+
650
+ if (key) {
651
+ this.set('Content-Type', normalizeType(key).value);
652
+ obj[key](req, this, next);
653
+ }
654
+ else if (obj.default) {
655
+ obj.default(req, this, next)
656
+ }
657
+ else {
658
+ next(createError(406, {
659
+ types: normalizeTypes(keys).map( o => o.value )
660
+ }))
661
+ }
662
+
663
+ return this;
664
+ };
665
+
666
+
667
+ /**
668
+ * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
669
+ *
670
+ * @param {String} filename
671
+ * @return {ServerResponse}
672
+ *
673
+ * @public
674
+ */
675
+ res.attachment = function attachment(filename) {
676
+ if (filename) {
677
+ this.type(extname(filename));
678
+ }
679
+
680
+ this.set('Content-Disposition', contentDisposition(filename));
681
+
682
+ return this;
683
+ };
684
+
685
+
686
+ /**
687
+ * Append additional header `field` with value `val`.
688
+ *
689
+ * Example:
690
+ *
691
+ * res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
692
+ * res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
693
+ * res.append('Warning', '199 Miscellaneous warning');
694
+ *
695
+ * @param {String} field
696
+ * @param {String|Array} val
697
+ * @return {ServerResponse} for chaining
698
+ *
699
+ * @public
700
+ */
701
+ res.append = function append(field, val) {
702
+ const prev = this.get(field);
703
+ let value = val;
704
+
705
+ if (prev) {
706
+ // concat the new and prev vals
707
+ value = Array.isArray(prev) ? prev.concat(val)
708
+ : Array.isArray(val) ? [prev].concat(val)
709
+ : [prev, val]
710
+ }
711
+
712
+ return this.set(field, value);
713
+ };
714
+
715
+
716
+ /**
717
+ * Set header `field` to `val`, or pass
718
+ * an object of header fields.
719
+ *
720
+ * Examples:
721
+ *
722
+ * res.set('Foo', ['bar', 'baz']);
723
+ * res.set('Accept', 'application/json');
724
+ * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
725
+ *
726
+ * Aliased as `res.header()`.
727
+ *
728
+ * @param {String|Object} field
729
+ * @param {String|Array} val
730
+ * @return {ServerResponse} for chaining
731
+ *
732
+ * @public
733
+ */
734
+ res.set =
735
+ res.header = function header(field, val) {
736
+ if (arguments.length === 2) {
737
+ let value = Array.isArray(val) ?
738
+ val.map(String)
739
+ :
740
+ String(val);
741
+
742
+ // add charset to content-type
743
+ if (field.toLowerCase() === 'content-type') {
744
+ if (Array.isArray(value)) {
745
+ throw new TypeError('Content-Type cannot be set to an Array');
746
+ }
747
+
748
+ if (!charsetRegExp.test(value)) {
749
+ const charset = mime.charsets.lookup(value.split(';')[0]);
750
+
751
+ if (charset) {
752
+ value += '; charset=' + charset.toLowerCase();
753
+ }
754
+ }
755
+ }
756
+
757
+ this.setHeader(field, value);
758
+ }
759
+ else {
760
+ for (let key in field) {
761
+ this.set(key, field[key]);
762
+ }
763
+ }
764
+ return this;
765
+ };
766
+
767
+
768
+ /**
769
+ * Get value for header `field`.
770
+ *
771
+ * @param {String} field
772
+ * @return {String}
773
+ *
774
+ * @public
775
+ */
776
+ res.get = function(field){
777
+ return this.getHeader(field);
778
+ };
779
+
780
+
781
+ /**
782
+ * Clear cookie `name`.
783
+ *
784
+ * @param {String} name
785
+ * @param {Object} [options]
786
+ * @return {ServerResponse} for chaining
787
+ *
788
+ * @public
789
+ */
790
+ res.clearCookie = function clearCookie(name, options) {
791
+ const defaultOpts = { expires: new Date(1), path: '/' };
792
+ const opts = merge(defaultOpts, options);
793
+
794
+ return this.cookie(name, '', opts);
795
+ };
796
+
797
+
798
+ /**
799
+ * Set cookie `name` to `value`, with the given `options`.
800
+ *
801
+ * Options:
802
+ *
803
+ * - `maxAge` max-age in milliseconds, converted to `expires`
804
+ * - `signed` sign the cookie
805
+ * - `path` defaults to "/"
806
+ *
807
+ * Examples:
808
+ *
809
+ * // "Remember Me" for 15 minutes
810
+ * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
811
+ *
812
+ * // same as above
813
+ * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
814
+ *
815
+ * @param {String} name
816
+ * @param {String|Object} value
817
+ * @param {Object} [options]
818
+ * @return {ServerResponse} for chaining
819
+ *
820
+ * @public
821
+ */
822
+ res.cookie = function (name, value, options) {
823
+ let opts = merge({}, options);
824
+ const secret = this.req.secret;
825
+ const signed = !!opts.signed;
826
+
827
+ if (signed && !secret) {
828
+ throw new Error('cookieParser("secret") required for signed cookies');
829
+ }
830
+
831
+ let val = typeof value === 'object'
832
+ ? 'j:' + JSON.stringify(value)
833
+ : String(value);
834
+
835
+ if (signed) {
836
+ val = 's:' + sign(val, secret);
837
+ }
838
+
839
+ if (opts.maxAge != null) {
840
+ const maxAge = opts.maxAge - 0
841
+
842
+ if (!isNaN(maxAge)) {
843
+ opts.expires = new Date(Date.now() + maxAge)
844
+ opts.maxAge = Math.floor(maxAge / 1000)
845
+ }
846
+ }
847
+
848
+ if (opts.path == null) {
849
+ opts.path = '/';
850
+ }
851
+
852
+ this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
853
+
854
+ return this;
855
+ };
856
+
857
+
858
+ /**
859
+ * Set the location header to `url`.
860
+ *
861
+ * The given `url` can also be "back", which redirects
862
+ * to the _Referrer_ or _Referer_ headers or "/".
863
+ *
864
+ * Examples:
865
+ *
866
+ * res.location('/foo/bar').;
867
+ * res.location('http://example.com');
868
+ * res.location('../login');
869
+ *
870
+ * @param {String} url
871
+ * @return {ServerResponse} for chaining
872
+ *
873
+ * @public
874
+ */
875
+ res.location = function location(url) {
876
+ let to = url;
877
+
878
+ // "back" is an alias for the referrer
879
+ if (url === 'back') {
880
+ to = this.req.get('Referrer') || '/';
881
+ }
882
+
883
+ // set location
884
+ return this.set('Location', encodeUrl(to));
885
+ };
886
+
887
+
888
+ /**
889
+ * Redirect to the given `url` with optional response `status`
890
+ * defaulting to 302.
891
+ *
892
+ * The resulting `url` is determined by `res.location()`, so
893
+ * it will play nicely with mounted apps, relative paths,
894
+ * `"back"` etc.
895
+ *
896
+ * Examples:
897
+ *
898
+ * res.redirect('/foo/bar');
899
+ * res.redirect('http://example.com');
900
+ * res.redirect(301, 'http://example.com');
901
+ * res.redirect('../login'); // /blog/post/1 -> /blog/login
902
+ *
903
+ * @public
904
+ */
905
+ res.redirect = function redirect(url) {
906
+ let address = url;
907
+ let body;
908
+ let status = 302;
909
+
910
+ // allow status / url
911
+ if (arguments.length === 2) {
912
+ if (typeof arguments[0] === 'number') {
913
+ status = arguments[0];
914
+ address = arguments[1];
915
+ }
916
+ else {
917
+ deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
918
+ status = arguments[1];
919
+ }
920
+ }
921
+
922
+ // Set location header
923
+ address = this.location(address).get('Location');
924
+
925
+ // Support text/{plain,html} by default
926
+ this.format({
927
+ text: function(){
928
+ body = statuses.message[status] + '. Redirecting to ' + address
929
+ },
930
+
931
+ html: function(){
932
+ const url = escapeHtml(address);
933
+ body = '<p>' + statuses.message[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>'
934
+ },
935
+
936
+ default: function(){
937
+ body = '';
938
+ }
939
+ });
940
+
941
+ // Respond
942
+ this.statusCode = status;
943
+ this.set('Content-Length', Buffer.byteLength(body));
944
+
945
+ if (this.req.method === 'HEAD') {
946
+ this.end();
947
+ }
948
+ else {
949
+ this.end(body);
950
+ }
951
+ };
952
+
953
+
954
+ /**
955
+ * Add `field` to Vary. If already present in the Vary set, then
956
+ * this call is simply ignored.
957
+ *
958
+ * @param {Array|String} field
959
+ * @return {ServerResponse} for chaining
960
+ *
961
+ * @public
962
+ */
963
+ res.vary = function(field) {
964
+ vary(this, field);
965
+ return this;
966
+ };
967
+
968
+
969
+ /**
970
+ * Render `view` with the given `options` and optional callback `fn`.
971
+ * When a callback function is given a response will _not_ be made
972
+ * automatically, otherwise a response of _200_ and _text/html_ is given.
973
+ *
974
+ * Options:
975
+ *
976
+ * - `cache` boolean hinting to the engine it should cache
977
+ * - `filename` filename of the view being rendered
978
+ *
979
+ * @public
980
+ */
981
+ res.render = function render(view, options, callback) {
982
+ const app = this.req.app;
983
+ let done = callback;
984
+ let opts = options || {};
985
+ const req = this.req;
986
+ const self = this;
987
+
988
+ // support callback function as second arg
989
+ if (typeof options === 'function') {
990
+ done = options;
991
+ opts = {};
992
+ }
993
+
994
+ // merge res.locals
995
+ opts._locals = self.locals;
996
+
997
+ // default callback to respond
998
+ done = done || function (err, str) {
999
+ if (err) return req.next(err);
1000
+ self.send(str);
1001
+ };
1002
+
1003
+ // render
1004
+ app.render(view, opts, done);
1005
+ };
1006
+
1007
+ // pipe the send file stream
1008
+ function sendfile(res, file, options, callback) {
1009
+ let done = false;
1010
+ let streaming;
1011
+
1012
+ // request aborted
1013
+ function onaborted() {
1014
+ if (done) return;
1015
+ done = true;
1016
+
1017
+ const err = new Error('Request aborted');
1018
+ err.code = 'ECONNABORTED';
1019
+ callback(err);
1020
+ }
1021
+
1022
+ // directory
1023
+ function ondirectory() {
1024
+ if (done)
1025
+ return;
1026
+
1027
+ done = true;
1028
+
1029
+ const err = new Error('EISDIR, read');
1030
+ err.code = 'EISDIR';
1031
+ callback(err);
1032
+ }
1033
+
1034
+ // errors
1035
+ function onerror(err) {
1036
+ if (done)
1037
+ return;
1038
+
1039
+ done = true;
1040
+ callback(err);
1041
+ }
1042
+
1043
+ // ended
1044
+ function onend() {
1045
+ if (done)
1046
+ return;
1047
+
1048
+ done = true;
1049
+ callback();
1050
+ }
1051
+
1052
+ // file
1053
+ function onfile() {
1054
+ streaming = false;
1055
+ }
1056
+
1057
+ // finished
1058
+ function onfinish(err) {
1059
+ if (err && err.code === 'ECONNRESET')
1060
+ return onaborted();
1061
+
1062
+ if (err)
1063
+ return onerror(err);
1064
+
1065
+ if (done)
1066
+ return;
1067
+
1068
+ setImmediate(function () {
1069
+ if (streaming !== false && !done) {
1070
+ onaborted();
1071
+ return;
1072
+ }
1073
+
1074
+ if (done) return;
1075
+ done = true;
1076
+ callback();
1077
+ });
1078
+ }
1079
+
1080
+ // streaming
1081
+ function onstream() {
1082
+ streaming = true;
1083
+ }
1084
+
1085
+ file.on('directory', ondirectory);
1086
+ file.on('end', onend);
1087
+ file.on('error', onerror);
1088
+ file.on('file', onfile);
1089
+ file.on('stream', onstream);
1090
+ onFinished(res, onfinish);
1091
+
1092
+ if (options.headers) {
1093
+ // set headers on successful transfer
1094
+ file.on('headers', function headers(res) {
1095
+ const _headers = options.headers;
1096
+ const keys = Object.keys(_headers);
1097
+
1098
+ for (let i = 0; i < keys.length; i++) {
1099
+ const key = keys[i];
1100
+ res.setHeader(key, _headers[k]);
1101
+ }
1102
+ });
1103
+ }
1104
+
1105
+ // pipe
1106
+ file.pipe(res);
1107
+ }