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