Projekt

Obecné

Profil

Stáhnout (4.46 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
/*!
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
9
'use strict'
10
11
/**
12
 * Module dependencies.
13
 * @private
14
 */
15
16
var encodeUrl = require('encodeurl')
17
var escapeHtml = require('escape-html')
18
var parseUrl = require('parseurl')
19
var resolve = require('path').resolve
20
var send = require('send')
21
var url = require('url')
22
23
/**
24
 * Module exports.
25
 * @public
26
 */
27
28
module.exports = serveStatic
29
module.exports.mime = send.mime
30
31
/**
32
 * @param {string} root
33
 * @param {object} [options]
34
 * @return {function}
35
 * @public
36
 */
37
38
function serveStatic (root, options) {
39
  if (!root) {
40
    throw new TypeError('root path required')
41
  }
42
43
  if (typeof root !== 'string') {
44
    throw new TypeError('root path must be a string')
45
  }
46
47
  // copy options object
48
  var opts = Object.create(options || null)
49
50
  // fall-though
51
  var fallthrough = opts.fallthrough !== false
52
53
  // default redirect
54
  var redirect = opts.redirect !== false
55
56
  // headers listener
57
  var setHeaders = opts.setHeaders
58
59
  if (setHeaders && typeof setHeaders !== 'function') {
60
    throw new TypeError('option setHeaders must be function')
61
  }
62
63
  // setup options for send
64
  opts.maxage = opts.maxage || opts.maxAge || 0
65
  opts.root = resolve(root)
66
67
  // construct directory listener
68
  var onDirectory = redirect
69
    ? createRedirectDirectoryListener()
70
    : createNotFoundDirectoryListener()
71
72
  return function serveStatic (req, res, next) {
73
    if (req.method !== 'GET' && req.method !== 'HEAD') {
74
      if (fallthrough) {
75
        return next()
76
      }
77
78
      // method not allowed
79
      res.statusCode = 405
80
      res.setHeader('Allow', 'GET, HEAD')
81
      res.setHeader('Content-Length', '0')
82
      res.end()
83
      return
84
    }
85
86
    var forwardError = !fallthrough
87
    var originalUrl = parseUrl.original(req)
88
    var path = parseUrl(req).pathname
89
90
    // make sure redirect occurs at mount
91
    if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
92
      path = ''
93
    }
94
95
    // create send stream
96
    var stream = send(req, path, opts)
97
98
    // add directory handler
99
    stream.on('directory', onDirectory)
100
101
    // add headers listener
102
    if (setHeaders) {
103
      stream.on('headers', setHeaders)
104
    }
105
106
    // add file listener for fallthrough
107
    if (fallthrough) {
108
      stream.on('file', function onFile () {
109
        // once file is determined, always forward error
110
        forwardError = true
111
      })
112
    }
113
114
    // forward errors
115
    stream.on('error', function error (err) {
116
      if (forwardError || !(err.statusCode < 500)) {
117
        next(err)
118
        return
119
      }
120
121
      next()
122
    })
123
124
    // pipe
125
    stream.pipe(res)
126
  }
127
}
128
129
/**
130
 * Collapse all leading slashes into a single slash
131
 * @private
132
 */
133
function collapseLeadingSlashes (str) {
134
  for (var i = 0; i < str.length; i++) {
135
    if (str.charCodeAt(i) !== 0x2f /* / */) {
136
      break
137
    }
138
  }
139
140
  return i > 1
141
    ? '/' + str.substr(i)
142
    : str
143
}
144
145
/**
146
 * Create a minimal HTML document.
147
 *
148
 * @param {string} title
149
 * @param {string} body
150
 * @private
151
 */
152
153
function createHtmlDocument (title, body) {
154
  return '<!DOCTYPE html>\n' +
155
    '<html lang="en">\n' +
156
    '<head>\n' +
157
    '<meta charset="utf-8">\n' +
158
    '<title>' + title + '</title>\n' +
159
    '</head>\n' +
160
    '<body>\n' +
161
    '<pre>' + body + '</pre>\n' +
162
    '</body>\n' +
163
    '</html>\n'
164
}
165
166
/**
167
 * Create a directory listener that just 404s.
168
 * @private
169
 */
170
171
function createNotFoundDirectoryListener () {
172
  return function notFound () {
173
    this.error(404)
174
  }
175
}
176
177
/**
178
 * Create a directory listener that performs a redirect.
179
 * @private
180
 */
181
182
function createRedirectDirectoryListener () {
183
  return function redirect (res) {
184
    if (this.hasTrailingSlash()) {
185
      this.error(404)
186
      return
187
    }
188
189
    // get original URL
190
    var originalUrl = parseUrl.original(this.req)
191
192
    // append trailing slash
193
    originalUrl.path = null
194
    originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
195
196
    // reformat the URL
197
    var loc = encodeUrl(url.format(originalUrl))
198
    var doc = createHtmlDocument('Redirecting', 'Redirecting to <a href="' + escapeHtml(loc) + '">' +
199
      escapeHtml(loc) + '</a>')
200
201
    // send redirect response
202
    res.statusCode = 301
203
    res.setHeader('Content-Type', 'text/html; charset=UTF-8')
204
    res.setHeader('Content-Length', Buffer.byteLength(doc))
205
    res.setHeader('Content-Security-Policy', "default-src 'none'")
206
    res.setHeader('X-Content-Type-Options', 'nosniff')
207
    res.setHeader('Location', loc)
208
    res.end(doc)
209
  }
210
}