thoughts/node_modules/express/node_modules/serve-static/index.js
node_modules/express/node_modules/serve-static/index.jsRaw
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
9'use strict'
10
11/**
12 * Module dependencies.
13 * @private
14 */
15
16var encodeUrl = require('encodeurl')
17var escapeHtml = require('escape-html')
18var parseUrl = require('parseurl')
19var resolve = require('path').resolve
20var send = require('send')
21var url = require('url')
22
23/**
24 * Module exports.
25 * @public
26 */
27
28module.exports = serveStatic
29module.exports.mime = send.mime
30
31/**
32 * @param {string} root
33 * @param {object} [options]
34 * @return {function}
35 * @public
36 */
37
38function 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 */
133function collapseLeadingSlashes (str) {
134 for (var i = 0; i < str.length; i++) {
135 if (str[i] !== '/') {
136 break
137 }
138 }
139
140 return i > 1
141 ? '/' + str.substr(i)
142 : str
143}
144
145/**
146 * Create a directory listener that just 404s.
147 * @private
148 */
149
150function createNotFoundDirectoryListener () {
151 return function notFound () {
152 this.error(404)
153 }
154}
155
156/**
157 * Create a directory listener that performs a redirect.
158 * @private
159 */
160
161function createRedirectDirectoryListener () {
162 return function redirect () {
163 if (this.hasTrailingSlash()) {
164 this.error(404)
165 return
166 }
167
168 // get original URL
169 var originalUrl = parseUrl.original(this.req)
170
171 // append trailing slash
172 originalUrl.path = null
173 originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
174
175 // reformat the URL
176 var loc = encodeUrl(url.format(originalUrl))
177 var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>\n'
178 var res = this.res
179
180 // send redirect response
181 res.statusCode = 301
182 res.setHeader('Content-Type', 'text/html; charset=UTF-8')
183 res.setHeader('Content-Length', Buffer.byteLength(msg))
184 res.setHeader('X-Content-Type-Options', 'nosniff')
185 res.setHeader('Location', loc)
186 res.end(msg)
187 }
188}