1313 * @private
1414 */
1515
16- var escapeHtml = require ( 'escape-html' ) ;
17- var parseurl = require ( 'parseurl' ) ;
18- var resolve = require ( 'path' ) . resolve ;
19- var send = require ( 'send' ) ;
20- var url = require ( 'url' ) ;
16+ var escapeHtml = require ( 'escape-html' )
17+ var parseUrl = require ( 'parseurl' )
18+ var resolve = require ( 'path' ) . resolve
19+ var send = require ( 'send' )
20+ var url = require ( 'url' )
2121
2222/**
2323 * Module exports.
@@ -46,12 +46,14 @@ function serveStatic(root, options) {
4646 // copy options object
4747 var opts = Object . create ( options || null )
4848
49+ // fall-though
50+ var fallthrough = opts . fallthrough !== false
51+
4952 // default redirect
5053 var redirect = opts . redirect !== false
5154
5255 // headers listener
5356 var setHeaders = opts . setHeaders
54- opts . setHeaders = undefined
5557
5658 if ( setHeaders && typeof setHeaders !== 'function' ) {
5759 throw new TypeError ( 'option setHeaders must be function' )
@@ -61,59 +63,61 @@ function serveStatic(root, options) {
6163 opts . maxage = opts . maxage || opts . maxAge || 0
6264 opts . root = resolve ( root )
6365
66+ // construct directory listener
67+ var onDirectory = redirect
68+ ? createRedirectDirectoryListener ( )
69+ : createNotFoundDirectoryListener ( )
70+
6471 return function serveStatic ( req , res , next ) {
6572 if ( req . method !== 'GET' && req . method !== 'HEAD' ) {
66- return next ( )
73+ if ( fallthrough ) {
74+ return next ( )
75+ }
76+
77+ // method not allowed
78+ res . statusCode = 405
79+ res . setHeader ( 'Allow' , 'GET, HEAD' )
80+ res . setHeader ( 'Content-Length' , '0' )
81+ res . end ( )
82+ return
6783 }
6884
69- var originalUrl = parseurl . original ( req )
70- var path = parseurl ( req ) . pathname
71- var hasTrailingSlash = originalUrl . pathname [ originalUrl . pathname . length - 1 ] === '/'
85+ var forwardError = ! fallthrough
86+ var originalUrl = parseUrl . original ( req )
87+ var path = parseUrl ( req ) . pathname
7288
73- if ( path === '/' && ! hasTrailingSlash ) {
74- // make sure redirect occurs at mount
89+ // make sure redirect occurs at mount
90+ if ( path === '/' && originalUrl . pathname . substr ( - 1 ) !== '/' ) {
7591 path = ''
7692 }
7793
7894 // create send stream
7995 var stream = send ( req , path , opts )
8096
81- if ( redirect ) {
82- // redirect relative to originalUrl
83- stream . on ( 'directory' , function redirect ( ) {
84- if ( hasTrailingSlash ) {
85- return next ( )
86- }
87-
88- // append trailing slash
89- originalUrl . path = null
90- originalUrl . pathname = collapseLeadingSlashes ( originalUrl . pathname + '/' )
91-
92- // reformat the URL
93- var loc = url . format ( originalUrl )
94- var msg = 'Redirecting to <a href="' + escapeHtml ( loc ) + '">' + escapeHtml ( loc ) + '</a>\n'
95-
96- // send redirect response
97- res . statusCode = 303
98- res . setHeader ( 'Content-Type' , 'text/html; charset=UTF-8' )
99- res . setHeader ( 'Content-Length' , Buffer . byteLength ( msg ) )
100- res . setHeader ( 'X-Content-Type-Options' , 'nosniff' )
101- res . setHeader ( 'Location' , loc )
102- res . end ( msg )
103- } )
104- } else {
105- // forward to next middleware on directory
106- stream . on ( 'directory' , next )
107- }
97+ // add directory handler
98+ stream . on ( 'directory' , onDirectory )
10899
109100 // add headers listener
110101 if ( setHeaders ) {
111102 stream . on ( 'headers' , setHeaders )
112103 }
113104
114- // forward non-404 errors
105+ // add file listener for fallthrough
106+ if ( fallthrough ) {
107+ stream . on ( 'file' , function onFile ( ) {
108+ // once file is determined, always forward error
109+ forwardError = true
110+ } )
111+ }
112+
113+ // forward errors
115114 stream . on ( 'error' , function error ( err ) {
116- next ( err . status === 404 ? null : err )
115+ if ( forwardError || ! ( err . statusCode < 500 ) ) {
116+ next ( err )
117+ return
118+ }
119+
120+ next ( )
117121 } )
118122
119123 // pipe
@@ -136,3 +140,48 @@ function collapseLeadingSlashes(str) {
136140 ? '/' + str . substr ( i )
137141 : str
138142}
143+
144+ /**
145+ * Create a directory listener that just 404s.
146+ * @private
147+ */
148+
149+ function createNotFoundDirectoryListener ( ) {
150+ return function notFound ( ) {
151+ this . error ( 404 )
152+ }
153+ }
154+
155+ /**
156+ * Create a directory listener that performs a redirect.
157+ * @private
158+ */
159+
160+ function createRedirectDirectoryListener ( ) {
161+ return function redirect ( ) {
162+ if ( this . hasTrailingSlash ( ) ) {
163+ this . error ( 404 )
164+ return
165+ }
166+
167+ // get original URL
168+ var originalUrl = parseUrl . original ( this . req )
169+
170+ // append trailing slash
171+ originalUrl . path = null
172+ originalUrl . pathname = collapseLeadingSlashes ( originalUrl . pathname + '/' )
173+
174+ // reformat the URL
175+ var loc = url . format ( originalUrl )
176+ var msg = 'Redirecting to <a href="' + escapeHtml ( loc ) + '">' + escapeHtml ( loc ) + '</a>\n'
177+ var res = this . res
178+
179+ // send redirect response
180+ res . statusCode = 303
181+ res . setHeader ( 'Content-Type' , 'text/html; charset=UTF-8' )
182+ res . setHeader ( 'Content-Length' , Buffer . byteLength ( msg ) )
183+ res . setHeader ( 'X-Content-Type-Options' , 'nosniff' )
184+ res . setHeader ( 'Location' , loc )
185+ res . end ( msg )
186+ }
187+ }
0 commit comments