ssh2-sftp-client
Advanced tools
Comparing version 9.1.0 to 10.0.0
{ | ||
"name": "ssh2-sftp-client", | ||
"version": "9.1.0", | ||
"version": "10.0.0", | ||
"description": "ssh2 sftp client for node", | ||
@@ -21,3 +21,3 @@ "main": "src/index.js", | ||
"engines": { | ||
"node": ">=10.24.1" | ||
"node": ">=16.20.2" | ||
}, | ||
@@ -34,3 +34,3 @@ "author": "Tim Cross", | ||
"devDependencies": { | ||
"chai": "^4.3.6", | ||
"chai": "^4.3.10", | ||
"chai-as-promised": "^7.1.1", | ||
@@ -40,14 +40,14 @@ "chai-subset": "^1.6.0", | ||
"dotenv": "^16.0.0", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-mocha": "^10.0.3", | ||
"eslint": "^8.51.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-mocha": "^10.2.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-unicorn": "^46.0.0", | ||
"eslint-plugin-unicorn": "^50.0.1", | ||
"mocha": "^10.0.0", | ||
"moment": "^2.29.1", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.6.1", | ||
"prettier": "^3.0.3", | ||
"through2": "^4.0.2", | ||
"winston": "^3.6.0" | ||
"winston": "^3.11.0" | ||
}, | ||
@@ -57,4 +57,4 @@ "dependencies": { | ||
"promise-retry": "^2.0.1", | ||
"ssh2": "^1.12.0" | ||
"ssh2": "^1.15.0" | ||
} | ||
} |
306
src/index.js
'use strict'; | ||
const { Client } = require('ssh2'); | ||
const fs = require('fs'); | ||
const fs = require('node:fs'); | ||
const concat = require('concat-stream'); | ||
const promiseRetry = require('promise-retry'); | ||
const { join, parse } = require('path'); | ||
const { join, parse } = require('node:path'); | ||
const { | ||
@@ -17,2 +17,3 @@ globalListener, | ||
haveLocalCreate, | ||
partition, | ||
} = require('./utils'); | ||
@@ -23,6 +24,6 @@ const { errorCode } = require('./constants'); | ||
constructor(clientName) { | ||
this.version = '9.0.4'; | ||
this.version = '10.0.0'; | ||
this.client = new Client(); | ||
this.sftp = undefined; | ||
this.clientName = clientName ? clientName : 'sftp'; | ||
this.clientName = clientName || 'sftp'; | ||
this.endCalled = false; | ||
@@ -34,3 +35,3 @@ this.errorHandled = false; | ||
this.debug = undefined; | ||
this.promiseLimit = 10; | ||
this.client.on('close', globalListener(this, 'close')); | ||
@@ -45,3 +46,3 @@ this.client.on('end', globalListener(this, 'end')); | ||
this.debug( | ||
`CLIENT[${this.clientName}]: ${msg} ${JSON.stringify(obj, null, ' ')}` | ||
`CLIENT[${this.clientName}]: ${msg} ${JSON.stringify(obj, null, ' ')}`, | ||
); | ||
@@ -66,3 +67,3 @@ } else { | ||
msg = `${name}: ${err}${retry}`; | ||
code = eCode ? eCode : errorCode.generic; | ||
code = eCode || errorCode.generic; | ||
} else if (err.custom) { | ||
@@ -73,15 +74,19 @@ msg = `${name}->${err.message}${retry}`; | ||
switch (err.code) { | ||
case 'ENOTFOUND': | ||
case 'ENOTFOUND': { | ||
msg = `${name}: Address lookup failed for host${retry}`; | ||
break; | ||
case 'ECONNREFUSED': | ||
} | ||
case 'ECONNREFUSED': { | ||
msg = `${name}: Remote host refused connection${retry}`; | ||
break; | ||
case 'ECONNRESET': | ||
} | ||
case 'ECONNRESET': { | ||
msg = `${name}: Remote host has reset the connection: ${err.message}${retry}`; | ||
break; | ||
default: | ||
} | ||
default: { | ||
msg = `${name}: ${err.message}${retry}`; | ||
} | ||
} | ||
code = err.code ? err.code : errorCode.generic; | ||
code = err.code || errorCode.generic; | ||
} | ||
@@ -126,3 +131,2 @@ const newError = new Error(msg); | ||
* @return {Promise<Object>} which will resolve to an sftp client object | ||
* | ||
*/ | ||
@@ -173,3 +177,2 @@ getConnection(config) { | ||
* @return {Promise<Object>} which will resolve to an sftp client object | ||
* | ||
*/ | ||
@@ -186,2 +189,3 @@ async connect(config) { | ||
} | ||
this.promiseLimit = config.promiseLimit ?? 10; | ||
if (this.sftp) { | ||
@@ -191,3 +195,3 @@ throw this.fmtError( | ||
'connect', | ||
errorCode.connect | ||
errorCode.connect, | ||
); | ||
@@ -197,3 +201,3 @@ } | ||
retries: config.retries ?? 1, | ||
factor: config.factor ?? 2, | ||
factor: config.retry_factor ?? 2, | ||
minTimeout: config.retry_minTimeout ?? 25000, | ||
@@ -209,4 +213,5 @@ }; | ||
case 'ECONNREFUSED': | ||
case 'ERR_SOCKET_BAD_PORT': | ||
case 'ERR_SOCKET_BAD_PORT': { | ||
throw err; | ||
} | ||
case undefined: { | ||
@@ -222,4 +227,5 @@ if ( | ||
} | ||
default: | ||
default: { | ||
retry(err); | ||
} | ||
} | ||
@@ -300,3 +306,3 @@ } | ||
return new Promise((resolve, reject) => { | ||
let cb = (err, stats) => { | ||
const cb = (err, stats) => { | ||
if (err) { | ||
@@ -352,3 +358,2 @@ if (err.code === 2 || err.code === 4) { | ||
* @return {Promise<Object>} stats - attributes info | ||
* | ||
*/ | ||
@@ -373,3 +378,2 @@ async stat(remotePath) { | ||
* @return {Promise<Object>} stats - attributes info | ||
* | ||
*/ | ||
@@ -455,5 +459,5 @@ async lstat(remotePath) { | ||
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, ''), | ||
user: item.longname.slice(1, 4).replaceAll(reg, ''), | ||
group: item.longname.slice(4, 7).replaceAll(reg, ''), | ||
other: item.longname.slice(7, 10).replaceAll(reg, ''), | ||
}, | ||
@@ -509,3 +513,6 @@ owner: item.attrs.uid, | ||
readStreamOptions: { ...options?.readStreamOptions, autoClose: true }, | ||
writeStreamOptions: { ...options?.writeStreamOptions, autoClose: true }, | ||
writeStreamOptions: { | ||
...options?.writeStreamOptions, | ||
autoClose: true, | ||
}, | ||
pipeOptions: { ...options?.pipeOptions, end: true }, | ||
@@ -530,3 +537,5 @@ }; | ||
const localCheck = haveLocalCreate(dst); | ||
if (!localCheck.status) { | ||
if (localCheck.status) { | ||
wtr = fs.createWriteStream(dst, options.writeStreamOptions); | ||
} else { | ||
reject( | ||
@@ -536,8 +545,5 @@ this.fmtError( | ||
'get', | ||
localCheck.code | ||
) | ||
localCheck.code, | ||
), | ||
); | ||
return; | ||
} else { | ||
wtr = fs.createWriteStream(dst, options.writeStreamOptions); | ||
} | ||
@@ -553,4 +559,4 @@ } else { | ||
'get', | ||
err.code | ||
) | ||
err.code, | ||
), | ||
); | ||
@@ -581,2 +587,6 @@ }); | ||
* | ||
* WARNING: The functionality of fastGet is heavily dependent on the capabilities | ||
* of the remote SFTP server. Not all sftp server support or fully support this | ||
* functionality. See the Platform Quirks & Warnings section of the README. | ||
* | ||
* @param {String} remotePath | ||
@@ -612,3 +622,3 @@ * @param {String} localPath | ||
if (ftype !== '-') { | ||
const msg = `${!ftype ? 'No such file ' : 'Not a regular file'} ${remotePath}`; | ||
const msg = `${ftype ? 'Not a regular file' : 'No such file '} ${remotePath}`; | ||
throw this.fmtError(msg, 'fastGet', errorCode.badPath); | ||
@@ -621,3 +631,3 @@ } | ||
'fastGet', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -639,2 +649,6 @@ } | ||
* | ||
* WARNING: The fastPut functionality is heavily dependent on the capabilities of | ||
* the remote sftp server. Many sftp servers do not support or do not fully support this | ||
* functionality. See the Platform Quirks & Warnings section of the README for more details. | ||
* | ||
* @param {String} localPath - path to local file to put | ||
@@ -659,4 +673,4 @@ * @param {String} remotePath - destination path for put file | ||
'fastPut', | ||
err.code | ||
) | ||
err.code, | ||
), | ||
); | ||
@@ -682,3 +696,3 @@ } | ||
'fastPut', | ||
localCheck.code | ||
localCheck.code, | ||
); | ||
@@ -689,3 +703,3 @@ } else if (localCheck.status && localExists(localPath) === 'd') { | ||
'fastgPut', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -731,3 +745,7 @@ } | ||
reject( | ||
this.fmtError(`Write stream error: ${err.message} ${rPath}`, '_put', err.code) | ||
this.fmtError( | ||
`Write stream error: ${err.message} ${rPath}`, | ||
'_put', | ||
err.code, | ||
), | ||
); | ||
@@ -753,4 +771,4 @@ }); | ||
'_put', | ||
err.code | ||
) | ||
err.code, | ||
), | ||
); | ||
@@ -776,3 +794,3 @@ }); | ||
'put', | ||
localCheck.code | ||
localCheck.code, | ||
); | ||
@@ -831,3 +849,3 @@ } | ||
'append', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -840,3 +858,3 @@ } | ||
'append', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -873,4 +891,4 @@ } | ||
'_doMkdir', | ||
errorCode.badPath | ||
) | ||
errorCode.badPath, | ||
), | ||
); | ||
@@ -882,4 +900,4 @@ } else if (err.code === 2) { | ||
'_doMkdir', | ||
errorCode.badPath | ||
) | ||
errorCode.badPath, | ||
), | ||
); | ||
@@ -908,3 +926,3 @@ } else { | ||
'_mkdir', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -926,3 +944,3 @@ } else if (targetExists) { | ||
'_mkdir', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -980,3 +998,3 @@ } | ||
this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`); | ||
let pList = []; | ||
const pList = []; | ||
for (const f of fileList) { | ||
@@ -997,4 +1015,4 @@ pList.push(this.delete(`${path}/${f.name}`, true, false)); | ||
this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`); | ||
let absPath = await normalizeRemotePath(this, remoteDir); | ||
let existStatus = await this.exists(absPath); | ||
const absPath = await normalizeRemotePath(this, remoteDir); | ||
const existStatus = await this.exists(absPath); | ||
this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`); | ||
@@ -1005,3 +1023,3 @@ if (!existStatus) { | ||
'rmdir', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1013,3 +1031,3 @@ } | ||
'rmdir', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1021,3 +1039,3 @@ } | ||
} | ||
let listing = await this.list(absPath); | ||
const listing = await this.list(absPath); | ||
this.debugMsg(`rmdir: listing count = ${listing.length}`); | ||
@@ -1028,5 +1046,5 @@ if (!listing.length) { | ||
} | ||
let fileList = listing.filter((i) => i.type !== 'd'); | ||
const 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'); | ||
const dirList = listing.filter((i) => i.type === 'd'); | ||
this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`); | ||
@@ -1055,3 +1073,2 @@ await _delFiles(absPath, fileList); | ||
* @return {Promise<String>} with string 'Successfully deleted file' once resolved | ||
* | ||
*/ | ||
@@ -1091,3 +1108,2 @@ delete(remotePath, notFoundOK = false, addListeners = true) { | ||
* @return {Promise<String>} | ||
* | ||
*/ | ||
@@ -1107,4 +1123,4 @@ rename(fPath, tPath, addListeners = true) { | ||
'_rename', | ||
err.code | ||
) | ||
err.code, | ||
), | ||
); | ||
@@ -1133,3 +1149,2 @@ } | ||
* @return {Promise<String>} | ||
* | ||
*/ | ||
@@ -1149,4 +1164,4 @@ posixRename(fPath, tPath, addListeners = true) { | ||
'_posixRename', | ||
err.code | ||
) | ||
err.code, | ||
), | ||
); | ||
@@ -1215,4 +1230,4 @@ } | ||
const getRemoteStatus = async (dstDir) => { | ||
let absDstDir = await normalizeRemotePath(this, dstDir); | ||
let status = await this.exists(absDstDir); | ||
const absDstDir = await normalizeRemotePath(this, dstDir); | ||
const status = await this.exists(absDstDir); | ||
if (status && status !== 'd') { | ||
@@ -1222,3 +1237,3 @@ throw this.fmtError( | ||
'getRemoteStatus', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1235,3 +1250,3 @@ } | ||
'getLocalStatus', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1243,3 +1258,3 @@ } | ||
'getLocalStatus', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1250,29 +1265,32 @@ } | ||
const uploadFiles = (srcDir, dstDir, fileList, useFastput) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'uploadFiles', reject); | ||
let uploads = []; | ||
const uploadFiles = async (srcDir, dstDir, fileList, useFastput) => { | ||
let listeners = addTempListeners(this, 'uploadFiles'); | ||
try { | ||
const uploadList = []; | ||
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`); | ||
const src = join(srcDir, f.name); | ||
const dst = `${dstDir}/${f.name}`; | ||
uploadList.push([src, dst]); | ||
} | ||
const uploadGroups = partition(uploadList, this.promiseLimit); | ||
const func = useFastput ? this._fastPut.bind(this) : this._put.bind(this); | ||
const uploadResults = []; | ||
for (const group of uploadGroups) { | ||
const pList = []; | ||
for (const [src, dst] of group) { | ||
pList.push(func(src, dst, null, false)); | ||
this.client.emit('upload', { source: src, destination: dst }); | ||
} | ||
const groupResults = await Promise.all(pList); | ||
for (const r of groupResults) { | ||
uploadResults.push(r); | ||
} | ||
} | ||
resolve(Promise.all(uploads)); | ||
}) | ||
.then((pList) => { | ||
return Promise.all(pList); | ||
}) | ||
.finally(() => { | ||
removeTempListeners(this, listeners, uploadFiles); | ||
}); | ||
return uploadResults; | ||
} catch (e) { | ||
throw this.fmtError(`${e.message} ${srcDir} to ${dstDir}`, 'uploadFiles', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, uploadFiles); | ||
} | ||
}; | ||
@@ -1283,5 +1301,5 @@ | ||
this.debugMsg( | ||
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}` | ||
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}`, | ||
); | ||
let { remoteDir, remoteStatus } = await getRemoteStatus(dstDir); | ||
const { remoteDir, remoteStatus } = await getRemoteStatus(dstDir); | ||
this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`); | ||
@@ -1299,7 +1317,7 @@ checkLocalStatus(srcDir); | ||
dirEntries = dirEntries.filter((item) => | ||
options.filter(join(srcDir, item.name), item.isDirectory()) | ||
options.filter(join(srcDir, item.name), item.isDirectory()), | ||
); | ||
} | ||
let dirUploads = dirEntries.filter((item) => item.isDirectory()); | ||
let fileUploads = dirEntries.filter((item) => !item.isDirectory()); | ||
const dirUploads = dirEntries.filter((item) => item.isDirectory()); | ||
const fileUploads = dirEntries.filter((item) => !item.isDirectory()); | ||
this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`); | ||
@@ -1309,4 +1327,4 @@ this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`); | ||
for (const d of dirUploads) { | ||
let src = join(srcDir, d.name); | ||
let dst = `${remoteDir}/${d.name}`; | ||
const src = join(srcDir, d.name); | ||
const dst = `${remoteDir}/${d.name}`; | ||
await this.uploadDir(src, dst, options); | ||
@@ -1340,8 +1358,8 @@ } | ||
async downloadDir(srcDir, dstDir, options = { filter: null, useFastget: false }) { | ||
const _getDownloadList = async (srcDir, filter) => { | ||
const getDownloadList = async (srcDir, filter) => { | ||
try { | ||
let listing = await this.list(srcDir); | ||
const listing = await this.list(srcDir); | ||
if (filter) { | ||
return listing.filter((item) => | ||
filter(`${srcDir}/${item.name}`, item.type === 'd') | ||
filter(`${srcDir}/${item.name}`, item.type === 'd'), | ||
); | ||
@@ -1355,3 +1373,3 @@ } | ||
const _prepareDestination = (dst) => { | ||
const prepareDestination = (dst) => { | ||
try { | ||
@@ -1363,3 +1381,3 @@ const localCheck = haveLocalCreate(dst); | ||
'prepareDestination', | ||
localCheck.code | ||
localCheck.code, | ||
); | ||
@@ -1372,3 +1390,3 @@ } else if (localCheck.status && !localCheck.type) { | ||
'_prepareDestination', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1383,21 +1401,36 @@ } | ||
const _downloadFiles = (remotePath, localPath, fileList, useFastget) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, '_downloadFIles', reject); | ||
let pList = []; | ||
const downloadFiles = async (remotePath, localPath, fileList, useFastget) => { | ||
let listeners = addTempListeners(this, 'downloadFIles'); | ||
try { | ||
const downloadList = []; | ||
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 { | ||
pList.push(this.get(src, dst, false)); | ||
const src = `${remotePath}/${f.name}`; | ||
const dst = join(localPath, f.name); | ||
downloadList.push([src, dst]); | ||
} | ||
const downloadGroups = partition(downloadList, this.promiseLimit); | ||
const func = useFastget ? this._fastGet.bind(this) : this.get.bind(this); | ||
const downloadResults = []; | ||
for (const group of downloadGroups) { | ||
const pList = []; | ||
for (const [src, dst] of group) { | ||
pList.push(func(src, dst, null, false)); | ||
this.client.emit('download', { source: src, destination: dst }); | ||
} | ||
this.client.emit('download', { source: src, destination: dst }); | ||
const groupResults = await Promise.all(pList); | ||
for (const r of groupResults) { | ||
downloadResults.push(r); | ||
} | ||
} | ||
return resolve(Promise.all(pList)); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_downloadFiles'); | ||
}); | ||
return downloadResults; | ||
} catch (e) { | ||
throw this.fmtError( | ||
`${e.message} ${srcDir} to ${dstDir}`, | ||
'downloadFiles', | ||
e.code, | ||
); | ||
} finally { | ||
removeTempListeners(this, listeners, 'downloadFiles'); | ||
} | ||
}; | ||
@@ -1407,13 +1440,13 @@ | ||
haveConnection(this, 'downloadDir'); | ||
let downloadList = await _getDownloadList(srcDir, options.filter); | ||
_prepareDestination(dstDir); | ||
let fileDownloads = downloadList.filter((i) => i.type !== 'd'); | ||
const downloadList = await getDownloadList(srcDir, options.filter); | ||
prepareDestination(dstDir); | ||
const fileDownloads = downloadList.filter((i) => i.type !== 'd'); | ||
if (fileDownloads.length) { | ||
await _downloadFiles(srcDir, dstDir, fileDownloads, options.useFastget); | ||
await downloadFiles(srcDir, dstDir, fileDownloads, options.useFastget); | ||
} | ||
let dirDownloads = downloadList.filter((i) => i.type === 'd'); | ||
const 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); | ||
const src = `${srcDir}/${d.name}`; | ||
const dst = join(dstDir, d.name); | ||
await this.downloadDir(src, dst, options); | ||
} | ||
@@ -1429,3 +1462,2 @@ return `${srcDir} downloaded to ${dstDir}`; | ||
/** | ||
* | ||
* Returns a read stream object. This is a low level method which will return a read stream | ||
@@ -1440,3 +1472,2 @@ * connected to the remote file object specified as an argument. Client code is fully responsible | ||
* @returns {Object} a read stream object | ||
* | ||
*/ | ||
@@ -1458,3 +1489,2 @@ createReadStream(remotePath, options) { | ||
/** | ||
* | ||
* Create a write stream object connected to a file on the remote sftp server. | ||
@@ -1469,3 +1499,2 @@ * This is a low level method which will return a write stream for the remote file specified | ||
* @returns {Object} a stream object | ||
* | ||
*/ | ||
@@ -1497,3 +1526,2 @@ createWriteStream(remotePath, options) { | ||
* @returns {String}. | ||
* | ||
*/ | ||
@@ -1528,3 +1556,3 @@ _rcopy(srcPath, dstPath) { | ||
'rcopy', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1541,3 +1569,3 @@ } | ||
'rcopy', | ||
errorCode.badPath | ||
errorCode.badPath, | ||
); | ||
@@ -1544,0 +1572,0 @@ } |
111
src/utils.js
@@ -1,3 +0,3 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const fs = require('node:fs'); | ||
const path = require('node:path'); | ||
const { errorCode } = require('./constants'); | ||
@@ -43,3 +43,3 @@ | ||
function endListener(client, name, reject) { | ||
function endListener(client, name) { | ||
const fn = function () { | ||
@@ -49,15 +49,7 @@ client.sftp = undefined; | ||
// end event already handled - ignore | ||
client.debugMsg(`${name} endListener - ignoring handled error`); | ||
client.debugMsg(`${name} endListener - handled end event`); | ||
return; | ||
} | ||
client.endHandled = true; | ||
client.debugMsg(`${name} Unexpected end event - ignoring`); | ||
// Don't reject/throw error, just log it and move on | ||
// after invalidating the connection | ||
// const err = new Error(`${name} Unexpected end event raised`); | ||
// if (reject) { | ||
// reject(err); | ||
// } else { | ||
// throw err; | ||
// } | ||
client.debugMsg(`${name} Unexpected end event`); | ||
}; | ||
@@ -67,3 +59,3 @@ return fn; | ||
function closeListener(client, name, reject) { | ||
function closeListener(client, name) { | ||
const fn = function () { | ||
@@ -78,15 +70,7 @@ client.sftp = undefined; | ||
// handled or expected close event - ignore | ||
client.debugMsg(`${name} closeListener - ignoring handled error`); | ||
client.debugMsg(`${name} closeListener - handled close event`); | ||
return; | ||
} | ||
client.closeHandled = true; | ||
client.debugMsg(`${name} Unexpected close event raised - ignoring`); | ||
// Don't throw/reject on close events. Just invalidate the connection | ||
// and move on. | ||
// const err = new Error(`${name}: Unexpected close event raised`); | ||
// if (reject) { | ||
// reject(err); | ||
// } else { | ||
// throw err; | ||
// } | ||
client.debugMsg(`${name} Unexpected close event`); | ||
}; | ||
@@ -98,4 +82,4 @@ return fn; | ||
const listeners = { | ||
end: endListener(client, name, reject), | ||
close: closeListener(client, name, reject), | ||
end: endListener(client, name), | ||
close: closeListener(client, name), | ||
error: errorListener(client, name, reject), | ||
@@ -178,3 +162,3 @@ }; | ||
switch (err.errno) { | ||
case -2: | ||
case -2: { | ||
return { | ||
@@ -186,3 +170,4 @@ status: false, | ||
}; | ||
case -13: | ||
} | ||
case -13: { | ||
return { | ||
@@ -194,3 +179,4 @@ status: false, | ||
}; | ||
case -20: | ||
} | ||
case -20: { | ||
return { | ||
@@ -201,3 +187,4 @@ status: false, | ||
}; | ||
default: | ||
} | ||
default: { | ||
return { | ||
@@ -208,2 +195,3 @@ status: false, | ||
}; | ||
} | ||
} | ||
@@ -224,22 +212,18 @@ } | ||
const { status, details, type } = haveLocalAccess(filePath, 'w'); | ||
if (!status && details === 'permission denied') { | ||
//throw new Error(`Bad path: ${filePath}: permission denied`); | ||
return { | ||
status, | ||
details, | ||
type, | ||
}; | ||
} else if (!status) { | ||
if (!status) { | ||
// filePath does not exist. Can we create it? | ||
if (details === 'permission denied') { | ||
// don't have permission | ||
return { | ||
status, | ||
details, | ||
type, | ||
}; | ||
} | ||
// to create it, parent must be directory and writeable | ||
const dirPath = path.dirname(filePath); | ||
const localCheck = haveLocalAccess(dirPath, 'w'); | ||
if (localCheck.status && localCheck.type !== 'd') { | ||
//throw new Error(`Bad path: ${dirPath}: not a directory`); | ||
if (!localCheck.status) { | ||
// no access to parent directory | ||
return { | ||
status: false, | ||
details: `${dirPath}: not a directory`, | ||
type: null, | ||
}; | ||
} else if (!localCheck.status) { | ||
//throw new Error(`Bad path: ${dirPath}: ${localCheck.details}`); | ||
return { | ||
status: localCheck.status, | ||
@@ -249,10 +233,17 @@ details: `${dirPath}: ${localCheck.details}`, | ||
}; | ||
} else { | ||
} | ||
// exists, is it a directory? | ||
if (localCheck.type !== 'd') { | ||
return { | ||
status: true, | ||
details: 'access OK', | ||
status: false, | ||
details: `${dirPath}: not a directory`, | ||
type: null, | ||
code: 0, | ||
}; | ||
} | ||
return { | ||
status: true, | ||
details: 'access OK', | ||
type: null, | ||
code: 0, | ||
}; | ||
} | ||
@@ -304,4 +295,4 @@ return { status, details, type }; | ||
try { | ||
if (isNaN(ms) || ms < 0) { | ||
reject('Argument must be anumber >= 0'); | ||
if (Number.isNaN(Number.parseInt(ms)) || ms < 0) { | ||
reject('Argument must be a number >= 0'); | ||
} else { | ||
@@ -318,2 +309,15 @@ setTimeout(() => { | ||
function partition(input, size) { | ||
let output = []; | ||
if (size < 1) { | ||
throw new Error('Partition size must be greater than zero'); | ||
} | ||
for (let i = 0; i < input.length; i += size) { | ||
output[output.length] = input.slice(i, i + size); | ||
} | ||
return output; | ||
} | ||
module.exports = { | ||
@@ -332,2 +336,3 @@ globalListener, | ||
sleep, | ||
partition, | ||
}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
228287
1818
1599
0
Updatedssh2@^1.15.0