UNPKG

yunkong2.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

1,151 lines (1,047 loc) 134 kB
/** * 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)) {