rest-fs
Version:
restful fileserver
314 lines (278 loc) • 8.35 kB
JavaScript
// fileserver
var bodyParser = require('body-parser');
var fileDriver = require('./fsDriver.js');
var url = require('url');
var mime = require('mime');
var path = require('path');
var mw = require('dat-middleware');
var flow = require('middleware-flow');
var morgan = require('morgan');
var error = require('debug')('rest-fs:fileserver');
var fileserver = function(app) {
if (!app) {
throw new Error('express app required');
}
app.set('etag', 'strong');
app.use(require('express-domain-middleware'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(morgan('combined', {
skip: function () { return process.env.LOG !== 'true'; }
}));
app.get(/^\/.+\/$/, getDir);
app.get(/^\/.+[^\/]$/, getFile);
app.post("/*", postFileOrDir);
app.put("/*", putFileOrDir);
app.delete(/^\/.+\/$/, delDir);
app.delete(/^\/.+[^\/]$/, delFile);
app.use(function (err, req, res, next) {
error('uncaught error', err.stack);
var outErr = {
message: err.message,
stack: err.stack
};
res.status(500).send(outErr);
});
return app;
};
/* GET
/path/to/dir/
list contents of directory
*optional*
?recursive = list recursively default false
return: list of files/dirs
res.body = [
{
"fullFilePath"
}, ...
]
*/
var getDir = function (req, res, next) {
if (req.query.stat) {
return statFile(req, res, next);
}
var dirPath = decodeURI(url.parse(req.url).pathname);
var isRecursive = req.query.recursive || "false";
var handList = function (err, files) {
if (err && err.code === 'ENOTDIR') {
// this this is a file, redirect to file path
var originalUrl = url.parse(req.originalUrl);
originalUrl.pathname = originalUrl.pathname.substr(0, originalUrl.pathname.length - 1);
var target = url.format(originalUrl);
res.statusCode = 303;
res.setHeader('Location', target);
return res.end('Redirecting to ' + target);
}
if (files) {
for (var i = files.length - 1; i >= 0; i--) {
files[i] = formatOutData(req, files[i]);
}
}
sendCode(200, req, res, next, files)(err);
};
if (isRecursive === "true") {
return fileDriver.listAll(dirPath, handList);
} else {
return fileDriver.list(dirPath, handList);
}
};
/* GET
/path/to/file
return contents of file
if dir, redirect to dir path
*optional*
?encoding = default utf8
return: data of file
res.body = {"content of specified file"}
*/
var getFile = function (req, res, next) {
if (req.query.stat) {
return statFile(req, res, next);
}
var filePath = decodeURI(url.parse(req.url).pathname);
var encoding = req.query.encoding || 'utf8';
fileDriver.readFile(filePath, encoding, function(err, data) {
if (err && err.code === 'EISDIR') {
// this this is a dir, redirect to dir path
var originalUrl = url.parse(req.originalUrl);
originalUrl.pathname += '/';
var target = url.format(originalUrl);
res.statusCode = 303;
res.setHeader('Location', target);
return res.end('Redirecting to ' + target);
}
res.set('content-type', mime.lookup(filePath));
sendCode(200, req, res, next, data)(err);
});
};
/* POST
/path/to/file/or/dir
creates or overwrites file
creates dir if it does not exisit.
renames or moves file if newPath exists
*optional*
body.newPath = if exist, move/rename file to this location.
body.clobber = if true will overwrite dest files (default false)
body.mkdirp = if true will create path to new location (default false)
body.mode = permissons of file (defaults: file 438(0666) dir 511(0777))
body.encoding = default utf8
returns: modified resource
res.body = {
"fullFilePath" or dir
}
*/
var postFileOrDir = function (req, res, next) {
var dirPath = decodeURI(url.parse(req.url).pathname);
var isDir = dirPath.substr(-1) === '/';
var options = {};
var isJson = false;
if (typeof req.headers['content-type'] === 'string') {
isJson = ~req.headers['content-type'].indexOf('application/json') === -1 ? true : false;
}
// move/rename if newPath exists
if (req.body.newPath) {
options.clobber = req.body.clobber || false;
options.mkdirp = req.body.mkdirp || false;
var newPath = req.body.newPath;
if (isDir && newPath.substr(-1) !== '/') {
newPath = newPath + '/';
}
return fileDriver.move(dirPath, newPath, options,
sendCode(200, req, res, next, formatOutData(req, newPath)));
}
if (isDir) {
var mode = req.body.mode || 511;
return fileDriver.mkdir(dirPath, mode,
sendCode(201, req, res, next, formatOutData(req, dirPath)));
}
if (!isJson) {
// default is to not clobber
options.encoding = req.query.encoding || 'utf8';
options.mode = req.query.mode || 438;
options.flags = req.query.clobber === 'true' ? 'w' : 'wx';
return fileDriver.writeFileStream(dirPath, req, options,
sendCode(201, req, res, next, formatOutData(req, dirPath)));
}
options.encoding = req.body.encoding || 'utf8';
options.mode = req.body.mode || 438;
var data = req.body.content || '';
fileDriver.writeFile(dirPath, data, options,
sendCode(201, req, res, next, formatOutData(req, dirPath)));
};
/* PUT
/path/to/file/or/dir
make file or dir
*optional*
body.mode = permissons of file (438 default 0666 octal)
body.encoding = default utf8
returns: modified resource
res.body = {
"fullFilePath"
}
*/
var putFileOrDir = function (req, res, next) {
var dirPath = decodeURI(url.parse(req.url).pathname);
var isDir = dirPath.substr(-1) === '/';
var options = {};
if (isDir) {
var mode = req.body.mode || 511;
fileDriver.mkdir(dirPath, mode,
sendCode(201, req, res, next, formatOutData(req, dirPath)));
} else {
options.encoding = req.body.encoding || 'utf8';
options.mode = req.body.mode || 438;
var data = req.body.content || '';
fileDriver.writeFile(dirPath, data, options,
sendCode(201, req, res, next, formatOutData(req, dirPath)));
}
};
/* DEL
/path/to/dir/
deletes dir
*optional*
body.clobber = will remove non-empty dir (defaut: false)
return:
res.body = {}
*/
var delDir = function (req, res, next) {
var dirPath = decodeURI(url.parse(req.url).pathname);
var clobber = req.body.clobber || false;
fileDriver.rmdir(dirPath, clobber, sendCode(200, req, res, next, {}));
};
/* DEL
/path/to/file
deletes file
return:
res.body = {}
*/
var delFile = function (req, res, next) {
var dirPath = decodeURI(url.parse(req.url).pathname);
fileDriver.unlink(dirPath, sendCode(200, req, res, next, {}));
};
/* GET
/path/to/dir/or/file
Returns stats for a file
return: stats for the file as determined by node
res.body = {
dev: 16777220,
mode: 16877,
nlink: 31,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 604862,
size: 1054,
blocks: 0,
atime: Thu Mar 05 2015 11:38:47 GMT-0800 (PST),
mtime: Thu Mar 05 2015 10:52:41 GMT-0800 (PST),
ctime: Thu Mar 05 2015 10:52:41 GMT-0800 (PST),
birthtime: Mon Mar 02 2015 10:55:37 GMT-0800 (PST)
}
*/
var statFile = function (req, res, next) {
var filePath = decodeURI(url.parse(req.url).pathname);
fileDriver.stat(filePath, function(err, stats) {
sendCode(200, req, res, next, stats)(err);
});
}
// Helpers
// formats out data based on client spec.
var formatOutData = function (req, filepath) {
var out = filepath;
if (typeof req.modifyOut === 'function') {
out = req.modifyOut(out);
}
return out;
};
var sendCode = function(code, req, res, next, out) {
return function (err) {
if (err) {
error('ERROR', req.url, err);
code = 500;
out = {
errno: err.errno,
code: err.code,
path: err.path,
message: err.message,
stack: err.stack
};
if (err.code === 'ENOENT') {
code = 404;
} if (err.code === 'EPERM') {
code = 403;
} if (err.code === 'ENOTDIR' ||
err.code === 'EISDIR') {
code = 400;
} if (err.code === 'ENOTEMPTY' ||
err.code === 'EEXIST' ||
err.code === 'EINVAL') {
code = 409;
}
}
res.status(code).send(out);
};
};
module.exports = fileserver;