/*!1* express2* Copyright(c) 2009-2013 TJ Holowaychuk3* Copyright(c) 2014-2015 Douglas Christopher Wilson4* MIT Licensed5*/67'use strict';89/**10* Module dependencies.11* @private12*/1314var contentDisposition = require('content-disposition');15var deprecate = require('depd')('express');16var encodeUrl = require('encodeurl');17var escapeHtml = require('escape-html');18var http = require('http');19var isAbsolute = require('./utils').isAbsolute;20var onFinished = require('on-finished');21var path = require('path');22var statuses = require('statuses')23var merge = require('utils-merge');24var sign = require('cookie-signature').sign;25var normalizeType = require('./utils').normalizeType;26var normalizeTypes = require('./utils').normalizeTypes;27var setCharset = require('./utils').setCharset;28var cookie = require('cookie');29var send = require('send');30var extname = path.extname;31var mime = send.mime;32var resolve = path.resolve;33var vary = require('vary');3435/**36* Response prototype.37* @public38*/3940var res = Object.create(http.ServerResponse.prototype)4142/**43* Module exports.44* @public45*/4647module.exports = res4849/**50* Module variables.51* @private52*/5354var charsetRegExp = /;\s*charset\s*=/;5556/**57* Set status `code`.58*59* @param {Number} code60* @return {ServerResponse}61* @public62*/6364res.status = function status(code) {65this.statusCode = code;66return this;67};6869/**70* Set Link header field with the given `links`.71*72* Examples:73*74* res.links({75* next: 'http://api.example.com/users?page=2',76* last: 'http://api.example.com/users?page=5'77* });78*79* @param {Object} links80* @return {ServerResponse}81* @public82*/8384res.links = function(links){85var link = this.get('Link') || '';86if (link) link += ', ';87return this.set('Link', link + Object.keys(links).map(function(rel){88return '<' + links[rel] + '>; rel="' + rel + '"';89}).join(', '));90};9192/**93* Send a response.94*95* Examples:96*97* res.send(new Buffer('wahoo'));98* res.send({ some: 'json' });99* res.send('<p>some html</p>');100*101* @param {string|number|boolean|object|Buffer} body102* @public103*/104105res.send = function send(body) {106var chunk = body;107var encoding;108var len;109var req = this.req;110var type;111112// settings113var app = this.app;114115// allow status / body116if (arguments.length === 2) {117// res.send(body, status) backwards compat118if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {119deprecate('res.send(body, status): Use res.status(status).send(body) instead');120this.statusCode = arguments[1];121} else {122deprecate('res.send(status, body): Use res.status(status).send(body) instead');123this.statusCode = arguments[0];124chunk = arguments[1];125}126}127128// disambiguate res.send(status) and res.send(status, num)129if (typeof chunk === 'number' && arguments.length === 1) {130// res.send(status) will set status message as text string131if (!this.get('Content-Type')) {132this.type('txt');133}134135deprecate('res.send(status): Use res.sendStatus(status) instead');136this.statusCode = chunk;137chunk = statuses[chunk]138}139140switch (typeof chunk) {141// string defaulting to html142case 'string':143if (!this.get('Content-Type')) {144this.type('html');145}146break;147case 'boolean':148case 'number':149case 'object':150if (chunk === null) {151chunk = '';152} else if (Buffer.isBuffer(chunk)) {153if (!this.get('Content-Type')) {154this.type('bin');155}156} else {157return this.json(chunk);158}159break;160}161162// write strings in utf-8163if (typeof chunk === 'string') {164encoding = 'utf8';165type = this.get('Content-Type');166167// reflect this in content-type168if (typeof type === 'string') {169this.set('Content-Type', setCharset(type, 'utf-8'));170}171}172173// populate Content-Length174if (chunk !== undefined) {175if (!Buffer.isBuffer(chunk)) {176// convert chunk to Buffer; saves later double conversions177chunk = new Buffer(chunk, encoding);178encoding = undefined;179}180181len = chunk.length;182this.set('Content-Length', len);183}184185// populate ETag186var etag;187var generateETag = len !== undefined && app.get('etag fn');188if (typeof generateETag === 'function' && !this.get('ETag')) {189if ((etag = generateETag(chunk, encoding))) {190this.set('ETag', etag);191}192}193194// freshness195if (req.fresh) this.statusCode = 304;196197// strip irrelevant headers198if (204 === this.statusCode || 304 === this.statusCode) {199this.removeHeader('Content-Type');200this.removeHeader('Content-Length');201this.removeHeader('Transfer-Encoding');202chunk = '';203}204205if (req.method === 'HEAD') {206// skip body for HEAD207this.end();208} else {209// respond210this.end(chunk, encoding);211}212213return this;214};215216/**217* Send JSON response.218*219* Examples:220*221* res.json(null);222* res.json({ user: 'tj' });223*224* @param {string|number|boolean|object} obj225* @public226*/227228res.json = function json(obj) {229var val = obj;230231// allow status / body232if (arguments.length === 2) {233// res.json(body, status) backwards compat234if (typeof arguments[1] === 'number') {235deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');236this.statusCode = arguments[1];237} else {238deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');239this.statusCode = arguments[0];240val = arguments[1];241}242}243244// settings245var app = this.app;246var replacer = app.get('json replacer');247var spaces = app.get('json spaces');248var body = stringify(val, replacer, spaces);249250// content-type251if (!this.get('Content-Type')) {252this.set('Content-Type', 'application/json');253}254255return this.send(body);256};257258/**259* Send JSON response with JSONP callback support.260*261* Examples:262*263* res.jsonp(null);264* res.jsonp({ user: 'tj' });265*266* @param {string|number|boolean|object} obj267* @public268*/269270res.jsonp = function jsonp(obj) {271var val = obj;272273// allow status / body274if (arguments.length === 2) {275// res.json(body, status) backwards compat276if (typeof arguments[1] === 'number') {277deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');278this.statusCode = arguments[1];279} else {280deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');281this.statusCode = arguments[0];282val = arguments[1];283}284}285286// settings287var app = this.app;288var replacer = app.get('json replacer');289var spaces = app.get('json spaces');290var body = stringify(val, replacer, spaces);291var callback = this.req.query[app.get('jsonp callback name')];292293// content-type294if (!this.get('Content-Type')) {295this.set('X-Content-Type-Options', 'nosniff');296this.set('Content-Type', 'application/json');297}298299// fixup callback300if (Array.isArray(callback)) {301callback = callback[0];302}303304// jsonp305if (typeof callback === 'string' && callback.length !== 0) {306this.charset = 'utf-8';307this.set('X-Content-Type-Options', 'nosniff');308this.set('Content-Type', 'text/javascript');309310// restrict callback charset311callback = callback.replace(/[^\[\]\w$.]/g, '');312313// replace chars not allowed in JavaScript that are in JSON314body = body315.replace(/\u2028/g, '\\u2028')316.replace(/\u2029/g, '\\u2029');317318// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"319// the typeof check is just to reduce client error noise320body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';321}322323return this.send(body);324};325326/**327* Send given HTTP status code.328*329* Sets the response status to `statusCode` and the body of the330* response to the standard description from node's http.STATUS_CODES331* or the statusCode number if no description.332*333* Examples:334*335* res.sendStatus(200);336*337* @param {number} statusCode338* @public339*/340341res.sendStatus = function sendStatus(statusCode) {342var body = statuses[statusCode] || String(statusCode)343344this.statusCode = statusCode;345this.type('txt');346347return this.send(body);348};349350/**351* Transfer the file at the given `path`.352*353* Automatically sets the _Content-Type_ response header field.354* The callback `callback(err)` is invoked when the transfer is complete355* or when an error occurs. Be sure to check `res.sentHeader`356* if you wish to attempt responding, as the header and some data357* may have already been transferred.358*359* Options:360*361* - `maxAge` defaulting to 0 (can be string converted by `ms`)362* - `root` root directory for relative filenames363* - `headers` object of headers to serve with file364* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them365*366* Other options are passed along to `send`.367*368* Examples:369*370* The following example illustrates how `res.sendFile()` may371* be used as an alternative for the `static()` middleware for372* dynamic situations. The code backing `res.sendFile()` is actually373* the same code, so HTTP cache support etc is identical.374*375* app.get('/user/:uid/photos/:file', function(req, res){376* var uid = req.params.uid377* , file = req.params.file;378*379* req.user.mayViewFilesFrom(uid, function(yes){380* if (yes) {381* res.sendFile('/uploads/' + uid + '/' + file);382* } else {383* res.send(403, 'Sorry! you cant see that.');384* }385* });386* });387*388* @public389*/390391res.sendFile = function sendFile(path, options, callback) {392var done = callback;393var req = this.req;394var res = this;395var next = req.next;396var opts = options || {};397398if (!path) {399throw new TypeError('path argument is required to res.sendFile');400}401402// support function as second arg403if (typeof options === 'function') {404done = options;405opts = {};406}407408if (!opts.root && !isAbsolute(path)) {409throw new TypeError('path must be absolute or specify root to res.sendFile');410}411412// create file stream413var pathname = encodeURI(path);414var file = send(req, pathname, opts);415416// transfer417sendfile(res, file, opts, function (err) {418if (done) return done(err);419if (err && err.code === 'EISDIR') return next();420421// next() all but write errors422if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {423next(err);424}425});426};427428/**429* Transfer the file at the given `path`.430*431* Automatically sets the _Content-Type_ response header field.432* The callback `callback(err)` is invoked when the transfer is complete433* or when an error occurs. Be sure to check `res.sentHeader`434* if you wish to attempt responding, as the header and some data435* may have already been transferred.436*437* Options:438*439* - `maxAge` defaulting to 0 (can be string converted by `ms`)440* - `root` root directory for relative filenames441* - `headers` object of headers to serve with file442* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them443*444* Other options are passed along to `send`.445*446* Examples:447*448* The following example illustrates how `res.sendfile()` may449* be used as an alternative for the `static()` middleware for450* dynamic situations. The code backing `res.sendfile()` is actually451* the same code, so HTTP cache support etc is identical.452*453* app.get('/user/:uid/photos/:file', function(req, res){454* var uid = req.params.uid455* , file = req.params.file;456*457* req.user.mayViewFilesFrom(uid, function(yes){458* if (yes) {459* res.sendfile('/uploads/' + uid + '/' + file);460* } else {461* res.send(403, 'Sorry! you cant see that.');462* }463* });464* });465*466* @public467*/468469res.sendfile = function (path, options, callback) {470var done = callback;471var req = this.req;472var res = this;473var next = req.next;474var opts = options || {};475476// support function as second arg477if (typeof options === 'function') {478done = options;479opts = {};480}481482// create file stream483var file = send(req, path, opts);484485// transfer486sendfile(res, file, opts, function (err) {487if (done) return done(err);488if (err && err.code === 'EISDIR') return next();489490// next() all but write errors491if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {492next(err);493}494});495};496497res.sendfile = deprecate.function(res.sendfile,498'res.sendfile: Use res.sendFile instead');499500/**501* Transfer the file at the given `path` as an attachment.502*503* Optionally providing an alternate attachment `filename`,504* and optional callback `callback(err)`. The callback is invoked505* when the data transfer is complete, or when an error has506* ocurred. Be sure to check `res.headersSent` if you plan to respond.507*508* This method uses `res.sendfile()`.509*510* @public511*/512513res.download = function download(path, filename, callback) {514var done = callback;515var name = filename;516517// support function as second arg518if (typeof filename === 'function') {519done = filename;520name = null;521}522523// set Content-Disposition when file is sent524var headers = {525'Content-Disposition': contentDisposition(name || path)526};527528// Resolve the full path for sendFile529var fullPath = resolve(path);530531return this.sendFile(fullPath, { headers: headers }, done);532};533534/**535* Set _Content-Type_ response header with `type` through `mime.lookup()`536* when it does not contain "/", or set the Content-Type to `type` otherwise.537*538* Examples:539*540* res.type('.html');541* res.type('html');542* res.type('json');543* res.type('application/json');544* res.type('png');545*546* @param {String} type547* @return {ServerResponse} for chaining548* @public549*/550551res.contentType =552res.type = function contentType(type) {553var ct = type.indexOf('/') === -1554? mime.lookup(type)555: type;556557return this.set('Content-Type', ct);558};559560/**561* Respond to the Acceptable formats using an `obj`562* of mime-type callbacks.563*564* This method uses `req.accepted`, an array of565* acceptable types ordered by their quality values.566* When "Accept" is not present the _first_ callback567* is invoked, otherwise the first match is used. When568* no match is performed the server responds with569* 406 "Not Acceptable".570*571* Content-Type is set for you, however if you choose572* you may alter this within the callback using `res.type()`573* or `res.set('Content-Type', ...)`.574*575* res.format({576* 'text/plain': function(){577* res.send('hey');578* },579*580* 'text/html': function(){581* res.send('<p>hey</p>');582* },583*584* 'appliation/json': function(){585* res.send({ message: 'hey' });586* }587* });588*589* In addition to canonicalized MIME types you may590* also use extnames mapped to these types:591*592* res.format({593* text: function(){594* res.send('hey');595* },596*597* html: function(){598* res.send('<p>hey</p>');599* },600*601* json: function(){602* res.send({ message: 'hey' });603* }604* });605*606* By default Express passes an `Error`607* with a `.status` of 406 to `next(err)`608* if a match is not made. If you provide609* a `.default` callback it will be invoked610* instead.611*612* @param {Object} obj613* @return {ServerResponse} for chaining614* @public615*/616617res.format = function(obj){618var req = this.req;619var next = req.next;620621var fn = obj.default;622if (fn) delete obj.default;623var keys = Object.keys(obj);624625var key = keys.length > 0626? req.accepts(keys)627: false;628629this.vary("Accept");630631if (key) {632this.set('Content-Type', normalizeType(key).value);633obj[key](req, this, next);634} else if (fn) {635fn();636} else {637var err = new Error('Not Acceptable');638err.status = err.statusCode = 406;639err.types = normalizeTypes(keys).map(function(o){ return o.value });640next(err);641}642643return this;644};645646/**647* Set _Content-Disposition_ header to _attachment_ with optional `filename`.648*649* @param {String} filename650* @return {ServerResponse}651* @public652*/653654res.attachment = function attachment(filename) {655if (filename) {656this.type(extname(filename));657}658659this.set('Content-Disposition', contentDisposition(filename));660661return this;662};663664/**665* Append additional header `field` with value `val`.666*667* Example:668*669* res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);670* res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');671* res.append('Warning', '199 Miscellaneous warning');672*673* @param {String} field674* @param {String|Array} val675* @return {ServerResponse} for chaining676* @public677*/678679res.append = function append(field, val) {680var prev = this.get(field);681var value = val;682683if (prev) {684// concat the new and prev vals685value = Array.isArray(prev) ? prev.concat(val)686: Array.isArray(val) ? [prev].concat(val)687: [prev, val];688}689690return this.set(field, value);691};692693/**694* Set header `field` to `val`, or pass695* an object of header fields.696*697* Examples:698*699* res.set('Foo', ['bar', 'baz']);700* res.set('Accept', 'application/json');701* res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });702*703* Aliased as `res.header()`.704*705* @param {String|Object} field706* @param {String|Array} val707* @return {ServerResponse} for chaining708* @public709*/710711res.set =712res.header = function header(field, val) {713if (arguments.length === 2) {714var value = Array.isArray(val)715? val.map(String)716: String(val);717718// add charset to content-type719if (field.toLowerCase() === 'content-type') {720if (Array.isArray(value)) {721throw new TypeError('Content-Type cannot be set to an Array');722}723if (!charsetRegExp.test(value)) {724var charset = mime.charsets.lookup(value.split(';')[0]);725if (charset) value += '; charset=' + charset.toLowerCase();726}727}728729this.setHeader(field, value);730} else {731for (var key in field) {732this.set(key, field[key]);733}734}735return this;736};737738/**739* Get value for header `field`.740*741* @param {String} field742* @return {String}743* @public744*/745746res.get = function(field){747return this.getHeader(field);748};749750/**751* Clear cookie `name`.752*753* @param {String} name754* @param {Object} [options]755* @return {ServerResponse} for chaining756* @public757*/758759res.clearCookie = function clearCookie(name, options) {760var opts = merge({ expires: new Date(1), path: '/' }, options);761762return this.cookie(name, '', opts);763};764765/**766* Set cookie `name` to `value`, with the given `options`.767*768* Options:769*770* - `maxAge` max-age in milliseconds, converted to `expires`771* - `signed` sign the cookie772* - `path` defaults to "/"773*774* Examples:775*776* // "Remember Me" for 15 minutes777* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });778*779* // save as above780* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })781*782* @param {String} name783* @param {String|Object} value784* @param {Object} [options]785* @return {ServerResponse} for chaining786* @public787*/788789res.cookie = function (name, value, options) {790var opts = merge({}, options);791var secret = this.req.secret;792var signed = opts.signed;793794if (signed && !secret) {795throw new Error('cookieParser("secret") required for signed cookies');796}797798var val = typeof value === 'object'799? 'j:' + JSON.stringify(value)800: String(value);801802if (signed) {803val = 's:' + sign(val, secret);804}805806if ('maxAge' in opts) {807opts.expires = new Date(Date.now() + opts.maxAge);808opts.maxAge /= 1000;809}810811if (opts.path == null) {812opts.path = '/';813}814815this.append('Set-Cookie', cookie.serialize(name, String(val), opts));816817return this;818};819820/**821* Set the location header to `url`.822*823* The given `url` can also be "back", which redirects824* to the _Referrer_ or _Referer_ headers or "/".825*826* Examples:827*828* res.location('/foo/bar').;829* res.location('http://example.com');830* res.location('../login');831*832* @param {String} url833* @return {ServerResponse} for chaining834* @public835*/836837res.location = function location(url) {838var loc = url;839840// "back" is an alias for the referrer841if (url === 'back') {842loc = this.req.get('Referrer') || '/';843}844845// set location846return this.set('Location', encodeUrl(loc));847};848849/**850* Redirect to the given `url` with optional response `status`851* defaulting to 302.852*853* The resulting `url` is determined by `res.location()`, so854* it will play nicely with mounted apps, relative paths,855* `"back"` etc.856*857* Examples:858*859* res.redirect('/foo/bar');860* res.redirect('http://example.com');861* res.redirect(301, 'http://example.com');862* res.redirect('../login'); // /blog/post/1 -> /blog/login863*864* @public865*/866867res.redirect = function redirect(url) {868var address = url;869var body;870var status = 302;871872// allow status / url873if (arguments.length === 2) {874if (typeof arguments[0] === 'number') {875status = arguments[0];876address = arguments[1];877} else {878deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');879status = arguments[1];880}881}882883// Set location header884address = this.location(address).get('Location');885886// Support text/{plain,html} by default887this.format({888text: function(){889body = statuses[status] + '. Redirecting to ' + address890},891892html: function(){893var u = escapeHtml(address);894body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'895},896897default: function(){898body = '';899}900});901902// Respond903this.statusCode = status;904this.set('Content-Length', Buffer.byteLength(body));905906if (this.req.method === 'HEAD') {907this.end();908} else {909this.end(body);910}911};912913/**914* Add `field` to Vary. If already present in the Vary set, then915* this call is simply ignored.916*917* @param {Array|String} field918* @return {ServerResponse} for chaining919* @public920*/921922res.vary = function(field){923// checks for back-compat924if (!field || (Array.isArray(field) && !field.length)) {925deprecate('res.vary(): Provide a field name');926return this;927}928929vary(this, field);930931return this;932};933934/**935* Render `view` with the given `options` and optional callback `fn`.936* When a callback function is given a response will _not_ be made937* automatically, otherwise a response of _200_ and _text/html_ is given.938*939* Options:940*941* - `cache` boolean hinting to the engine it should cache942* - `filename` filename of the view being rendered943*944* @public945*/946947res.render = function render(view, options, callback) {948var app = this.req.app;949var done = callback;950var opts = options || {};951var req = this.req;952var self = this;953954// support callback function as second arg955if (typeof options === 'function') {956done = options;957opts = {};958}959960// merge res.locals961opts._locals = self.locals;962963// default callback to respond964done = done || function (err, str) {965if (err) return req.next(err);966self.send(str);967};968969// render970app.render(view, opts, done);971};972973// pipe the send file stream974function sendfile(res, file, options, callback) {975var done = false;976var streaming;977978// request aborted979function onaborted() {980if (done) return;981done = true;982983var err = new Error('Request aborted');984err.code = 'ECONNABORTED';985callback(err);986}987988// directory989function ondirectory() {990if (done) return;991done = true;992993var err = new Error('EISDIR, read');994err.code = 'EISDIR';995callback(err);996}997998// errors999function onerror(err) {1000if (done) return;1001done = true;1002callback(err);1003}10041005// ended1006function onend() {1007if (done) return;1008done = true;1009callback();1010}10111012// file1013function onfile() {1014streaming = false;1015}10161017// finished1018function onfinish(err) {1019if (err && err.code === 'ECONNRESET') return onaborted();1020if (err) return onerror(err);1021if (done) return;10221023setImmediate(function () {1024if (streaming !== false && !done) {1025onaborted();1026return;1027}10281029if (done) return;1030done = true;1031callback();1032});1033}10341035// streaming1036function onstream() {1037streaming = true;1038}10391040file.on('directory', ondirectory);1041file.on('end', onend);1042file.on('error', onerror);1043file.on('file', onfile);1044file.on('stream', onstream);1045onFinished(res, onfinish);10461047if (options.headers) {1048// set headers on successful transfer1049file.on('headers', function headers(res) {1050var obj = options.headers;1051var keys = Object.keys(obj);10521053for (var i = 0; i < keys.length; i++) {1054var k = keys[i];1055res.setHeader(k, obj[k]);1056}1057});1058}10591060// pipe1061file.pipe(res);1062}10631064/**1065* Stringify JSON, like JSON.stringify, but v8 optimized.1066* @private1067*/10681069function stringify(value, replacer, spaces) {1070// v8 checks arguments.length for optimizing simple call1071// https://bugs.chromium.org/p/v8/issues/detail?id=47301072return replacer || spaces1073? JSON.stringify(value, replacer, spaces)1074: JSON.stringify(value);1075}107610771078