diff --git a/README.md b/README.md index e1ce82f..d01c215 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,25 @@ smb2Client.writeFile('path\\to\\my\\file.txt', 'Hello Node', function (err) { console.log('It\'s saved!'); }); ``` +### smb2Client.writeFileStream ( filename, readStream, fileSize, [options], callback ) +- ```filename``` String +- ```readStream``` read stream +- ```fileSize``` Number +- ```options``` Object + - ```encoding``` String | Null default = 'utf8' +- ```callback``` Function + +Asynchronously streams data to a file using multiple promises, replacing the file if it already exists. data must be a READ stream. + +The encoding option is ignored if data is a buffer. It defaults to 'utf8'. + +Example: +```javascript +smb2Client.writeFileStream('path\\to\\my\\file.txt', ReadStream, fileSize, function (err) { + if (err) throw err; + console.log('It\'s saved!'); +}); +``` ### smb2Client.mkdir ( path, [mode], callback ) Asynchronous mkdir(2). No arguments other than a possible exception are given to the completion callback. mode defaults to 0777. @@ -154,6 +173,10 @@ This function will close the open connection if opened, it will be called automa Copyright (C) 2014 Microsoft http://msdn.microsoft.com/en-us/library/cc246482.aspx +### Permissions References + 2.2.13.1.2 Directory_Access_Mask + https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/0a5934b1-80f1-4da0-b1bf-5e021c309b71 + ## License (The MIT License) diff --git a/lib/api/exists.js b/lib/api/exists.js index f418ee1..0ea509b 100644 --- a/lib/api/exists.js +++ b/lib/api/exists.js @@ -19,7 +19,7 @@ module.exports = function(path, cb){ var connection = this; - SMB2Request('open', {path:path}, connection, function(err, file){ + SMB2Request('open', {path:path, access: 0x80000000 }, connection, function(err, file){ if(err) cb && cb(null, false); else SMB2Request('close', file, connection, function(err){ cb && cb(null, true); diff --git a/lib/api/mkdir.js b/lib/api/mkdir.js index 2ff4efc..b0c3e21 100644 --- a/lib/api/mkdir.js +++ b/lib/api/mkdir.js @@ -31,7 +31,7 @@ module.exports = function(path, mode, cb){ else if(!exists){ // SMB2 open file - SMB2Request('create_folder', {path:path}, connection, function(err, file){ + SMB2Request('create_folder', {path:path, access: 0x40000000}, connection, function(err, file){ if(err) cb && cb(err); // SMB2 query directory else SMB2Request('close', file, connection, function(err){ diff --git a/lib/api/readfile.js b/lib/api/readfile.js index b69d256..cd39a85 100644 --- a/lib/api/readfile.js +++ b/lib/api/readfile.js @@ -26,7 +26,7 @@ module.exports = function(filename, options, cb){ options = {}; } - SMB2Request('open', {path:filename}, connection, function(err, file){ + SMB2Request('open', {path:filename, access: 0x80000000}, connection, function(err, file){ if(err) cb && cb(err); // SMB2 read file content else { diff --git a/lib/api/rename.js b/lib/api/rename.js index 6b0626b..6486edc 100644 --- a/lib/api/rename.js +++ b/lib/api/rename.js @@ -24,7 +24,7 @@ module.exports = function(oldPath, newPath, cb){ // SMB2 open the folder / file SMB2Request('open_folder', {path:oldPath}, connection, function(err, file){ - if(err) SMB2Request('open', {path:oldPath}, connection, function(err, file){ + if(err) SMB2Request('open', {path:oldPath, access: 0x00010042 }, connection, function(err, file){ if(err) cb && cb(err); else rename(connection, file, newPath, cb); }); diff --git a/lib/api/unlink.js b/lib/api/unlink.js index 5522867..4be1b5e 100644 --- a/lib/api/unlink.js +++ b/lib/api/unlink.js @@ -28,7 +28,7 @@ module.exports = function(path, cb){ else if(exists){ // SMB2 open file - SMB2Request('create', {path:path}, connection, function(err, file){ + SMB2Request('create', { path:path, access: 0x00010040 }, connection, function(err, file){ if(err) cb && cb(err); // SMB2 query directory else SMB2Request('set_info', {FileId:file.FileId, FileInfoClass:'FileDispositionInformation',Buffer:(new bigint(1,1)).toBuffer()}, connection, function(err, files){ diff --git a/lib/api/writeFileStream.js b/lib/api/writeFileStream.js new file mode 100644 index 0000000..feda16d --- /dev/null +++ b/lib/api/writeFileStream.js @@ -0,0 +1,183 @@ +const Stream = require('stream'); +const SMB2Forge = require('../tools/smb2-forge') +, SMB2Request = SMB2Forge.request +, bigint = require('../tools/bigint') +; + +/* + * writeFileStream + * ========= + * + * create and write file on the share + * + * - create the file + * + * - set info of the file + * + * - pipe contents of the file in multiple Streams + * + * - close the file + * + */ +module.exports = + async function(filename, data, fileLength, options, cb){ + let cbFunc = cb; + if(typeof options === 'function'){ + cbFunc = options; + } + + options.encoding = options.encoding || 'utf8'; + + let connection = this; + + try { + let file = await createFile(filename, connection); + await setFileSize(file, fileLength, connection); + let writeStream = new SambaWriter(connection, file); + await new Promise((resolve, reject) => { + writeStream.on('end', () => { + resolve(); + }); + writeStream.on('error', (err) => { + if(err.code !== 'STATUS_PENDING'){ + reject(err); + } + }); + data.pipe(writeStream); + }); + + await closeFile(file, connection); + cbFunc(); + } catch (err) { + cbFunc(err); + } +}; + +class SambaWriter extends Stream.Writable { + + constructor(client, file) { + super(); + this.promises = []; + this.offset = 0; + this.maxPacketSize = new bigint(8, 0x00010000 - 0x71).toNumber(); + this.parallelParts = 20; + this.current = Buffer.alloc(0); + this.file = file; + this.client = client; + this.on('finish', this._finish); + } + + _finish() { + if(this.current) { + this.promises.push(this.writeBuffer(this.current, this.offset, () => {})); + } + Promise.all(this.promises) + .then(() => { + this.emit('end'); + }) + .catch((err) => { + this.emit('error', err); + }); + } + + writeBuffer(chunk, currentOffset, done) { + let pr = new Promise((resolve, reject) => { + SMB2Request('write', { + 'FileId': this.file.FileId, + 'Offset': new bigint(8, currentOffset).toBuffer(), + 'Buffer': chunk + }, + this.client, (err) => { + if(!err) { + resolve(); + } else { + reject(err); + } + done(err); + } + ); + }); + pr.then(() => { + let index = this.promises.findIndex(x => x === pr); + if(index >= 0) { + this.promises.splice(index, 1); + } + return true; + }); + + return pr; + } + + _write(chunk, encoding, done) { + if((this.current).length + chunk.length < this.maxPacketSize) { + this.current = Buffer.concat([this.current, chunk]); + done(); + return; + } + + let buffer = this.current; + this.current = chunk; + let currentOffset = this.offset; + let pr = this.writeBuffer(buffer, currentOffset, done); + this.promises.push(pr); + this.offset += buffer.length; + + if(this.promises.length >= this.parallelParts) { + this.promises[0] + .then(() => { + this.emit('drain'); + }) + .catch((err) => { + this.emit('error', err); + }); + return false; + } + } +} + +function createFile(filename, connection){ + return new Promise((resolve, reject) => { + SMB2Request('create', {path:filename}, connection, function(err, f){ + if(err) { + reject(err); + } + else { + resolve(f); + } + }); + }); +} + +function closeFile(file, connection){ + return new Promise((resolve, reject) => { + SMB2Request('close', file, connection, function(err){ + if(err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +function setFileSize(file, fileLength, connection){ + return new Promise((resolve, reject) => { + SMB2Request('set_info', + { + FileId:file.FileId, + FileInfoClass:'FileEndOfFileInformation', + Buffer:new bigint(8, fileLength).toBuffer() + }, + connection, + function(err){ + if(err){ + reject(err); + } + else { + resolve(); + } + }); + }); +} + + diff --git a/lib/api/writefile.js b/lib/api/writefile.js index 54bcbcd..637ffe3 100644 --- a/lib/api/writefile.js +++ b/lib/api/writefile.js @@ -36,7 +36,7 @@ module.exports = function(filename, data, options, cb){ ; function createFile(fileCreated){ - SMB2Request('create', {path:filename}, connection, function(err, f){ + SMB2Request('create', {path:filename, access: 0x00000082 }, connection, function(err, f){ if(err) cb && cb(err); // SMB2 set file size else { diff --git a/lib/messages/create.js b/lib/messages/create.js index efffb0e..e49ee26 100644 --- a/lib/messages/create.js +++ b/lib/messages/create.js @@ -20,7 +20,7 @@ module.exports = message({ } , request:{ 'Buffer':buffer - , 'DesiredAccess':0x001701DF + , 'DesiredAccess': params.access == undefined? 0x001701DF : params.access , 'FileAttributes':0x00000080 , 'ShareAccess':0x00000000 , 'CreateDisposition':0x00000005 diff --git a/lib/messages/create_folder.js b/lib/messages/create_folder.js index a58f2a1..a513c2b 100644 --- a/lib/messages/create_folder.js +++ b/lib/messages/create_folder.js @@ -20,7 +20,7 @@ module.exports = message({ } , request:{ 'Buffer':buffer - , 'DesiredAccess':0x001701DF + , 'DesiredAccess': params.access == undefined? 0x001701DF : params.access , 'FileAttributes':0x00000000 , 'ShareAccess':0x00000000 , 'CreateDisposition':0x00000002 diff --git a/lib/messages/open.js b/lib/messages/open.js index 41456c1..b822197 100644 --- a/lib/messages/open.js +++ b/lib/messages/open.js @@ -20,7 +20,7 @@ module.exports = message({ } , request:{ 'Buffer':buffer - , 'DesiredAccess':0x001701DF + , 'DesiredAccess': params.access == undefined? 0x001701DF : params.access , 'NameOffset':0x0078 , 'CreateContextsOffset':0x007A+buffer.length } diff --git a/lib/smb2.d.ts b/lib/smb2.d.ts new file mode 100644 index 0000000..97c895b --- /dev/null +++ b/lib/smb2.d.ts @@ -0,0 +1,22 @@ + +class SMB2 { + exists(path: string, callback: (error: string, exists: boolean) => void): void; + rename(oldPath: string, newPath: string, callback: (error: string) => void): void; + + readFile(filename: string, callback: (error: string) => void): void; + readFile(filename: string, options: { encoding: string }, callback: (error: string, data: string) => void): void; + + writeFile(filename: string, data: string | Buffer, callback: (error: string) => void): void; + writeFile(filename: string, data: string | Buffer, options: { encoding: string }, callback: (error: string) => void): void; + + unlink(path: string, callback: (error: string) => void); + readdir(path: string, callback: (error: string, files: string[]) => void); + rmdir(path: string, callback: (error: string) => void); + + mkdir(path: string, callback: (error: string) => void); + mkdir(path: string, mode: string, callback: (error: string) => void); + + writeFileStream(filename: string, data: any, fileLength: number, options: {encoding: string}, callback: (error: string) => void): Primise; +} + +export default SMB2; \ No newline at end of file diff --git a/lib/smb2.js b/lib/smb2.js index c2d700b..9cf5741 100644 --- a/lib/smb2.js +++ b/lib/smb2.js @@ -99,6 +99,7 @@ proto.readdir = SMB2Connection.requireConnect(require('./api/readdir')); proto.rmdir = SMB2Connection.requireConnect(require('./api/rmdir')); proto.mkdir = SMB2Connection.requireConnect(require('./api/mkdir')); +proto.writeFileStream = SMB2Connection.requireConnect(require('./api/writeFileStream')); diff --git a/package.json b/package.json index 72d33c8..fea39f6 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { - "name": "smb2", + "name": "@moe-tech/smb2", "description": "SMB2 Client", - "homepage": "https://github.com/bchelli/node-smb2", + "homepage": "https://github.com/mutual-of-enumclaw/node-smb2", "version": "0.2.11", - "engines": [ - "node" - ], + "engines": { + "node": ">= 10.0.0" + }, "author": { - "name": "Benjamin Chelli", - "email": "benjamin@chelli.net", - "url": "https://github.com/bchelli" + "name": "Mutual of Enumclaw", + "email": "nkeating@mutualofenumclaw.com", + "url": "https://github.com/nkeating-mutualofenumclaw" }, "main": "lib/smb2.js", "repository": { "type": "git", - "url": "https://github.com/bchelli/node-smb2" + "url": "https://github.com/mutual-of-enumclaw/node-smb2" }, "dependencies": { "ntlm": "^0.1.3"