cleanup: remove unnecessary require for global Buffer
[express.git] / lib / response.js
blobe439a06ae8648aa3072be1cb43b299a1f6351f6d
1 /*!
2 * express
3 * Copyright(c) 2009-2013 TJ Holowaychuk
4 * Copyright(c) 2014-2015 Douglas Christopher Wilson
5 * MIT Licensed
6 */
8 'use strict';
10 /**
11 * Module dependencies.
12 * @private
15 var contentDisposition = require('content-disposition');
16 var createError = require('http-errors')
17 var encodeUrl = require('encodeurl');
18 var escapeHtml = require('escape-html');
19 var http = require('http');
20 var onFinished = require('on-finished');
21 var mime = require('mime-types')
22 var path = require('path');
23 var pathIsAbsolute = require('path').isAbsolute;
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 resolve = path.resolve;
34 var vary = require('vary');
36 /**
37 * Response prototype.
38 * @public
41 var res = Object.create(http.ServerResponse.prototype)
43 /**
44 * Module exports.
45 * @public
48 module.exports = res
50 /**
51 * Set the HTTP status code for the response.
53 * Expects an integer value between 100 and 999 inclusive.
54 * Throws an error if the provided status code is not an integer or if it's outside the allowable range.
56 * @param {number} code - The HTTP status code to set.
57 * @return {ServerResponse} - Returns itself for chaining methods.
58 * @throws {TypeError} If `code` is not an integer.
59 * @throws {RangeError} If `code` is outside the range 100 to 999.
60 * @public
63 res.status = function status(code) {
64 // Check if the status code is not an integer
65 if (!Number.isInteger(code)) {
66 throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
68 // Check if the status code is outside of Node's valid range
69 if (code < 100 || code > 999) {
70 throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
73 this.statusCode = code;
74 return this;
77 /**
78 * Set Link header field with the given `links`.
80 * Examples:
82 * res.links({
83 * next: 'http://api.example.com/users?page=2',
84 * last: 'http://api.example.com/users?page=5'
85 * });
87 * @param {Object} links
88 * @return {ServerResponse}
89 * @public
92 res.links = function(links){
93 var link = this.get('Link') || '';
94 if (link) link += ', ';
95 return this.set('Link', link + Object.keys(links).map(function(rel){
96 return '<' + links[rel] + '>; rel="' + rel + '"';
97 }).join(', '));
101 * Send a response.
103 * Examples:
105 * res.send(Buffer.from('wahoo'));
106 * res.send({ some: 'json' });
107 * res.send('<p>some html</p>');
109 * @param {string|number|boolean|object|Buffer} body
110 * @public
113 res.send = function send(body) {
114 var chunk = body;
115 var encoding;
116 var req = this.req;
117 var type;
119 // settings
120 var app = this.app;
122 switch (typeof chunk) {
123 // string defaulting to html
124 case 'string':
125 if (!this.get('Content-Type')) {
126 this.type('html');
128 break;
129 case 'boolean':
130 case 'number':
131 case 'object':
132 if (chunk === null) {
133 chunk = '';
134 } else if (Buffer.isBuffer(chunk)) {
135 if (!this.get('Content-Type')) {
136 this.type('bin');
138 } else {
139 return this.json(chunk);
141 break;
144 // write strings in utf-8
145 if (typeof chunk === 'string') {
146 encoding = 'utf8';
147 type = this.get('Content-Type');
149 // reflect this in content-type
150 if (typeof type === 'string') {
151 this.set('Content-Type', setCharset(type, 'utf-8'));
155 // determine if ETag should be generated
156 var etagFn = app.get('etag fn')
157 var generateETag = !this.get('ETag') && typeof etagFn === 'function'
159 // populate Content-Length
160 var len
161 if (chunk !== undefined) {
162 if (Buffer.isBuffer(chunk)) {
163 // get length of Buffer
164 len = chunk.length
165 } else if (!generateETag && chunk.length < 1000) {
166 // just calculate length when no ETag + small chunk
167 len = Buffer.byteLength(chunk, encoding)
168 } else {
169 // convert chunk to Buffer and calculate
170 chunk = Buffer.from(chunk, encoding)
171 encoding = undefined;
172 len = chunk.length
175 this.set('Content-Length', len);
178 // populate ETag
179 var etag;
180 if (generateETag && len !== undefined) {
181 if ((etag = etagFn(chunk, encoding))) {
182 this.set('ETag', etag);
186 // freshness
187 if (req.fresh) this.status(304);
189 // strip irrelevant headers
190 if (204 === this.statusCode || 304 === this.statusCode) {
191 this.removeHeader('Content-Type');
192 this.removeHeader('Content-Length');
193 this.removeHeader('Transfer-Encoding');
194 chunk = '';
197 // alter headers for 205
198 if (this.statusCode === 205) {
199 this.set('Content-Length', '0')
200 this.removeHeader('Transfer-Encoding')
201 chunk = ''
204 if (req.method === 'HEAD') {
205 // skip body for HEAD
206 this.end();
207 } else {
208 // respond
209 this.end(chunk, encoding);
212 return this;
216 * Send JSON response.
218 * Examples:
220 * res.json(null);
221 * res.json({ user: 'tj' });
223 * @param {string|number|boolean|object} obj
224 * @public
227 res.json = function json(obj) {
228 // settings
229 var app = this.app;
230 var escape = app.get('json escape')
231 var replacer = app.get('json replacer');
232 var spaces = app.get('json spaces');
233 var body = stringify(obj, replacer, spaces, escape)
235 // content-type
236 if (!this.get('Content-Type')) {
237 this.set('Content-Type', 'application/json');
240 return this.send(body);
244 * Send JSON response with JSONP callback support.
246 * Examples:
248 * res.jsonp(null);
249 * res.jsonp({ user: 'tj' });
251 * @param {string|number|boolean|object} obj
252 * @public
255 res.jsonp = function jsonp(obj) {
256 // settings
257 var app = this.app;
258 var escape = app.get('json escape')
259 var replacer = app.get('json replacer');
260 var spaces = app.get('json spaces');
261 var body = stringify(obj, replacer, spaces, escape)
262 var callback = this.req.query[app.get('jsonp callback name')];
264 // content-type
265 if (!this.get('Content-Type')) {
266 this.set('X-Content-Type-Options', 'nosniff');
267 this.set('Content-Type', 'application/json');
270 // fixup callback
271 if (Array.isArray(callback)) {
272 callback = callback[0];
275 // jsonp
276 if (typeof callback === 'string' && callback.length !== 0) {
277 this.set('X-Content-Type-Options', 'nosniff');
278 this.set('Content-Type', 'text/javascript');
280 // restrict callback charset
281 callback = callback.replace(/[^\[\]\w$.]/g, '');
283 if (body === undefined) {
284 // empty argument
285 body = ''
286 } else if (typeof body === 'string') {
287 // replace chars not allowed in JavaScript that are in JSON
288 body = body
289 .replace(/\u2028/g, '\\u2028')
290 .replace(/\u2029/g, '\\u2029')
293 // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
294 // the typeof check is just to reduce client error noise
295 body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
298 return this.send(body);
302 * Send given HTTP status code.
304 * Sets the response status to `statusCode` and the body of the
305 * response to the standard description from node's http.STATUS_CODES
306 * or the statusCode number if no description.
308 * Examples:
310 * res.sendStatus(200);
312 * @param {number} statusCode
313 * @public
316 res.sendStatus = function sendStatus(statusCode) {
317 var body = statuses.message[statusCode] || String(statusCode)
319 this.status(statusCode);
320 this.type('txt');
322 return this.send(body);
326 * Transfer the file at the given `path`.
328 * Automatically sets the _Content-Type_ response header field.
329 * The callback `callback(err)` is invoked when the transfer is complete
330 * or when an error occurs. Be sure to check `res.headersSent`
331 * if you wish to attempt responding, as the header and some data
332 * may have already been transferred.
334 * Options:
336 * - `maxAge` defaulting to 0 (can be string converted by `ms`)
337 * - `root` root directory for relative filenames
338 * - `headers` object of headers to serve with file
339 * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
341 * Other options are passed along to `send`.
343 * Examples:
345 * The following example illustrates how `res.sendFile()` may
346 * be used as an alternative for the `static()` middleware for
347 * dynamic situations. The code backing `res.sendFile()` is actually
348 * the same code, so HTTP cache support etc is identical.
350 * app.get('/user/:uid/photos/:file', function(req, res){
351 * var uid = req.params.uid
352 * , file = req.params.file;
354 * req.user.mayViewFilesFrom(uid, function(yes){
355 * if (yes) {
356 * res.sendFile('/uploads/' + uid + '/' + file);
357 * } else {
358 * res.send(403, 'Sorry! you cant see that.');
360 * });
361 * });
363 * @public
366 res.sendFile = function sendFile(path, options, callback) {
367 var done = callback;
368 var req = this.req;
369 var res = this;
370 var next = req.next;
371 var opts = options || {};
373 if (!path) {
374 throw new TypeError('path argument is required to res.sendFile');
377 if (typeof path !== 'string') {
378 throw new TypeError('path must be a string to res.sendFile')
381 // support function as second arg
382 if (typeof options === 'function') {
383 done = options;
384 opts = {};
387 if (!opts.root && !pathIsAbsolute(path)) {
388 throw new TypeError('path must be absolute or specify root to res.sendFile');
391 // create file stream
392 var pathname = encodeURI(path);
393 var file = send(req, pathname, opts);
395 // transfer
396 sendfile(res, file, opts, function (err) {
397 if (done) return done(err);
398 if (err && err.code === 'EISDIR') return next();
400 // next() all but write errors
401 if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
402 next(err);
408 * Transfer the file at the given `path` as an attachment.
410 * Optionally providing an alternate attachment `filename`,
411 * and optional callback `callback(err)`. The callback is invoked
412 * when the data transfer is complete, or when an error has
413 * occurred. Be sure to check `res.headersSent` if you plan to respond.
415 * Optionally providing an `options` object to use with `res.sendFile()`.
416 * This function will set the `Content-Disposition` header, overriding
417 * any `Content-Disposition` header passed as header options in order
418 * to set the attachment and filename.
420 * This method uses `res.sendFile()`.
422 * @public
425 res.download = function download (path, filename, options, callback) {
426 var done = callback;
427 var name = filename;
428 var opts = options || null
430 // support function as second or third arg
431 if (typeof filename === 'function') {
432 done = filename;
433 name = null;
434 opts = null
435 } else if (typeof options === 'function') {
436 done = options
437 opts = null
440 // support optional filename, where options may be in it's place
441 if (typeof filename === 'object' &&
442 (typeof options === 'function' || options === undefined)) {
443 name = null
444 opts = filename
447 // set Content-Disposition when file is sent
448 var headers = {
449 'Content-Disposition': contentDisposition(name || path)
452 // merge user-provided headers
453 if (opts && opts.headers) {
454 var keys = Object.keys(opts.headers)
455 for (var i = 0; i < keys.length; i++) {
456 var key = keys[i]
457 if (key.toLowerCase() !== 'content-disposition') {
458 headers[key] = opts.headers[key]
463 // merge user-provided options
464 opts = Object.create(opts)
465 opts.headers = headers
467 // Resolve the full path for sendFile
468 var fullPath = !opts.root
469 ? resolve(path)
470 : path
472 // send file
473 return this.sendFile(fullPath, opts, done)
477 * Set _Content-Type_ response header with `type` through `mime.contentType()`
478 * when it does not contain "/", or set the Content-Type to `type` otherwise.
479 * When no mapping is found though `mime.contentType()`, the type is set to
480 * "application/octet-stream".
482 * Examples:
484 * res.type('.html');
485 * res.type('html');
486 * res.type('json');
487 * res.type('application/json');
488 * res.type('png');
490 * @param {String} type
491 * @return {ServerResponse} for chaining
492 * @public
495 res.contentType =
496 res.type = function contentType(type) {
497 var ct = type.indexOf('/') === -1
498 ? (mime.contentType(type) || 'application/octet-stream')
499 : type;
501 return this.set('Content-Type', ct);
505 * Respond to the Acceptable formats using an `obj`
506 * of mime-type callbacks.
508 * This method uses `req.accepted`, an array of
509 * acceptable types ordered by their quality values.
510 * When "Accept" is not present the _first_ callback
511 * is invoked, otherwise the first match is used. When
512 * no match is performed the server responds with
513 * 406 "Not Acceptable".
515 * Content-Type is set for you, however if you choose
516 * you may alter this within the callback using `res.type()`
517 * or `res.set('Content-Type', ...)`.
519 * res.format({
520 * 'text/plain': function(){
521 * res.send('hey');
522 * },
524 * 'text/html': function(){
525 * res.send('<p>hey</p>');
526 * },
528 * 'application/json': function () {
529 * res.send({ message: 'hey' });
531 * });
533 * In addition to canonicalized MIME types you may
534 * also use extnames mapped to these types:
536 * res.format({
537 * text: function(){
538 * res.send('hey');
539 * },
541 * html: function(){
542 * res.send('<p>hey</p>');
543 * },
545 * json: function(){
546 * res.send({ message: 'hey' });
548 * });
550 * By default Express passes an `Error`
551 * with a `.status` of 406 to `next(err)`
552 * if a match is not made. If you provide
553 * a `.default` callback it will be invoked
554 * instead.
556 * @param {Object} obj
557 * @return {ServerResponse} for chaining
558 * @public
561 res.format = function(obj){
562 var req = this.req;
563 var next = req.next;
565 var keys = Object.keys(obj)
566 .filter(function (v) { return v !== 'default' })
568 var key = keys.length > 0
569 ? req.accepts(keys)
570 : false;
572 this.vary("Accept");
574 if (key) {
575 this.set('Content-Type', normalizeType(key).value);
576 obj[key](req, this, next);
577 } else if (obj.default) {
578 obj.default(req, this, next)
579 } else {
580 next(createError(406, {
581 types: normalizeTypes(keys).map(function (o) { return o.value })
585 return this;
589 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
591 * @param {String} filename
592 * @return {ServerResponse}
593 * @public
596 res.attachment = function attachment(filename) {
597 if (filename) {
598 this.type(extname(filename));
601 this.set('Content-Disposition', contentDisposition(filename));
603 return this;
607 * Append additional header `field` with value `val`.
609 * Example:
611 * res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
612 * res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
613 * res.append('Warning', '199 Miscellaneous warning');
615 * @param {String} field
616 * @param {String|Array} val
617 * @return {ServerResponse} for chaining
618 * @public
621 res.append = function append(field, val) {
622 var prev = this.get(field);
623 var value = val;
625 if (prev) {
626 // concat the new and prev vals
627 value = Array.isArray(prev) ? prev.concat(val)
628 : Array.isArray(val) ? [prev].concat(val)
629 : [prev, val]
632 return this.set(field, value);
636 * Set header `field` to `val`, or pass
637 * an object of header fields.
639 * Examples:
641 * res.set('Foo', ['bar', 'baz']);
642 * res.set('Accept', 'application/json');
643 * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
645 * Aliased as `res.header()`.
647 * When the set header is "Content-Type", the type is expanded to include
648 * the charset if not present using `mime.contentType()`.
650 * @param {String|Object} field
651 * @param {String|Array} val
652 * @return {ServerResponse} for chaining
653 * @public
656 res.set =
657 res.header = function header(field, val) {
658 if (arguments.length === 2) {
659 var value = Array.isArray(val)
660 ? val.map(String)
661 : String(val);
663 // add charset to content-type
664 if (field.toLowerCase() === 'content-type') {
665 if (Array.isArray(value)) {
666 throw new TypeError('Content-Type cannot be set to an Array');
668 value = mime.contentType(value)
671 this.setHeader(field, value);
672 } else {
673 for (var key in field) {
674 this.set(key, field[key]);
677 return this;
681 * Get value for header `field`.
683 * @param {String} field
684 * @return {String}
685 * @public
688 res.get = function(field){
689 return this.getHeader(field);
693 * Clear cookie `name`.
695 * @param {String} name
696 * @param {Object} [options]
697 * @return {ServerResponse} for chaining
698 * @public
701 res.clearCookie = function clearCookie(name, options) {
702 // Force cookie expiration by setting expires to the past
703 const opts = { path: '/', ...options, expires: new Date(1)};
704 // ensure maxAge is not passed
705 delete opts.maxAge
707 return this.cookie(name, '', opts);
711 * Set cookie `name` to `value`, with the given `options`.
713 * Options:
715 * - `maxAge` max-age in milliseconds, converted to `expires`
716 * - `signed` sign the cookie
717 * - `path` defaults to "/"
719 * Examples:
721 * // "Remember Me" for 15 minutes
722 * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
724 * // same as above
725 * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
727 * @param {String} name
728 * @param {String|Object} value
729 * @param {Object} [options]
730 * @return {ServerResponse} for chaining
731 * @public
734 res.cookie = function (name, value, options) {
735 var opts = merge({}, options);
736 var secret = this.req.secret;
737 var signed = opts.signed;
739 if (signed && !secret) {
740 throw new Error('cookieParser("secret") required for signed cookies');
743 var val = typeof value === 'object'
744 ? 'j:' + JSON.stringify(value)
745 : String(value);
747 if (signed) {
748 val = 's:' + sign(val, secret);
751 if (opts.maxAge != null) {
752 var maxAge = opts.maxAge - 0
754 if (!isNaN(maxAge)) {
755 opts.expires = new Date(Date.now() + maxAge)
756 opts.maxAge = Math.floor(maxAge / 1000)
760 if (opts.path == null) {
761 opts.path = '/';
764 this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
766 return this;
770 * Set the location header to `url`.
772 * The given `url` can also be "back", which redirects
773 * to the _Referrer_ or _Referer_ headers or "/".
775 * Examples:
777 * res.location('/foo/bar').;
778 * res.location('http://example.com');
779 * res.location('../login');
781 * @param {String} url
782 * @return {ServerResponse} for chaining
783 * @public
786 res.location = function location(url) {
787 return this.set('Location', encodeUrl(url));
791 * Redirect to the given `url` with optional response `status`
792 * defaulting to 302.
794 * Examples:
796 * res.redirect('/foo/bar');
797 * res.redirect('http://example.com');
798 * res.redirect(301, 'http://example.com');
799 * res.redirect('../login'); // /blog/post/1 -> /blog/login
801 * @public
804 res.redirect = function redirect(url) {
805 var address = url;
806 var body;
807 var status = 302;
809 // allow status / url
810 if (arguments.length === 2) {
811 status = arguments[0]
812 address = arguments[1]
815 // Set location header
816 address = this.location(address).get('Location');
818 // Support text/{plain,html} by default
819 this.format({
820 text: function(){
821 body = statuses.message[status] + '. Redirecting to ' + address
824 html: function(){
825 var u = escapeHtml(address);
826 body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
829 default: function(){
830 body = '';
834 // Respond
835 this.status(status);
836 this.set('Content-Length', Buffer.byteLength(body));
838 if (this.req.method === 'HEAD') {
839 this.end();
840 } else {
841 this.end(body);
846 * Add `field` to Vary. If already present in the Vary set, then
847 * this call is simply ignored.
849 * @param {Array|String} field
850 * @return {ServerResponse} for chaining
851 * @public
854 res.vary = function(field){
855 vary(this, field);
857 return this;
861 * Render `view` with the given `options` and optional callback `fn`.
862 * When a callback function is given a response will _not_ be made
863 * automatically, otherwise a response of _200_ and _text/html_ is given.
865 * Options:
867 * - `cache` boolean hinting to the engine it should cache
868 * - `filename` filename of the view being rendered
870 * @public
873 res.render = function render(view, options, callback) {
874 var app = this.req.app;
875 var done = callback;
876 var opts = options || {};
877 var req = this.req;
878 var self = this;
880 // support callback function as second arg
881 if (typeof options === 'function') {
882 done = options;
883 opts = {};
886 // merge res.locals
887 opts._locals = self.locals;
889 // default callback to respond
890 done = done || function (err, str) {
891 if (err) return req.next(err);
892 self.send(str);
895 // render
896 app.render(view, opts, done);
899 // pipe the send file stream
900 function sendfile(res, file, options, callback) {
901 var done = false;
902 var streaming;
904 // request aborted
905 function onaborted() {
906 if (done) return;
907 done = true;
909 var err = new Error('Request aborted');
910 err.code = 'ECONNABORTED';
911 callback(err);
914 // directory
915 function ondirectory() {
916 if (done) return;
917 done = true;
919 var err = new Error('EISDIR, read');
920 err.code = 'EISDIR';
921 callback(err);
924 // errors
925 function onerror(err) {
926 if (done) return;
927 done = true;
928 callback(err);
931 // ended
932 function onend() {
933 if (done) return;
934 done = true;
935 callback();
938 // file
939 function onfile() {
940 streaming = false;
943 // finished
944 function onfinish(err) {
945 if (err && err.code === 'ECONNRESET') return onaborted();
946 if (err) return onerror(err);
947 if (done) return;
949 setImmediate(function () {
950 if (streaming !== false && !done) {
951 onaborted();
952 return;
955 if (done) return;
956 done = true;
957 callback();
961 // streaming
962 function onstream() {
963 streaming = true;
966 file.on('directory', ondirectory);
967 file.on('end', onend);
968 file.on('error', onerror);
969 file.on('file', onfile);
970 file.on('stream', onstream);
971 onFinished(res, onfinish);
973 if (options.headers) {
974 // set headers on successful transfer
975 file.on('headers', function headers(res) {
976 var obj = options.headers;
977 var keys = Object.keys(obj);
979 for (var i = 0; i < keys.length; i++) {
980 var k = keys[i];
981 res.setHeader(k, obj[k]);
986 // pipe
987 file.pipe(res);
991 * Stringify JSON, like JSON.stringify, but v8 optimized, with the
992 * ability to escape characters that can trigger HTML sniffing.
994 * @param {*} value
995 * @param {function} replacer
996 * @param {number} spaces
997 * @param {boolean} escape
998 * @returns {string}
999 * @private
1002 function stringify (value, replacer, spaces, escape) {
1003 // v8 checks arguments.length for optimizing simple call
1004 // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1005 var json = replacer || spaces
1006 ? JSON.stringify(value, replacer, spaces)
1007 : JSON.stringify(value);
1009 if (escape && typeof json === 'string') {
1010 json = json.replace(/[<>&]/g, function (c) {
1011 switch (c.charCodeAt(0)) {
1012 case 0x3c:
1013 return '\\u003c'
1014 case 0x3e:
1015 return '\\u003e'
1016 case 0x26:
1017 return '\\u0026'
1018 /* istanbul ignore next: unreachable default */
1019 default:
1020 return c
1025 return json