Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow returning file details in JSON #39

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ the express example).

Serve index accepts these properties in the options object.


##### filter

Apply this filter function to files. Defaults to `false`. The `filter` function
Expand All @@ -50,6 +51,17 @@ Display hidden (dot) files. Defaults to `false`.

Display icons. Defaults to `false`.

##### jsonStats

Enables detailed information to be served via JSON when the request header includes `Accept: application/json`. Defaults to `false`.

When this flag is disabled, JSON requests will return a simple array of filenames. With this flag enabled, JSON requests will return an array of objects, each containing objects with the following properties:

- `name` is the relative name for the file.
- `stat` is subset of the `fs.Stats` object for the file.

The following "safe" properties will be present as file stats: `isFile`,`isDirectory`,`size`, `atime`, `mtime`, `ctime` and `birthtime`.

##### stylesheet

Optional path to a CSS stylesheet. Defaults to a built-in stylesheet.
Expand Down
49 changes: 42 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function serveIndex(root, options) {
// resolve root to absolute and normalize
var rootPath = normalize(resolve(root) + sep);

var jsonStats = opts.jsonStats || false;
var filter = opts.filter;
var hidden = opts.hidden;
var icons = opts.icons;
Expand Down Expand Up @@ -160,7 +161,7 @@ function serveIndex(root, options) {

// not acceptable
if (!type) return next(createError(406));
serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet, jsonStats);
});
});
};
Expand Down Expand Up @@ -222,13 +223,47 @@ serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path
* Respond with application/json.
*/

serveIndex.json = function _json(req, res, files) {
var body = JSON.stringify(files);
var buf = new Buffer(body, 'utf8');
serveIndex.json = function _json(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet, jsonStats) {

res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Length', buf.length);
res.end(buf);
if (! jsonStats) {
//return the simple file list with no stats
var body = JSON.stringify(files);
var buf = new Buffer(body, 'utf8');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Length', buf.length);
res.end(buf);
}

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

// combine the stats into the file list
var filesAndStats = files.map(function (file, i) {
var stat = {};

stat["isDirectory"] = stats[i].isDirectory();
stat["isFile"] = stats[i].isFile();

//next select only the safe and non-machine-specific fields from the stat
var safeFields = ["size", "atime", "mtime", "ctime", "birthtime"]
safeFields.forEach(function (field) {
stat[field] = stats[i][field];
});

return { name: file, stat: stat };
});


//return the file list with stats
var body = JSON.stringify(filesAndStats);
var buf = new Buffer(body, 'utf8');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Length', buf.length);
res.end(buf);
});
}
};

/**
Expand Down
68 changes: 68 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,74 @@ describe('serveIndex(root)', function () {
});
});

describe('when Accept: application/json is given', function () {
describe('when requesting file stats via JSON', function() {
it('should correctly identify files and directories', function (done) {

var server = createServer(fixtures, {'jsonStats': true});

request(server)
.get('/')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(hasTodoStats)
.expect(hasUserStats)
.expect(200, done);

function hasTodoStats(res) {
//check the todo.txt record
var todo = res.body.find(function(value) {
return value.name == "todo.txt";
})

if (! todo.stat.size == 11)
throw new Error("'todo.txt' size should be 11");
if (! todo.stat.isFile)
throw new Error("'todo.txt' isFile should be true");
if (todo.stat.isDirectory)
throw new Error("'todo.txt' isDirectory should be false");
}

function hasUserStats(res) {
//check the users record
var users = res.body.find(function(value) {
return value.name == "users";
});

if (users.stat.isFile)
throw new Error("'users' isFile should be false");
if (!users.stat.isDirectory)
throw new Error("'users' isDirectory should be true");
}
});

it('should not return unsafe stats', function (done) {

var server = createServer(fixtures, {'jsonStats': true});

request(server)
.get('/')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(noUnsafeStats)
.expect(200, done);

function noUnsafeStats(res) {
var safeFields = ["isFile","isDirectory","size", "atime", "mtime", "ctime", "birthtime"];

res.body.forEach(function (item) {
safeFields.forEach(function (field) {
delete item.stat[field];
});

if (Object.keys(item.stat).length>0)
throw new Error("stats contains unsafe keys");
});
}
});

});
});
describe('when set with trailing slash', function () {
var server;
before(function () {
Expand Down