Socket
Socket
Sign inDemoInstall

ssh2-sftp-client

Package Overview
Dependencies
Maintainers
1
Versions
74
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ssh2-sftp-client - npm Package Compare versions

Comparing version 9.0.4 to 9.1.0

6

package.json
{
"name": "ssh2-sftp-client",
"version": "9.0.4",
"version": "9.1.0",
"description": "ssh2 sftp client for node",

@@ -43,3 +43,3 @@ "main": "src/index.js",

"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-unicorn": "^43.0.2",
"eslint-plugin-unicorn": "^46.0.0",
"mocha": "^10.0.0",

@@ -55,4 +55,4 @@ "moment": "^2.29.1",

"promise-retry": "^2.0.1",
"ssh2": "^1.11.0"
"ssh2": "^1.12.0"
}
}

@@ -9,2 +9,3 @@ 'use strict';

const {
globalListener,
addTempListeners,

@@ -30,38 +31,8 @@ removeTempListeners,

this.endHandled = false;
this.remotePathSep = '/';
this.remotePlatform = 'unix';
this.debug = undefined;
this.client.on('close', () => {
if (this.endCalled || this.errorHandled || this.closeHandled) {
// we are processing an expected end event or close event handled elsewhere
this.debugMsg('Global: Ignoring handled close event');
} else {
this.debugMsg('Global: Handling unexpected close event');
this.sftp = undefined;
}
});
this.client.on('end', () => {
if (this.endCalled || this.errorHandled || this.endHandled) {
// end event expected or handled elsewhere
this.debugMsg('Global: Ignoring hanlded end event');
} else {
this.debugMsg('Global: Handling unexpected end event');
this.sftp = undefined;
}
});
this.client.on('error', (err) => {
if (this.endCalled || this.errorHandled) {
// error event expected or handled elsewhere
this.debugMsg(`Global: Ignoring handled error: ${err.message}`);
} else {
this.debugMsg(`Global; Handling unexpected error; ${err.message}`);
this.sftp = undefined;
console.log(
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}`
);
}
});
this.client.on('close', globalListener(this, 'close'));
this.client.on('end', globalListener(this, 'end'));
this.client.on('error', globalListener(this, 'error'));
}

@@ -180,3 +151,2 @@

} else {
this.debugMsg('getSftpChannel: SFTP channel established');
this.sftp = sftp;

@@ -267,37 +237,32 @@ resolve(sftp);

* @param {String} remotePath - remote path, may be relative
* @param {Boolean} addListeners - (Optional) add event listeners. Default = true
* @returns {Promise<String>} - remote absolute path or ''
*/
_realPath(rPath) {
realPath(remotePath, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.debugMsg(`_realPath -> ${rPath}`);
this.sftp.realpath(rPath, (err, absPath) => {
if (addListeners) {
listeners = addTempListeners(this, 'realPath', reject);
}
this.debugMsg(`realPath -> ${remotePath}`);
this.sftp.realpath(remotePath, (err, absPath) => {
if (err) {
if (err.code === 2) {
this.debugMsg('_realPath <- ""');
this.debugMsg('realPath <- ""');
resolve('');
} else {
reject(this.fmtError(`${err.message} ${rPath}`, 'realPath', err.code));
this.debugMsg(`${err.message} ${remotePath}`, 'realPath');
reject(this.fmtError(`${err.message} ${remotePath}`, 'realPath', err.code));
}
}
this.debugMsg(`_realPath <- ${absPath}`);
this.debugMsg(`realPath <- ${absPath}`);
resolve(absPath);
});
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'realPath');
}
});
}
async realPath(remotePath) {
let listeners;
try {
listeners = addTempListeners(this, 'realPath');
haveConnection(this, 'realPath');
return await this._realPath(remotePath);
} catch (e) {
throw e.custom
? e
: this.fmtError(`${e.message} ${remotePath}`, 'realPath', e.code);
} finally {
removeTempListeners(this, listeners, 'realPath');
}
}
/**

@@ -315,16 +280,19 @@ * @async

/**
* Retrieves attributes for path
* Retrieves attributes for path using cmd, which is either
* this.sftp.stat or this.sftp.lstat
*
* @param {Function} cmd - either this.sftp.stat or this.sftp.lstat
* @param {String} remotePath - a string containing the path to a file
* @param {Boolean} addListeners - (Optional) if true add event listeners. Default true.
* @return {Promise<Object>} stats - attributes info
*/
_stat(aPath) {
_xstat(cmd, aPath, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.debugMsg(`_stat: ${aPath}`);
this.sftp.stat(aPath, (err, stats) => {
let cb = (err, stats) => {
if (err) {
if (err.code === 2 || err.code === 4) {
reject(this.fmtError(`No such file: ${aPath}`, '_stat', errorCode.notexist));
reject(this.fmtError(`No such file: ${aPath}`, '_xstat', errorCode.notexist));
} else {
reject(this.fmtError(`${err.message} ${aPath}`, '_stat', err.code));
reject(this.fmtError(`${err.message} ${aPath}`, '_xstat', err.code));
}

@@ -347,23 +315,61 @@ } else {

};
this.debugMsg('_stat: stats <- ', result);
this.debugMsg('_xstat: result = ', result);
resolve(result);
}
});
};
if (addListeners) {
listeners = addTempListeners(this, '_xstat', reject);
}
if (cmd === 'stat') {
this.sftp.stat(aPath, cb);
} else {
this.sftp.lstat(aPath, cb);
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_xstat');
}
});
}
/*
* Use the stat command to obtain attributes associated with a remote path.
* THe difference between stat and lstat is that stat, in the case of symbolic
* links, will return the attributes associated with the target of the link. With
* lstat, attributes associated with the symbolic link rather than the target are
* returned.
*
* @param {String} remotePath - path to an object on the remote server
* @return {Promise<Object>} stats - attributes info
*
*/
async stat(remotePath) {
let listeners;
try {
listeners = addTempListeners(this, 'stat');
haveConnection(this, 'stat');
const absPath = await normalizeRemotePath(this, remotePath);
return await this._stat(absPath);
return await this._xstat('stat', remotePath);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'stat', err.code);
} finally {
removeTempListeners(this, listeners, 'stat');
}
}
/*
* Use the lstat command to obtain attributes associated with a remote path.
* THe difference between stat and lstat is that stat, in the case of symbolic
* links, will return the attributes associated with the target of the link. With
* lstat, attributes associated with the symbolic link rather than the target are
* returned.
*
* @param {String} remotePath - path to an object on the remote server
* @return {Promise<Object>} stats - attributes info
*
*/
async lstat(remotePath) {
try {
haveConnection(this, 'lstat');
return await this._xstat('lstat', remotePath);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'lstat', err.code);
}
}
/**

@@ -380,25 +386,21 @@ * @async

*/
async _exists(rPath) {
async exists(remotePath) {
this.debugMsg(`exists: remotePath = ${remotePath}`);
try {
const absPath = await normalizeRemotePath(this, rPath);
this.debugMsg(`exists: ${rPath} -> ${absPath}`);
const info = await this._stat(absPath);
if (remotePath === '.') {
return 'd';
}
const info = await this.lstat(remotePath);
this.debugMsg('exists: <- ', info);
if (info.isDirectory) {
this.debugMsg(`exists: ${rPath} = d`);
return 'd';
}
if (info.isSymbolicLink) {
this.debugMsg(`exists: ${rPath} = l`);
} else if (info.isSymbolicLink) {
return 'l';
}
if (info.isFile) {
this.debugMsg(`exists: ${rPath} = -`);
} else if (info.isFile) {
return '-';
} else {
return false;
}
this.debugMsg(`exists: ${rPath} = false`);
return false;
} catch (err) {
if (err.code === errorCode.notexist) {
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`);
return false;

@@ -410,18 +412,2 @@ }

async exists(remotePath) {
let listeners;
try {
listeners = addTempListeners(this, 'exists');
haveConnection(this, 'exists');
if (remotePath === '.') {
return 'd';
}
return await this._exists(remotePath);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'exists', err.code);
} finally {
removeTempListeners(this, listeners, 'exists');
}
}
/**

@@ -438,51 +424,49 @@ * @async

* @param {function} filter - a filter function used to select return entries
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
* @returns {Promise<Array>} array of file description objects
*/
_list(remotePath, filter) {
list(remotePath, filter, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.readdir(remotePath, (err, fileList) => {
if (err) {
reject(this.fmtError(`${err.message} ${remotePath}`, 'list', err.code));
} else {
const reg = /-/gi;
const newList = fileList.map((item) => {
return {
type: item.longname.slice(0, 1),
name: item.filename,
size: item.attrs.size,
modifyTime: item.attrs.mtime * 1000,
accessTime: item.attrs.atime * 1000,
rights: {
user: item.longname.slice(1, 4).replace(reg, ''),
group: item.longname.slice(4, 7).replace(reg, ''),
other: item.longname.slice(7, 10).replace(reg, ''),
},
owner: item.attrs.uid,
group: item.attrs.gid,
longname: item.longname,
};
});
if (filter) {
resolve(newList.filter((item) => filter(item)));
if (addListeners) {
listeners = addTempListeners(this, 'list', reject);
}
if (haveConnection(this, 'list', reject)) {
this.sftp.readdir(remotePath, (err, fileList) => {
if (err) {
reject(this.fmtError(`${err.message} ${remotePath}`, 'list', err.code));
} else {
resolve(newList);
const reg = /-/gi;
const newList = fileList.map((item) => {
return {
type: item.longname.slice(0, 1),
name: item.filename,
size: item.attrs.size,
modifyTime: item.attrs.mtime * 1000,
accessTime: item.attrs.atime * 1000,
rights: {
user: item.longname.slice(1, 4).replace(reg, ''),
group: item.longname.slice(4, 7).replace(reg, ''),
other: item.longname.slice(7, 10).replace(reg, ''),
},
owner: item.attrs.uid,
group: item.attrs.gid,
longname: item.longname,
};
});
if (filter) {
resolve(newList.filter((item) => filter(item)));
} else {
resolve(newList);
}
}
}
});
});
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'list');
}
});
}
async list(remotePath, filter) {
let listeners;
try {
listeners = addTempListeners(this, 'list');
haveConnection(this, 'list');
return await this._list(remotePath, filter);
} catch (e) {
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'list', e.code);
} finally {
removeTempListeners(this, listeners, 'list');
}
}
/**

@@ -500,2 +484,3 @@ * get file

* writeStreamOptions and pipeOptions.
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
*

@@ -508,79 +493,74 @@ * *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls

*/
_get(rPath, dst, opts) {
let rdr, wtr;
get(remotePath, dst, options, addListeners = true) {
let listeners, rdr, wtr;
return new Promise((resolve, reject) => {
opts = {
...opts,
readStreamOptions: { autoClose: true },
writeStreamOptions: { autoClose: true },
pipeOptions: { end: true },
};
rdr = this.sftp.createReadStream(rPath, opts.readStreamOptions);
rdr.once('error', (err) => {
if (dst && typeof dst !== 'string' && !dst.destroyed) {
dst.destroy();
if (addListeners) {
listeners = addTempListeners(this, 'get', reject);
}
if (haveConnection(this, 'get', reject)) {
options = {
readStreamOptions: { ...options?.readStreamOptions, autoClose: true },
writeStreamOptions: { ...options?.writeStreamOptions, autoClose: true },
pipeOptions: { ...options?.pipeOptions, end: true },
};
rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions);
rdr.once('error', (err) => {
if (dst && typeof dst !== 'string' && !dst.destroyed) {
dst.destroy();
}
reject(this.fmtError(`${err.message} ${remotePath}`, 'get', err.code));
});
if (dst === undefined) {
// no dst specified, return buffer of data
this.debugMsg('get resolving buffer of data');
wtr = concat((buff) => {
resolve(buff);
});
} else if (typeof dst === 'string') {
// dst local file path
this.debugMsg('get returning local file');
const localCheck = haveLocalCreate(dst);
if (!localCheck.status) {
reject(
this.fmtError(
`Bad path: ${dst}: ${localCheck.details}`,
'get',
localCheck.code
)
);
return;
} else {
wtr = fs.createWriteStream(dst, options.writeStreamOptions);
}
} else {
this.debugMsg('get: returning data into supplied stream');
wtr = dst;
}
reject(this.fmtError(`${err.message} ${rPath}`, '_get', err.code));
});
if (dst === undefined) {
// no dst specified, return buffer of data
this.debugMsg('get resolving buffer of data');
wtr = concat((buff) => {
resolve(buff);
});
} else if (typeof dst === 'string') {
// dst local file path
this.debugMsg('get returning local file');
const localCheck = haveLocalCreate(dst);
if (!localCheck.status) {
wtr.once('error', (err) => {
reject(
this.fmtError(
`Bad path: ${dst}: ${localCheck.details}`,
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
'get',
localCheck.code
err.code
)
);
return;
} else {
wtr = fs.createWriteStream(dst, opts.writeStreamOptions);
}
} else {
this.debugMsg('get: returning data into supplied stream');
wtr = dst;
});
rdr.once('end', () => {
if (typeof dst === 'string') {
this.debugMsg('get: resolving with dst filename');
resolve(dst);
} else if (dst !== undefined) {
this.debugMsg('get: resolving with writer stream object');
resolve(wtr);
}
});
rdr.pipe(wtr, options.pipeOptions);
}
wtr.once('error', (err) => {
reject(
this.fmtError(
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
'get',
err.code
)
);
});
rdr.once('end', () => {
if (typeof dst === 'string') {
this.debugMsg('get: resolving with dst filename');
resolve(dst);
} else if (dst !== undefined) {
this.debugMsg('get: resolving with writer stream object');
resolve(wtr);
}
});
rdr.pipe(wtr, opts.pipeOptions);
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'get');
}
});
}
async get(remotePath, dst, options) {
let listeners;
try {
listeners = addTempListeners(this, 'get');
haveConnection(this, 'get');
return await this._get(remotePath, dst, options);
} catch (e) {
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'get', e.code);
} finally {
removeTempListeners(this, listeners, 'get');
}
}
/**

@@ -596,10 +576,20 @@ * Use SSH2 fastGet for downloading the file.

*/
_fastGet(rPath, lPath, opts) {
_fastGet(rPath, lPath, opts, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.fastGet(rPath, lPath, opts, (err) => {
if (err) {
reject(this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`));
}
resolve(`${rPath} was successfully download to ${lPath}!`);
});
if (addListeners) {
listeners = addTempListeners(this, '_fastGet', reject);
}
if (haveConnection(this, '_fastGet', reject)) {
this.sftp.fastGet(rPath, lPath, opts, (err) => {
if (err) {
reject(this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`));
}
resolve(`${rPath} was successfully download to ${lPath}!`);
});
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_fastGet');
}
});

@@ -609,6 +599,3 @@ }

async fastGet(remotePath, localPath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'fastGet');
haveConnection(this, 'fastGet');
const ftype = await this.exists(remotePath);

@@ -630,4 +617,2 @@ if (ftype !== '-') {

throw this.fmtError(err, 'fastGet');
} finally {
removeTempListeners(this, listeners, 'fastGet');
}

@@ -644,21 +629,32 @@ }

*
* @param {String} localPath
* @param {String} remotePath
* @param {Object} options
* @param {String} localPath - path to local file to put
* @param {String} remotePath - destination path for put file
* @param {Object} options - additonal fastPut options
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true.
* @return {Promise<String>} the result of downloading the file
*/
_fastPut(lPath, rPath, opts) {
_fastPut(lPath, rPath, opts, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.fastPut(lPath, rPath, opts, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} Local: ${lPath} Remote: ${rPath}`,
'fastPut',
err.code
)
);
}
resolve(`${lPath} was successfully uploaded to ${rPath}!`);
});
if (addListeners) {
listeners = addTempListeners(this, '_fastPut', reject);
}
if (haveConnection(this, '_fastPut', reject)) {
this.sftp.fastPut(lPath, rPath, opts, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} Local: ${lPath} Remote: ${rPath}`,
'fastPut',
err.code
)
);
}
resolve(`${lPath} was successfully uploaded to ${rPath}!`);
});
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_fastPut');
}
});

@@ -668,7 +664,4 @@ }

async fastPut(localPath, remotePath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'fastPut');
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
haveConnection(this, 'fastPut');
const localCheck = haveLocalAccess(localPath);

@@ -691,4 +684,2 @@ if (!localCheck.status) {

throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code);
} finally {
removeTempListeners(this, listeners, 'fastPut');
}

@@ -714,37 +705,49 @@ }

*/
_put(lPath, rPath, opts) {
let wtr, rdr;
_put(lPath, rPath, opts, addListeners = true) {
let listeners, wtr, rdr;
return new Promise((resolve, reject) => {
if (addListeners) {
listeners = addTempListeners(this, '_put', reject);
}
opts = {
...opts,
readStreamOptions: { autoClose: true },
writeStreamOptions: { autoClose: true },
pipeOptions: { end: true },
readStreamOptions: { ...opts?.readStreamOptions, autoClose: true },
writeStreamOptions: { ...opts?.writeStreamOptions, autoClose: true },
pipeOptions: { ...opts?.pipeOptions, end: true },
};
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
wtr.once('error', (err) => {
reject(this.fmtError(`${err.message} ${rPath}`, 'put', err.code));
});
wtr.once('close', () => {
resolve(`Uploaded data stream to ${rPath}`);
});
if (lPath instanceof Buffer) {
this.debugMsg('put source is a buffer');
wtr.end(lPath);
} else {
rdr =
typeof lPath === 'string'
? fs.createReadStream(lPath, opts.readStreamOptions)
: lPath;
rdr.once('error', (err) => {
if (haveConnection(this, '_put', reject)) {
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
wtr.once('error', (err) => {
reject(
this.fmtError(
`${err.message} ${typeof lPath === 'string' ? lPath : '<stream>'}`,
'_put',
err.code
)
this.fmtError(`Write stream error: ${err.message} ${rPath}`, '_put', err.code)
);
});
rdr.pipe(wtr, opts.pipeOptions);
wtr.once('close', () => {
resolve(`Uploaded data stream to ${rPath}`);
});
if (lPath instanceof Buffer) {
this.debugMsg('put source is a buffer');
wtr.end(lPath);
} else {
rdr =
typeof lPath === 'string'
? fs.createReadStream(lPath, opts.readStreamOptions)
: lPath;
rdr.once('error', (err) => {
reject(
this.fmtError(
`Read stream error: ${err.message} ${
typeof lPath === 'string' ? lPath : '<stream>'
}`,
'_put',
err.code
)
);
});
rdr.pipe(wtr, opts.pipeOptions);
}
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_put');
}
});

@@ -754,6 +757,3 @@ }

async put(localSrc, remotePath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'put');
haveConnection(this, 'put');
if (typeof localSrc === 'string') {

@@ -771,5 +771,3 @@ const localCheck = haveLocalAccess(localSrc);

} catch (e) {
throw e.custom ? e : this.fmtError(e.message, 'put', e.code);
} finally {
removeTempListeners(this, listeners, 'put');
throw e.custom ? e : this.fmtError(`Re-thrown: ${e.message}`, 'put', e.code);
}

@@ -786,19 +784,29 @@ }

*/
_append(input, rPath, opts) {
_append(input, rPath, opts, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.debugMsg(`append -> remote: ${rPath} `, opts);
opts.flags = 'a';
const stream = this.sftp.createWriteStream(rPath, opts);
stream.on('error', (err) => {
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code));
});
stream.on('close', () => {
resolve(`Appended data to ${rPath}`);
});
if (input instanceof Buffer) {
stream.write(input);
stream.end();
} else {
input.pipe(stream);
if (addListeners) {
listeners = addTempListeners(this, '_append', reject);
}
if (haveConnection(this, '_append', reject)) {
this.debugMsg(`append -> remote: ${rPath} `, opts);
opts.flags = 'a';
const stream = this.sftp.createWriteStream(rPath, opts);
stream.on('error', (err) => {
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code));
});
stream.on('close', () => {
resolve(`Appended data to ${rPath}`);
});
if (input instanceof Buffer) {
stream.write(input);
stream.end();
} else {
input.pipe(stream);
}
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_append');
}
});

@@ -808,5 +816,3 @@ }

async append(input, remotePath, options = {}) {
let listeners;
try {
listeners = addTempListeners(this, 'append');
if (typeof input === 'string') {

@@ -819,3 +825,2 @@ throw this.fmtError(

}
haveConnection(this, 'append');
const fileType = await this.exists(remotePath);

@@ -832,4 +837,2 @@ if (fileType && fileType === 'd') {

throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
} finally {
removeTempListeners(this, listeners, 'append');
}

@@ -847,4 +850,8 @@ }

*/
_doMkdir(p) {
_doMkdir(p, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
if (addListeners) {
listeners = addTempListeners(this, '_doMkdir', reject);
}
this.sftp.mkdir(p, (err) => {

@@ -876,2 +883,6 @@ if (err) {

});
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, '_doMkdir');
}
});

@@ -918,5 +929,3 @@ }

async mkdir(remotePath, recursive = false) {
let listeners;
try {
listeners = addTempListeners(this, '_mkdir');
haveConnection(this, 'mkdir');

@@ -926,4 +935,2 @@ return await this._mkdir(remotePath, recursive);

throw this.fmtError(`${err.message}`, 'mkdir', err.code);
} finally {
removeTempListeners(this, listeners, 'append');
}

@@ -942,65 +949,81 @@ }

*/
async rmdir(remotePath, recursive = false) {
const _rmdir = (p) => {
async rmdir(remoteDir, recursive = false) {
const _rmdir = (dir) => {
let listeners;
return new Promise((resolve, reject) => {
this.debugMsg(`rmdir -> ${p}`);
this.sftp.rmdir(p, (err) => {
listeners = addTempListeners(this, '_rmdir', reject);
this.debugMsg(`_rmdir: dir = ${dir}`);
this.sftp.rmdir(dir, (err) => {
if (err) {
reject(this.fmtError(`${err.message} ${p}`, 'rmdir', err.code));
reject(this.fmtError(`${err.message} ${dir}`, 'rmdir', err.code));
}
resolve('Successfully removed directory');
});
}).finally(() => {
removeTempListeners(this, listeners, '_rmdir');
});
};
const _dormdir = async (p, recur) => {
try {
if (recur) {
const list = await this.list(p);
if (list.length) {
const files = list.filter((item) => item.type !== 'd');
const dirs = list.filter((item) => item.type === 'd');
this.debugMsg('rmdir contents (files): ', files);
this.debugMsg('rmdir contents (dirs): ', dirs);
for (const d of dirs) {
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true);
}
const promiseList = [];
for (const f of files) {
promiseList.push(this._delete(`${p}${this.remotePathSep}${f.name}`));
}
await Promise.all(promiseList);
}
const _delFiles = (path, fileList) => {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, '_delFiles', reject);
this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`);
let pList = [];
for (const f of fileList) {
pList.push(this.delete(`${path}/${f.name}`, true, false));
}
return await _rmdir(p);
} catch (err) {
throw err.custom ? err : this.fmtError(err, '_dormdir', err.code);
}
resolve(pList);
})
.then((p) => {
return Promise.all(p);
})
.finally(() => {
removeTempListeners(this, listeners, '_delFiles');
});
};
let listeners;
try {
listeners = addTempListeners(this, 'rmdir');
haveConnection(this, 'rmdir');
const absPath = await normalizeRemotePath(this, remotePath);
const dirStatus = await this.exists(absPath);
if (dirStatus && dirStatus !== 'd') {
this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`);
let absPath = await normalizeRemotePath(this, remoteDir);
let existStatus = await this.exists(absPath);
this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`);
if (!existStatus) {
throw this.fmtError(
`Bad path: ${absPath} not a directory`,
`Bad Path: ${remoteDir}: No such directory`,
'rmdir',
errorCode.badPath
);
} else if (!dirStatus) {
}
if (existStatus !== 'd') {
throw this.fmtError(
`Bad path: ${absPath} No such file`,
`Bad Path: ${remoteDir}: Not a directory`,
'rmdir',
errorCode.badPath
);
} else {
return await _dormdir(absPath, recursive);
}
if (!recursive) {
this.debugMsg('rmdir: non-recursive - just try to remove it');
return await _rmdir(absPath);
}
let listing = await this.list(absPath);
this.debugMsg(`rmdir: listing count = ${listing.length}`);
if (!listing.length) {
this.debugMsg('rmdir: No sub dir or files, just rmdir');
return await _rmdir(absPath);
}
let fileList = listing.filter((i) => i.type !== 'd');
this.debugMsg(`rmdir: dir content files to remove = ${fileList.length}`);
let dirList = listing.filter((i) => i.type === 'd');
this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`);
await _delFiles(absPath, fileList);
for (const d of dirList) {
await this.rmdir(`${absPath}/${d.name}`, true);
}
await _rmdir(absPath);
return 'Successfully removed directory';
} catch (err) {
throw err.custom ? err : this.fmtError(err.message, 'rmdir', err.code);
} finally {
removeTempListeners(this, listeners, 'rmdir');
throw err.custom
? err
: this.fmtError(`${err.message} ${remoteDir}`, 'rmdir', err.code);
}

@@ -1020,30 +1043,25 @@ }

*/
_delete(rPath, notFoundOK) {
delete(remotePath, notFoundOK = false, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.unlink(rPath, (err) => {
if (addListeners) {
listeners = addTempListeners(this, 'delete', reject);
}
this.sftp.unlink(remotePath, (err) => {
if (err) {
if (notFoundOK && err.code === 2) {
resolve(`Successfully deleted ${rPath}`);
resolve(`Successfully deleted ${remotePath}`);
} else {
reject(this.fmtError(`${err.message} ${rPath}`, 'delete', err.code));
reject(this.fmtError(`${err.message} ${remotePath}`, 'delete', err.code));
}
}
resolve(`Successfully deleted ${rPath}`);
resolve(`Successfully deleted ${remotePath}`);
});
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'delete');
}
});
}
async delete(remotePath, notFoundOK = false) {
let listeners;
try {
listeners = addTempListeners(this, 'delete');
haveConnection(this, 'delete');
return await this._delete(remotePath, notFoundOK);
} catch (err) {
throw err.custom ? err : this.fmtError(err.message, 'delete', err.code);
} finally {
removeTempListeners(this, listeners, 'delete');
}
}
/**

@@ -1056,2 +1074,3 @@ * @async

* @param {string} toPath - path to the new name.
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
*

@@ -1061,34 +1080,29 @@ * @return {Promise<String>}

*/
_rename(fPath, tPath) {
rename(fPath, tPath, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.rename(fPath, tPath, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} From: ${fPath} To: ${tPath}`,
'_rename',
err.code
)
);
}
resolve(`Successfully renamed ${fPath} to ${tPath}`);
});
if (addListeners) {
listeners = addTempListeners(this, 'rename', reject);
}
if (haveConnection(this, 'rename', reject)) {
this.sftp.rename(fPath, tPath, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} From: ${fPath} To: ${tPath}`,
'_rename',
err.code
)
);
}
resolve(`Successfully renamed ${fPath} to ${tPath}`);
});
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'rename');
}
});
}
async rename(fromPath, toPath) {
let listeners;
try {
listeners = addTempListeners(this, 'rename');
haveConnection(this, 'rename');
return await this._rename(fromPath, toPath);
} catch (err) {
throw err.custom
? err
: this.fmtError(`${err.message} ${fromPath} ${toPath}`, 'rename', err.code);
} finally {
removeTempListeners(this, listeners, 'rename');
}
}
/**

@@ -1102,2 +1116,3 @@ * @async

* @param {string} toPath - path the new name.
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
*

@@ -1107,34 +1122,27 @@ * @return {Promise<String>}

*/
_posixRename(fPath, tPath) {
posixRename(fPath, tPath, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.ext_openssh_rename(fPath, tPath, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} From: ${fPath} To: ${tPath}`,
'_posixRename',
err.code
)
);
}
resolve(`Successful POSIX rename ${fPath} to ${tPath}`);
});
if (addListeners) {
listeners = addTempListeners(this, 'posixRename', reject);
}
if (haveConnection(this, 'posixRename', reject)) {
this.sftp.ext_openssh_rename(fPath, tPath, (err) => {
if (err) {
reject(
this.fmtError(
`${err.message} From: ${fPath} To: ${tPath}`,
'_posixRename',
err.code
)
);
}
resolve(`Successful POSIX rename ${fPath} to ${tPath}`);
});
}
}).finally(() => {
removeTempListeners(this, listeners, 'posixRename');
});
}
async posixRename(fromPath, toPath) {
let listeners;
try {
listeners = addTempListeners(this, 'posixRename');
haveConnection(this, 'posixRename');
return await this._posixRename(fromPath, toPath);
} catch (err) {
throw err.custom
? err
: this.fmtError(`${err.message} ${fromPath} ${toPath}`, 'posixRename', err.code);
} finally {
removeTempListeners(this, listeners, 'posixRename');
}
}
/**

@@ -1147,31 +1155,27 @@ * @async

* @param {number | string} mode - the new octal mode to set
* @param {boolean} addListeners - (Optional) if true, add listeners. Default true.
*
* @return {Promise<String>}
*/
_chmod(rPath, mode) {
chmod(rPath, mode, addListeners = true) {
let listeners;
return new Promise((resolve, reject) => {
this.sftp.chmod(rPath, mode, (err) => {
if (err) {
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code));
}
resolve('Successfully change file mode');
});
if (addListeners) {
listeners = addTempListeners(this, 'chmod', reject);
}
if (haveConnection(this, 'chmod', reject)) {
this.sftp.chmod(rPath, mode, (err) => {
if (err) {
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code));
}
resolve('Successfully change file mode');
});
}
}).finally(() => {
if (addListeners) {
removeTempListeners(this, listeners, 'chmod');
}
});
}
async chmod(remotePath, mode) {
let listeners;
try {
listeners = addTempListeners(this, 'chmod');
haveConnection(this, 'chmod');
return await this._chmod(remotePath, mode);
} catch (err) {
throw err.custom
? err
: this.fmtError(`${err.message} ${remotePath}`, 'chmod', err.code);
} finally {
removeTempListeners(this, listeners, 'chmod');
}
}
/**

@@ -1186,5 +1190,6 @@ * @async

* @param {Object} options - (Optional) An object with 2 supported properties,
* 'filter' and 'useFastput'. The first argument is the full path of the item
* 'filter' and 'useFastput'. Filter is a function of two arguments.
* The first argument is the full path of a directory entry from the directory
* to be uploaded and the second argument is a boolean, which will be true if
* the target path is for a directory. If the function returns true, the item
* the target path is for a directory. If the function returns true, this item
* will be uploaded and excluded when it returns false. The 'useFastput' property is a

@@ -1194,8 +1199,19 @@ * boolean value. When true, the 'fastPut()' method will be used to upload files. Default

*
* @returns {Promise<String>}
* @returns {Promise<Array>}
*/
async _uploadDir(srcDir, dstDir, options) {
try {
const absDstDir = await normalizeRemotePath(this, dstDir);
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`);
async uploadDir(srcDir, dstDir, options) {
const getRemoteStatus = async (dstDir) => {
let absDstDir = await normalizeRemotePath(this, dstDir);
let status = await this.exists(absDstDir);
if (status && status !== 'd') {
throw this.fmtError(
`Bad path ${absDstDir} Not a directory`,
'getRemoteStatus',
errorCode.badPath
);
}
return { remoteDir: absDstDir, remoteStatus: status };
};
const checkLocalStatus = (srcDir) => {
const srcType = localExists(srcDir);

@@ -1205,3 +1221,3 @@ if (!srcType) {

`Bad path: ${srcDir} not exist`,
'_uploadDir',
'getLocalStatus',
errorCode.badPath

@@ -1213,17 +1229,49 @@ );

`Bad path: ${srcDir}: not a directory`,
'_uploadDir',
'getLocalStatus',
errorCode.badPath
);
}
const dstStatus = await this.exists(absDstDir);
if (dstStatus && dstStatus !== 'd') {
throw this.fmtError(
`Bad path ${absDstDir} Not a directory`,
'_uploadDir',
errorCode.badPath
);
return srcType;
};
const uploadFiles = (srcDir, dstDir, fileList, useFastput) => {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'uploadFiles', reject);
let uploads = [];
for (const f of fileList) {
const newSrc = join(srcDir, f.name);
const newDst = `${dstDir}/${f.name}`;
if (f.isFile()) {
if (useFastput) {
uploads.push(this._fastPut(newSrc, newDst, null, false));
} else {
uploads.push(this._put(newSrc, newDst, null, false));
}
this.client.emit('upload', { source: newSrc, destination: newDst });
} else {
this.debugMsg(`uploadFiles: File ignored: ${f.name} not a regular file`);
}
}
resolve(Promise.all(uploads));
})
.then((pList) => {
return Promise.all(pList);
})
.finally(() => {
removeTempListeners(this, listeners, uploadFiles);
});
};
try {
haveConnection(this, 'uploadDir');
this.debugMsg(
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}`
);
let { remoteDir, remoteStatus } = await getRemoteStatus(dstDir);
this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`);
checkLocalStatus(srcDir);
if (!remoteStatus) {
await this._mkdir(remoteDir, true);
}
if (!dstStatus) {
await this._mkdir(absDstDir, true);
}
let dirEntries = fs.readdirSync(srcDir, {

@@ -1233,2 +1281,3 @@ encoding: 'utf8',

});
this.debugMsg(`uploadDir: dirEntries = ${dirEntries}`);
if (options?.filter) {

@@ -1239,42 +1288,20 @@ dirEntries = dirEntries.filter((item) =>

}
let fileUploads = [];
for (const e of dirEntries) {
const newSrc = join(srcDir, e.name);
const newDst = `${absDstDir}${this.remotePathSep}${e.name}`;
if (e.isDirectory()) {
await this.uploadDir(newSrc, newDst, options);
} else if (e.isFile()) {
if (options?.useFastput) {
fileUploads.push(this._fastPut(newSrc, newDst));
} else {
fileUploads.push(this._put(newSrc, newDst));
}
this.client.emit('upload', { source: newSrc, destination: newDst });
} else {
this.debugMsg(`uploadDir: File ignored: ${e.name} not a regular file`);
}
await Promise.all(fileUploads);
let dirUploads = dirEntries.filter((item) => item.isDirectory());
let fileUploads = dirEntries.filter((item) => !item.isDirectory());
this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`);
this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`);
await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput);
for (const d of dirUploads) {
let src = join(srcDir, d.name);
let dst = `${remoteDir}/${d.name}`;
await this.uploadDir(src, dst, options);
}
return `${srcDir} uploaded to ${absDstDir}`;
return `${srcDir} uploaded to ${dstDir}`;
} catch (err) {
throw err.custom
? err
: this.fmtError(`${err.message} ${srcDir}`, '_uploadDir', err.code);
: this.fmtError(`${err.message} ${srcDir}`, 'uploadDir', err.code);
}
}
async uploadDir(srcDir, dstDir, options) {
let listeners;
try {
listeners = addTempListeners(this, 'uploadDir');
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`);
haveConnection(this, 'uploadDir');
return await this._uploadDir(srcDir, dstDir, options);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'uploadDir');
} finally {
removeTempListeners(this, listeners, 'chmod');
}
}
/**

@@ -1295,49 +1322,79 @@ * @async

*
* @returns {Promise<String>}
* @returns {Promise<Array>}
*/
async _downloadDir(srcDir, dstDir, options) {
try {
let fileList = await this._list(srcDir);
if (options?.filter) {
fileList = fileList.filter((item) =>
options.filter(
`${srcDir}${this.remotePathSep}${item.name}`,
item.type === 'd' ? true : false
)
);
async downloadDir(srcDir, dstDir, options = { filter: null, useFastget: false }) {
const _getDownloadList = async (srcDir, filter) => {
try {
let listing = await this.list(srcDir);
if (filter) {
return listing.filter((item) =>
filter(`${srcDir}/${item.name}`, item.type === 'd')
);
}
return listing;
} catch (err) {
throw err.custom ? err : this.fmtError(err.message, '_getDownloadList', err.code);
}
const localCheck = haveLocalCreate(dstDir);
if (!localCheck.status && localCheck.details === 'permission denied') {
throw this.fmtError(
`Bad path: ${dstDir}: ${localCheck.details}`,
'downloadDir',
localCheck.code
);
} else if (localCheck.status && !localCheck.type) {
fs.mkdirSync(dstDir, { recursive: true });
} else if (localCheck.status && localCheck.type !== 'd') {
throw this.fmtError(
`Bad path: ${dstDir}: not a directory`,
'downloadDir',
errorCode.badPath
);
};
const _prepareDestination = (dst) => {
try {
const localCheck = haveLocalCreate(dst);
if (!localCheck.status && localCheck.details === 'permission denied') {
throw this.fmtError(
`Bad path: ${dst}: ${localCheck.details}`,
'prepareDestination',
localCheck.code
);
} else if (localCheck.status && !localCheck.type) {
fs.mkdirSync(dst, { recursive: true });
} else if (localCheck.status && localCheck.type !== 'd') {
throw this.fmtError(
`Bad path: ${dstDir}: not a directory`,
'_prepareDestination',
errorCode.badPath
);
}
} catch (err) {
throw err.custom
? err
: this.fmtError(err.message, '_prepareDestination', err.code);
}
let downloadFiles = [];
for (const f of fileList) {
const newSrc = `${srcDir}${this.remotePathSep}${f.name}`;
const newDst = join(dstDir, f.name);
if (f.type === 'd') {
await this._downloadDir(newSrc, newDst, options);
} else if (f.type === '-') {
if (options?.useFasget) {
downloadFiles.push(this._fastGet(newSrc, newDst));
};
const _downloadFiles = (remotePath, localPath, fileList, useFastget) => {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, '_downloadFIles', reject);
let pList = [];
for (const f of fileList) {
let src = `${remotePath}/${f.name}`;
let dst = join(localPath, f.name);
if (useFastget) {
pList.push(this.fastGet(src, dst, false));
} else {
downloadFiles.push(this._get(newSrc, newDst));
pList.push(this.get(src, dst, false));
}
this.client.emit('download', { source: newSrc, destination: newDst });
} else {
this.debugMsg(`downloadDir: File ignored: ${f.name} not regular file`);
this.client.emit('download', { source: src, destination: dst });
}
return resolve(Promise.all(pList));
}).finally(() => {
removeTempListeners(this, listeners, '_downloadFiles');
});
};
try {
haveConnection(this, 'downloadDir');
let downloadList = await _getDownloadList(srcDir, options.filter);
_prepareDestination(dstDir);
let fileDownloads = downloadList.filter((i) => i.type !== 'd');
if (fileDownloads.length) {
await _downloadFiles(srcDir, dstDir, fileDownloads, options.useFastget);
}
await Promise.all(downloadFiles);
let dirDownloads = downloadList.filter((i) => i.type === 'd');
for (const d of dirDownloads) {
let src = `${srcDir}/${d.name}`;
let dst = join(dstDir, d.name);
await this.downloadDir(src, dst);
}
return `${srcDir} downloaded to ${dstDir}`;

@@ -1347,19 +1404,6 @@ } catch (err) {

? err
: this.fmtError(`${err.message} ${srcDir}`, '_downloadDir', err.code);
: this.fmtError(`${err.message}: ${srcDir}`, 'downloadDir', err.code);
}
}
async downloadDir(srcDir, dstDir, options) {
let listeners;
try {
listeners = addTempListeners(this, 'downloadDir');
haveConnection(this, 'downloadDir');
return await this._downloadDir(srcDir, dstDir, options);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'downloadDir', err.code);
} finally {
removeTempListeners(this, listeners, 'downloadDir');
}
}
/**

@@ -1500,3 +1544,3 @@ *

this.on('close', endCloseHandler);
if (this.client.sftp) {
if (this.sftp) {
this.client.end();

@@ -1503,0 +1547,0 @@ } else {

@@ -31,2 +31,14 @@ const fs = require('fs');

function globalListener(client, evt) {
return () => {
if (client.endCalled || client.errorHandled || client.closeHandled) {
// we are processing an expected event handled elsewhere
client.debugMsg(`Global ${evt} event: Ignoring expected and handled event`);
} else {
client.debugMsg(`Global ${evt} event: Handling unexpected event`);
client.sftp = undefined;
}
};
}
function endListener(client, name, reject) {

@@ -243,6 +255,6 @@ const fn = function () {

const root = await client.realPath('..');
return root + client.remotePathSep + aPath.slice(3);
return `${root}/${aPath.slice(3)}`;
} else if (aPath.startsWith('.')) {
const root = await client.realPath('.');
return root + client.remotePathSep + aPath.slice(2);
return `${root}/${aPath.slice(2)}`;
}

@@ -282,5 +294,9 @@ return aPath;

try {
setTimeout(() => {
resolve(true);
}, ms);
if (isNaN(ms) || ms < 0) {
reject('Argument must be anumber >= 0');
} else {
setTimeout(() => {
resolve(true);
}, ms);
}
} catch (err) {

@@ -293,2 +309,3 @@ reject(err);

module.exports = {
globalListener,
errorListener,

@@ -295,0 +312,0 @@ endListener,

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc