response.js 28 KB

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