From 02eadcf42a4a85e3f1513aea4b93b2c96cdc15b9 Mon Sep 17 00:00:00 2001 From: Steve Leach Date: Thu, 16 Jul 2020 08:30:15 -0700 Subject: [PATCH 01/10] add writeFileStream with multiple promised writes --- lib/api/writeFileStream.js | 184 +++++++++++++++++++++++++++++++++++++ lib/smb2.js | 1 + 2 files changed, 185 insertions(+) create mode 100644 lib/api/writeFileStream.js diff --git a/lib/api/writeFileStream.js b/lib/api/writeFileStream.js new file mode 100644 index 0000000..914ad96 --- /dev/null +++ b/lib/api/writeFileStream.js @@ -0,0 +1,184 @@ +var Stream = require('stream'); +var SMB2Forge = require('../tools/smb2-forge') +, SMB2Request = SMB2Forge.request +, bigint = require('../tools/bigint') +; +var SMB2Connection = require('../tools/smb2-connection'); + +/* + * 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'; + + var connection = this; + + try { + var file = await createFile(filename, connection); + await setFileSize(file, fileLength, connection); + var writeStream = new SambaWriter(connection, file); + await new Promise((resolve, reject) => { + writeStream.on('end', () => { + resolve(); + }); + writeStream.on('error', (err) => { + console.log(JSON.stringify(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) { + var 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(() => { + var 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; + } + + var buffer = this.current; + this.current = chunk; + var currentOffset = this.offset; + var 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/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')); From 64a9305fe5582df38f9604beb396e9ac98f6c3fb Mon Sep 17 00:00:00 2001 From: Steve Leach Date: Thu, 16 Jul 2020 08:39:03 -0700 Subject: [PATCH 02/10] Added README changes --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e1ce82f..41315d5 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,18 @@ smb2Client.writeFile('path\\to\\my\\file.txt', 'Hello Node', function (err) { console.log('It\'s saved!'); }); ``` +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. From e172d2d337b8a8a2cd00f8c9ad02071a8c1be6d7 Mon Sep 17 00:00:00 2001 From: Steve Leach Date: Thu, 16 Jul 2020 09:07:09 -0700 Subject: [PATCH 03/10] remove console.log --- lib/api/writeFileStream.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/api/writeFileStream.js b/lib/api/writeFileStream.js index 914ad96..e0abb83 100644 --- a/lib/api/writeFileStream.js +++ b/lib/api/writeFileStream.js @@ -40,7 +40,6 @@ module.exports = resolve(); }); writeStream.on('error', (err) => { - console.log(JSON.stringify(err)); if(err.code !== 'STATUS_PENDING'){ reject(err); } From f6f7a015db5d8d34f74d7dbbfccc3f2407fcd98d Mon Sep 17 00:00:00 2001 From: Steve Leach Date: Thu, 16 Jul 2020 09:18:13 -0700 Subject: [PATCH 04/10] updt README and minor chgs --- README.md | 9 ++++++++- lib/api/writeFileStream.js | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 41315d5..cd3d3cc 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,14 @@ 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'. @@ -101,7 +109,6 @@ smb2Client.writeFileStream('path\\to\\my\\file.txt', ReadStream, fileSize, funct }); ``` - ### 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. diff --git a/lib/api/writeFileStream.js b/lib/api/writeFileStream.js index e0abb83..feda16d 100644 --- a/lib/api/writeFileStream.js +++ b/lib/api/writeFileStream.js @@ -1,9 +1,8 @@ -var Stream = require('stream'); -var SMB2Forge = require('../tools/smb2-forge') +const Stream = require('stream'); +const SMB2Forge = require('../tools/smb2-forge') , SMB2Request = SMB2Forge.request , bigint = require('../tools/bigint') ; -var SMB2Connection = require('../tools/smb2-connection'); /* * writeFileStream @@ -29,12 +28,12 @@ module.exports = options.encoding = options.encoding || 'utf8'; - var connection = this; + let connection = this; try { - var file = await createFile(filename, connection); + let file = await createFile(filename, connection); await setFileSize(file, fileLength, connection); - var writeStream = new SambaWriter(connection, file); + let writeStream = new SambaWriter(connection, file); await new Promise((resolve, reject) => { writeStream.on('end', () => { resolve(); @@ -82,7 +81,7 @@ class SambaWriter extends Stream.Writable { } writeBuffer(chunk, currentOffset, done) { - var pr = new Promise((resolve, reject) => { + let pr = new Promise((resolve, reject) => { SMB2Request('write', { 'FileId': this.file.FileId, 'Offset': new bigint(8, currentOffset).toBuffer(), @@ -99,7 +98,7 @@ class SambaWriter extends Stream.Writable { ); }); pr.then(() => { - var index = this.promises.findIndex(x => x === pr); + let index = this.promises.findIndex(x => x === pr); if(index >= 0) { this.promises.splice(index, 1); } @@ -116,10 +115,10 @@ class SambaWriter extends Stream.Writable { return; } - var buffer = this.current; + let buffer = this.current; this.current = chunk; - var currentOffset = this.offset; - var pr = this.writeBuffer(buffer, currentOffset, done); + let currentOffset = this.offset; + let pr = this.writeBuffer(buffer, currentOffset, done); this.promises.push(pr); this.offset += buffer.length; @@ -181,3 +180,4 @@ function setFileSize(file, fileLength, connection){ }); } + From 1e45a2f8c95a72e5ee594b87bacfe5b29a317279 Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 12:40:28 -0700 Subject: [PATCH 05/10] Adding updates to limit permission requests when making changes --- lib/api/exists.js | 2 +- lib/api/mkdir.js | 2 +- lib/api/readfile.js | 2 +- lib/api/rename.js | 2 +- lib/api/unlink.js | 2 +- lib/api/writefile.js | 2 +- lib/messages/create.js | 2 +- lib/messages/create_folder.js | 2 +- lib/messages/open.js | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) 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..ffa9327 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: 0x40000000 }, 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..625db0c 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: 0x40000000}, 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/writefile.js b/lib/api/writefile.js index 54bcbcd..1ec50b9 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: 0x40000000 }, 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 } From c772832d19fd68b1a15433ec34423a6032d0c920 Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 12:42:44 -0700 Subject: [PATCH 06/10] Updating readme with pointer to security masking --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cd3d3cc..d01c215 100644 --- a/README.md +++ b/README.md @@ -173,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) From 4068b1583aa62679888a23afceb828df7bcca10b Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 14:30:16 -0700 Subject: [PATCH 07/10] Adding update for deleting to limit requested permissions --- lib/api/unlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/unlink.js b/lib/api/unlink.js index 625db0c..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, access: 0x40000000}, 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){ From 7004e5b64723a44b75db6fed27e1a4bd7dd785a7 Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 14:44:47 -0700 Subject: [PATCH 08/10] Eleminating permissions for write operations --- lib/api/writefile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/writefile.js b/lib/api/writefile.js index 1ec50b9..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, access: 0x40000000 }, connection, function(err, f){ + SMB2Request('create', {path:filename, access: 0x00000082 }, connection, function(err, f){ if(err) cb && cb(err); // SMB2 set file size else { From f1a5b477e2911b83b477b2e6ba7d044669cb556f Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 15:09:12 -0700 Subject: [PATCH 09/10] Removing excessive permissions from rename Creating definition file for ease of use --- lib/api/rename.js | 2 +- lib/smb2.d.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 lib/smb2.d.ts diff --git a/lib/api/rename.js b/lib/api/rename.js index ffa9327..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, access: 0x40000000 }, 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/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 From 2c12efdf5630893875ba5b67c7a6f215639b3810 Mon Sep 17 00:00:00 2001 From: Nikody Keating Date: Tue, 22 Sep 2020 15:21:40 -0700 Subject: [PATCH 10/10] Updating package.json to make this published by Mutual of Enumclaw --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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"