The LM Control website. Simple yet efficient.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

response.js 27KB

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