yunkong2.js-controller
Version:
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
1,151 lines (1,047 loc) • 134 kB
JavaScript
/**
* Object DB in memory - Server
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
/* jshint -W061 */
'use strict';
const extend = require('node.extend');
const fs = require('fs');
const path = require('path');
const socketio = require('socket.io');
const tools = require(__dirname + '/../tools');
const getDefaultDataDir = tools.getDefaultDataDir;
const stream = require('stream');
const util = require('util');
const Writable = stream.Writable;
let memStore = {};
const ACCESS_EVERY_EXEC = 0x1;
const ACCESS_EVERY_WRITE = 0x2;
const ACCESS_EVERY_READ = 0x4;
const ACCESS_EVERY_RW = ACCESS_EVERY_WRITE | ACCESS_EVERY_READ;
const ACCESS_EVERY_ALL = ACCESS_EVERY_WRITE | ACCESS_EVERY_READ | ACCESS_EVERY_EXEC;
const ACCESS_GROUP_EXEC = 0x10;
const ACCESS_GROUP_WRITE = 0x20;
const ACCESS_GROUP_READ = 0x40;
const ACCESS_GROUP_RW = ACCESS_GROUP_WRITE | ACCESS_GROUP_READ;
const ACCESS_GROUP_ALL = ACCESS_GROUP_WRITE | ACCESS_GROUP_READ | ACCESS_GROUP_EXEC;
const ACCESS_USER_EXEC = 0x100;
const ACCESS_USER_WRITE = 0x200;
const ACCESS_USER_READ = 0x400;
const ACCESS_USER_RW = ACCESS_USER_WRITE | ACCESS_USER_READ;
const ACCESS_USER_ALL = ACCESS_USER_WRITE | ACCESS_USER_READ | ACCESS_USER_EXEC;
const ACCESS_WRITE = 0x2;
const ACCESS_READ = 0x4;
const ACCESS_LIST = 'list';
const ACCESS_DELETE = 'delete';
const ACCESS_CREATE = 'create';
const SYSTEM_ADMIN_USER = 'system.user.admin';
const SYSTEM_ADMIN_GROUP = 'system.group.administrator';
/* Writable memory stream */
function WMStrm(key, options) {
// allow use without new operator
if (!(this instanceof WMStrm)) return new WMStrm(key, options);
Writable.call(this, options); // init super
this.key = key; // save key
memStore[key] = new Buffer(''); // empty
}
util.inherits(WMStrm, Writable);
WMStrm.prototype._write = function (chunk, enc, cb) {
if (chunk) {
// our memory store stores things in buffers
let buffer = (Buffer.isBuffer(chunk)) ?
chunk : // already is Buffer use it
new Buffer(chunk, enc); // string, convert
// concatenate to the buffer already there
if (!memStore[this.key]) {
memStore[this.key] = new Buffer('');
console.log('memstore for ' + this.key + ' is null');
}
memStore[this.key] = Buffer.concat([memStore[this.key], buffer]);
}
if (!cb) throw 'Callback is empty';
cb();
};
function ObjectsInMemServer(settings) {
if (!(this instanceof ObjectsInMemServer)) return new ObjectsInMemServer(settings);
settings = settings || {};
let change;
let zlib;
let that = this;
let objects = {};
let fileOptions = {};
let files = {};
let configTimer = null;
let writeTimer = null;
let writeIds = [];
let users = {};
let groups = {};
let preserveSettings = [];
let regUser = /^system\.user/;
let regGroup = /^system\.group/;
let regCheckId = /[\]\[*,;'"`<>\\?]/;
let defaultAcl = {
groups: [],
acl: {
file: {
list: false,
read: false,
write: false,
create: false,
'delete': false
},
object: {
list: false,
read: false,
write: false,
'delete': false
}
}
};
let defaultNewAcl = settings.defaultNewAcl || null;
let namespace = settings.namespace || settings.hostname || '';
let lastSave = null;
let dataDir = (settings.connection.dataDir || getDefaultDataDir());
if (dataDir) {
if (dataDir[0] === '.' && dataDir[1] === '.') {
dataDir = __dirname + '/../../' + dataDir;
} else if (dataDir[0] === '.' && dataDir[1] === '/') {
dataDir = __dirname + '/../../' + dataDir.substring(2);
}
}
dataDir = path.normalize(dataDir);
dataDir = dataDir.replace(/\\/g, '/');
if (dataDir[dataDir.length - 1] !== '/') dataDir += '/';
// Create data directory
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir);
}
let objectsName = dataDir + 'objects.json';
const objectsDir = dataDir + 'files/';
settings.backup = settings.backup || {
disabled: false, // deactivates
files: 24, // minimum number of files
hours: 48, // hours
period: 120, // minutes
path: '' // absolute path
};
const backupDir = settings.backup.path || (dataDir + 'backup-objects/');
if (!settings.backup.disabled) {
zlib = zlib || require('zlib');
// Interval in minutes => to milliseconds
settings.backup.period = settings.backup.period === undefined ? 120 : parseInt(settings.backup.period);
if (isNaN(settings.backup.period)) {
settings.backup.period = 120;
}
settings.backup.period *= 60000;
settings.backup.files = settings.backup.files === undefined ? 24 : parseInt(settings.backup.files);
if (isNaN(settings.backup.files)) {
settings.backup.files = 24;
}
settings.backup.hours = settings.backup.hours === undefined ? 48 : parseInt(settings.backup.hours);
if (isNaN(settings.backup.hours)) {
settings.backup.hours = 48;
}
// Create backup directory
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir);
}
}
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
let server = {
app: null,
server: null,
io: null,
settings: settings
};
/*function prepareRights(options) {
let fOptions = {};
options = options || {};
if (!options.user) {
options = {
user: SYSTEM_ADMIN_USER,
params: options
};
}
// acl.owner = user that creates or owns the file
// acl.group = group, that assigned to file
// acl.permissions = '0777' - default 1 (execute, 2 write, 4 read
if (!options.user) {
fOptions.acl = {
owner: SYSTEM_ADMIN_USER,
ownerGroup: SYSTEM_ADMIN_GROUP,
permissions: 0x644 // '0777'
};
} else {
fOptions.acl = {
owner: options.user
};
fOptions.acl.ownerGroup = options.group;
fOptions.acl.permissions = 0x644;
}
fOptions.acl.ownerGroup = fOptions.acl.ownerGroup || SYSTEM_ADMIN_GROUP;
return fOptions;
}*/
// -------------- FILE FUNCTIONS -------------------------------------------
// memServer specific function
function mkpathSync(rootpath, dirpath) {
// Remove filename
dirpath = dirpath.split('/');
dirpath.pop();
if (!dirpath.length) return;
for (let i = 0; i < dirpath.length; i++) {
rootpath += dirpath[i] + '/';
if (!fs.existsSync(rootpath)) {
fs.mkdirSync(rootpath);
}
}
}
function saveFileSettings(id, force) {
if (typeof id === 'boolean') {
force = id;
id = undefined;
}
if (id !== undefined && writeIds.indexOf(id) === -1) writeIds.push(id);
if (writeTimer) clearTimeout(writeTimer);
// if store immediately
if (force) {
writeTimer = null;
// Store dirs description
for (let _id = 0; _id < writeIds.length; _id++) {
try {
fs.writeFileSync(objectsDir + writeIds[_id] + '/_data.json', JSON.stringify(fileOptions[writeIds[_id]]));
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + writeIds[_id] + '/_data.json: ' + e.message);
}
}
writeIds = [];
} else {
writeTimer = setTimeout(function () {
// Store dirs description
for (let id = 0; id < writeIds.length; id++) {
try {
fs.writeFileSync(objectsDir + writeIds[id] + '/_data.json', JSON.stringify(fileOptions[writeIds[id]]));
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + writeIds[id] + '/_data.json: ' + e.message);
}
}
writeIds = [];
}, 1000);
}
}
function checkFile(id, name, options, flag) {
if (typeof fileOptions[id][name].acl !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: (defaultNewAcl && defaultNewAcl.file) || (ACCESS_USER_RW | ACCESS_GROUP_READ | ACCESS_EVERY_READ) // '0644'
}
};
}
// Set default owner group
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP;
fileOptions[id][name].acl.owner = fileOptions[id][name].acl.owner || (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER;
fileOptions[id][name].acl.permissions = fileOptions[id][name].acl.permissions || (defaultNewAcl && defaultNewAcl.file) || (ACCESS_USER_RW | ACCESS_GROUP_READ | ACCESS_EVERY_READ); // '0644'
if (options.user !== SYSTEM_ADMIN_USER &&
options.groups.indexOf(SYSTEM_ADMIN_GROUP) === -1 &&
fileOptions[id][name].acl) {
if (fileOptions[id][name].acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) !== -1) {
// Check group rights
if (!(fileOptions[id][name].acl.permissions & (flag << 4))) {
return false;
}
} else {
// everybody
if (!(fileOptions[id][name].acl.permissions & flag)) {
return false;
}
}
} else {
// Check user rights
if (!(fileOptions[id][name].acl.permissions & (flag << 8))) {
return false;
}
}
}
return true;
}
function checkFileRights(id, name, options, flag, callback) {
options = options || {};
if (!options.user) {
// Before files converted, lets think: if no options it is admin
options = {
user: SYSTEM_ADMIN_USER,
params: options,
group: SYSTEM_ADMIN_GROUP
};
}
if (!options.acl) {
return that.getUserGroup(options.user, function (user, groups, acl) {
options.acl = acl || {};
options.groups = groups;
options.group = groups ? groups[0] : null;
checkFileRights(id, name, options, flag, callback);
});
}
// If user may write
if (flag === ACCESS_WRITE && !options.acl.file.write) {// write
return callback('permissionError', options);
}
// If user may read
if (flag === ACCESS_READ && !options.acl.file.read) {// read
return callback('permissionError', options);
}
// read rights of file
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
if (!name || !fileOptions[id] || !fileOptions[id][name]) {
return callback(null, options);
}
if (checkFile(id, name, options,flag)) {
return callback(null, options);
} else {
return callback('permissionError', options);
}
/*if (typeof fileOptions[id][name].acl !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: SYSTEM_ADMIN_USER,
permissions: 0x644,
ownerGroup: SYSTEM_ADMIN_GROUP
}
};
}
// Set default onwer group
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || SYSTEM_ADMIN_GROUP;
if (options.user !== SYSTEM_ADMIN_USER &&
options.groups.indexOf(SYSTEM_ADMIN_GROUP) === -1 &&
fileOptions[id][name].acl) {
if (fileOptions[id][name].acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) !== -1) {
// Check group rights
if (!(fileOptions[id][name].acl.permissions & (flag << 4))) {
return callback('permissionError', options);
}
} else {
// everybody
if (!(fileOptions[id][name].acl.permissions & flag)) {
return callback('permissionError', options);
}
}
} else {
// Check user rights
if (!(fileOptions[id][name].acl.permissions & (flag << 8))) {
return callback('permissionError', options);
}
}
}
return callback(null, options);*/
}
function setDefaultAcl(callback) {
try {
defaultNewAcl = Object.assign({}, objects['system.config'].common.defaultNewAcl);
} catch (e) {
defaultNewAcl = {
owner: SYSTEM_ADMIN_USER,
ownerGroup: SYSTEM_ADMIN_GROUP,
object: (ACCESS_USER_RW | ACCESS_GROUP_RW | ACCESS_EVERY_READ),
state: (ACCESS_USER_RW | ACCESS_GROUP_RW | ACCESS_EVERY_READ),
file: (ACCESS_USER_RW | ACCESS_GROUP_RW | ACCESS_EVERY_READ)
};
objects['system.config'].common.defaultNewAcl = Object.assign({}, defaultNewAcl);
}
let count = 0;
// Set all objects without ACL to this one
for (let id in objects) {
if (objects.hasOwnProperty(id) && objects[id] && !objects[id].acl) {
objects[id].acl = Object.assign({}, defaultNewAcl);
delete objects[id].acl.file;
if (objects[id].type !== 'state') {
delete objects[id].acl.state;
}
count++;
}
}
if (typeof callback === 'function') callback(null, count);
}
this.getUserGroup = function (user, callback) {
if (!user || typeof user !== 'string' || !user.match(/^system\.user\./)) {
console.log('invalid user name: ' + user);
user = JSON.stringify(user);
return callback.call(this, user, [], Object.assign({}, defaultAcl.acl));
}
if (users[user]) {
return callback.call(this, user, users[user].groups, users[user].acl);
}
// Read all groups
this.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, {checked: true}, (err, arr) => {
if (err) log.error(namespace + ' ' + err);
groups = [];
if (arr) {
// Read all groups
for (let g = 0; g < arr.rows.length; g++) {
groups[g] = arr.rows[g].value;
if (groups[g]._id === SYSTEM_ADMIN_GROUP) {
groups[g].common.acl = {
file: {
list: true,
read: true,
write: true,
create: true,
'delete': true
},
object: {
list: true,
read: true,
write: true,
create: true,
'delete': true
},
users: {
list: true,
read: true,
write: true,
create: true,
'delete': true
}
};
}
}
}
this.getObjectList({startkey: 'system.user.', endkey: 'system.user.\u9999'}, {checked: true}, (err, arr) => {
if (err) log.error(namespace + ' ' + err);
users = {};
if (arr) {
for (let i = 0; i < arr.rows.length; i++) {
users[arr.rows[i].value._id] = Object.assign({}, defaultAcl);
if (arr.rows[i].value._id === SYSTEM_ADMIN_USER) {
users[SYSTEM_ADMIN_USER].acl.file = {
list: true,
read: true,
write: true,
create: true,
'delete': true
};
users[SYSTEM_ADMIN_USER].acl.object = {
create: true,
list: true,
read: true,
write: true,
'delete': true
};
users[SYSTEM_ADMIN_USER].acl.users = {
create: true,
list: true,
read: true,
write: true,
'delete': true
};
}
}
}
for (let g = 0; g < groups.length; g++) {
if (!groups[g].common.members) continue;
for (let m = 0; m < groups[g].common.members.length; m++) {
let u = groups[g].common.members[m];
if (!users[u]) {
log.warn('Unknown user in group "' + g + '": ' + u);
continue;
}
users[u].groups.push(groups[g]._id);
if (groups[g].common.acl && groups[g].common.acl.file) {
if (!users[u].acl || !users[u].acl.file) {
users[u].acl = users[u].acl || {};
users[u].acl.file = users[u].acl.file || {};
users[u].acl.file.create = groups[g].common.acl.file.create;
users[u].acl.file.read = groups[g].common.acl.file.read;
users[u].acl.file.write = groups[g].common.acl.file.write;
users[u].acl.file['delete'] = groups[g].common.acl.file['delete'];
users[u].acl.file.list = groups[g].common.acl.file.list;
} else {
users[u].acl.file.create = users[u].acl.file.create || groups[g].common.acl.file.create;
users[u].acl.file.read = users[u].acl.file.read || groups[g].common.acl.file.read;
users[u].acl.file.write = users[u].acl.file.write || groups[g].common.acl.file.write;
users[u].acl.file['delete'] = users[u].acl.file['delete'] || groups[g].common.acl.file['delete'];
users[u].acl.file.list = users[u].acl.file.list || groups[g].common.acl.file.list;
}
}
if (groups[g].common.acl && groups[g].common.acl.object) {
if (!users[u].acl || !users[u].acl.object) {
users[u].acl = users[u].acl || {};
users[u].acl.object = users[u].acl.object || {};
users[u].acl.object.create = groups[g].common.acl.object.create;
users[u].acl.object.read = groups[g].common.acl.object.read;
users[u].acl.object.write = groups[g].common.acl.object.write;
users[u].acl.object['delete'] = groups[g].common.acl.object['delete'];
users[u].acl.object.list = groups[g].common.acl.object.list;
} else {
users[u].acl.object.create = users[u].acl.object.create || groups[g].common.acl.object.create;
users[u].acl.object.read = users[u].acl.object.read || groups[g].common.acl.object.read;
users[u].acl.object.write = users[u].acl.object.write || groups[g].common.acl.object.write;
users[u].acl.object['delete'] = users[u].acl.object['delete'] || groups[g].common.acl.object['delete'];
users[u].acl.object.list = users[u].acl.object.list || groups[g].common.acl.object.list;
}
}
if (groups[g].common.acl && groups[g].common.acl.users) {
if (!users[u].acl || !users[u].acl.users) {
users[u].acl = users[u].acl || {};
users[u].acl.users = users[u].acl.users || {};
users[u].acl.users.create = groups[g].common.acl.users.create;
users[u].acl.users.read = groups[g].common.acl.users.read;
users[u].acl.users.write = groups[g].common.acl.users.write;
users[u].acl.users['delete'] = groups[g].common.acl.users['delete'];
users[u].acl.users.list = groups[g].common.acl.users.list;
} else {
users[u].acl.users.create = users[u].acl.users.create || groups[g].common.acl.users.create;
users[u].acl.users.read = users[u].acl.users.read || groups[g].common.acl.users.read;
users[u].acl.users.write = users[u].acl.users.write || groups[g].common.acl.users.write;
users[u].acl.users['delete'] = users[u].acl.users['delete'] || groups[g].common.acl.users['delete'];
users[u].acl.users.list = users[u].acl.users.list || groups[g].common.acl.users.list;
}
}
}
}
callback.call(this, user, users[user] ? users[user].groups : [], users[user] ? users[user].acl : Object.assign({}, defaultAcl.acl));
});
});
};
const mimeTypes = {
'.css': {type: 'text/css', binary: false},
'.bmp': {type: 'image/bmp', binary: true},
'.png': {type: 'image/png', binary: true},
'.jpg': {type: 'image/jpeg', binary: true},
'.jpeg': {type: 'image/jpeg', binary: true},
'.gif': {type: 'image/gif', binary: true},
'.ico': {type: 'image/x-icon', binary: true},
'.webp': {type: 'image/webp', binary: true},
'.wbmp': {type: 'image/vnd.wap.wbmp', binary: true},
'.tif': {type: 'image/tiff', binary: true},
'.js': {type: 'application/javascript', binary: false},
'.html': {type: 'text/html', binary: false},
'.htm': {type: 'text/html', binary: false},
'.json': {type: 'application/json', binary: false},
'.md': {type: 'text/markdown', binary: false},
'.xml': {type: 'text/xml', binary: false},
'.svg': {type: 'image/svg+xml', binary: false},
'.eot': {type: 'application/vnd.ms-fontobject', binary: true},
'.ttf': {type: 'application/font-sfnt', binary: true},
'.cur': {type: 'application/x-win-bitmap', binary: true},
'.woff': {type: 'application/font-woff', binary: true},
'.wav': {type: 'audio/wav', binary: true},
'.mp3': {type: 'audio/mpeg3', binary: true},
'.avi': {type: 'video/avi', binary: true},
'.qt': {type: 'video/quicktime', binary: true},
'.ppt': {type: 'application/vnd.ms-powerpoint', binary: true},
'.pptx': {type: 'application/vnd.ms-powerpoint', binary: true},
'.doc': {type: 'application/msword', binary: true},
'.docx': {type: 'application/msword', binary: true},
'.xls': {type: 'application/vnd.ms-excel', binary: true},
'.xlsx': {type: 'application/vnd.ms-excel', binary: true},
'.mp4': {type: 'video/mp4', binary: true},
'.mkv': {type: 'video/mkv', binary: true},
'.zip': {type: 'application/zip', binary: true},
'.ogg': {type: 'audio/ogg', binary: true},
'.manifest':{type: 'text/cache-manifest', binary: false},
'.pdf': {type: 'application/pdf', binary: true},
'.gz': {type: 'application/gzip', binary: true},
'.gzip': {type: 'application/gzip', binary: true},
};
this.getMimeType = function (ext) {
if (ext instanceof Array) ext = ext[0];
let _mimeType = 'text/javascript';
let isBinary = false;
if (mimeTypes[ext]) {
_mimeType = mimeTypes[ext].type;
isBinary = mimeTypes[ext].binary;
} else {
_mimeType = 'text/javascript';
}
return {mimeType: _mimeType, isBinary: isBinary};
};
this.insert = function (id, attName, ignore, options, obj, callback) {
if (typeof options === 'string') {
options = {mimeType: options};
}
//return pipe for write into redis
let strm = new WMStrm(id + '/' + attName);
strm.on('finish', () => {
if (!memStore[id + '/' + attName]) log.error(namespace + ' File ' + id + ' / ' + attName + ' is empty');
this.writeFile(id, attName, memStore[id + '/' + attName] || '', options, function () {
if (memStore[id + '/' + attName] !== undefined) delete memStore[id + '/' + attName];
if (callback) setImmediate(callback, null, null);
});
});
return strm;
};
function sanitizePath(id, name, callback) {
if (name[0] === '/') name = name.substring(1);
if (!id) {
if (typeof callback === 'function') {
callback('Empty ID');
}
return;
}
if (id) {
id = id.replace(/\.\./g, ''); // do not allow to write in parent directories
}
if (name.indexOf('..') !== -1) {
name = path.normalize(name);
name = name.replace(/\//g, '/');
}
if (name[0] === '/') name = name.substring(1); // do not allow absolute paths
return {id: id, name: name};
}
function _writeFile(id, name, data, options, callback) {
try {
try {
if (!fs.existsSync(objectsDir)) fs.mkdirSync(objectsDir);
if (!fs.existsSync(objectsDir + id)) fs.mkdirSync(objectsDir + id);
} catch (e) {
log.error(namespace + ' Cannot create directories: ' + objectsDir + id + ': ' + e.message);
log.error(namespace + ' Check the permissions! Or call "sudo chmod -R 774 *" in ' + tools.appName +' dir');
if (typeof callback === 'function') callback(e.message);
return;
}
let isBinary;
let ext = name.match(/\.[^.]+$/);
let mime = that.getMimeType(ext);
let _mimeType = mime.mimeType;
isBinary = mime.isBinary;
if (!fileOptions[id][name]) {
fileOptions[id][name] = {createdAt: Date.now()};
}
if (!fileOptions[id][name].acl) {
fileOptions[id][name].acl = {
owner: options.user || (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: options.group || (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: options.mode || (defaultNewAcl && defaultNewAcl.file) || (ACCESS_USER_RW | ACCESS_GROUP_READ | ACCESS_EVERY_READ)// 0x644
};
}
fileOptions[id][name].mimeType = options.mimeType || _mimeType;
fileOptions[id][name].binary = isBinary;
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP;
fileOptions[id][name].modifiedAt = Date.now();
if (isBinary) {
// Reload by read
delete files[id][name];
} else {
files[id][name] = data;
}
try {
// Create directories if complex structure
mkpathSync(objectsDir + id + '/', name);
// Store file
fs.writeFileSync(objectsDir + id + '/' + name, data, {'flag': 'w', 'encoding': isBinary ? 'binary' : 'utf8'});
// Store dir description
saveFileSettings(id);
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + id + '/' + name + ': ' + e.message);
if (typeof callback === 'function') callback(e.message);
return;
}
if (typeof callback === 'function') callback();
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
}
this.writeFile = function (id, name, data, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (typeof options === 'string') {
options = {mimeType: options};
}
if (options && options.acl) {
options.acl = null;
}
if (!callback) {
return new Promise((resolve, reject) => {
this.writeFile(id, name, options, (err, res, mimeType) =>{
if (!err) {
resolve();
} else {
reject(err);
}
});
});
}
let _path = sanitizePath(id, name, callback);
id = _path.id;
name = _path.name;
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
files[id] = files[id] || {};
// If file yet exists => check the permissions
return checkFileRights(id, name, options, ACCESS_WRITE, (err, options) => {
if (err) {
if (typeof callback === 'function') {
callback(err);
}
} else {
return _writeFile(id, name, data, options, callback);
}
});
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
function _readFile(id, name, options, callback) {
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
fileOptions[id] = {};
}
} else {
fileOptions[id] = {};
}
}
if (!files[id]) files[id] = {};
if (!files[id][name] || settings.connection.noFileCache || options.noFileCache) {
if (fs.existsSync(objectsDir + id + '/' + name)) {
// Create description object if not exists
if (!fileOptions[id][name]) {
fileOptions[id][name] = {
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || (ACCESS_USER_ALL | ACCESS_GROUP_ALL | ACCESS_EVERY_ALL) // 777
}
};
}
if (typeof fileOptions[id][name] !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || (ACCESS_USER_ALL | ACCESS_GROUP_ALL | ACCESS_EVERY_ALL) // 777
}
};
}
files[id][name] = fs.readFileSync(objectsDir + id + '/' + name);
if (fileOptions[id][name].binary === undefined) {
let pos = name.lastIndexOf('.');
let ext = '';
if (pos !== -1) ext = name.substring(pos);
let mimeType = that.getMimeType(ext);
fileOptions[id][name].binary = mimeType.isBinary;
fileOptions[id][name].mimeType = mimeType.mimeType;
}
if (!fileOptions[id][name].binary) {
if (files[id][name]) files[id][name] = files[id][name].toString();
}
} else {
if (fileOptions[id][name] !== undefined) delete fileOptions[id][name];
if (files[id][name] !== undefined) delete files[id][name];
}
}
if (fileOptions[id][name] && !fileOptions[id][name].acl) {
// all files belongs to admin by default, but everyone can edit it
fileOptions[id][name].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || (ACCESS_USER_ALL | ACCESS_GROUP_ALL | ACCESS_EVERY_RW) // 776
};
}
if (typeof callback === 'function') {
if (fileOptions[id][name] !== null && fileOptions[id][name] !== undefined) {
if (!fileOptions[id][name].mimeType) {
let _pos = name.lastIndexOf('.');
let _ext = '';
if (_pos !== -1) _ext = name.substring(_pos);
let _mimeType = that.getMimeType(_ext);
fileOptions[id][name].mimeType = _mimeType.mimeType;
}
callback(null, files[id][name], fileOptions[id][name].mimeType);
} else {
callback('Not exists');
}
}
} catch (e) {
log.warn(`Cannot read file ${id} / ${name}: ${JSON.stringify(e)}`);
if (typeof callback === 'function') {
callback(e.message);
}
}
}
this.readFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (options && options.acl) {
options.acl = null;
}
if (!callback) {
return new Promise((resolve, reject) => {
this.readFile(id, name, options, (err, res, mimeType) =>{
if (!err) {
resolve({data: res, mimeType: mimeType});
} else {
reject(err);
}
});
});
}
let _path = sanitizePath(id, name, callback);
if (!_path) return;
id = _path.id;
name = _path.name;
checkFileRights(id, name, options, ACCESS_READ, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return _readFile(id, name, options, callback);
}
});
};
function _unlink(id, name, options, callback) {
try {
let changed = false;
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
if (fileOptions[id][name]) {
changed = true;
delete fileOptions[id][name];
}
if (files[id] && files[id][name]) {
delete files[id][name];
}
if (fs.existsSync(objectsDir + id + '/' + name)) {
let stat = fs.statSync(objectsDir + id + '/' + name);
if (stat.isDirectory()) {
// read all entries and delete every one
let fdir = fs.readdirSync(objectsDir + id + '/' + name);
let cnt = 0;
for (let f = 0; f < fdir.length; f++) {
cnt++;
that.unlink(id, name + '/' + fdir[f], options, function (err) {
if (!--cnt) {
log.debug('Delete directory ' + id + '/' + name);
try {
fs.rmdirSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete directory "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(err);
});
}
}
});
}
if (!cnt) {
log.debug('Delete directory ' + id + '/' + name);
try {
fs.rmdirSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete directory "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
}
} else {
log.debug('Delete file ' + id + '/' + name);
try {
fs.unlinkSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete file "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
}
} else {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Not exists');
});
}
}
// Store dir description
if (changed) saveFileSettings(id);
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e.message);
});
}
}
}
this.unlink = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (options && options.acl) {
options.acl = null;
}
let _path = sanitizePath(id, name, callback);
if (!_path) return;
id = _path.id;
name = _path.name;
checkFileRights(id, name, options, ACCESS_WRITE, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file['delete']) {
if (typeof callback === 'function') callback('permissionError');
} else {
return _unlink(id, name, options, callback);
}
}
});
};
this.delFile = this.unlink;
function _readDir(id, name, options, callback) {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
// Find all files and directories starts with name
let _files = [];
if (name && name[name.length - 1] !== '/') name += '/';
let len = (name) ? name.length : 0;
for (let f in fileOptions[id]) {
if (fileOptions[id].hasOwnProperty(f) && (!name || f.substring(0, len) === name)) {
/** @type {string|string[]} */
let rest = f.substring(len);
rest = rest.split('/', 2);
if (rest[0] && _files.indexOf(rest[0]) === -1) {
_files.push(rest[0]);
}
}
}
if (fs.existsSync(objectsDir + id + '/' + name)) {
try {
let dirFiles = fs.readdirSync(objectsDir + id + '/' + name);
for (let i = 0; i < dirFiles.length; i++) {
if (dirFiles[i] === '..' || dirFiles[i] === '.') continue;
if (dirFiles[i] !== '_data.json' && _files.indexOf(dirFiles[i]) === -1) {
_files.push(dirFiles[i]);
}
}
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e, []);
});
}
return;
}
} else {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Not exists', []);
});
}
return;
}
_files.sort();
let res = [];
for (let j = 0; j < _files.length; j++) {
if (_files[j] === '..' || _files[j] === '.') continue;
if (fs.existsSync(objectsDir + id + '/' + name + _files[j])) {
let stats = fs.statSync(objectsDir + id + '/' + name + _files[j]);
let acl = (fileOptions[id][name + _files[j]] && fileOptions[id][name + _files[j]].acl) ?
Object.assign({}, fileOptions[id][name + _files[j]].acl) : // copy settings
{
read: true,
write : true,
owner: (defaultNewAcl && defaultNewAcl.owner) || SYSTEM_ADMIN_USER,
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || SYSTEM_ADMIN_GROUP,
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || (ACCESS_USER_RW | ACCESS_GROUP_READ | ACCESS_EVERY_READ)
};
try {
// if filter for user
if (options.filter && acl) {
// If user may not write
if (!options.acl.file.write) {// write
acl.permissions &= ~(ACCESS_USER_WRITE | ACCESS_GROUP_WRITE | ACCESS_EVERY_WRITE);
}
// If user may not read
if (!options.acl.file.read) {// read
acl.permissions &= ~(ACCESS_USER_READ | ACCESS_GROUP_READ | ACCESS_EVERY_READ);
}
if (options.user !== SYSTEM_ADMIN_USER && options.groups.indexOf(SYSTEM_ADMIN_GROUP) === -1) {
if (acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(acl.ownerGroup) !== -1) {
// Check group rights
if (!(acl.permissions & ACCESS_GROUP_RW)) {
continue;
}
acl.read = !!(acl.permissions & ACCESS_GROUP_READ);
acl.write = !!(acl.permissions & ACCESS_GROUP_WRITE);
} else {
// everybody
if (!(acl.permissions & ACCESS_EVERY_RW)) {