response.js 24 KB

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