UNPKG

node-smb-server

Version:

A Pure JavaScript SMB Server Implementation

618 lines (549 loc) 15.2 kB
/* * Copyright 2016 Adobe Systems Incorporated. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ var events = require('events'); var EventEmitter = events.EventEmitter; var Datastore = require('nedb'); var async = require('async'); var util = require('util'); var utils = require('../../lib/utils'); var TestStream = require('./test-stream'); function TestFS() { this.clearAll(); this.allowDirDelete = false; EventEmitter.call(this); } util.inherits(TestFS, EventEmitter); function _findByPath(path, cb) { path = _trimSlash(path); var dir = utils.getParentPath(path); var name = utils.getPathName(path); this.allFiles.find({path: dir, name: name}, function (err, docs) { if (err) { cb(err); } else if (docs.length > 1) { cb('duplicate file encountered ' + path); } else { cb(null, docs.length > 0 ? docs[0] : false, dir, name); } }); } function _findByPathSync(path) { var self = this; var sync = true; var syncErr = null; var data = null; _findByPath.call(self, path, function(err, file) { syncErr = err; data = file; sync = false; }); while(sync) {require('deasync').sleep(100);} if (syncErr) { throw syncErr; } else if (!data) { throw path + ' not found'; } return data; } function _findById(id, cb) { this.allFiles.find({_id: id}, function (err, docs) { if (err) { cb(err); } else if (docs.length ==0 ) { cb('cannot find file with id ' + id); } else { cb(null, docs[0]); } }); } function _extendDoc(doc) { var self = this; doc['isDirectory'] = function () { return doc.isdir; }; doc['isFile'] = function () { return !doc.isdir; }; Object.defineProperty(doc, 'mtime', { get: function() { return this._mtime; }, set: function (modified) { this._mtime = modified; _updateByIdSync.call(self, this._id, {_mtime: modified, ctime: modified}, false); } }); } function _updateByFilter(filter, updateData, newCreated, cb) { var self = this; setTimeout(function () { // pause to ensure dates change var date = new Date(); if (!updateData['_mtime']) { updateData['_mtime'] = date; updateData['ctime'] = date; } updateData['atime'] = date; if (newCreated) { updateData['birthtime'] = date; } self.allFiles.update(filter, {$set: updateData}, {}, cb); }, 5); } function _updateByName(name, updateData, newCreated, cb) { var dir = utils.getParentPath(name); var name = utils.getPathName(name); _updateByFilter.call(this, {path: dir, name: name}, updateData, newCreated, cb); } function _updateById(id, updateData, newCreated, cb) { _updateByFilter.call(this, {_id: id}, updateData, newCreated, cb); } function _updateByIdSync(id, updateData, newCreated) { var self = this; var sync = true; var syncErr = null; _updateById.call(self, id, updateData, newCreated, function(err) { syncErr = err; sync = false; }); while(sync) {require('deasync').sleep(100);} if (syncErr) { throw syncErr; } return false; } function _trimSlash(path) { if (path.charAt(path.length - 1) == '/') { path = path.substr(0, path.length - 1); } return path; } TestFS.prototype.allowDeleteNonEmptyDir = function (allow) { this.allowDirDelete = allow ? true : false; }; TestFS.prototype.printAll = function (cb) { this.allFiles.find({}, function (err, docs) { console.log(docs); if (cb) { cb(); } }); }; TestFS.prototype.mkdirp = function (path, cb) { var self = this; var paths = path.toString().split('/'); var parent = '/'; async.eachSeries(paths, function (currPath, eachCb) { if (currPath) { self.createEntityIfNoExist(parent + currPath, true, function (err) { parent += currPath + '/'; eachCb(err); }); } else { eachCb(); } }, function (err) { cb(err); }); }; TestFS.prototype.createEntityIfNoExist = function (path, isDir, cb) { var self = this; _findByPath.call(self, path, function (err, file) { if (err) { cb(err); } else if (!file) { self.createEntity(path, isDir, function (err) { cb(err); }); } else { cb(); } }); }; TestFS.prototype.createEntityWithDates = function (path, isDir, content, created, lastModified, cb) { var self = this; var dir = ''; var name = path; if (path != '/') { dir = utils.getParentPath(path); name = utils.getPathName(path); } var size = content ? content.length : 0; self.mkdirp(dir, function (err) { if (err) { cb(err); } else { var entity = { path: dir, name: name, mode: 33188, size: 0, blocks: 1, blksize: 0, birthtime: created, _mtime: lastModified, atime: lastModified, ctime: lastModified, isdir: isDir }; if (!isDir) { entity['data'] = content; entity.size = size; entity.blksize = size; } self.allFiles.insert(entity, function (err, doc) { if (err) { cb(err); } else { cb(null, doc._id); } }); } }); }; TestFS.prototype.createEntity = function (path, isDir, cb) { var date = new Date(); this.createEntityWithDates(path, isDir, '', date, date, cb); }; TestFS.prototype.open = function (path, mode, cb) { var self = this; _findByPath.call(self, path, function (err, file, dir, name) { if (err) { cb(err); } else { if (mode.indexOf('r') >= 0) { if (!file) { cb('file opened for reading does not exist: ' + path); } else { cb(null, file._id); } } else if (mode.indexOf('w') >= 0) { if (mode.indexOf('x') >= 0 && file) { cb('file opened for writing already exists ' + path); } else if (file) { cb(null, file._id); } else { self.createEntity(path, false, cb); } } else { cb('unsupported open mode ' + mode); } } }); }; TestFS.prototype.close = function (fd, cb) { cb(); }; TestFS.prototype.clearAll = function () { this.allFiles = new Datastore(); this.pipeDelay = 0; }; TestFS.prototype.setPipeDelay = function (delay) { this.pipeDelay = delay; }; TestFS.prototype.createReadStream = function (filePath) { var self = this; var file = _findByPathSync.call(this, filePath); var stream = new TestStream(filePath); stream.setPipeDelay(self.pipeDelay); stream.setReadStream(function (readCb) { var buff = new Array(file.size); self.read(file._id, buff, 0, file.size, 0, function (err) { if (err) { readCb(err); } else { var data = buff.join(''); readCb(null, data); } }); }); return stream; }; TestFS.prototype.createWriteStream = function (filePath) { var self = this; var stream = new events(); stream.path = filePath; stream.data = ''; stream.write = function (chunk, encoding, callback) { stream.data += chunk; if (callback) { callback(); } }; stream.end = function (chunk, encoding, callback) { function _finishStream(err) { if (err) { stream.emit('error', err); } else { stream.emit('finish'); } if (callback) { callback(err); } } stream.data += chunk; self.open(filePath, 'wx', function (err, fd) { if (err) { _finishStream(err); } else if (stream.data.length > 0) { self.truncate(filePath, stream.data.length, function (err) { if (err) { _finishStream(err); } else { self.write(fd, stream.data, 0, stream.data.length, 0, function (err) { _finishStream(err); }); } }); } else { _finishStream(); } }); }; return stream; }; TestFS.prototype.writeFileSync = function (filePath, data) { this.setTestFile(filePath, data); }; TestFS.prototype.statSync = function (filePath) { var self = this; var sync = true; var data = null; var syncErr = null; self.stat(filePath, function(err, stat) { data = stat; syncErr = err; sync = false; }); while(sync) {require('deasync').sleep(100);} if (syncErr) { throw syncErr; } return data; }; TestFS.prototype.stat = function (filePath, cb) { var self = this; _findByPath.call(self, filePath, function (err, file) { if (err) { cb(err); } else if (!file) { cb({code: 'ENOENT', message: 'file to stat not found ' + filePath}); } else { _extendDoc.call(self, file); cb(null, file); } }); }; TestFS.prototype.truncate = function (path, length, cb) { var self = this; var dir = utils.getParentPath(path); var name = utils.getPathName(path); _updateByName.call(self, path, {size: length, blksize: length}, false, function (err) { if (err) { cb(err); } else { cb(); } }); }; TestFS.prototype.write = function (fd, data, offset, length, position, cb) { var self = this; _findById.call(self, fd, function (err, file) { if (err) { cb(err); } else { length = position + length > file.size ? file.size - position : length; var currValue = file.data; var before = ''; var after = ''; if (currValue.length > position) { before = currValue.substr(0, position); } if (currValue.length > position + length) { after = currValue.substr(position + length); } var written = ''; for (var i = offset; i < offset + length; i++) { var b; if (data instanceof Buffer) { b = data.toString('utf8', i, i + 1); } else { b = data[i]; } written += b; } _updateById.call(self, fd, {data: before + written + after}, false, function (err) { if (err) { cb(err); } else { cb(null, length, written); } }); } }); }; TestFS.prototype.read = function (fd, buffer, offset, length, position, cb) { var self = this; _findById.call(self, fd, function (err, file) { if (err) { cb(err); } else { var data = file.data; var lastIndex = position + length; if (lastIndex >= data.length) { lastIndex = data.length; }; var totalToRead = lastIndex - position; var retBuff = new Buffer(totalToRead); var currRead = 0; for (var i = position; i < lastIndex; i++) { var targetIndex = offset + currRead; if (buffer instanceof Buffer) { buffer.write(data[i], targetIndex); } else { buffer[targetIndex] = data[i]; } retBuff.write(data[i], currRead); currRead++; } cb(null, totalToRead, retBuff); } }); }; TestFS.prototype.unlinkSync = function (path) { var self = this; var sync = true; var syncErr = null; self.unlink(path, function(err) { syncErr = err; sync = false; }); while(sync) {require('deasync').sleep(100);} if (syncErr) { throw syncErr; } return true; }; TestFS.prototype.unlink = function (path, cb) { var dir = utils.getParentPath(path); var name = utils.getPathName(path); this.allFiles.remove({path: dir, name: name}, {}, function (err, numRemoved) { if (err) { cb(err); } else if (numRemoved != 1) { cb('unexpected number of entries removed when unlinking ' + path + ': ' + numRemoved); } else { cb(); } }); }; TestFS.prototype.readFile = function (filePath, callback) { var err = undefined; var data = undefined; try { data = this.readFileSync(filePath); } catch (e) { err = e; } callback(err, data); }; TestFS.prototype.writeFile = function (filePath, data, callback) { try { this.writeFileSync(filePath, data); callback(); } catch (e) { callback(e); } }; TestFS.prototype.readdir = function (folderPath, callback) { folderPath = _trimSlash(folderPath); this.allFiles.find({path: folderPath}, function (err, docs) { if (err) { callback(err); } else { var names = []; for (var i = 0; i < docs.length; i++) { names.push(docs[i].name); } callback(null, names); } }); }; TestFS.prototype.rmdir = function (path ,cb) { var self = this; var dir = utils.getParentPath(path); var name = utils.getPathName(path); self.readdir(path, function (err, files) { if (err) { cb(err); } else if (files.length && !self.allowDirDelete) { cb('directory to remove is not empty: ' + path); } else { self.allFiles.remove({path: dir, name: name, isdir: true}, {}, function (err, numRemoved) { if (numRemoved != 1) { cb('directory to remove was not found: ' + path) } else { cb(); } }); } }); }; TestFS.prototype.rename = function (oldName, newName, cb) { var self = this; var newDir = utils.getParentPath(newName); var newPathName = utils.getPathName(newName); // remove target file if needed self.allFiles.remove({path: newDir, name: newPathName}, {}, function (err) { if (err) { cb(err); } else { var newDate = new Date(); _updateByName.call(self, oldName, {path: newDir, name: newPathName}, true, function (err, numUpdated) { if (err) { cb(err); } else if (numUpdated != 1) { cb('unexpected number of items renamed from ' + oldName + ' to ' + newName + ': ' + numUpdated); } else { var regex = new RegExp('^' + oldName.replace('/', '\\/'), "g"); self.allFiles.find({path: regex}, function (err, docs) { async.each(docs, function (doc, eachCb) { var doReplace = false; if (doc.path == oldName) { doReplace = newName; } else if (doc.path.length > oldName.length) { if (doc.path.substr(0, oldName.length + 1) == oldName + '/') { doReplace = newName + doc.path.substr(oldName.length); } } if (doReplace) { self.allFiles.update({_id: doc._id}, {$set: {path: doReplace}}, {}, function (err) { eachCb(err); }); } else { eachCb(); } }, function (err) { cb(err); }); }); } }); } }); }; TestFS.prototype.chmod = function (path, mode, cb) { cb(); }; TestFS.prototype.fsync = function (fd, cb) { cb(); }; module.exports = TestFS;