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
149 changes: 105 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ function serveIndex(root, options) {
return;
}

// content-negotiation
var accept = accepts(req);
var type = accept.type(mediaTypes);

// not acceptable
if (!type) return next(createError(406));

// parse URLs
var url = parseUrl(req);
var originalUrl = parseUrl.original(req);
Expand Down Expand Up @@ -154,13 +161,27 @@ function serveIndex(root, options) {
});
files.sort();

// content-negotiation
var accept = accepts(req);
var type = accept.type(mediaTypes);
// add parent directory as first
if (showUp) {
files.unshift('..');
}

// stat all files
fstat(path, files, function (err, stats) {
if (err) return next(err);

// not acceptable
if (!type) return next(createError(406));
serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
// combine the stats into the file list,
// ignoring ENOENT / null stat objects
var fileList = files.map(function (file, i) {
return { name: file, stat: stats[i] };
}).filter(function (file) { return file.stat });

// sort file list
fileList.sort(fileSort);

var dir = { name: originalDir, stat: stat };
serveIndex[mediaType[type]](req, res, dir, fileList, next, showUp, icons, path, view, template, stylesheet);
});
});
});
};
Expand All @@ -170,46 +191,29 @@ function serveIndex(root, options) {
* Respond with text/html.
*/

serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
serveIndex.html = function _html(req, res, dir, files, next, showUp, icons, path, view, template, stylesheet) {
var render = typeof template !== 'function'
? createHtmlRender(template)
: template

if (showUp) {
files.unshift('..');
}

// stat all files
stat(path, files, function (err, stats) {
// read stylesheet
fs.readFile(stylesheet, 'utf8', function (err, style) {
if (err) return next(err);

// combine the stats into the file list
var fileList = files.map(function (file, i) {
return { name: file, stat: stats[i] };
});

// sort file list
fileList.sort(fileSort);
// create locals for rendering
var locals = {
directory: dir.name,
displayIcons: Boolean(icons),
fileList: files,
path: path,
style: style,
viewName: view
};

// read stylesheet
fs.readFile(stylesheet, 'utf8', function (err, style) {
// render html
render(locals, function (err, body) {
if (err) return next(err);

// create locals for rendering
var locals = {
directory: dir,
displayIcons: Boolean(icons),
fileList: fileList,
path: path,
style: style,
viewName: view
};

// render html
render(locals, function (err, body) {
if (err) return next(err);
send(res, 'text/html', body)
});
send(res, 'text/html', body)
});
});
};
Expand All @@ -218,16 +222,49 @@ serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path
* Respond with application/json.
*/

serveIndex.json = function _json(req, res, files) {
send(res, 'application/json', JSON.stringify(files))
serveIndex.json = function _json(req, res, dir, files) {
var directory = {
name: dir.name,
type: 'inode/directory',
size: dir.stat.size,
lastModified: dir.stat.mtime.toISOString()
}

// similar to W3 FileAPI Object
var nodes = files.map(function (file) {
var ext = extname(file.name)
var mimetype = mime.lookup(ext)
return {
name: file.name,
type: file.stat.isDirectory() ? 'inode/directory' : mimetype,
size: file.stat.size,
lastModified: file.stat.mtime.toISOString()
}
})
send(res, 'application/json', JSON.stringify({ directory: directory, nodes: nodes }))
};

/**
* Respond with text/plain.
*/

serveIndex.plain = function _plain(req, res, files) {
send(res, 'text/plain', (files.join('\n') + '\n'))
serveIndex.plain = function _plain(req, res, dir, files) {
var directory = {
name: dir.name.replace(/\/$/, '\/')
};

// include size and date
var nodes = files.map(function (file) {
// human readable
var size = hsize(file.stat.size)

return [
file.stat.mtime.toISOString(),
size,
file.name + (file.stat.isDirectory() ? '/' : '')
].join('\t')
})
send(res, 'text/plain', (directory.name + '\n' + nodes.join('\n') + '\n'))
};

/**
Expand Down Expand Up @@ -506,20 +543,44 @@ function send (res, type, body) {
res.end(body, 'utf8')
}

/**
* Convert size to human-readable size
*/

var SIZES = [ 'B', 'K', 'M', 'G', 'T', 'P', 'E' ]
function hsize(size) {
var i = 0
while (size > 1024 && i < SIZES.length) {
size = Math.floor(size/1024)
i += 1
}
return size + SIZES[i]
}


/**
* Stat all files and return array of stat
* in same order.
*/

function stat(dir, files, cb) {
function fstat(dir, files, cb) {
var batch = new Batch();

batch.concurrency(10);

files.forEach(function(file){
batch.push(function(done){
fs.stat(join(dir, file), function(err, stat){
if (err && err.code !== 'ENOENT') return done(err);
// communicate errors via fake stat
if (err && err.code !== 'ENOENT') {
stat = {
size: 0,
mtime: new Date(0),
error: err.toString(),
code: err.code,
isDirectory: function () { return false }
}
}

// pass ENOENT as null stat, not error
done(null, stat || null);
Expand Down
14 changes: 7 additions & 7 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,9 @@ describe('serveIndex(root)', function () {
it('should get file list', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files) {
serveIndex.html = function (req, res, dir, files) {
var text = files
.filter(function (f) { return /\.txt$/.test(f) })
.filter(function (f) { return /\.txt$/.test(f.name) })
.sort()
res.setHeader('Content-Type', 'text/html')
res.end('<b>' + text.length + ' text files</b>')
Expand All @@ -521,9 +521,9 @@ describe('serveIndex(root)', function () {
it('should get dir name', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir) {
serveIndex.html = function (req, res, dir, files, next) {
res.setHeader('Content-Type', 'text/html')
res.end('<b>' + dir + '</b>')
res.end('<b>' + dir.name + '</b>')
}

request(server)
Expand All @@ -535,7 +535,7 @@ describe('serveIndex(root)', function () {
it('should get template path', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(template)))
}
Expand All @@ -549,7 +549,7 @@ describe('serveIndex(root)', function () {
it('should get template with tokens', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(fs.readFileSync(template, 'utf8'))
}
Expand All @@ -567,7 +567,7 @@ describe('serveIndex(root)', function () {
it('should get stylesheet path', function (done) {
var server = createServer()

serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
serveIndex.html = function (req, res, dir, files, next, showUp, icons, path, view, template, stylesheet) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(stylesheet)))
}
Expand Down