index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. /*!
  2. * send
  3. * Copyright(c) 2012 TJ Holowaychuk
  4. * Copyright(c) 2014-2022 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. var createError = require('http-errors')
  13. var debug = require('debug')('send')
  14. var destroy = require('destroy')
  15. var encodeUrl = require('encodeurl')
  16. var escapeHtml = require('escape-html')
  17. var etag = require('etag')
  18. var fresh = require('fresh')
  19. var fs = require('fs')
  20. var mime = require('mime-types')
  21. var ms = require('ms')
  22. var onFinished = require('on-finished')
  23. var parseRange = require('range-parser')
  24. var path = require('path')
  25. var statuses = require('statuses')
  26. var Stream = require('stream')
  27. var util = require('util')
  28. /**
  29. * Path function references.
  30. * @private
  31. */
  32. var extname = path.extname
  33. var join = path.join
  34. var normalize = path.normalize
  35. var resolve = path.resolve
  36. var sep = path.sep
  37. /**
  38. * Regular expression for identifying a bytes Range header.
  39. * @private
  40. */
  41. var BYTES_RANGE_REGEXP = /^ *bytes=/
  42. /**
  43. * Maximum value allowed for the max age.
  44. * @private
  45. */
  46. var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000 // 1 year
  47. /**
  48. * Regular expression to match a path with a directory up component.
  49. * @private
  50. */
  51. var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/
  52. /**
  53. * Module exports.
  54. * @public
  55. */
  56. module.exports = send
  57. /**
  58. * Return a `SendStream` for `req` and `path`.
  59. *
  60. * @param {object} req
  61. * @param {string} path
  62. * @param {object} [options]
  63. * @return {SendStream}
  64. * @public
  65. */
  66. function send (req, path, options) {
  67. return new SendStream(req, path, options)
  68. }
  69. /**
  70. * Initialize a `SendStream` with the given `path`.
  71. *
  72. * @param {Request} req
  73. * @param {String} path
  74. * @param {object} [options]
  75. * @private
  76. */
  77. function SendStream (req, path, options) {
  78. Stream.call(this)
  79. var opts = options || {}
  80. this.options = opts
  81. this.path = path
  82. this.req = req
  83. this._acceptRanges = opts.acceptRanges !== undefined
  84. ? Boolean(opts.acceptRanges)
  85. : true
  86. this._cacheControl = opts.cacheControl !== undefined
  87. ? Boolean(opts.cacheControl)
  88. : true
  89. this._etag = opts.etag !== undefined
  90. ? Boolean(opts.etag)
  91. : true
  92. this._dotfiles = opts.dotfiles !== undefined
  93. ? opts.dotfiles
  94. : 'ignore'
  95. if (this._dotfiles !== 'ignore' && this._dotfiles !== 'allow' && this._dotfiles !== 'deny') {
  96. throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"')
  97. }
  98. this._extensions = opts.extensions !== undefined
  99. ? normalizeList(opts.extensions, 'extensions option')
  100. : []
  101. this._immutable = opts.immutable !== undefined
  102. ? Boolean(opts.immutable)
  103. : false
  104. this._index = opts.index !== undefined
  105. ? normalizeList(opts.index, 'index option')
  106. : ['index.html']
  107. this._lastModified = opts.lastModified !== undefined
  108. ? Boolean(opts.lastModified)
  109. : true
  110. this._maxage = opts.maxAge || opts.maxage
  111. this._maxage = typeof this._maxage === 'string'
  112. ? ms(this._maxage)
  113. : Number(this._maxage)
  114. this._maxage = !isNaN(this._maxage)
  115. ? Math.min(Math.max(0, this._maxage), MAX_MAXAGE)
  116. : 0
  117. this._root = opts.root
  118. ? resolve(opts.root)
  119. : null
  120. }
  121. /**
  122. * Inherits from `Stream`.
  123. */
  124. util.inherits(SendStream, Stream)
  125. /**
  126. * Emit error with `status`.
  127. *
  128. * @param {number} status
  129. * @param {Error} [err]
  130. * @private
  131. */
  132. SendStream.prototype.error = function error (status, err) {
  133. // emit if listeners instead of responding
  134. if (hasListeners(this, 'error')) {
  135. return this.emit('error', createHttpError(status, err))
  136. }
  137. var res = this.res
  138. var msg = statuses.message[status] || String(status)
  139. var doc = createHtmlDocument('Error', escapeHtml(msg))
  140. // clear existing headers
  141. clearHeaders(res)
  142. // add error headers
  143. if (err && err.headers) {
  144. setHeaders(res, err.headers)
  145. }
  146. // send basic response
  147. res.statusCode = status
  148. res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  149. res.setHeader('Content-Length', Buffer.byteLength(doc))
  150. res.setHeader('Content-Security-Policy', "default-src 'none'")
  151. res.setHeader('X-Content-Type-Options', 'nosniff')
  152. res.end(doc)
  153. }
  154. /**
  155. * Check if the pathname ends with "/".
  156. *
  157. * @return {boolean}
  158. * @private
  159. */
  160. SendStream.prototype.hasTrailingSlash = function hasTrailingSlash () {
  161. return this.path[this.path.length - 1] === '/'
  162. }
  163. /**
  164. * Check if this is a conditional GET request.
  165. *
  166. * @return {Boolean}
  167. * @api private
  168. */
  169. SendStream.prototype.isConditionalGET = function isConditionalGET () {
  170. return this.req.headers['if-match'] ||
  171. this.req.headers['if-unmodified-since'] ||
  172. this.req.headers['if-none-match'] ||
  173. this.req.headers['if-modified-since']
  174. }
  175. /**
  176. * Check if the request preconditions failed.
  177. *
  178. * @return {boolean}
  179. * @private
  180. */
  181. SendStream.prototype.isPreconditionFailure = function isPreconditionFailure () {
  182. var req = this.req
  183. var res = this.res
  184. // if-match
  185. var match = req.headers['if-match']
  186. if (match) {
  187. var etag = res.getHeader('ETag')
  188. return !etag || (match !== '*' && parseTokenList(match).every(function (match) {
  189. return match !== etag && match !== 'W/' + etag && 'W/' + match !== etag
  190. }))
  191. }
  192. // if-unmodified-since
  193. var unmodifiedSince = parseHttpDate(req.headers['if-unmodified-since'])
  194. if (!isNaN(unmodifiedSince)) {
  195. var lastModified = parseHttpDate(res.getHeader('Last-Modified'))
  196. return isNaN(lastModified) || lastModified > unmodifiedSince
  197. }
  198. return false
  199. }
  200. /**
  201. * Strip various content header fields for a change in entity.
  202. *
  203. * @private
  204. */
  205. SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields () {
  206. var res = this.res
  207. res.removeHeader('Content-Encoding')
  208. res.removeHeader('Content-Language')
  209. res.removeHeader('Content-Length')
  210. res.removeHeader('Content-Range')
  211. res.removeHeader('Content-Type')
  212. }
  213. /**
  214. * Respond with 304 not modified.
  215. *
  216. * @api private
  217. */
  218. SendStream.prototype.notModified = function notModified () {
  219. var res = this.res
  220. debug('not modified')
  221. this.removeContentHeaderFields()
  222. res.statusCode = 304
  223. res.end()
  224. }
  225. /**
  226. * Raise error that headers already sent.
  227. *
  228. * @api private
  229. */
  230. SendStream.prototype.headersAlreadySent = function headersAlreadySent () {
  231. var err = new Error('Can\'t set headers after they are sent.')
  232. debug('headers already sent')
  233. this.error(500, err)
  234. }
  235. /**
  236. * Check if the request is cacheable, aka
  237. * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
  238. *
  239. * @return {Boolean}
  240. * @api private
  241. */
  242. SendStream.prototype.isCachable = function isCachable () {
  243. var statusCode = this.res.statusCode
  244. return (statusCode >= 200 && statusCode < 300) ||
  245. statusCode === 304
  246. }
  247. /**
  248. * Handle stat() error.
  249. *
  250. * @param {Error} error
  251. * @private
  252. */
  253. SendStream.prototype.onStatError = function onStatError (error) {
  254. switch (error.code) {
  255. case 'ENAMETOOLONG':
  256. case 'ENOENT':
  257. case 'ENOTDIR':
  258. this.error(404, error)
  259. break
  260. default:
  261. this.error(500, error)
  262. break
  263. }
  264. }
  265. /**
  266. * Check if the cache is fresh.
  267. *
  268. * @return {Boolean}
  269. * @api private
  270. */
  271. SendStream.prototype.isFresh = function isFresh () {
  272. return fresh(this.req.headers, {
  273. etag: this.res.getHeader('ETag'),
  274. 'last-modified': this.res.getHeader('Last-Modified')
  275. })
  276. }
  277. /**
  278. * Check if the range is fresh.
  279. *
  280. * @return {Boolean}
  281. * @api private
  282. */
  283. SendStream.prototype.isRangeFresh = function isRangeFresh () {
  284. var ifRange = this.req.headers['if-range']
  285. if (!ifRange) {
  286. return true
  287. }
  288. // if-range as etag
  289. if (ifRange.indexOf('"') !== -1) {
  290. var etag = this.res.getHeader('ETag')
  291. return Boolean(etag && ifRange.indexOf(etag) !== -1)
  292. }
  293. // if-range as modified date
  294. var lastModified = this.res.getHeader('Last-Modified')
  295. return parseHttpDate(lastModified) <= parseHttpDate(ifRange)
  296. }
  297. /**
  298. * Redirect to path.
  299. *
  300. * @param {string} path
  301. * @private
  302. */
  303. SendStream.prototype.redirect = function redirect (path) {
  304. var res = this.res
  305. if (hasListeners(this, 'directory')) {
  306. this.emit('directory', res, path)
  307. return
  308. }
  309. if (this.hasTrailingSlash()) {
  310. this.error(403)
  311. return
  312. }
  313. var loc = encodeUrl(collapseLeadingSlashes(this.path + '/'))
  314. var doc = createHtmlDocument('Redirecting', 'Redirecting to ' + escapeHtml(loc))
  315. // redirect
  316. res.statusCode = 301
  317. res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  318. res.setHeader('Content-Length', Buffer.byteLength(doc))
  319. res.setHeader('Content-Security-Policy', "default-src 'none'")
  320. res.setHeader('X-Content-Type-Options', 'nosniff')
  321. res.setHeader('Location', loc)
  322. res.end(doc)
  323. }
  324. /**
  325. * Pipe to `res.
  326. *
  327. * @param {Stream} res
  328. * @return {Stream} res
  329. * @api public
  330. */
  331. SendStream.prototype.pipe = function pipe (res) {
  332. // root path
  333. var root = this._root
  334. // references
  335. this.res = res
  336. // decode the path
  337. var path = decode(this.path)
  338. if (path === -1) {
  339. this.error(400)
  340. return res
  341. }
  342. // null byte(s)
  343. if (~path.indexOf('\0')) {
  344. this.error(400)
  345. return res
  346. }
  347. var parts
  348. if (root !== null) {
  349. // normalize
  350. if (path) {
  351. path = normalize('.' + sep + path)
  352. }
  353. // malicious path
  354. if (UP_PATH_REGEXP.test(path)) {
  355. debug('malicious path "%s"', path)
  356. this.error(403)
  357. return res
  358. }
  359. // explode path parts
  360. parts = path.split(sep)
  361. // join / normalize from optional root dir
  362. path = normalize(join(root, path))
  363. } else {
  364. // ".." is malicious without "root"
  365. if (UP_PATH_REGEXP.test(path)) {
  366. debug('malicious path "%s"', path)
  367. this.error(403)
  368. return res
  369. }
  370. // explode path parts
  371. parts = normalize(path).split(sep)
  372. // resolve the path
  373. path = resolve(path)
  374. }
  375. // dotfile handling
  376. if (containsDotFile(parts)) {
  377. debug('%s dotfile "%s"', this._dotfiles, path)
  378. switch (this._dotfiles) {
  379. case 'allow':
  380. break
  381. case 'deny':
  382. this.error(403)
  383. return res
  384. case 'ignore':
  385. default:
  386. this.error(404)
  387. return res
  388. }
  389. }
  390. // index file support
  391. if (this._index.length && this.hasTrailingSlash()) {
  392. this.sendIndex(path)
  393. return res
  394. }
  395. this.sendFile(path)
  396. return res
  397. }
  398. /**
  399. * Transfer `path`.
  400. *
  401. * @param {String} path
  402. * @api public
  403. */
  404. SendStream.prototype.send = function send (path, stat) {
  405. var len = stat.size
  406. var options = this.options
  407. var opts = {}
  408. var res = this.res
  409. var req = this.req
  410. var ranges = req.headers.range
  411. var offset = options.start || 0
  412. if (res.headersSent) {
  413. // impossible to send now
  414. this.headersAlreadySent()
  415. return
  416. }
  417. debug('pipe "%s"', path)
  418. // set header fields
  419. this.setHeader(path, stat)
  420. // set content-type
  421. this.type(path)
  422. // conditional GET support
  423. if (this.isConditionalGET()) {
  424. if (this.isPreconditionFailure()) {
  425. this.error(412)
  426. return
  427. }
  428. if (this.isCachable() && this.isFresh()) {
  429. this.notModified()
  430. return
  431. }
  432. }
  433. // adjust len to start/end options
  434. len = Math.max(0, len - offset)
  435. if (options.end !== undefined) {
  436. var bytes = options.end - offset + 1
  437. if (len > bytes) len = bytes
  438. }
  439. // Range support
  440. if (this._acceptRanges && BYTES_RANGE_REGEXP.test(ranges)) {
  441. // parse
  442. ranges = parseRange(len, ranges, {
  443. combine: true
  444. })
  445. // If-Range support
  446. if (!this.isRangeFresh()) {
  447. debug('range stale')
  448. ranges = -2
  449. }
  450. // unsatisfiable
  451. if (ranges === -1) {
  452. debug('range unsatisfiable')
  453. // Content-Range
  454. res.setHeader('Content-Range', contentRange('bytes', len))
  455. // 416 Requested Range Not Satisfiable
  456. return this.error(416, {
  457. headers: { 'Content-Range': res.getHeader('Content-Range') }
  458. })
  459. }
  460. // valid (syntactically invalid/multiple ranges are treated as a regular response)
  461. if (ranges !== -2 && ranges.length === 1) {
  462. debug('range %j', ranges)
  463. // Content-Range
  464. res.statusCode = 206
  465. res.setHeader('Content-Range', contentRange('bytes', len, ranges[0]))
  466. // adjust for requested range
  467. offset += ranges[0].start
  468. len = ranges[0].end - ranges[0].start + 1
  469. }
  470. }
  471. // clone options
  472. for (var prop in options) {
  473. opts[prop] = options[prop]
  474. }
  475. // set read options
  476. opts.start = offset
  477. opts.end = Math.max(offset, offset + len - 1)
  478. // content-length
  479. res.setHeader('Content-Length', len)
  480. // HEAD support
  481. if (req.method === 'HEAD') {
  482. res.end()
  483. return
  484. }
  485. this.stream(path, opts)
  486. }
  487. /**
  488. * Transfer file for `path`.
  489. *
  490. * @param {String} path
  491. * @api private
  492. */
  493. SendStream.prototype.sendFile = function sendFile (path) {
  494. var i = 0
  495. var self = this
  496. debug('stat "%s"', path)
  497. fs.stat(path, function onstat (err, stat) {
  498. var pathEndsWithSep = path[path.length - 1] === sep
  499. if (err && err.code === 'ENOENT' && !extname(path) && !pathEndsWithSep) {
  500. // not found, check extensions
  501. return next(err)
  502. }
  503. if (err) return self.onStatError(err)
  504. if (stat.isDirectory()) return self.redirect(path)
  505. if (pathEndsWithSep) return self.error(404)
  506. self.emit('file', path, stat)
  507. self.send(path, stat)
  508. })
  509. function next (err) {
  510. if (self._extensions.length <= i) {
  511. return err
  512. ? self.onStatError(err)
  513. : self.error(404)
  514. }
  515. var p = path + '.' + self._extensions[i++]
  516. debug('stat "%s"', p)
  517. fs.stat(p, function (err, stat) {
  518. if (err) return next(err)
  519. if (stat.isDirectory()) return next()
  520. self.emit('file', p, stat)
  521. self.send(p, stat)
  522. })
  523. }
  524. }
  525. /**
  526. * Transfer index for `path`.
  527. *
  528. * @param {String} path
  529. * @api private
  530. */
  531. SendStream.prototype.sendIndex = function sendIndex (path) {
  532. var i = -1
  533. var self = this
  534. function next (err) {
  535. if (++i >= self._index.length) {
  536. if (err) return self.onStatError(err)
  537. return self.error(404)
  538. }
  539. var p = join(path, self._index[i])
  540. debug('stat "%s"', p)
  541. fs.stat(p, function (err, stat) {
  542. if (err) return next(err)
  543. if (stat.isDirectory()) return next()
  544. self.emit('file', p, stat)
  545. self.send(p, stat)
  546. })
  547. }
  548. next()
  549. }
  550. /**
  551. * Stream `path` to the response.
  552. *
  553. * @param {String} path
  554. * @param {Object} options
  555. * @api private
  556. */
  557. SendStream.prototype.stream = function stream (path, options) {
  558. var self = this
  559. var res = this.res
  560. // pipe
  561. var stream = fs.createReadStream(path, options)
  562. this.emit('stream', stream)
  563. stream.pipe(res)
  564. // cleanup
  565. function cleanup () {
  566. destroy(stream, true)
  567. }
  568. // response finished, cleanup
  569. onFinished(res, cleanup)
  570. // error handling
  571. stream.on('error', function onerror (err) {
  572. // clean up stream early
  573. cleanup()
  574. // error
  575. self.onStatError(err)
  576. })
  577. // end
  578. stream.on('end', function onend () {
  579. self.emit('end')
  580. })
  581. }
  582. /**
  583. * Set content-type based on `path`
  584. * if it hasn't been explicitly set.
  585. *
  586. * @param {String} path
  587. * @api private
  588. */
  589. SendStream.prototype.type = function type (path) {
  590. var res = this.res
  591. if (res.getHeader('Content-Type')) return
  592. var ext = extname(path)
  593. var type = mime.contentType(ext) || 'application/octet-stream'
  594. debug('content-type %s', type)
  595. res.setHeader('Content-Type', type)
  596. }
  597. /**
  598. * Set response header fields, most
  599. * fields may be pre-defined.
  600. *
  601. * @param {String} path
  602. * @param {Object} stat
  603. * @api private
  604. */
  605. SendStream.prototype.setHeader = function setHeader (path, stat) {
  606. var res = this.res
  607. this.emit('headers', res, path, stat)
  608. if (this._acceptRanges && !res.getHeader('Accept-Ranges')) {
  609. debug('accept ranges')
  610. res.setHeader('Accept-Ranges', 'bytes')
  611. }
  612. if (this._cacheControl && !res.getHeader('Cache-Control')) {
  613. var cacheControl = 'public, max-age=' + Math.floor(this._maxage / 1000)
  614. if (this._immutable) {
  615. cacheControl += ', immutable'
  616. }
  617. debug('cache-control %s', cacheControl)
  618. res.setHeader('Cache-Control', cacheControl)
  619. }
  620. if (this._lastModified && !res.getHeader('Last-Modified')) {
  621. var modified = stat.mtime.toUTCString()
  622. debug('modified %s', modified)
  623. res.setHeader('Last-Modified', modified)
  624. }
  625. if (this._etag && !res.getHeader('ETag')) {
  626. var val = etag(stat)
  627. debug('etag %s', val)
  628. res.setHeader('ETag', val)
  629. }
  630. }
  631. /**
  632. * Clear all headers from a response.
  633. *
  634. * @param {object} res
  635. * @private
  636. */
  637. function clearHeaders (res) {
  638. var headers = getHeaderNames(res)
  639. for (var i = 0; i < headers.length; i++) {
  640. res.removeHeader(headers[i])
  641. }
  642. }
  643. /**
  644. * Collapse all leading slashes into a single slash
  645. *
  646. * @param {string} str
  647. * @private
  648. */
  649. function collapseLeadingSlashes (str) {
  650. for (var i = 0; i < str.length; i++) {
  651. if (str[i] !== '/') {
  652. break
  653. }
  654. }
  655. return i > 1
  656. ? '/' + str.substr(i)
  657. : str
  658. }
  659. /**
  660. * Determine if path parts contain a dotfile.
  661. *
  662. * @api private
  663. */
  664. function containsDotFile (parts) {
  665. for (var i = 0; i < parts.length; i++) {
  666. var part = parts[i]
  667. if (part.length > 1 && part[0] === '.') {
  668. return true
  669. }
  670. }
  671. return false
  672. }
  673. /**
  674. * Create a Content-Range header.
  675. *
  676. * @param {string} type
  677. * @param {number} size
  678. * @param {array} [range]
  679. */
  680. function contentRange (type, size, range) {
  681. return type + ' ' + (range ? range.start + '-' + range.end : '*') + '/' + size
  682. }
  683. /**
  684. * Create a minimal HTML document.
  685. *
  686. * @param {string} title
  687. * @param {string} body
  688. * @private
  689. */
  690. function createHtmlDocument (title, body) {
  691. return '<!DOCTYPE html>\n' +
  692. '<html lang="en">\n' +
  693. '<head>\n' +
  694. '<meta charset="utf-8">\n' +
  695. '<title>' + title + '</title>\n' +
  696. '</head>\n' +
  697. '<body>\n' +
  698. '<pre>' + body + '</pre>\n' +
  699. '</body>\n' +
  700. '</html>\n'
  701. }
  702. /**
  703. * Create a HttpError object from simple arguments.
  704. *
  705. * @param {number} status
  706. * @param {Error|object} err
  707. * @private
  708. */
  709. function createHttpError (status, err) {
  710. if (!err) {
  711. return createError(status)
  712. }
  713. return err instanceof Error
  714. ? createError(status, err, { expose: false })
  715. : createError(status, err)
  716. }
  717. /**
  718. * decodeURIComponent.
  719. *
  720. * Allows V8 to only deoptimize this fn instead of all
  721. * of send().
  722. *
  723. * @param {String} path
  724. * @api private
  725. */
  726. function decode (path) {
  727. try {
  728. return decodeURIComponent(path)
  729. } catch (err) {
  730. return -1
  731. }
  732. }
  733. /**
  734. * Get the header names on a response.
  735. *
  736. * @param {object} res
  737. * @returns {array[string]}
  738. * @private
  739. */
  740. function getHeaderNames (res) {
  741. return typeof res.getHeaderNames !== 'function'
  742. ? Object.keys(res._headers || {})
  743. : res.getHeaderNames()
  744. }
  745. /**
  746. * Determine if emitter has listeners of a given type.
  747. *
  748. * The way to do this check is done three different ways in Node.js >= 0.10
  749. * so this consolidates them into a minimal set using instance methods.
  750. *
  751. * @param {EventEmitter} emitter
  752. * @param {string} type
  753. * @returns {boolean}
  754. * @private
  755. */
  756. function hasListeners (emitter, type) {
  757. var count = typeof emitter.listenerCount !== 'function'
  758. ? emitter.listeners(type).length
  759. : emitter.listenerCount(type)
  760. return count > 0
  761. }
  762. /**
  763. * Normalize the index option into an array.
  764. *
  765. * @param {boolean|string|array} val
  766. * @param {string} name
  767. * @private
  768. */
  769. function normalizeList (val, name) {
  770. var list = [].concat(val || [])
  771. for (var i = 0; i < list.length; i++) {
  772. if (typeof list[i] !== 'string') {
  773. throw new TypeError(name + ' must be array of strings or false')
  774. }
  775. }
  776. return list
  777. }
  778. /**
  779. * Parse an HTTP Date into a number.
  780. *
  781. * @param {string} date
  782. * @private
  783. */
  784. function parseHttpDate (date) {
  785. var timestamp = date && Date.parse(date)
  786. return typeof timestamp === 'number'
  787. ? timestamp
  788. : NaN
  789. }
  790. /**
  791. * Parse a HTTP token list.
  792. *
  793. * @param {string} str
  794. * @private
  795. */
  796. function parseTokenList (str) {
  797. var end = 0
  798. var list = []
  799. var start = 0
  800. // gather tokens
  801. for (var i = 0, len = str.length; i < len; i++) {
  802. switch (str.charCodeAt(i)) {
  803. case 0x20: /* */
  804. if (start === end) {
  805. start = end = i + 1
  806. }
  807. break
  808. case 0x2c: /* , */
  809. if (start !== end) {
  810. list.push(str.substring(start, end))
  811. }
  812. start = end = i + 1
  813. break
  814. default:
  815. end = i + 1
  816. break
  817. }
  818. }
  819. // final token
  820. if (start !== end) {
  821. list.push(str.substring(start, end))
  822. }
  823. return list
  824. }
  825. /**
  826. * Set an object of headers on a response.
  827. *
  828. * @param {object} res
  829. * @param {object} headers
  830. * @private
  831. */
  832. function setHeaders (res, headers) {
  833. var keys = Object.keys(headers)
  834. for (var i = 0; i < keys.length; i++) {
  835. var key = keys[i]
  836. res.setHeader(key, headers[key])
  837. }
  838. }