Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -606,17 +606,27 @@ SendStream.prototype.sendFile = function sendFile (path) {
var pathEndsWithSep = path[path.length - 1] === sep
if (err && err.code === 'ENOENT' && !extname(path) && !pathEndsWithSep) {
// not found, check extensions
return next(err)
return next(err, false)
}
if (err) return self.onStatError(err)
if (stat.isDirectory()) return self.redirect(path)
if (stat.isDirectory()) {
// if extensions are configured and path has no extension, try extensions first
if (self._extensions.length > 0 && !extname(path) && !pathEndsWithSep) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this condition at all? Wouldn't it be simpler to always call next(null, true) and it would already redirect if isDir === true?

The extname check feels like it could open a new issue along the same line, e.g. why can't it be contact.foo and contact.foo.html? Feels like it should act the same.

return next(null, true)
}
return self.redirect(path)
}
if (pathEndsWithSep) return self.error(404)
self.emit('file', path, stat)
self.send(path, stat)
})

function next (err) {
function next (err, isDir) {
if (self._extensions.length <= i) {
// if original path was a directory, redirect to it
if (isDir) {
return self.redirect(path)
}
return err
? self.onStatError(err)
: self.error(404)
Expand All @@ -626,8 +636,8 @@ SendStream.prototype.sendFile = function sendFile (path) {

debug('stat "%s"', p)
fs.stat(p, function (err, stat) {
if (err) return next(err)
if (stat.isDirectory()) return next()
if (err) return next(err, isDir)
if (stat.isDirectory()) return next(null, isDir)
self.emit('file', p, stat)
self.send(p, stat)
})
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
About Page
1 change: 1 addition & 0 deletions test/fixtures/about/team.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Team Page
30 changes: 30 additions & 0 deletions test/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,36 @@ describe('send(file, options)', function () {
.get('/thing.html')
.expect(404, done)
})

it('should serve file with extension when no directory exists', function (done) {
// /contact (no directory exists) → serves contact.html
request(createServer({ extensions: ['html'], root: fixtures }))
.get('/tobi')
.expect(200, '<p>tobi</p>', done)
})

it('should serve file with extension when directory also exists', function (done) {
// /about (directory exists) → serves about.html (not redirect to /about/)
request(createServer({ extensions: ['html'], root: fixtures }))
.get('/about')
.expect(200, 'About Page', done)
})

it('should redirect when directory exists and extensions not configured', function (done) {
// When extensions are not configured, should still redirect when directory exists
request(createServer({ root: fixtures }))
.get('/about')
.expect('Location', '/about/')
.expect(301, done)
})

it('should redirect to directory when file with extension does not exist', function (done) {
// /about with only about/ directory → redirects to /about/
request(createServer({ extensions: ['html'], root: fixtures }))
.get('/pets')
.expect('Location', '/pets/')
.expect(301, done)
})
})

describe('lastModified', function () {
Expand Down