serve.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*!
  2. * serve-static
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * Copyright(c) 2014-2016 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. 'use strict'
  9. /**
  10. * Module dependencies.
  11. * @private
  12. */
  13. var encodeUrl = require('encodeurl')
  14. var escapeHtml = require('escape-html')
  15. var parseUrl = require('parseurl')
  16. var pathTool = require('path')
  17. var resolve = pathTool.resolve
  18. var fs = require('fs')
  19. var send = require('send')
  20. var url = require('url')
  21. /**
  22. * Module exports.
  23. * @public
  24. */
  25. module.exports = serveStatic
  26. module.exports.mime = send.mime
  27. /**
  28. * @param {string} root
  29. * @param {object} [options]
  30. * @return {function}
  31. * @public
  32. */
  33. function serveStatic (root, renderExt, options) {
  34. if (!root) {
  35. throw new TypeError('root path required')
  36. }
  37. if (typeof root !== 'string') {
  38. throw new TypeError('root path must be a string')
  39. }
  40. if (typeof renderExt !== 'string') {
  41. throw new TypeError('renderExt path must be a string')
  42. }
  43. // copy options object
  44. var opts = Object.create(options || null)
  45. // fall-though
  46. var fallthrough = opts.fallthrough !== false
  47. // default redirect
  48. var redirect = opts.redirect !== false
  49. // headers listener
  50. var setHeaders = opts.setHeaders
  51. if (setHeaders && typeof setHeaders !== 'function') {
  52. throw new TypeError('option setHeaders must be function')
  53. }
  54. // setup options for send
  55. opts.maxage = opts.maxage || opts.maxAge || 0
  56. opts.root = resolve(root)
  57. // construct directory listener
  58. var onDirectory = redirect
  59. ? createRedirectDirectoryListener()
  60. : createNotFoundDirectoryListener()
  61. return function serveStatic (req, res, next) {
  62. if (req.method !== 'GET' && req.method !== 'HEAD') {
  63. if (fallthrough) {
  64. return next()
  65. }
  66. // method not allowed
  67. res.statusCode = 405
  68. res.setHeader('Allow', 'GET, HEAD')
  69. res.setHeader('Content-Length', '0')
  70. res.end()
  71. return
  72. }
  73. var forwardError = !fallthrough
  74. var originalUrl = parseUrl.original(req)
  75. var path = parseUrl(req).pathname
  76. // make sure redirect occurs at mount
  77. if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
  78. path = ''
  79. }
  80. // OWN CODE
  81. var spath = path.slice(1);
  82. if((fs.existsSync(pathTool.join(root, spath) + "." + (renderExt || ""))) ||
  83. (spath === "" &&
  84. (fs.existsSync(pathTool.join(root, spath, "index" + "." + (renderExt || "")))))){
  85. res.render(spath);
  86. return;
  87. }
  88. // END OF OWN CODE
  89. // create send stream
  90. var stream = send(req, path, opts)
  91. // add directory handler
  92. stream.on('directory', onDirectory)
  93. // add headers listener
  94. if (setHeaders) {
  95. stream.on('headers', setHeaders)
  96. }
  97. // add file listener for fallthrough
  98. if (fallthrough) {
  99. stream.on('file', function onFile () {
  100. // once file is determined, always forward error
  101. forwardError = true
  102. })
  103. }
  104. // forward errors
  105. stream.on('error', function error (err) {
  106. if (forwardError || !(err.statusCode < 500)) {
  107. next(err)
  108. return
  109. }
  110. next()
  111. })
  112. // pipe
  113. stream.pipe(res)
  114. }
  115. }
  116. /**
  117. * Collapse all leading slashes into a single slash
  118. * @private
  119. */
  120. function collapseLeadingSlashes (str) {
  121. for (var i = 0; i < str.length; i++) {
  122. if (str[i] !== '/') {
  123. break
  124. }
  125. }
  126. return i > 1
  127. ? '/' + str.substr(i)
  128. : str
  129. }
  130. /**
  131. * Create a directory listener that just 404s.
  132. * @private
  133. */
  134. function createNotFoundDirectoryListener () {
  135. return function notFound () {
  136. this.error(404)
  137. }
  138. }
  139. /**
  140. * Create a directory listener that performs a redirect.
  141. * @private
  142. */
  143. function createRedirectDirectoryListener () {
  144. return function redirect () {
  145. if (this.hasTrailingSlash()) {
  146. this.error(404)
  147. return
  148. }
  149. // get original URL
  150. var originalUrl = parseUrl.original(this.req)
  151. // append trailing slash
  152. originalUrl.path = null
  153. originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
  154. // reformat the URL
  155. var loc = encodeUrl(url.format(originalUrl))
  156. var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>\n'
  157. var res = this.res
  158. // send redirect response
  159. res.statusCode = 301
  160. res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  161. res.setHeader('Content-Length', Buffer.byteLength(msg))
  162. res.setHeader('X-Content-Type-Options', 'nosniff')
  163. res.setHeader('Location', loc)
  164. res.end(msg)
  165. }
  166. }