index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /*!
  2. * send
  3. * Copyright(c) 2012 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 createError = require('http-errors')
  13. var debug = require('debug')('send')
  14. var deprecate = require('depd')('send')
  15. var destroy = require('destroy')
  16. var escapeHtml = require('escape-html')
  17. , parseRange = require('range-parser')
  18. , Stream = require('stream')
  19. , mime = require('mime')
  20. , fresh = require('fresh')
  21. , path = require('path')
  22. , fs = require('fs')
  23. , normalize = path.normalize
  24. , join = path.join
  25. var etag = require('etag')
  26. var EventEmitter = require('events').EventEmitter;
  27. var ms = require('ms');
  28. var onFinished = require('on-finished')
  29. var statuses = require('statuses')
  30. /**
  31. * Variables.
  32. */
  33. var extname = path.extname
  34. var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year
  35. var resolve = path.resolve
  36. var sep = path.sep
  37. var toString = Object.prototype.toString
  38. var upPathRegexp = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/
  39. /**
  40. * Module exports.
  41. * @public
  42. */
  43. module.exports = send
  44. module.exports.mime = mime
  45. /**
  46. * Shim EventEmitter.listenerCount for node.js < 0.10
  47. */
  48. /* istanbul ignore next */
  49. var listenerCount = EventEmitter.listenerCount
  50. || function(emitter, type){ return emitter.listeners(type).length; };
  51. /**
  52. * Return a `SendStream` for `req` and `path`.
  53. *
  54. * @param {object} req
  55. * @param {string} path
  56. * @param {object} [options]
  57. * @return {SendStream}
  58. * @public
  59. */
  60. function send(req, path, options) {
  61. return new SendStream(req, path, options);
  62. }
  63. /**
  64. * Initialize a `SendStream` with the given `path`.
  65. *
  66. * @param {Request} req
  67. * @param {String} path
  68. * @param {object} [options]
  69. * @private
  70. */
  71. function SendStream(req, path, options) {
  72. var opts = options || {}
  73. this.options = opts
  74. this.path = path
  75. this.req = req
  76. this._etag = opts.etag !== undefined
  77. ? Boolean(opts.etag)
  78. : true
  79. this._dotfiles = opts.dotfiles !== undefined
  80. ? opts.dotfiles
  81. : 'ignore'
  82. if (this._dotfiles !== 'ignore' && this._dotfiles !== 'allow' && this._dotfiles !== 'deny') {
  83. throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"')
  84. }
  85. this._hidden = Boolean(opts.hidden)
  86. if (opts.hidden !== undefined) {
  87. deprecate('hidden: use dotfiles: \'' + (this._hidden ? 'allow' : 'ignore') + '\' instead')
  88. }
  89. // legacy support
  90. if (opts.dotfiles === undefined) {
  91. this._dotfiles = undefined
  92. }
  93. this._extensions = opts.extensions !== undefined
  94. ? normalizeList(opts.extensions, 'extensions option')
  95. : []
  96. this._index = opts.index !== undefined
  97. ? normalizeList(opts.index, 'index option')
  98. : ['index.html']
  99. this._lastModified = opts.lastModified !== undefined
  100. ? Boolean(opts.lastModified)
  101. : true
  102. this._maxage = opts.maxAge || opts.maxage
  103. this._maxage = typeof this._maxage === 'string'
  104. ? ms(this._maxage)
  105. : Number(this._maxage)
  106. this._maxage = !isNaN(this._maxage)
  107. ? Math.min(Math.max(0, this._maxage), maxMaxAge)
  108. : 0
  109. this._root = opts.root
  110. ? resolve(opts.root)
  111. : null
  112. if (!this._root && opts.from) {
  113. this.from(opts.from)
  114. }
  115. }
  116. /**
  117. * Inherits from `Stream.prototype`.
  118. */
  119. SendStream.prototype.__proto__ = Stream.prototype;
  120. /**
  121. * Enable or disable etag generation.
  122. *
  123. * @param {Boolean} val
  124. * @return {SendStream}
  125. * @api public
  126. */
  127. SendStream.prototype.etag = deprecate.function(function etag(val) {
  128. val = Boolean(val);
  129. debug('etag %s', val);
  130. this._etag = val;
  131. return this;
  132. }, 'send.etag: pass etag as option');
  133. /**
  134. * Enable or disable "hidden" (dot) files.
  135. *
  136. * @param {Boolean} path
  137. * @return {SendStream}
  138. * @api public
  139. */
  140. SendStream.prototype.hidden = deprecate.function(function hidden(val) {
  141. val = Boolean(val);
  142. debug('hidden %s', val);
  143. this._hidden = val;
  144. this._dotfiles = undefined
  145. return this;
  146. }, 'send.hidden: use dotfiles option');
  147. /**
  148. * Set index `paths`, set to a falsy
  149. * value to disable index support.
  150. *
  151. * @param {String|Boolean|Array} paths
  152. * @return {SendStream}
  153. * @api public
  154. */
  155. SendStream.prototype.index = deprecate.function(function index(paths) {
  156. var index = !paths ? [] : normalizeList(paths, 'paths argument');
  157. debug('index %o', paths);
  158. this._index = index;
  159. return this;
  160. }, 'send.index: pass index as option');
  161. /**
  162. * Set root `path`.
  163. *
  164. * @param {String} path
  165. * @return {SendStream}
  166. * @api public
  167. */
  168. SendStream.prototype.root = function(path){
  169. path = String(path);
  170. this._root = resolve(path)
  171. return this;
  172. };
  173. SendStream.prototype.from = deprecate.function(SendStream.prototype.root,
  174. 'send.from: pass root as option');
  175. SendStream.prototype.root = deprecate.function(SendStream.prototype.root,
  176. 'send.root: pass root as option');
  177. /**
  178. * Set max-age to `maxAge`.
  179. *
  180. * @param {Number} maxAge
  181. * @return {SendStream}
  182. * @api public
  183. */
  184. SendStream.prototype.maxage = deprecate.function(function maxage(maxAge) {
  185. maxAge = typeof maxAge === 'string'
  186. ? ms(maxAge)
  187. : Number(maxAge);
  188. if (isNaN(maxAge)) maxAge = 0;
  189. if (Infinity == maxAge) maxAge = 60 * 60 * 24 * 365 * 1000;
  190. debug('max-age %d', maxAge);
  191. this._maxage = maxAge;
  192. return this;
  193. }, 'send.maxage: pass maxAge as option');
  194. /**
  195. * Emit error with `status`.
  196. *
  197. * @param {number} status
  198. * @param {Error} [error]
  199. * @private
  200. */
  201. SendStream.prototype.error = function error(status, error) {
  202. // emit if listeners instead of responding
  203. if (listenerCount(this, 'error') !== 0) {
  204. return this.emit('error', createError(error, status, {
  205. expose: false
  206. }))
  207. }
  208. var res = this.res
  209. var msg = statuses[status]
  210. // wipe all existing headers
  211. res._headers = null
  212. // send basic response
  213. res.statusCode = status
  214. res.setHeader('Content-Type', 'text/plain; charset=UTF-8')
  215. res.setHeader('Content-Length', Buffer.byteLength(msg))
  216. res.setHeader('X-Content-Type-Options', 'nosniff')
  217. res.end(msg)
  218. }
  219. /**
  220. * Check if the pathname ends with "/".
  221. *
  222. * @return {Boolean}
  223. * @api private
  224. */
  225. SendStream.prototype.hasTrailingSlash = function(){
  226. return '/' == this.path[this.path.length - 1];
  227. };
  228. /**
  229. * Check if this is a conditional GET request.
  230. *
  231. * @return {Boolean}
  232. * @api private
  233. */
  234. SendStream.prototype.isConditionalGET = function(){
  235. return this.req.headers['if-none-match']
  236. || this.req.headers['if-modified-since'];
  237. };
  238. /**
  239. * Strip content-* header fields.
  240. *
  241. * @private
  242. */
  243. SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields() {
  244. var res = this.res
  245. var headers = Object.keys(res._headers || {})
  246. for (var i = 0; i < headers.length; i++) {
  247. var header = headers[i]
  248. if (header.substr(0, 8) === 'content-' && header !== 'content-location') {
  249. res.removeHeader(header)
  250. }
  251. }
  252. }
  253. /**
  254. * Respond with 304 not modified.
  255. *
  256. * @api private
  257. */
  258. SendStream.prototype.notModified = function(){
  259. var res = this.res;
  260. debug('not modified');
  261. this.removeContentHeaderFields();
  262. res.statusCode = 304;
  263. res.end();
  264. };
  265. /**
  266. * Raise error that headers already sent.
  267. *
  268. * @api private
  269. */
  270. SendStream.prototype.headersAlreadySent = function headersAlreadySent(){
  271. var err = new Error('Can\'t set headers after they are sent.');
  272. debug('headers already sent');
  273. this.error(500, err);
  274. };
  275. /**
  276. * Check if the request is cacheable, aka
  277. * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
  278. *
  279. * @return {Boolean}
  280. * @api private
  281. */
  282. SendStream.prototype.isCachable = function(){
  283. var res = this.res;
  284. return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode;
  285. };
  286. /**
  287. * Handle stat() error.
  288. *
  289. * @param {Error} error
  290. * @private
  291. */
  292. SendStream.prototype.onStatError = function onStatError(error) {
  293. switch (error.code) {
  294. case 'ENAMETOOLONG':
  295. case 'ENOENT':
  296. case 'ENOTDIR':
  297. this.error(404, error)
  298. break
  299. default:
  300. this.error(500, error)
  301. break
  302. }
  303. }
  304. /**
  305. * Check if the cache is fresh.
  306. *
  307. * @return {Boolean}
  308. * @api private
  309. */
  310. SendStream.prototype.isFresh = function(){
  311. return fresh(this.req.headers, this.res._headers);
  312. };
  313. /**
  314. * Check if the range is fresh.
  315. *
  316. * @return {Boolean}
  317. * @api private
  318. */
  319. SendStream.prototype.isRangeFresh = function isRangeFresh(){
  320. var ifRange = this.req.headers['if-range'];
  321. if (!ifRange) return true;
  322. return ~ifRange.indexOf('"')
  323. ? ~ifRange.indexOf(this.res._headers['etag'])
  324. : Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange);
  325. };
  326. /**
  327. * Redirect to path.
  328. *
  329. * @param {string} path
  330. * @private
  331. */
  332. SendStream.prototype.redirect = function redirect(path) {
  333. if (listenerCount(this, 'directory') !== 0) {
  334. this.emit('directory')
  335. return
  336. }
  337. if (this.hasTrailingSlash()) {
  338. this.error(403)
  339. return
  340. }
  341. var loc = path + '/'
  342. var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>\n'
  343. var res = this.res
  344. // redirect
  345. res.statusCode = 301
  346. res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  347. res.setHeader('Content-Length', Buffer.byteLength(msg))
  348. res.setHeader('X-Content-Type-Options', 'nosniff')
  349. res.setHeader('Location', loc)
  350. res.end(msg)
  351. }
  352. /**
  353. * Pipe to `res.
  354. *
  355. * @param {Stream} res
  356. * @return {Stream} res
  357. * @api public
  358. */
  359. SendStream.prototype.pipe = function(res){
  360. var self = this
  361. , args = arguments
  362. , root = this._root;
  363. // references
  364. this.res = res;
  365. // decode the path
  366. var path = decode(this.path)
  367. if (path === -1) return this.error(400)
  368. // null byte(s)
  369. if (~path.indexOf('\0')) return this.error(400);
  370. var parts
  371. if (root !== null) {
  372. // malicious path
  373. if (upPathRegexp.test(normalize('.' + sep + path))) {
  374. debug('malicious path "%s"', path)
  375. return this.error(403)
  376. }
  377. // join / normalize from optional root dir
  378. path = normalize(join(root, path))
  379. root = normalize(root + sep)
  380. // explode path parts
  381. parts = path.substr(root.length).split(sep)
  382. } else {
  383. // ".." is malicious without "root"
  384. if (upPathRegexp.test(path)) {
  385. debug('malicious path "%s"', path)
  386. return this.error(403)
  387. }
  388. // explode path parts
  389. parts = normalize(path).split(sep)
  390. // resolve the path
  391. path = resolve(path)
  392. }
  393. // dotfile handling
  394. if (containsDotFile(parts)) {
  395. var access = this._dotfiles
  396. // legacy support
  397. if (access === undefined) {
  398. access = parts[parts.length - 1][0] === '.'
  399. ? (this._hidden ? 'allow' : 'ignore')
  400. : 'allow'
  401. }
  402. debug('%s dotfile "%s"', access, path)
  403. switch (access) {
  404. case 'allow':
  405. break
  406. case 'deny':
  407. return this.error(403)
  408. case 'ignore':
  409. default:
  410. return this.error(404)
  411. }
  412. }
  413. // index file support
  414. if (this._index.length && this.path[this.path.length - 1] === '/') {
  415. this.sendIndex(path);
  416. return res;
  417. }
  418. this.sendFile(path);
  419. return res;
  420. };
  421. /**
  422. * Transfer `path`.
  423. *
  424. * @param {String} path
  425. * @api public
  426. */
  427. SendStream.prototype.send = function(path, stat){
  428. var len = stat.size;
  429. var options = this.options
  430. var opts = {}
  431. var res = this.res;
  432. var req = this.req;
  433. var ranges = req.headers.range;
  434. var offset = options.start || 0;
  435. if (res._header) {
  436. // impossible to send now
  437. return this.headersAlreadySent();
  438. }
  439. debug('pipe "%s"', path)
  440. // set header fields
  441. this.setHeader(path, stat);
  442. // set content-type
  443. this.type(path);
  444. // conditional GET support
  445. if (this.isConditionalGET()
  446. && this.isCachable()
  447. && this.isFresh()) {
  448. return this.notModified();
  449. }
  450. // adjust len to start/end options
  451. len = Math.max(0, len - offset);
  452. if (options.end !== undefined) {
  453. var bytes = options.end - offset + 1;
  454. if (len > bytes) len = bytes;
  455. }
  456. // Range support
  457. if (ranges) {
  458. ranges = parseRange(len, ranges);
  459. // If-Range support
  460. if (!this.isRangeFresh()) {
  461. debug('range stale');
  462. ranges = -2;
  463. }
  464. // unsatisfiable
  465. if (-1 == ranges) {
  466. debug('range unsatisfiable');
  467. res.setHeader('Content-Range', 'bytes */' + stat.size);
  468. return this.error(416);
  469. }
  470. // valid (syntactically invalid/multiple ranges are treated as a regular response)
  471. if (-2 != ranges && ranges.length === 1) {
  472. debug('range %j', ranges);
  473. // Content-Range
  474. res.statusCode = 206;
  475. res.setHeader('Content-Range', 'bytes '
  476. + ranges[0].start
  477. + '-'
  478. + ranges[0].end
  479. + '/'
  480. + len);
  481. offset += ranges[0].start;
  482. len = ranges[0].end - ranges[0].start + 1;
  483. }
  484. }
  485. // clone options
  486. for (var prop in options) {
  487. opts[prop] = options[prop]
  488. }
  489. // set read options
  490. opts.start = offset
  491. opts.end = Math.max(offset, offset + len - 1)
  492. // content-length
  493. res.setHeader('Content-Length', len);
  494. // HEAD support
  495. if ('HEAD' == req.method) return res.end();
  496. this.stream(path, opts)
  497. };
  498. /**
  499. * Transfer file for `path`.
  500. *
  501. * @param {String} path
  502. * @api private
  503. */
  504. SendStream.prototype.sendFile = function sendFile(path) {
  505. var i = 0
  506. var self = this
  507. debug('stat "%s"', path);
  508. fs.stat(path, function onstat(err, stat) {
  509. if (err && err.code === 'ENOENT'
  510. && !extname(path)
  511. && path[path.length - 1] !== sep) {
  512. // not found, check extensions
  513. return next(err)
  514. }
  515. if (err) return self.onStatError(err)
  516. if (stat.isDirectory()) return self.redirect(self.path)
  517. self.emit('file', path, stat)
  518. self.send(path, stat)
  519. })
  520. function next(err) {
  521. if (self._extensions.length <= i) {
  522. return err
  523. ? self.onStatError(err)
  524. : self.error(404)
  525. }
  526. var p = path + '.' + self._extensions[i++]
  527. debug('stat "%s"', p)
  528. fs.stat(p, function (err, stat) {
  529. if (err) return next(err)
  530. if (stat.isDirectory()) return next()
  531. self.emit('file', p, stat)
  532. self.send(p, stat)
  533. })
  534. }
  535. }
  536. /**
  537. * Transfer index for `path`.
  538. *
  539. * @param {String} path
  540. * @api private
  541. */
  542. SendStream.prototype.sendIndex = function sendIndex(path){
  543. var i = -1;
  544. var self = this;
  545. function next(err){
  546. if (++i >= self._index.length) {
  547. if (err) return self.onStatError(err);
  548. return self.error(404);
  549. }
  550. var p = join(path, self._index[i]);
  551. debug('stat "%s"', p);
  552. fs.stat(p, function(err, stat){
  553. if (err) return next(err);
  554. if (stat.isDirectory()) return next();
  555. self.emit('file', p, stat);
  556. self.send(p, stat);
  557. });
  558. }
  559. next();
  560. };
  561. /**
  562. * Stream `path` to the response.
  563. *
  564. * @param {String} path
  565. * @param {Object} options
  566. * @api private
  567. */
  568. SendStream.prototype.stream = function(path, options){
  569. // TODO: this is all lame, refactor meeee
  570. var finished = false;
  571. var self = this;
  572. var res = this.res;
  573. var req = this.req;
  574. // pipe
  575. var stream = fs.createReadStream(path, options);
  576. this.emit('stream', stream);
  577. stream.pipe(res);
  578. // response finished, done with the fd
  579. onFinished(res, function onfinished(){
  580. finished = true;
  581. destroy(stream);
  582. });
  583. // error handling code-smell
  584. stream.on('error', function onerror(err){
  585. // request already finished
  586. if (finished) return;
  587. // clean up stream
  588. finished = true;
  589. destroy(stream);
  590. // error
  591. self.onStatError(err);
  592. });
  593. // end
  594. stream.on('end', function onend(){
  595. self.emit('end');
  596. });
  597. };
  598. /**
  599. * Set content-type based on `path`
  600. * if it hasn't been explicitly set.
  601. *
  602. * @param {String} path
  603. * @api private
  604. */
  605. SendStream.prototype.type = function(path){
  606. var res = this.res;
  607. if (res.getHeader('Content-Type')) return;
  608. var type = mime.lookup(path);
  609. var charset = mime.charsets.lookup(type);
  610. debug('content-type %s', type);
  611. res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
  612. };
  613. /**
  614. * Set response header fields, most
  615. * fields may be pre-defined.
  616. *
  617. * @param {String} path
  618. * @param {Object} stat
  619. * @api private
  620. */
  621. SendStream.prototype.setHeader = function setHeader(path, stat){
  622. var res = this.res;
  623. this.emit('headers', res, path, stat);
  624. if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes');
  625. if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000));
  626. if (this._lastModified && !res.getHeader('Last-Modified')) {
  627. var modified = stat.mtime.toUTCString()
  628. debug('modified %s', modified)
  629. res.setHeader('Last-Modified', modified)
  630. }
  631. if (this._etag && !res.getHeader('ETag')) {
  632. var val = etag(stat)
  633. debug('etag %s', val)
  634. res.setHeader('ETag', val)
  635. }
  636. };
  637. /**
  638. * Determine if path parts contain a dotfile.
  639. *
  640. * @api private
  641. */
  642. function containsDotFile(parts) {
  643. for (var i = 0; i < parts.length; i++) {
  644. if (parts[i][0] === '.') {
  645. return true
  646. }
  647. }
  648. return false
  649. }
  650. /**
  651. * decodeURIComponent.
  652. *
  653. * Allows V8 to only deoptimize this fn instead of all
  654. * of send().
  655. *
  656. * @param {String} path
  657. * @api private
  658. */
  659. function decode(path) {
  660. try {
  661. return decodeURIComponent(path)
  662. } catch (err) {
  663. return -1
  664. }
  665. }
  666. /**
  667. * Normalize the index option into an array.
  668. *
  669. * @param {boolean|string|array} val
  670. * @param {string} name
  671. * @private
  672. */
  673. function normalizeList(val, name) {
  674. var list = [].concat(val || [])
  675. for (var i = 0; i < list.length; i++) {
  676. if (typeof list[i] !== 'string') {
  677. throw new TypeError(name + ' must be array of strings or false')
  678. }
  679. }
  680. return list
  681. }