| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179 | /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */'use strict';/** * Module dependencies. * @private */var Buffer = require('safe-buffer').Buffervar contentDisposition = require('content-disposition');var createError = require('http-errors')var deprecate = require('depd')('express');var encodeUrl = require('encodeurl');var escapeHtml = require('escape-html');var http = require('http');var isAbsolute = require('./utils').isAbsolute;var onFinished = require('on-finished');var path = require('path');var statuses = require('statuses')var merge = require('utils-merge');var sign = require('cookie-signature').sign;var normalizeType = require('./utils').normalizeType;var normalizeTypes = require('./utils').normalizeTypes;var setCharset = require('./utils').setCharset;var cookie = require('cookie');var send = require('send');var extname = path.extname;var mime = send.mime;var resolve = path.resolve;var vary = require('vary');/** * Response prototype. * @public */var res = Object.create(http.ServerResponse.prototype)/** * Module exports. * @public */module.exports = res/** * Module variables. * @private */var charsetRegExp = /;\s*charset\s*=/;/** * Set status `code`. * * @param {Number} code * @return {ServerResponse} * @public */res.status = function status(code) {  if ((typeof code === 'string' || Math.floor(code) !== code) && code > 99 && code < 1000) {    deprecate('res.status(' + JSON.stringify(code) + '): use res.status(' + Math.floor(code) + ') instead')  }  this.statusCode = code;  return this;};/** * Set Link header field with the given `links`. * * Examples: * *    res.links({ *      next: 'http://api.example.com/users?page=2', *      last: 'http://api.example.com/users?page=5' *    }); * * @param {Object} links * @return {ServerResponse} * @public */res.links = function(links){  var link = this.get('Link') || '';  if (link) link += ', ';  return this.set('Link', link + Object.keys(links).map(function(rel){    return '<' + links[rel] + '>; rel="' + rel + '"';  }).join(', '));};/** * Send a response. * * Examples: * *     res.send(Buffer.from('wahoo')); *     res.send({ some: 'json' }); *     res.send('<p>some html</p>'); * * @param {string|number|boolean|object|Buffer} body * @public */res.send = function send(body) {  var chunk = body;  var encoding;  var req = this.req;  var type;  // settings  var app = this.app;  // allow status / body  if (arguments.length === 2) {    // res.send(body, status) backwards compat    if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {      deprecate('res.send(body, status): Use res.status(status).send(body) instead');      this.statusCode = arguments[1];    } else {      deprecate('res.send(status, body): Use res.status(status).send(body) instead');      this.statusCode = arguments[0];      chunk = arguments[1];    }  }  // disambiguate res.send(status) and res.send(status, num)  if (typeof chunk === 'number' && arguments.length === 1) {    // res.send(status) will set status message as text string    if (!this.get('Content-Type')) {      this.type('txt');    }    deprecate('res.send(status): Use res.sendStatus(status) instead');    this.statusCode = chunk;    chunk = statuses.message[chunk]  }  switch (typeof chunk) {    // string defaulting to html    case 'string':      if (!this.get('Content-Type')) {        this.type('html');      }      break;    case 'boolean':    case 'number':    case 'object':      if (chunk === null) {        chunk = '';      } else if (Buffer.isBuffer(chunk)) {        if (!this.get('Content-Type')) {          this.type('bin');        }      } else {        return this.json(chunk);      }      break;  }  // write strings in utf-8  if (typeof chunk === 'string') {    encoding = 'utf8';    type = this.get('Content-Type');    // reflect this in content-type    if (typeof type === 'string') {      this.set('Content-Type', setCharset(type, 'utf-8'));    }  }  // determine if ETag should be generated  var etagFn = app.get('etag fn')  var generateETag = !this.get('ETag') && typeof etagFn === 'function'  // populate Content-Length  var len  if (chunk !== undefined) {    if (Buffer.isBuffer(chunk)) {      // get length of Buffer      len = chunk.length    } else if (!generateETag && chunk.length < 1000) {      // just calculate length when no ETag + small chunk      len = Buffer.byteLength(chunk, encoding)    } else {      // convert chunk to Buffer and calculate      chunk = Buffer.from(chunk, encoding)      encoding = undefined;      len = chunk.length    }    this.set('Content-Length', len);  }  // populate ETag  var etag;  if (generateETag && len !== undefined) {    if ((etag = etagFn(chunk, encoding))) {      this.set('ETag', etag);    }  }  // freshness  if (req.fresh) this.statusCode = 304;  // strip irrelevant headers  if (204 === this.statusCode || 304 === this.statusCode) {    this.removeHeader('Content-Type');    this.removeHeader('Content-Length');    this.removeHeader('Transfer-Encoding');    chunk = '';  }  // alter headers for 205  if (this.statusCode === 205) {    this.set('Content-Length', '0')    this.removeHeader('Transfer-Encoding')    chunk = ''  }  if (req.method === 'HEAD') {    // skip body for HEAD    this.end();  } else {    // respond    this.end(chunk, encoding);  }  return this;};/** * Send JSON response. * * Examples: * *     res.json(null); *     res.json({ user: 'tj' }); * * @param {string|number|boolean|object} obj * @public */res.json = function json(obj) {  var val = obj;  // allow status / body  if (arguments.length === 2) {    // res.json(body, status) backwards compat    if (typeof arguments[1] === 'number') {      deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');      this.statusCode = arguments[1];    } else {      deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');      this.statusCode = arguments[0];      val = arguments[1];    }  }  // settings  var app = this.app;  var escape = app.get('json escape')  var replacer = app.get('json replacer');  var spaces = app.get('json spaces');  var body = stringify(val, replacer, spaces, escape)  // content-type  if (!this.get('Content-Type')) {    this.set('Content-Type', 'application/json');  }  return this.send(body);};/** * Send JSON response with JSONP callback support. * * Examples: * *     res.jsonp(null); *     res.jsonp({ user: 'tj' }); * * @param {string|number|boolean|object} obj * @public */res.jsonp = function jsonp(obj) {  var val = obj;  // allow status / body  if (arguments.length === 2) {    // res.jsonp(body, status) backwards compat    if (typeof arguments[1] === 'number') {      deprecate('res.jsonp(obj, status): Use res.status(status).jsonp(obj) instead');      this.statusCode = arguments[1];    } else {      deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');      this.statusCode = arguments[0];      val = arguments[1];    }  }  // settings  var app = this.app;  var escape = app.get('json escape')  var replacer = app.get('json replacer');  var spaces = app.get('json spaces');  var body = stringify(val, replacer, spaces, escape)  var callback = this.req.query[app.get('jsonp callback name')];  // content-type  if (!this.get('Content-Type')) {    this.set('X-Content-Type-Options', 'nosniff');    this.set('Content-Type', 'application/json');  }  // fixup callback  if (Array.isArray(callback)) {    callback = callback[0];  }  // jsonp  if (typeof callback === 'string' && callback.length !== 0) {    this.set('X-Content-Type-Options', 'nosniff');    this.set('Content-Type', 'text/javascript');    // restrict callback charset    callback = callback.replace(/[^\[\]\w$.]/g, '');    if (body === undefined) {      // empty argument      body = ''    } else if (typeof body === 'string') {      // replace chars not allowed in JavaScript that are in JSON      body = body        .replace(/\u2028/g, '\\u2028')        .replace(/\u2029/g, '\\u2029')    }    // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"    // the typeof check is just to reduce client error noise    body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';  }  return this.send(body);};/** * Send given HTTP status code. * * Sets the response status to `statusCode` and the body of the * response to the standard description from node's http.STATUS_CODES * or the statusCode number if no description. * * Examples: * *     res.sendStatus(200); * * @param {number} statusCode * @public */res.sendStatus = function sendStatus(statusCode) {  var body = statuses.message[statusCode] || String(statusCode)  this.statusCode = statusCode;  this.type('txt');  return this.send(body);};/** * Transfer the file at the given `path`. * * Automatically sets the _Content-Type_ response header field. * The callback `callback(err)` is invoked when the transfer is complete * or when an error occurs. Be sure to check `res.headersSent` * if you wish to attempt responding, as the header and some data * may have already been transferred. * * Options: * *   - `maxAge`   defaulting to 0 (can be string converted by `ms`) *   - `root`     root directory for relative filenames *   - `headers`  object of headers to serve with file *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them * * Other options are passed along to `send`. * * Examples: * *  The following example illustrates how `res.sendFile()` may *  be used as an alternative for the `static()` middleware for *  dynamic situations. The code backing `res.sendFile()` is actually *  the same code, so HTTP cache support etc is identical. * *     app.get('/user/:uid/photos/:file', function(req, res){ *       var uid = req.params.uid *         , file = req.params.file; * *       req.user.mayViewFilesFrom(uid, function(yes){ *         if (yes) { *           res.sendFile('/uploads/' + uid + '/' + file); *         } else { *           res.send(403, 'Sorry! you cant see that.'); *         } *       }); *     }); * * @public */res.sendFile = function sendFile(path, options, callback) {  var done = callback;  var req = this.req;  var res = this;  var next = req.next;  var opts = options || {};  if (!path) {    throw new TypeError('path argument is required to res.sendFile');  }  if (typeof path !== 'string') {    throw new TypeError('path must be a string to res.sendFile')  }  // support function as second arg  if (typeof options === 'function') {    done = options;    opts = {};  }  if (!opts.root && !isAbsolute(path)) {    throw new TypeError('path must be absolute or specify root to res.sendFile');  }  // create file stream  var pathname = encodeURI(path);  var file = send(req, pathname, opts);  // transfer  sendfile(res, file, opts, function (err) {    if (done) return done(err);    if (err && err.code === 'EISDIR') return next();    // next() all but write errors    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {      next(err);    }  });};/** * Transfer the file at the given `path`. * * Automatically sets the _Content-Type_ response header field. * The callback `callback(err)` is invoked when the transfer is complete * or when an error occurs. Be sure to check `res.headersSent` * if you wish to attempt responding, as the header and some data * may have already been transferred. * * Options: * *   - `maxAge`   defaulting to 0 (can be string converted by `ms`) *   - `root`     root directory for relative filenames *   - `headers`  object of headers to serve with file *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them * * Other options are passed along to `send`. * * Examples: * *  The following example illustrates how `res.sendfile()` may *  be used as an alternative for the `static()` middleware for *  dynamic situations. The code backing `res.sendfile()` is actually *  the same code, so HTTP cache support etc is identical. * *     app.get('/user/:uid/photos/:file', function(req, res){ *       var uid = req.params.uid *         , file = req.params.file; * *       req.user.mayViewFilesFrom(uid, function(yes){ *         if (yes) { *           res.sendfile('/uploads/' + uid + '/' + file); *         } else { *           res.send(403, 'Sorry! you cant see that.'); *         } *       }); *     }); * * @public */res.sendfile = function (path, options, callback) {  var done = callback;  var req = this.req;  var res = this;  var next = req.next;  var opts = options || {};  // support function as second arg  if (typeof options === 'function') {    done = options;    opts = {};  }  // create file stream  var file = send(req, path, opts);  // transfer  sendfile(res, file, opts, function (err) {    if (done) return done(err);    if (err && err.code === 'EISDIR') return next();    // next() all but write errors    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {      next(err);    }  });};res.sendfile = deprecate.function(res.sendfile,  'res.sendfile: Use res.sendFile instead');/** * Transfer the file at the given `path` as an attachment. * * Optionally providing an alternate attachment `filename`, * and optional callback `callback(err)`. The callback is invoked * when the data transfer is complete, or when an error has * occurred. Be sure to check `res.headersSent` if you plan to respond. * * Optionally providing an `options` object to use with `res.sendFile()`. * This function will set the `Content-Disposition` header, overriding * any `Content-Disposition` header passed as header options in order * to set the attachment and filename. * * This method uses `res.sendFile()`. * * @public */res.download = function download (path, filename, options, callback) {  var done = callback;  var name = filename;  var opts = options || null  // support function as second or third arg  if (typeof filename === 'function') {    done = filename;    name = null;    opts = null  } else if (typeof options === 'function') {    done = options    opts = null  }  // support optional filename, where options may be in it's place  if (typeof filename === 'object' &&    (typeof options === 'function' || options === undefined)) {    name = null    opts = filename  }  // set Content-Disposition when file is sent  var headers = {    'Content-Disposition': contentDisposition(name || path)  };  // merge user-provided headers  if (opts && opts.headers) {    var keys = Object.keys(opts.headers)    for (var i = 0; i < keys.length; i++) {      var key = keys[i]      if (key.toLowerCase() !== 'content-disposition') {        headers[key] = opts.headers[key]      }    }  }  // merge user-provided options  opts = Object.create(opts)  opts.headers = headers  // Resolve the full path for sendFile  var fullPath = !opts.root    ? resolve(path)    : path  // send file  return this.sendFile(fullPath, opts, done)};/** * Set _Content-Type_ response header with `type` through `mime.lookup()` * when it does not contain "/", or set the Content-Type to `type` otherwise. * * Examples: * *     res.type('.html'); *     res.type('html'); *     res.type('json'); *     res.type('application/json'); *     res.type('png'); * * @param {String} type * @return {ServerResponse} for chaining * @public */res.contentType =res.type = function contentType(type) {  var ct = type.indexOf('/') === -1    ? mime.lookup(type)    : type;  return this.set('Content-Type', ct);};/** * Respond to the Acceptable formats using an `obj` * of mime-type callbacks. * * This method uses `req.accepted`, an array of * acceptable types ordered by their quality values. * When "Accept" is not present the _first_ callback * is invoked, otherwise the first match is used. When * no match is performed the server responds with * 406 "Not Acceptable". * * Content-Type is set for you, however if you choose * you may alter this within the callback using `res.type()` * or `res.set('Content-Type', ...)`. * *    res.format({ *      'text/plain': function(){ *        res.send('hey'); *      }, * *      'text/html': function(){ *        res.send('<p>hey</p>'); *      }, * *      'application/json': function () { *        res.send({ message: 'hey' }); *      } *    }); * * In addition to canonicalized MIME types you may * also use extnames mapped to these types: * *    res.format({ *      text: function(){ *        res.send('hey'); *      }, * *      html: function(){ *        res.send('<p>hey</p>'); *      }, * *      json: function(){ *        res.send({ message: 'hey' }); *      } *    }); * * By default Express passes an `Error` * with a `.status` of 406 to `next(err)` * if a match is not made. If you provide * a `.default` callback it will be invoked * instead. * * @param {Object} obj * @return {ServerResponse} for chaining * @public */res.format = function(obj){  var req = this.req;  var next = req.next;  var keys = Object.keys(obj)    .filter(function (v) { return v !== 'default' })  var key = keys.length > 0    ? req.accepts(keys)    : false;  this.vary("Accept");  if (key) {    this.set('Content-Type', normalizeType(key).value);    obj[key](req, this, next);  } else if (obj.default) {    obj.default(req, this, next)  } else {    next(createError(406, {      types: normalizeTypes(keys).map(function (o) { return o.value })    }))  }  return this;};/** * Set _Content-Disposition_ header to _attachment_ with optional `filename`. * * @param {String} filename * @return {ServerResponse} * @public */res.attachment = function attachment(filename) {  if (filename) {    this.type(extname(filename));  }  this.set('Content-Disposition', contentDisposition(filename));  return this;};/** * Append additional header `field` with value `val`. * * Example: * *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']); *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly'); *    res.append('Warning', '199 Miscellaneous warning'); * * @param {String} field * @param {String|Array} val * @return {ServerResponse} for chaining * @public */res.append = function append(field, val) {  var prev = this.get(field);  var value = val;  if (prev) {    // concat the new and prev vals    value = Array.isArray(prev) ? prev.concat(val)      : Array.isArray(val) ? [prev].concat(val)        : [prev, val]  }  return this.set(field, value);};/** * Set header `field` to `val`, or pass * an object of header fields. * * Examples: * *    res.set('Foo', ['bar', 'baz']); *    res.set('Accept', 'application/json'); *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); * * Aliased as `res.header()`. * * @param {String|Object} field * @param {String|Array} val * @return {ServerResponse} for chaining * @public */res.set =res.header = function header(field, val) {  if (arguments.length === 2) {    var value = Array.isArray(val)      ? val.map(String)      : String(val);    // add charset to content-type    if (field.toLowerCase() === 'content-type') {      if (Array.isArray(value)) {        throw new TypeError('Content-Type cannot be set to an Array');      }      if (!charsetRegExp.test(value)) {        var charset = mime.charsets.lookup(value.split(';')[0]);        if (charset) value += '; charset=' + charset.toLowerCase();      }    }    this.setHeader(field, value);  } else {    for (var key in field) {      this.set(key, field[key]);    }  }  return this;};/** * Get value for header `field`. * * @param {String} field * @return {String} * @public */res.get = function(field){  return this.getHeader(field);};/** * Clear cookie `name`. * * @param {String} name * @param {Object} [options] * @return {ServerResponse} for chaining * @public */res.clearCookie = function clearCookie(name, options) {  if (options) {    if (options.maxAge) {      deprecate('res.clearCookie: Passing "options.maxAge" is deprecated. In v5.0.0 of Express, this option will be ignored, as res.clearCookie will automatically set cookies to expire immediately. Please update your code to omit this option.');    }    if (options.expires) {      deprecate('res.clearCookie: Passing "options.expires" is deprecated. In v5.0.0 of Express, this option will be ignored, as res.clearCookie will automatically set cookies to expire immediately. Please update your code to omit this option.');    }  }  var opts = merge({ expires: new Date(1), path: '/' }, options);  return this.cookie(name, '', opts);};/** * Set cookie `name` to `value`, with the given `options`. * * Options: * *    - `maxAge`   max-age in milliseconds, converted to `expires` *    - `signed`   sign the cookie *    - `path`     defaults to "/" * * Examples: * *    // "Remember Me" for 15 minutes *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); * *    // same as above *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) * * @param {String} name * @param {String|Object} value * @param {Object} [options] * @return {ServerResponse} for chaining * @public */res.cookie = function (name, value, options) {  var opts = merge({}, options);  var secret = this.req.secret;  var signed = opts.signed;  if (signed && !secret) {    throw new Error('cookieParser("secret") required for signed cookies');  }  var val = typeof value === 'object'    ? 'j:' + JSON.stringify(value)    : String(value);  if (signed) {    val = 's:' + sign(val, secret);  }  if (opts.maxAge != null) {    var maxAge = opts.maxAge - 0    if (!isNaN(maxAge)) {      opts.expires = new Date(Date.now() + maxAge)      opts.maxAge = Math.floor(maxAge / 1000)    }  }  if (opts.path == null) {    opts.path = '/';  }  this.append('Set-Cookie', cookie.serialize(name, String(val), opts));  return this;};/** * Set the location header to `url`. * * The given `url` can also be "back", which redirects * to the _Referrer_ or _Referer_ headers or "/". * * Examples: * *    res.location('/foo/bar').; *    res.location('http://example.com'); *    res.location('../login'); * * @param {String} url * @return {ServerResponse} for chaining * @public */res.location = function location(url) {  var loc;  // "back" is an alias for the referrer  if (url === 'back') {    deprecate('res.location("back"): use res.location(req.get("Referrer") || "/") and refer to https://dub.sh/security-redirect for best practices');    loc = this.req.get('Referrer') || '/';  } else {    loc = String(url);  }  return this.set('Location', encodeUrl(loc));};/** * Redirect to the given `url` with optional response `status` * defaulting to 302. * * The resulting `url` is determined by `res.location()`, so * it will play nicely with mounted apps, relative paths, * `"back"` etc. * * Examples: * *    res.redirect('/foo/bar'); *    res.redirect('http://example.com'); *    res.redirect(301, 'http://example.com'); *    res.redirect('../login'); // /blog/post/1 -> /blog/login * * @public */res.redirect = function redirect(url) {  var address = url;  var body;  var status = 302;  // allow status / url  if (arguments.length === 2) {    if (typeof arguments[0] === 'number') {      status = arguments[0];      address = arguments[1];    } else {      deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');      status = arguments[1];    }  }  // Set location header  address = this.location(address).get('Location');  // Support text/{plain,html} by default  this.format({    text: function(){      body = statuses.message[status] + '. Redirecting to ' + address    },    html: function(){      var u = escapeHtml(address);      body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'    },    default: function(){      body = '';    }  });  // Respond  this.statusCode = status;  this.set('Content-Length', Buffer.byteLength(body));  if (this.req.method === 'HEAD') {    this.end();  } else {    this.end(body);  }};/** * Add `field` to Vary. If already present in the Vary set, then * this call is simply ignored. * * @param {Array|String} field * @return {ServerResponse} for chaining * @public */res.vary = function(field){  // checks for back-compat  if (!field || (Array.isArray(field) && !field.length)) {    deprecate('res.vary(): Provide a field name');    return this;  }  vary(this, field);  return this;};/** * Render `view` with the given `options` and optional callback `fn`. * When a callback function is given a response will _not_ be made * automatically, otherwise a response of _200_ and _text/html_ is given. * * Options: * *  - `cache`     boolean hinting to the engine it should cache *  - `filename`  filename of the view being rendered * * @public */res.render = function render(view, options, callback) {  var app = this.req.app;  var done = callback;  var opts = options || {};  var req = this.req;  var self = this;  // support callback function as second arg  if (typeof options === 'function') {    done = options;    opts = {};  }  // merge res.locals  opts._locals = self.locals;  // default callback to respond  done = done || function (err, str) {    if (err) return req.next(err);    self.send(str);  };  // render  app.render(view, opts, done);};// pipe the send file streamfunction sendfile(res, file, options, callback) {  var done = false;  var streaming;  // request aborted  function onaborted() {    if (done) return;    done = true;    var err = new Error('Request aborted');    err.code = 'ECONNABORTED';    callback(err);  }  // directory  function ondirectory() {    if (done) return;    done = true;    var err = new Error('EISDIR, read');    err.code = 'EISDIR';    callback(err);  }  // errors  function onerror(err) {    if (done) return;    done = true;    callback(err);  }  // ended  function onend() {    if (done) return;    done = true;    callback();  }  // file  function onfile() {    streaming = false;  }  // finished  function onfinish(err) {    if (err && err.code === 'ECONNRESET') return onaborted();    if (err) return onerror(err);    if (done) return;    setImmediate(function () {      if (streaming !== false && !done) {        onaborted();        return;      }      if (done) return;      done = true;      callback();    });  }  // streaming  function onstream() {    streaming = true;  }  file.on('directory', ondirectory);  file.on('end', onend);  file.on('error', onerror);  file.on('file', onfile);  file.on('stream', onstream);  onFinished(res, onfinish);  if (options.headers) {    // set headers on successful transfer    file.on('headers', function headers(res) {      var obj = options.headers;      var keys = Object.keys(obj);      for (var i = 0; i < keys.length; i++) {        var k = keys[i];        res.setHeader(k, obj[k]);      }    });  }  // pipe  file.pipe(res);}/** * Stringify JSON, like JSON.stringify, but v8 optimized, with the * ability to escape characters that can trigger HTML sniffing. * * @param {*} value * @param {function} replacer * @param {number} spaces * @param {boolean} escape * @returns {string} * @private */function stringify (value, replacer, spaces, escape) {  // v8 checks arguments.length for optimizing simple call  // https://bugs.chromium.org/p/v8/issues/detail?id=4730  var json = replacer || spaces    ? JSON.stringify(value, replacer, spaces)    : JSON.stringify(value);  if (escape && typeof json === 'string') {    json = json.replace(/[<>&]/g, function (c) {      switch (c.charCodeAt(0)) {        case 0x3c:          return '\\u003c'        case 0x3e:          return '\\u003e'        case 0x26:          return '\\u0026'        /* istanbul ignore next: unreachable default */        default:          return c      }    })  }  return json}
 |