@mila.solutions/express 5.2.1-mila.0

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