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 8.1.0 to 9.0.0

.dir-local.el

8

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

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

"dotenv": "^16.0.0",
"eslint": "^8.12.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",

@@ -45,3 +45,3 @@ "eslint-plugin-mocha": "^10.0.3",

"eslint-plugin-unicorn": "^42.0.0",
"mocha": "^9.2.2",
"mocha": "^10.0.0",
"moment": "^2.29.1",

@@ -56,4 +56,4 @@ "nyc": "^15.1.0",

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

@@ -8,2 +8,3 @@ const errorCode = {

notdir: 'ENOTDIR',
badAuth: 'ERR_BAD_AUTH',
};

@@ -10,0 +11,0 @@

@@ -1,5 +0,1 @@

/**
* ssh2 sftp client for node
*/
'use strict';

@@ -13,3 +9,2 @@

const {
fmtError,
addTempListeners,

@@ -22,3 +17,2 @@ removeTempListeners,

haveLocalCreate,
sleep,
} = require('./utils');

@@ -29,2 +23,3 @@ const { errorCode } = require('./constants');

constructor(clientName) {
this.version = '9.0.0';
this.client = new Client();

@@ -87,2 +82,47 @@ this.sftp = undefined;

fmtError(err, name = 'sftp', eCode, retryCount) {
let msg = '';
let code = '';
const retry = retryCount
? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}`
: '';
if (err === undefined) {
msg = `${name}: Undefined error - probably a bug!`;
code = errorCode.generic;
} else if (typeof err === 'string') {
msg = `${name}: ${err}${retry}`;
code = eCode ? eCode : errorCode.generic;
} else if (err.custom) {
msg = `${name}->${err.message}${retry}`;
code = err.code;
} else {
switch (err.code) {
case 'ENOTFOUND':
msg =
`${name}: ${err.level} error. ` +
`Address lookup failed for host ${err.hostname}${retry}`;
break;
case 'ECONNREFUSED':
msg =
`${name}: ${err.level} error. Remote host at ` +
`${err.address} refused connection${retry}`;
break;
case 'ECONNRESET':
msg =
`${name}: Remote host has reset the connection: ` +
`${err.message}${retry}`;
break;
default:
msg = `${name}: ${err.message}${retry}`;
}
code = err.code ? err.code : errorCode.generic;
}
let newError = new Error(msg);
newError.code = code;
newError.custom = true;
this.debugMsg(`${newError.message} (${newError.code})`);
return newError;
}
/**

@@ -98,3 +138,2 @@ * Add a listner to the client object. This is rarely necessary and can be

on(eventType, callback) {
this.debugMsg(`Adding listener to ${eventType} event`);
this.client.prependListener(eventType, callback);

@@ -104,3 +143,2 @@ }

removeListener(eventType, callback) {
this.debugMsg(`Removing listener from ${eventType} event`);
this.client.removeListener(eventType, callback);

@@ -129,3 +167,2 @@ }

listeners = addTempListeners(this, 'getConnection', reject);
this.debugMsg('getConnection: created promise');
doReady = () => {

@@ -139,5 +176,3 @@ this.debugMsg(

this.client.connect(config);
}).finally(async () => {
this.debugMsg('getConnection: finally clause fired');
await sleep(500);
}).finally(() => {
this.removeListener('ready', doReady);

@@ -150,11 +185,7 @@ removeTempListeners(this, listeners, 'getConnection');

getSftpChannel() {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'getSftpChannel', reject);
this.debugMsg('getSftpChannel: created promise');
this.client.sftp((err, sftp) => {
if (err) {
this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`);
this.client.end();
reject(fmtError(err, 'getSftpChannel', err.code));
reject(this.fmtError(err, 'getSftpChannel', err.code));
} else {

@@ -166,6 +197,2 @@ this.debugMsg('getSftpChannel: SFTP channel established');

});
}).finally(() => {
this.debugMsg('getSftpChannel: finally clause fired');
removeTempListeners(this, listeners, 'getSftpChannel');
this._resetEventFlags();
});

@@ -187,10 +214,16 @@ }

async connect(config) {
let listeners;
try {
listeners = addTempListeners(this, 'connect');
if (config.debug) {
this.debug = config.debug;
this.debugMsg('connect: Debugging turned on');
this.debugMsg(
`ssh2-sftp-client Version: ${this.version} `,
process.versions
);
}
if (this.sftp) {
this.debugMsg('connect: Already connected - reject');
throw fmtError(
throw this.fmtError(
'An existing SFTP connection is already defined',

@@ -201,30 +234,45 @@ 'connect',

}
await promiseRetry(
(retry, attempt) => {
let retryOpts = {
retries: config.retries || 1,
factor: config.factor || 2,
minTimeout: config.retry_minTimeout || 25000,
};
await promiseRetry(retryOpts, async (retry, attempt) => {
try {
this.debugMsg(`connect: Connect attempt ${attempt}`);
return this.getConnection(config).catch((err) => {
this.debugMsg(
`getConnection retry catch: ${err.message} Code: ${err.code}`
);
switch (err.code) {
case 'ENOTFOUND':
case 'ECONNREFUSED':
case 'ERR_SOCKET_BAD_PORT':
throw err;
default:
retry(err);
await this.getConnection(config);
} catch (err) {
switch (err.code) {
case 'ENOTFOUND':
case 'ECONNREFUSED':
case 'ERR_SOCKET_BAD_PORT':
throw err;
case undefined: {
if (
err.message.endsWith(
'All configured authentication methods failed'
)
) {
throw this.fmtError(
err.message,
'getConnection',
errorCode.badAuth
);
}
retry(err);
break;
}
});
},
{
retries: config.retries || 1,
factor: config.retry_factor || 2,
minTimeout: config.retry_minTimeout || 1000,
default:
retry(err);
}
}
);
return this.getSftpChannel();
});
let sftp = await this.getSftpChannel();
return sftp;
} catch (err) {
this.debugMsg(`connect: Error ${err.message}`);
this.end();
throw err.custom ? err : this.fmtError(err, 'connect');
} finally {
removeTempListeners(this, listeners, 'connect');
this._resetEventFlags();
throw fmtError(err, 'connect');
}

@@ -244,27 +292,36 @@ }

*/
realPath(remotePath) {
let listeners;
_realPath(rPath) {
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'realPath', reject);
this.debugMsg(`realPath -> ${remotePath}`);
if (haveConnection(this, 'realPath', reject)) {
this.sftp.realpath(remotePath, (err, absPath) => {
if (err) {
this.debugMsg(`realPath Error: ${err.message} Code: ${err.code}`);
if (err.code === 2) {
resolve('');
} else {
reject(
fmtError(`${err.message} ${remotePath}`, 'realPath', err.code)
);
}
this.debugMsg(`_realPath -> ${rPath}`);
this.sftp.realpath(rPath, (err, absPath) => {
if (err) {
if (err.code === 2) {
this.debugMsg('_realPath <- ""');
resolve('');
} else {
reject(
this.fmtError(`${err.message} ${rPath}`, 'realPath', err.code)
);
}
this.debugMsg(`realPath <- ${absPath}`);
resolve(absPath);
});
}
}).finally(() => {
}
this.debugMsg(`_realPath <- ${absPath}`);
resolve(absPath);
});
});
}
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');
this._resetEventFlags();
});
}
}

@@ -289,56 +346,53 @@

*/
async stat(remotePath) {
const _stat = (aPath) => {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, '_stat', reject);
this.debugMsg(`_stat: ${aPath}`);
this.sftp.stat(aPath, (err, stats) => {
if (err) {
this.debugMsg(`_stat: Error ${err.message} code: ${err.code}`);
if (err.code === 2 || err.code === 4) {
reject(
fmtError(
`No such file: ${remotePath}`,
'_stat',
errorCode.notexist
)
);
} else {
reject(
fmtError(`${err.message} ${remotePath}`, '_stat', err.code)
);
}
_stat(aPath) {
return new Promise((resolve, reject) => {
this.debugMsg(`_stat: ${aPath}`);
this.sftp.stat(aPath, (err, stats) => {
if (err) {
if (err.code === 2 || err.code === 4) {
reject(
this.fmtError(
`No such file: ${aPath}`,
'_stat',
errorCode.notexist
)
);
} else {
let result = {
mode: stats.mode,
uid: stats.uid,
gid: stats.gid,
size: stats.size,
accessTime: stats.atime * 1000,
modifyTime: stats.mtime * 1000,
isDirectory: stats.isDirectory(),
isFile: stats.isFile(),
isBlockDevice: stats.isBlockDevice(),
isCharacterDevice: stats.isCharacterDevice(),
isSymbolicLink: stats.isSymbolicLink(),
isFIFO: stats.isFIFO(),
isSocket: stats.isSocket(),
};
this.debugMsg('_stat: stats <- ', result);
resolve(result);
reject(this.fmtError(`${err.message} ${aPath}`, '_stat', err.code));
}
});
}).finally(() => {
removeTempListeners(this, listeners, '_stat');
} else {
const result = {
mode: stats.mode,
uid: stats.uid,
gid: stats.gid,
size: stats.size,
accessTime: stats.atime * 1000,
modifyTime: stats.mtime * 1000,
isDirectory: stats.isDirectory(),
isFile: stats.isFile(),
isBlockDevice: stats.isBlockDevice(),
isCharacterDevice: stats.isCharacterDevice(),
isSymbolicLink: stats.isSymbolicLink(),
isFIFO: stats.isFIFO(),
isSocket: stats.isSocket(),
};
this.debugMsg('_stat: stats <- ', result);
resolve(result);
}
});
};
});
}
async stat(remotePath) {
let listeners;
try {
listeners = addTempListeners(this, 'stat');
haveConnection(this, 'stat');
let absPath = await normalizeRemotePath(this, remotePath);
return _stat(absPath);
const absPath = await normalizeRemotePath(this, remotePath);
return await this._stat(absPath);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'stat', err.code);
} finally {
removeTempListeners(this, listeners, 'stat');
this._resetEventFlags();
throw err.custom ? err : fmtError(err, 'stat', err.code);
}

@@ -358,44 +412,45 @@ }

*/
async exists(remotePath) {
async _exists(rPath) {
try {
if (haveConnection(this, 'exists')) {
if (remotePath === '.') {
this.debugMsg('exists: . = d');
return 'd';
}
let absPath = await normalizeRemotePath(this, remotePath);
try {
this.debugMsg(`exists: ${remotePath} -> ${absPath}`);
let info = await this.stat(absPath);
this.debugMsg('exists: <- ', info);
if (info.isDirectory) {
this.debugMsg(`exists: ${remotePath} = d`);
return 'd';
}
if (info.isSymbolicLink) {
this.debugMsg(`exists: ${remotePath} = l`);
return 'l';
}
if (info.isFile) {
this.debugMsg(`exists: ${remotePath} = -`);
return '-';
}
this.debugMsg(`exists: ${remotePath} = false`);
return false;
} catch (err) {
if (err.code === errorCode.notexist) {
this.debugMsg(
`exists: ${remotePath} = false errorCode = ${err.code}`
);
return false;
}
this.debugMsg(`exists: throw error ${err.message} ${err.code}`);
throw err;
}
const absPath = await normalizeRemotePath(this, rPath);
this.debugMsg(`exists: ${rPath} -> ${absPath}`);
const info = await this._stat(absPath);
this.debugMsg('exists: <- ', info);
if (info.isDirectory) {
this.debugMsg(`exists: ${rPath} = d`);
return 'd';
}
this.debugMsg(`exists: default ${remotePath} = false`);
if (info.isSymbolicLink) {
this.debugMsg(`exists: ${rPath} = l`);
return 'l';
}
if (info.isFile) {
this.debugMsg(`exists: ${rPath} = -`);
return '-';
}
this.debugMsg(`exists: ${rPath} = false`);
return false;
} catch (err) {
if (err.code === errorCode.notexist) {
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`);
return false;
}
throw err.custom ? err : this.fmtError(err.message, 'exists', err.code);
}
}
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');
this._resetEventFlags();
throw err.custom ? err : fmtError(err, 'exists', err.code);
}

@@ -414,56 +469,55 @@ }

* @param {String} remotePath - path to remote directory
* @param {RegExp} pattern - regular expression to match filenames
* @param {function} filter - a filter function used to select return entries
* @returns {Promise<Array>} array of file description objects
*/
list(remotePath, pattern = /.*/) {
let listeners;
_list(remotePath, filter) {
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'list', reject);
if (haveConnection(this, 'list', reject)) {
const reg = /-/gi;
this.debugMsg(`list: ${remotePath} filter: ${pattern}`);
this.sftp.readdir(remotePath, (err, fileList) => {
if (err) {
this.debugMsg(`list: Error ${err.message} code: ${err.code}`);
reject(fmtError(`${err.message} ${remotePath}`, 'list', err.code));
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)));
} else {
let newList = [];
// reset file info
if (fileList) {
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,
};
});
}
// provide some compatibility for auxList
let regex;
if (pattern instanceof RegExp) {
regex = pattern;
} else {
let newPattern = pattern.replace(/\*([^*])*?/gi, '.*');
regex = new RegExp(newPattern);
}
let filteredList = newList.filter((item) => regex.test(item.name));
this.debugMsg('list: result: ', filteredList);
resolve(filteredList);
resolve(newList);
}
});
}
}).finally(() => {
}
});
});
}
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');
this._resetEventFlags();
});
}
}

@@ -484,106 +538,81 @@

*
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been
* added to support low-level access to stream objects.
*
* @return {Promise<String|Stream|Buffer>}
*/
get(
remotePath,
dst,
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
) {
let rdr, wtr, listeners;
_get(rPath, dst, opts) {
let rdr, wtr;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'get', reject);
if (haveConnection(this, 'get', reject)) {
this.debugMsg(`get -> ${remotePath} `, options);
rdr = this.sftp.createReadStream(
remotePath,
options.readStreamOptions ? options.readStreamOptions : {}
);
rdr.once('error', (err) => {
reject(fmtError(`${err.message} ${remotePath}`, 'get', err.code));
opts = {
...opts,
readStreamOptions: { autoClose: true },
writeStreamOptions: { autoClose: true },
pipeOptions: { end: true },
};
rdr = this.sftp.createReadStream(rPath, opts.readStreamOptions);
rdr.once('error', (err) => {
reject(this.fmtError(`${err.message} ${rPath}`, '_get', err.code));
});
if (dst === undefined) {
// no dst specified, return buffer of data
this.debugMsg('get returning buffer of data');
wtr = concat((buff) => {
resolve(buff);
});
if (dst === undefined) {
// no dst specified, return buffer of data
this.debugMsg('get returning buffer of data');
wtr = concat((buff) => {
//rdr.removeAllListeners('error');
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 {
if (typeof dst === 'string') {
// dst local file path
this.debugMsg('get returning local file');
const localCheck = haveLocalCreate(dst);
if (!localCheck.status) {
return reject(
fmtError(
`Bad path: ${dst}: ${localCheck.details}`,
'get',
localCheck.code
)
);
}
wtr = fs.createWriteStream(
dst,
options.writeStreamOptions ? options.writeStreamOptions : {}
);
} else {
this.debugMsg('get returning data into supplied stream');
wtr = dst;
}
wtr.once('error', (err) => {
reject(
fmtError(
`${err.message} ${typeof dst === 'string' ? dst : ''}`,
'get',
err.code
)
);
});
if (
Object.hasOwnProperty.call(options, 'pipeOptions') &&
Object.hasOwnProperty.call(options.pipeOptions, 'end') &&
!options.pipeOptions.end
) {
rdr.once('end', () => {
this.debugMsg('get resolved on reader end event');
if (typeof dst === 'string') {
resolve(dst);
} else {
resolve(wtr);
}
});
} else {
wtr.once('finish', () => {
this.debugMsg('get resolved on writer finish event');
if (typeof dst === 'string') {
resolve(dst);
} else {
resolve(wtr);
}
});
}
wtr = fs.createWriteStream(dst, opts.writeStreamOptions);
}
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
} else {
this.debugMsg('get: returning data into supplied stream');
wtr = dst;
}
}).finally(() => {
wtr.once('error', (err) => {
reject(
this.fmtError(
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
'get',
err.code
)
);
});
rdr.once('end', () => {
if (typeof dst === 'string') {
resolve(dst);
} else {
resolve(wtr);
}
});
rdr.pipe(wtr, opts.pipeOptions);
});
}
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');
this._resetEventFlags();
if (
rdr &&
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
options.readStreamOptions.autoClose === false
) {
rdr.destroy();
}
if (
wtr &&
Object.hasOwnProperty.call(options, 'writeStreamOptions') &&
Object.hasOwnProperty.call(options.writeStreamOptions, 'autoClose') &&
options.writeStreamOptions.autoClose === false &&
typeof dst === 'string'
) {
wtr.destroy();
}
});
}
}

@@ -601,43 +630,41 @@

*/
_fastGet(rPath, lPath, opts) {
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}!`);
});
});
}
async fastGet(remotePath, localPath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'fastGet');
haveConnection(this, 'fastGet');
const ftype = await this.exists(remotePath);
if (ftype !== '-') {
const msg =
ftype === false
? `No such file ${remotePath}`
: `Not a regular file ${remotePath}`;
let err = new Error(msg);
err.code = errorCode.badPath;
throw err;
const msg = `${
!ftype ? 'No such file ' : 'Not a regular file'
} ${remotePath}`;
throw this.fmtError(msg, 'fastGet', errorCode.badPath);
}
const localCheck = haveLocalCreate(localPath);
if (!localCheck.status) {
let err = new Error(`Bad path: ${localPath}: ${localCheck.details}`);
err.code = errorCode.badPath;
throw err;
throw this.fmtError(
`Bad path: ${localPath}: ${localCheck.details}`,
'fastGet',
errorCode.badPath
);
}
let listeners;
let rslt = await new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'fastGet', reject);
if (haveConnection(this, 'fastGet', reject)) {
this.debugMsg(
`fastGet -> remote: ${remotePath} local: ${localPath} `,
options
);
this.sftp.fastGet(remotePath, localPath, options, (err) => {
if (err) {
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
reject(err);
}
resolve(`${remotePath} was successfully download to ${localPath}!`);
});
}
}).finally(() => {
removeTempListeners(this, listeners, 'fastGet');
});
return rslt;
return await this._fastGet(remotePath, localPath, options);
} catch (err) {
throw this.fmtError(err, 'fastGet');
} finally {
removeTempListeners(this, listeners, 'fastGet');
this._resetEventFlags();
throw fmtError(err, 'fastGet');
}

@@ -659,49 +686,46 @@ }

*/
fastPut(localPath, remotePath, options) {
_fastPut(lPath, rPath, opts) {
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}!`);
});
});
}
async fastPut(localPath, remotePath, options) {
let listeners;
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'fastPut', reject);
try {
listeners = addTempListeners(this, 'fastPut');
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
haveConnection(this, 'fastPut');
const localCheck = haveLocalAccess(localPath);
if (!localCheck.status) {
reject(
fmtError(
`Bad path: ${localPath}: ${localCheck.details}`,
'fastPut',
localCheck.code
)
throw this.fmtError(
`Bad path: ${localPath}: ${localCheck.details}`,
'fastPut',
localCheck.code
);
} else if (localCheck.status && localExists(localPath) === 'd') {
reject(
fmtError(
`Bad path: ${localPath} not a regular file`,
'fastPut',
errorCode.badPath
)
throw this.fmtError(
`Bad path: ${localPath} not a regular file`,
'fastgPut',
errorCode.badPath
);
} else if (haveConnection(this, 'fastPut', reject)) {
this.debugMsg(
`fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
options
)}`
);
this.sftp.fastPut(localPath, remotePath, options, (err) => {
if (err) {
this.debugMsg(`fastPut error ${err.message} ${err.code}`);
reject(
fmtError(
`${err.message} Local: ${localPath} Remote: ${remotePath}`,
'fastPut',
err.code
)
);
}
this.debugMsg('fastPut file transferred');
resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
});
}
}).finally(() => {
return await this._fastPut(localPath, remotePath, options);
} catch (e) {
throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code);
} finally {
removeTempListeners(this, listeners, 'fastPut');
this._resetEventFlags();
});
}
}

@@ -719,86 +743,71 @@

* writeStreamOptions and pipeOptions.
*
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been
* added to support low-level access to stream objects.
*
* @return {Promise<String>}
*/
put(
localSrc,
remotePath,
options = {
readStreamOptions: {},
writeStreamOptions: { autoClose: true },
pipeOptions: {},
}
) {
let wtr, rdr, listeners;
_put(lPath, rPath, opts) {
let wtr, rdr;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'put', reject);
opts = {
...opts,
readStreamOptions: { autoClose: true },
writeStreamOptions: { autoClose: true },
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) => {
reject(
this.fmtError(
`${err.message} ${
typeof lPath === 'string' ? lPath : '<stream>'
}`,
'_put',
err.code
)
);
});
rdr.pipe(wtr, opts.pipeOptions);
}
});
}
async put(localSrc, remotePath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'put');
haveConnection(this, 'put');
if (typeof localSrc === 'string') {
const localCheck = haveLocalAccess(localSrc);
if (!localCheck.status) {
this.debugMsg(`put: local source check error ${localCheck.details}`);
return reject(
fmtError(
`Bad path: ${localSrc}: ${localCheck.details}`,
'put',
localCheck.code
)
throw this.fmtError(
`Bad path: ${localSrc} ${localCheck.details}`,
'put',
localCheck.code
);
}
}
if (haveConnection(this, 'put')) {
wtr = this.sftp.createWriteStream(
remotePath,
options.writeStreamOptions
? { ...options.writeStreamOptions, autoClose: true }
: {}
);
wtr.once('error', (err) => {
this.debugMsg(`put: write stream error ${err.message}`);
reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
});
wtr.once('close', () => {
this.debugMsg('put: promise resolved');
resolve(`Uploaded data stream to ${remotePath}`);
});
if (localSrc instanceof Buffer) {
this.debugMsg('put source is a buffer');
wtr.end(localSrc);
} else {
if (typeof localSrc === 'string') {
this.debugMsg(`put source is a file path: ${localSrc}`);
rdr = fs.createReadStream(
localSrc,
options.readStreamOptions ? options.readStreamOptions : {}
);
} else {
this.debugMsg('put source is a stream');
rdr = localSrc;
}
rdr.once('error', (err) => {
this.debugMsg(`put: read stream error ${err.message}`);
reject(
fmtError(
`${err.message} ${
typeof localSrc === 'string' ? localSrc : ''
}`,
'put',
err.code
)
);
});
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
}
}
}).finally(() => {
return await this._put(localSrc, remotePath, options);
} catch (e) {
throw e.custom ? e : this.fmtError(e.message, 'put', e.code);
} finally {
removeTempListeners(this, listeners, 'put');
this._resetEventFlags();
if (
rdr &&
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
options.readStreamOptions.autoClose === false &&
typeof localSrc === 'string'
) {
rdr.destroy();
}
});
}
}

@@ -814,30 +823,23 @@

*/
_append(input, rPath, opts) {
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);
}
});
}
async append(input, remotePath, options = {}) {
const _append = (input, remotePath, options) => {
return new Promise((resolve, reject) => {
this.debugMsg(`append -> remote: ${remotePath} `, options);
options.flags = 'a';
let stream = this.sftp.createWriteStream(remotePath, options);
stream.on('error', (err) => {
this.debugMsg(
`append: Error ${err.message} appending to ${remotePath}`
);
reject(fmtError(`${err.message} ${remotePath}`, 'append', err.code));
});
stream.on('close', () => {
this.debugMsg(`append: data appended to ${remotePath}`);
resolve(`Appended data to ${remotePath}`);
});
if (input instanceof Buffer) {
this.debugMsg('append: writing data buffer to remote file');
stream.write(input);
stream.end();
} else {
this.debugMsg('append: writing stream to remote file');
input.pipe(stream);
}
});
};
let listeners;

@@ -847,4 +849,3 @@ try {

if (typeof input === 'string') {
this.debugMsg('append: attempt to append two files - throw');
throw fmtError(
throw this.fmtError(
'Cannot append one file to another',

@@ -855,16 +856,14 @@ 'append',

}
if (haveConnection(this, 'append')) {
const fileType = await this.exists(remotePath);
if (fileType && fileType === 'd') {
this.debugMsg(`append: Error ${remotePath} not a file`);
throw fmtError(
`Bad path: ${remotePath}: cannot append to a directory`,
'append',
errorCode.badPath
);
}
await _append(input, remotePath, options);
haveConnection(this, 'append');
const fileType = await this.exists(remotePath);
if (fileType && fileType === 'd') {
throw this.fmtError(
`Bad path: ${remotePath}: cannot append to a directory`,
'append',
errorCode.badPath
);
}
await this._append(input, remotePath, options);
} catch (e) {
throw e.custom ? e : fmtError(e.message, 'append', e.code);
throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
} finally {

@@ -885,44 +884,43 @@ removeTempListeners(this, listeners, 'append');

*/
async mkdir(remotePath, recursive = false) {
const _mkdir = (p) => {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, '_mkdir', reject);
this.debugMsg(`_mkdir: create ${p}`);
this.sftp.mkdir(p, (err) => {
if (err) {
this.debugMsg(`_mkdir: Error ${err.message} code: ${err.code}`);
if (err.code === 4) {
//fix for windows dodgy error messages
let error = new Error(`Bad path: ${p} permission denied`);
error.code = errorCode.badPath;
reject(error);
} else if (err.code === 2) {
let error = new Error(
`Bad path: ${p} parent not a directory or not exist`
);
error.code = errorCode.badPath;
reject(error);
} else {
reject(err);
}
_doMkdir(p) {
return new Promise((resolve, reject) => {
this.sftp.mkdir(p, (err) => {
if (err) {
if (err.code === 4) {
//fix for windows dodgy error messages
reject(
this.fmtError(
`Bad path: ${p} permission denied`,
'_doMkdir',
errorCode.badPath
)
);
} else if (err.code === 2) {
reject(
this.fmtError(
`Bad path: ${p} parent not a directory or not exist`,
'_doMkdir',
errorCode.badPath
)
);
} else {
this.debugMsg('_mkdir: directory created');
resolve(`${p} directory created`);
reject(this.fmtError(`${err.message} ${p}`, '_doMkdir', err.code));
}
});
}).finally(() => {
removeTempListeners(this, listeners, '_mkdir');
this._resetEventFlags();
} else {
resolve(`${p} directory created`);
}
});
};
});
}
async _mkdir(remotePath, recursive) {
try {
haveConnection(this, 'mkdir');
let rPath = await normalizeRemotePath(this, remotePath);
let targetExists = await this.exists(rPath);
const rPath = await normalizeRemotePath(this, remotePath);
const targetExists = await this.exists(rPath);
if (targetExists && targetExists !== 'd') {
let error = new Error(`Bad path: ${rPath} already exists as a file`);
error.code = errorCode.badPath;
throw error;
throw this.fmtError(
`Bad path: ${rPath} already exists as a file`,
'_mkdir',
errorCode.badPath
);
} else if (targetExists) {

@@ -932,21 +930,39 @@ return `${rPath} already exists`;

if (!recursive) {
return await _mkdir(rPath);
return await this._doMkdir(rPath);
}
let dir = parse(rPath).dir;
const dir = parse(rPath).dir;
if (dir) {
let dirExists = await this.exists(dir);
const dirExists = await this.exists(dir);
if (!dirExists) {
await this.mkdir(dir, true);
await this._mkdir(dir, true);
} else if (dirExists !== 'd') {
let error = new Error(`Bad path: ${dir} not a directory`);
error.code = errorCode.badPath;
throw error;
throw this.fmtError(
`Bad path: ${dir} not a directory`,
'_mkdir',
errorCode.badPath
);
}
}
return await _mkdir(rPath);
return await this._doMkdir(rPath);
} catch (err) {
throw fmtError(`${err.message}`, 'mkdir', err.code);
throw err.custom
? err
: this.fmtError(`${err.message} ${remotePath}`, '_mkdir', err.code);
}
}
async mkdir(remotePath, recursive = false) {
let listeners;
try {
listeners = addTempListeners(this, '_mkdir');
haveConnection(this, 'mkdir');
return await this._mkdir(remotePath, recursive);
} catch (err) {
throw this.fmtError(`${err.message}`, 'mkdir', err.code);
} finally {
removeTempListeners(this, listeners, 'append');
this._resetEventFlags();
}
}
/**

@@ -963,13 +979,2 @@ * @async

async rmdir(remotePath, recursive = false) {
const _delete = (remotePath) => {
return new Promise((resolve, reject) => {
this.sftp.unlink(remotePath, (err) => {
if (err && err.code !== 2) {
reject(fmtError(`${err.message} ${remotePath}`, 'rmdir', err.code));
}
resolve(true);
});
});
};
const _rmdir = (p) => {

@@ -980,9 +985,6 @@ return new Promise((resolve, reject) => {

if (err) {
this.debugMsg(`rmdir error ${err.message} code: ${err.code}`);
reject(fmtError(`${err.message} ${p}`, 'rmdir', err.code));
reject(this.fmtError(`${err.message} ${p}`, 'rmdir', err.code));
}
resolve('Successfully removed directory');
});
}).finally(() => {
removeTempListeners(this, listeners, '_rmdir');
});

@@ -994,14 +996,16 @@ };

if (recur) {
let list = await this.list(p);
const list = await this.list(p);
if (list.length) {
let files = list.filter((item) => item.type !== 'd');
let dirs = list.filter((item) => item.type === 'd');
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 (let d of dirs) {
for (const d of dirs) {
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true);
}
let promiseList = [];
for (let f of files) {
promiseList.push(_delete(`${p}${this.remotePathSep}${f.name}`));
const promiseList = [];
for (const f of files) {
promiseList.push(
this._delete(`${p}${this.remotePathSep}${f.name}`)
);
}

@@ -1013,3 +1017,3 @@ await Promise.all(promiseList);

} catch (err) {
throw err.custom ? err : fmtError(err, '_dormdir', err.code);
throw err.custom ? err : this.fmtError(err, '_dormdir', err.code);
}

@@ -1022,6 +1026,6 @@ };

haveConnection(this, 'rmdir');
let absPath = await normalizeRemotePath(this, remotePath);
let dirStatus = await this.exists(absPath);
const absPath = await normalizeRemotePath(this, remotePath);
const dirStatus = await this.exists(absPath);
if (dirStatus && dirStatus !== 'd') {
throw fmtError(
throw this.fmtError(
`Bad path: ${absPath} not a directory`,

@@ -1032,3 +1036,3 @@ 'rmdir',

} else if (!dirStatus) {
throw fmtError(
throw this.fmtError(
`Bad path: ${absPath} No such file`,

@@ -1042,6 +1046,6 @@ 'rmdir',

} catch (err) {
this._resetEventFlags();
throw err.custom ? err : fmtError(err, 'rmdir', err.code);
throw err.custom ? err : this.fmtError(err.message, 'rmdir', err.code);
} finally {
removeTempListeners(this, listeners, 'rmdir');
this._resetEventFlags();
}

@@ -1061,27 +1065,31 @@ }

*/
delete(remotePath, notFoundOK = false) {
let listeners;
_delete(rPath, notFoundOK) {
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'delete', reject);
if (haveConnection(this, 'delete', reject)) {
this.debugMsg(`delete -> ${remotePath}`);
this.sftp.unlink(remotePath, (err) => {
if (err) {
this.debugMsg(`delete error ${err.message} code: ${err.code}`);
if (notFoundOK && err.code === 2) {
this.debugMsg('delete ignore missing target error');
resolve(`Successfully deleted ${remotePath}`);
} else {
reject(
fmtError(`${err.message} ${remotePath}`, 'delete', err.code)
);
}
this.sftp.unlink(rPath, (err) => {
if (err) {
if (notFoundOK && err.code === 2) {
resolve(`Successfully deleted ${rPath}`);
} else {
reject(
this.fmtError(`${err.message} ${rPath}`, 'delete', err.code)
);
}
resolve(`Successfully deleted ${remotePath}`);
});
}
}).finally(() => {
}
resolve(`Successfully deleted ${rPath}`);
});
});
}
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');
this._resetEventFlags();
});
}
}

@@ -1100,26 +1108,37 @@

*/
rename(fromPath, toPath) {
_rename(fPath, tPath) {
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}`);
});
});
}
async rename(fromPath, toPath) {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'rename', reject);
if (haveConnection(this, 'rename', reject)) {
this.debugMsg(`rename -> ${fromPath} ${toPath}`);
this.sftp.rename(fromPath, toPath, (err) => {
if (err) {
this.debugMsg(`rename error ${err.message} code: ${err.code}`);
reject(
fmtError(
`${err.message} From: ${fromPath} To: ${toPath}`,
'rename',
err.code
)
);
}
resolve(`Successfully renamed ${fromPath} to ${toPath}`);
});
}
}).finally(() => {
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');
this._resetEventFlags();
});
}
}

@@ -1139,26 +1158,37 @@

*/
posixRename(fromPath, toPath) {
_posixRename(fPath, tPath) {
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}`);
});
});
}
async posixRename(fromPath, toPath) {
let listeners;
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'posixRename', reject);
if (haveConnection(this, 'posixRename', reject)) {
this.debugMsg(`posixRename -> ${fromPath} ${toPath}`);
this.sftp.ext_openssh_rename(fromPath, toPath, (err) => {
if (err) {
this.debugMsg(`posixRename error ${err.message} code: ${err.code}`);
reject(
fmtError(
`${err.message} From: ${fromPath} To: ${toPath}`,
'posixRename',
err.code
)
);
}
resolve(`Successful POSIX rename ${fromPath} to ${toPath}`);
});
}
}).finally(() => {
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');
this._resetEventFlags();
});
}
}

@@ -1176,17 +1206,27 @@

*/
chmod(remotePath, mode) {
let listeners;
_chmod(rPath, mode) {
return new Promise((resolve, reject) => {
listeners = addTempListeners(this, 'chmod', reject);
this.debugMsg(`chmod -> ${remotePath} ${mode}`);
this.sftp.chmod(remotePath, mode, (err) => {
this.sftp.chmod(rPath, mode, (err) => {
if (err) {
reject(fmtError(`${err.message} ${remotePath}`, 'chmod', err.code));
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code));
}
resolve('Successfully change file mode');
});
}).finally(() => {
});
}
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');
this._resetEventFlags();
});
}
}

@@ -1202,18 +1242,21 @@

* @param {String} dstDir - remote destination directory
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item 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 will be uploaded
* @param {Object} options - (Optional) An object with 2 supported properties,
* 'filter' and 'useFastput'. The first argument is the full path of the item
* 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
* will be uploaded and excluded when it returns false. The 'useFastput' property is a
* boolean value. When true, the 'fastPut()' method will be used to upload files. Default
* is to use the slower, but more supported 'put()' method.
*
* @returns {Promise<String>}
*/
async uploadDir(srcDir, dstDir, filter) {
async _uploadDir(srcDir, dstDir, options) {
try {
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`);
haveConnection(this, 'uploadDir');
//let absSrcDir = fs.realpathSync(srcDir);
let absDstDir = await normalizeRemotePath(this, dstDir);
const absDstDir = await normalizeRemotePath(this, dstDir);
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`);
const srcType = localExists(srcDir);
if (!srcType) {
throw fmtError(
throw this.fmtError(
`Bad path: ${srcDir} not exist`,
'uploadDir',
'_uploadDir',
errorCode.badPath

@@ -1223,14 +1266,13 @@ );

if (srcType !== 'd') {
throw fmtError(
throw this.fmtError(
`Bad path: ${srcDir}: not a directory`,
'uploadDir',
'_uploadDir',
errorCode.badPath
);
}
let dstStatus = await this.exists(absDstDir);
const dstStatus = await this.exists(absDstDir);
if (dstStatus && dstStatus !== 'd') {
this.debugMsg(`UploadDir: DST ${absDstDir} exists but not a directory`);
throw fmtError(
throw this.fmtError(
`Bad path ${absDstDir} Not a directory`,
'uploadDir',
'_uploadDir',
errorCode.badPath

@@ -1240,4 +1282,3 @@ );

if (!dstStatus) {
this.debugMsg(`UploadDir: Creating directory ${absDstDir}`);
await this.mkdir(absDstDir, true);
await this._mkdir(absDstDir, true);
}

@@ -1248,14 +1289,19 @@ let dirEntries = fs.readdirSync(srcDir, {

});
if (filter) {
if (options?.filter) {
dirEntries = dirEntries.filter((item) =>
filter(join(srcDir, item.name), item.isDirectory())
options.filter(join(srcDir, item.name), item.isDirectory())
);
}
for (let e of dirEntries) {
let newSrc = join(srcDir, e.name);
let newDst = `${absDstDir}${this.remotePathSep}${e.name}`;
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, filter);
await this.uploadDir(newSrc, newDst, options);
} else if (e.isFile()) {
await this.put(newSrc, newDst);
if (options?.useFastput) {
fileUploads.push(this._fastPut(newSrc, newDst));
} else {
fileUploads.push(this._put(newSrc, newDst));
}
this.client.emit('upload', { source: newSrc, destination: newDst });

@@ -1267,7 +1313,24 @@ } else {

}
await Promise.all(fileUploads);
}
return `${srcDir} uploaded to ${absDstDir}`;
} catch (err) {
throw err.custom
? err
: 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');
this._resetEventFlags();
throw err.custom ? err : fmtError(err, 'uploadDir');
}

@@ -1284,13 +1347,17 @@ }

* @param {String} dstDir - local destination directory
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item to be downloaded 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 will be downloaded
* @param {Object} options - (Optional) Object with 2 supported properties,
* 'filter' and 'useFastget'. The filter property is a function of two
* arguments. The first argument is the full path of the item to be downloaded
* 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 will be
* downloaded and excluded if teh function returns false.
*
* @returns {Promise<String>}
*/
async downloadDir(srcDir, dstDir, filter) {
async _downloadDir(srcDir, dstDir, options) {
try {
this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`);
haveConnection(this, 'downloadDir');
let fileList = await this.list(srcDir);
if (filter) {
let fileList = await this._list(srcDir);
if (options?.filter) {
fileList = fileList.filter((item) =>
filter(
options.filter(
`${srcDir}${this.remotePathSep}${item.name}`,

@@ -1303,3 +1370,3 @@ item.type === 'd' ? true : false

if (!localCheck.status && localCheck.details === 'permission denied') {
throw fmtError(
throw this.fmtError(
`Bad path: ${dstDir}: ${localCheck.details}`,

@@ -1312,3 +1379,3 @@ 'downloadDir',

} else if (localCheck.status && localCheck.type !== 'd') {
throw fmtError(
throw this.fmtError(
`Bad path: ${dstDir}: not a directory`,

@@ -1319,9 +1386,14 @@ 'downloadDir',

}
for (let f of fileList) {
let newSrc = `${srcDir}${this.remotePathSep}${f.name}`;
let newDst = join(dstDir, f.name);
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, filter);
await this._downloadDir(newSrc, newDst, options);
} else if (f.type === '-') {
await this.get(newSrc, newDst);
if (options?.useFasget) {
downloadFiles.push(this._fastGet(newSrc, newDst));
} else {
downloadFiles.push(this._get(newSrc, newDst));
}
this.client.emit('download', { source: newSrc, destination: newDst });

@@ -1334,6 +1406,22 @@ } else {

}
await Promise.all(downloadFiles);
return `${srcDir} downloaded to ${dstDir}`;
} catch (err) {
throw err.custom
? err
: 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');
this._resetEventFlags();
throw err.custom ? err : fmtError(err, 'downloadDir', err.code);
}

@@ -1343,4 +1431,132 @@ }

/**
*
* Returns a read stream object. This is a low level method which will return a read stream
* connected to the remote file object specified as an argument. Client code is fully responsible
* for managing this stream object i.e. adding any necessary listeners and disposing of the object etc.
* See the SSH2 sftp documentation for details on possible options which can be used.
*
* @param {String} remotePath - path to remote file to attach stream to
* @param {Object} options - options to pass to the create stream process
*
* @returns {Object} a read stream object
*
*/
createReadStream(remotePath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'createReadStream');
haveConnection(this, 'createReadStream');
const stream = this.sftp.createReadStream(remotePath, options);
return stream;
} catch (err) {
throw err.custom
? err
: this.fmtError(err.message, 'createReadStream', err.code);
} finally {
removeTempListeners(this, listeners, 'createReadStreame');
this._resetEventFlags();
}
}
/**
*
* Create a write stream object connected to a file on the remote sftp server.
* This is a low level method which will return a write stream for the remote file specified
* in the 'remotePath' argument. Client code to responsible for managing this object once created.
* This includes disposing of file handles, setting up any necessary event listeners etc.
*
* @param {String} remotePath - path to the remote file on the sftp server
* @param (Object} options - options to pass to the create write stream process)
*
* @returns {Object} a stream object
*
*/
createWriteStream(remotePath, options) {
let listeners;
try {
listeners = addTempListeners(this, 'createWriteStream');
haveConnection(this, 'createWriteStream');
const stream = this.sftp.createWriteStream(remotePath, options);
return stream;
} catch (err) {
throw err.custom
? err
: this.fmtError(err.message, 'createWriteStream', err.code);
} finally {
removeTempListeners(this, listeners, 'createWriteStream');
this._resetEventFlags();
}
}
/**
* @async
*
* Make a remote copy of a remote file. Create a copy of a remote file on the remote
* server. It is assumed the directory where the copy will be placed already exists.
* The destination file must not already exist.
*
* @param {String} srcPath - path to the remote file to be copied
* @param {String} dstPath - destination path for the copy.
*
* @returns {String}.
*
*/
_rcopy(srcPath, dstPath) {
return new Promise((resolve, reject) => {
const ws = this.sftp.createWriteStream(dstPath);
const rs = this.sftp.createReadStream(srcPath);
ws.on('error', (err) => {
reject(this.fmtError(`${err.message} ${dstPath}`, '_rcopy'));
});
rs.on('error', (err) => {
reject(this.fmtError(`${err.message} ${srcPath}`, '_rcopy'));
});
ws.on('close', () => {
resolve(`${srcPath} copied to ${dstPath}`);
});
rs.pipe(ws);
});
}
async rcopy(src, dst) {
let listeners;
try {
listeners = addTempListeners(this, 'rcopy');
haveConnection(this, 'rcopy');
const srcPath = await normalizeRemotePath(this, src);
const srcExists = await this.exists(srcPath);
if (!srcExists) {
throw this.fmtError(
`Source does not exist ${srcPath}`,
'rcopy',
errorCode.badPath
);
}
if (srcExists !== '-') {
throw this.fmtError(
`Source not a file ${srcPath}`,
'rcopy',
errorCode.badPath
);
}
const dstPath = await normalizeRemotePath(this, dst);
const dstExists = await this.exists(dstPath);
if (dstExists) {
throw this.fmtError(
`Destination already exists ${dstPath}`,
'rcopy',
errorCode.badPath
);
}
return await this._rcopy(srcPath, dstPath);
} catch (err) {
throw err.custom ? err : this.fmtError(err, 'rcopy');
} finally {
removeTempListeners(this, listeners, 'rcopy');
this._resetEventFlags();
}
}
/**
* @async
*
* End the SFTP connection

@@ -1362,7 +1578,5 @@ *

if (haveConnection(this, 'end', reject)) {
this.debugMsg('end: Have connection - calling end()');
this.client.end();
}
}).finally(() => {
this.debugMsg('end: finally clause fired');
removeTempListeners(this, listeners, 'end');

@@ -1369,0 +1583,0 @@ this.removeListener('close', endCloseHandler);

@@ -1,62 +0,6 @@

'use strict';
const fs = require('fs');
const path = require('path');
const { errorCode } = require('./constants');
/**
* Generate a new Error object with a reformatted error message which
* is a little more informative and useful to users.
*
* @param {Error|string} err - The Error object the new error will be based on
* @param {number} retryCount - For those functions which use retry. Number of
* attempts to complete before giving up
* @returns {Error} New error with custom error message
*/
function fmtError(err, name = 'sftp', eCode, retryCount) {
let msg = '';
let code = '';
let retry = retryCount
? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}`
: '';
if (err === undefined) {
msg = `${name}: Undefined error - probably a bug!`;
code = errorCode.generic;
} else if (typeof err === 'string') {
msg = `${name}: ${err}${retry}`;
code = eCode ? eCode : errorCode.generic;
} else if (err.custom) {
msg = `${name}->${err.message}${retry}`;
code = err.code;
} else {
switch (err.code) {
case 'ENOTFOUND':
msg =
`${name}: ${err.level} error. ` +
`Address lookup failed for host ${err.hostname}${retry}`;
break;
case 'ECONNREFUSED':
msg =
`${name}: ${err.level} error. Remote host at ` +
`${err.address} refused connection${retry}`;
break;
case 'ECONNRESET':
msg =
`${name}: Remote host has reset the connection: ` +
`${err.message}${retry}`;
break;
default:
msg = `${name}: ${err.message}${retry}`;
}
code = err.code ? err.code : errorCode.generic;
}
let newError = new Error(msg);
newError.code = code;
newError.custom = true;
return newError;
}
/**
* Simple default error listener. Will reformat the error message and

@@ -71,13 +15,12 @@ * throw a new error.

if (client.endCalled || client.errorHandled) {
client.debugMsg(`${name} Error: Ignoring handled error: ${err.message}`);
// error already handled or expected - ignore
return;
}
client.errorHandled = true;
let newError = new Error(`${name}: ${err.message}`);
newError.code = err.code;
if (reject) {
reject(newError);
} else {
client.debugMsg(`${name} Error: Handling error: ${err.message}`);
client.errorHandled = true;
if (reject) {
client.debugMsg(`${name} Error: handled error with reject`);
reject(fmtError(err, name, err.code));
} else {
client.debugMsg(`${name} Error: handling error with throw`);
throw fmtError(err, name, err.code);
}
throw newError;
}

@@ -91,14 +34,12 @@ };

if (client.endCalled || client.endHandled) {
client.debugMsg(`${name} End: Ignoring expected end event`);
// end event already handled - ignore
return;
}
client.sftp = undefined;
client.endHandled = true;
let err = new Error(`${name} Unexpected end event raised`);
if (reject) {
reject(err);
} else {
client.debugMsg(`${name} End: Handling end event`);
client.sftp = undefined;
client.endHandled = true;
if (reject) {
client.debugMsg(`${name} End: handling end event with reject'`);
reject(fmtError('Unexpected end event raised', name));
} else {
client.debugMsg(`${name} End: handling end event with throw`);
throw fmtError('Unexpected end event raised', name);
}
throw err;
}

@@ -112,14 +53,12 @@ };

if (client.endCalled || client.closeHandled) {
client.debugMsg(`${name} Close: ignoring expected close event`);
// handled or expected close event - ignore
return;
}
client.sftp = undefined;
client.closeHandled = true;
let err = new Error(`${name}: Unexpected close event raised`);
if (reject) {
reject(err);
} else {
client.debugMsg(`${name} Close: handling unexpected close event`);
client.sftp = undefined;
client.closeHandled = true;
if (reject) {
client.debugMsg(`${name} Close: handling close event with reject`);
reject(fmtError('Unexpected close event raised', name));
} else {
client.debugMsg(`${name} Close: handling close event with throw`);
throw fmtError('Unexpected close event raised', name);
}
throw err;
}

@@ -136,3 +75,2 @@ };

};
client.debugMsg(`${name}: Adding temp event listeners`);
client.on('end', listeners.end);

@@ -145,6 +83,9 @@ client.on('close', listeners.close);

function removeTempListeners(client, listeners, name) {
client.debugMsg(`${name}: Removing temp event listeners`);
client.removeListener('end', listeners.end);
client.removeListener('close', listeners.close);
client.removeListener('error', listeners.error);
try {
client.removeListener('end', listeners.end);
client.removeListener('close', listeners.close);
client.removeListener('error', listeners.error);
} catch (err) {
throw new Error(`${name}: Error removing temp listeners: ${err.message}`);
}
}

@@ -173,7 +114,7 @@

} else {
throw fmtError(
`Bad path: ${filePath}: target must be a file or directory`,
'localExists',
errorCode.badPath
let err = new Error(
`Bad path: ${filePath}: target must be a file or directory`
);
err.code = errorCode.badPath;
throw err;
}

@@ -293,6 +234,6 @@ }

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

@@ -302,3 +243,3 @@ }

} catch (err) {
throw fmtError(err, 'normalizeRemotePath');
throw new Error(`normalizeRemotePath: ${err.message}`);
}

@@ -319,7 +260,4 @@ }

if (!client.sftp) {
let newError = fmtError(
'No SFTP connection available',
name,
errorCode.connect
);
let newError = new Error(`${name}: No SFTP connection available`);
newError.code = errorCode.connect;
if (reject) {

@@ -348,3 +286,2 @@ reject(newError);

module.exports = {
fmtError,
errorListener,

@@ -351,0 +288,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