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.

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