source: node_modules/express/lib/response.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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