"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.joinRemote = exports.hasListener = exports.dumpListeners = exports.haveConnection = exports.checkRemotePath = exports.checkWriteObject = exports.checkWriteDir = exports.checkWriteFile = exports.checkReadDir = exports.checkReadFile = exports.checkReadObject = exports.normalizeRemotePath = exports.checkLocalPath = exports.checkLocalWriteDir = exports.checkLocalWriteFile = exports.checkLocalReadDir = exports.checkLocalReadFile = exports.localAccess = exports.classifyError = exports.localExists = exports.makeCloseListener = exports.makeEndListener = exports.makeErrorListener = exports.handleError = exports.formatError = void 0; var fs_1 = __importDefault(require("fs")); var path_1 = __importDefault(require("path")); var constant_1 = require("./constant"); var types_1 = require("./types"); /** * 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 formatError(err, name, eCode, retryCount) { if (name === void 0) { name = 'sftp'; } if (eCode === void 0) { eCode = constant_1.errorCode.generic; } var msg = ''; var code = ''; var retry = retryCount ? " after " + retryCount + " " + (retryCount > 1 ? 'attempts' : 'attempt') : ''; if (err === undefined) { msg = name + ": Undefined error - probably a bug!"; code = constant_1.errorCode.generic; } else if (typeof err === 'string') { msg = name + ": " + err + retry; code = eCode; } 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; case 'ENOENT': msg = name + ": " + err.message + retry; break; default: msg = name + ": " + err.message + retry; } code = err.code ? err.code : eCode; } var newError = new types_1.ErrorCustom(msg); newError.code = code; newError.custom = true; return newError; } exports.formatError = formatError; /** * Tests an error to see if it is one which has already been customised * by this module or not. If not, applies appropriate customisation. * * @param {Error} err - an Error object * @param {String} name - name to be used in customised error message * @param {Function} reject - If defined, call this function instead of * throwing the error * @throws {Error} */ function handleError(err, name, reject) { if (reject) { if (err.custom) { reject(err); } else { reject(formatError(err, name, undefined, undefined)); } } else { if (err.custom) { throw err; } else { throw formatError(err, name, undefined, undefined); } } } exports.handleError = handleError; /** * Remove all ready, error and end listeners. * * @param {Emitter} emitter - The emitter object to remove listeners from */ // function removeListeners(emitter) { // const listeners = emitter.eventNames() // listeners.forEach((name) => { // emitter.removeAllListeners(name) // }) // } /** * Simple default error listener. Will reformat the error message and * throw a new error. * * @param {Error} err - source for defining new error * @throws {Error} Throws new error */ function makeErrorListener(reject, client, name) { return function (err) { client.errorHandled = true; reject(formatError(err, name)); }; } exports.makeErrorListener = makeErrorListener; function makeEndListener(client) { return function () { if (!client.endCalled) { console.error('End Listener: Connection ended unexpectedly'); } }; } exports.makeEndListener = makeEndListener; function makeCloseListener(client, reject, name) { return function () { if (!client.endCalled) { if (reject) { reject(formatError('Connection closed unexpectedly', name)); } else { console.error('Connection closed unexpectedly'); } } client.sftpWrapper = null; }; } exports.makeCloseListener = makeCloseListener; /** * @async * * Tests to see if a path identifies an existing item. Returns either * 'd' = directory, 'l' = sym link or '-' regular file if item exists. Returns * false if it does not * * @param {String} localPath * @returns {Boolean | String} */ function localExists(localPath) { return new Promise(function (resolve, reject) { fs_1.default.stat(localPath, function (err, stats) { if (err) { if (err.code === 'ENOENT') { resolve('ENOENT'); } else { reject(err); } } else { if (stats.isDirectory()) { resolve('d'); } else if (stats.isSymbolicLink()) { resolve('l'); } else if (stats.isFile()) { resolve('-'); } else { resolve(''); } } }); }); } exports.localExists = localExists; /** * Used by checkRemotePath and checkLocalPath to help ensure consistent * error messages. * * @param {Error} err - original error * @param {String} testPath - path associated with the error * @returns {Object} with properties of 'msg' and 'code'. */ function classifyError(err, testPath) { switch (err.code) { case 'EACCES': return { msg: "Permission denied: " + testPath, code: constant_1.errorCode.permission }; case 'ENOENT': return { msg: "No such file: " + testPath, code: constant_1.errorCode.notexist }; case 'ENOTDIR': return { msg: "Not a directory: " + testPath, code: constant_1.errorCode.notdir }; default: return { msg: err.message, code: err.code ? err.code : constant_1.errorCode.generic }; } } exports.classifyError = classifyError; function localAccess(localPath, mode) { return new Promise(function (resolve) { fs_1.default.access(localPath, mode, function (err) { if (err) { var _a = classifyError(err, localPath), msg = _a.msg, code = _a.code; resolve({ path: localPath, valid: false, msg: msg, code: code }); } else { resolve({ path: localPath, valid: true }); } }); }); } exports.localAccess = localAccess; function checkLocalReadFile(localPath, localType) { return __awaiter(this, void 0, void 0, function () { var rslt, access, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 5]); rslt = { path: localPath, type: localType }; if (!(localType === 'd')) return [3 /*break*/, 1]; rslt.valid = false; rslt.msg = "Bad path: " + localPath + " must be a file"; rslt.code = constant_1.errorCode.badPath; return [2 /*return*/, rslt]; case 1: return [4 /*yield*/, localAccess(localPath, fs_1.default.constants.R_OK)]; case 2: access = _a.sent(); if (access.valid) { rslt.valid = true; return [2 /*return*/, rslt]; } else { rslt.valid = false; rslt.msg = access.msg; rslt.code = access.code; return [2 /*return*/, rslt]; } _a.label = 3; case 3: return [3 /*break*/, 5]; case 4: err_1 = _a.sent(); throw formatError(err_1, 'checkLocalReadFile'); case 5: return [2 /*return*/]; } }); }); } exports.checkLocalReadFile = checkLocalReadFile; function checkLocalReadDir(localPath, localType) { return __awaiter(this, void 0, void 0, function () { var rslt, access, err_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 5, , 6]); rslt = { path: localPath, type: localType }; if (!!localType) return [3 /*break*/, 1]; rslt.valid = false; rslt.msg = "No such directory: " + localPath; rslt.code = constant_1.errorCode.notdir; return [2 /*return*/, rslt]; case 1: if (!(localType !== 'd')) return [3 /*break*/, 2]; rslt.valid = false; rslt.msg = "Bad path: " + localPath + " must be a directory"; rslt.code = constant_1.errorCode.badPath; return [2 /*return*/, rslt]; case 2: return [4 /*yield*/, localAccess(localPath, fs_1.default.constants.R_OK | fs_1.default.constants.X_OK)]; case 3: access = _a.sent(); if (!access.valid) { rslt.valid = false; rslt.msg = access.msg; rslt.code = access.code; return [2 /*return*/, rslt]; } rslt.valid = true; return [2 /*return*/, rslt]; case 4: return [3 /*break*/, 6]; case 5: err_2 = _a.sent(); throw formatError(err_2, 'checkLocalReadDir'); case 6: return [2 /*return*/]; } }); }); } exports.checkLocalReadDir = checkLocalReadDir; function checkLocalWriteFile(localPath, localType) { return __awaiter(this, void 0, void 0, function () { var rslt, dir, parent_1, access, err_3; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 6, , 7]); rslt = { path: localPath, type: localType }; if (!(localType === 'd')) return [3 /*break*/, 1]; rslt.valid = false; rslt.msg = "Bad path: " + localPath + " must be a file"; rslt.code = constant_1.errorCode.badPath; return [2 /*return*/, rslt]; case 1: if (!!localType) return [3 /*break*/, 3]; dir = path_1.default.parse(localPath).dir; return [4 /*yield*/, localAccess(dir, fs_1.default.constants.W_OK)]; case 2: parent_1 = _a.sent(); if (parent_1.valid) { rslt.valid = true; return [2 /*return*/, rslt]; } else { rslt.valid = false; rslt.msg = parent_1.msg; rslt.code = parent_1.code; return [2 /*return*/, rslt]; } return [3 /*break*/, 5]; case 3: return [4 /*yield*/, localAccess(localPath, fs_1.default.constants.W_OK)]; case 4: access = _a.sent(); if (access.valid) { rslt.valid = true; return [2 /*return*/, rslt]; } else { rslt.valid = false; rslt.msg = access.msg; rslt.code = access.code; return [2 /*return*/, rslt]; } _a.label = 5; case 5: return [3 /*break*/, 7]; case 6: err_3 = _a.sent(); throw formatError(err_3, 'checkLocalWriteFile'); case 7: return [2 /*return*/]; } }); }); } exports.checkLocalWriteFile = checkLocalWriteFile; function checkLocalWriteDir(localPath, localType) { return __awaiter(this, void 0, void 0, function () { var rslt, parent_2, access, access, err_4; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 6, , 7]); rslt = { path: localPath, type: localType }; if (!!localType) return [3 /*break*/, 2]; parent_2 = path_1.default.parse(localPath).dir; return [4 /*yield*/, localAccess(parent_2, fs_1.default.constants.W_OK)]; case 1: access = _a.sent(); if (access.valid) { rslt.valid = true; return [2 /*return*/, rslt]; } else { rslt.valid = false; rslt.msg = access.msg; rslt.code = access.code; return [2 /*return*/, rslt]; } return [3 /*break*/, 5]; case 2: if (!(localType !== 'd')) return [3 /*break*/, 3]; rslt.valid = false; rslt.msg = "Bad path: " + localPath + " must be a directory"; rslt.code = constant_1.errorCode.badPath; return [2 /*return*/, rslt]; case 3: return [4 /*yield*/, localAccess(localPath, fs_1.default.constants.W_OK)]; case 4: access = _a.sent(); if (access.valid) { rslt.valid = true; return [2 /*return*/, rslt]; } else { rslt.valid = false; rslt.msg = access.msg; rslt.code = access.code; return [2 /*return*/, rslt]; } _a.label = 5; case 5: return [3 /*break*/, 7]; case 6: err_4 = _a.sent(); throw formatError(err_4, 'checkLocalWriteDir'); case 7: return [2 /*return*/]; } }); }); } exports.checkLocalWriteDir = checkLocalWriteDir; function checkLocalPath(lPath, target) { if (target === void 0) { target = constant_1.targetType.readFile; } return __awaiter(this, void 0, void 0, function () { var localPath, type, err_5; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); localPath = path_1.default.resolve(lPath); return [4 /*yield*/, localExists(localPath)]; case 1: type = _a.sent(); switch (target) { case constant_1.targetType.readFile: return [2 /*return*/, checkLocalReadFile(localPath, type)]; case constant_1.targetType.readDir: return [2 /*return*/, checkLocalReadDir(localPath, type)]; case constant_1.targetType.writeFile: return [2 /*return*/, checkLocalWriteFile(localPath, type)]; case constant_1.targetType.writeDir: return [2 /*return*/, checkLocalWriteDir(localPath, type)]; default: return [2 /*return*/, { path: localPath, type: type, valid: true }]; } return [3 /*break*/, 3]; case 2: err_5 = _a.sent(); throw new Error(err_5); case 3: return [2 /*return*/]; } }); }); } exports.checkLocalPath = checkLocalPath; function normalizeRemotePath(client, aPath) { return __awaiter(this, void 0, void 0, function () { var root, root, err_6; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 5, , 6]); if (!aPath.startsWith('..')) return [3 /*break*/, 2]; return [4 /*yield*/, client.realPath('..')]; case 1: root = _a.sent(); return [2 /*return*/, root + client.remotePathSep + aPath.substring(3)]; case 2: if (!aPath.startsWith('.')) return [3 /*break*/, 4]; return [4 /*yield*/, client.realPath('.')]; case 3: root = _a.sent(); return [2 /*return*/, root + client.remotePathSep + aPath.substring(2)]; case 4: return [2 /*return*/, aPath]; case 5: err_6 = _a.sent(); throw formatError(err_6, 'normalizeRemotePath'); case 6: return [2 /*return*/]; } }); }); } exports.normalizeRemotePath = normalizeRemotePath; function checkReadObject(aPath, type) { return { path: aPath, type: type, valid: type ? true : false, msg: type ? undefined : "No such file " + aPath, code: type ? undefined : constant_1.errorCode.notexist }; } exports.checkReadObject = checkReadObject; function checkReadFile(aPath, type) { if (!type) { return { path: aPath, type: type, valid: false, msg: "No such file: " + aPath, code: constant_1.errorCode.notexist }; } else if (type === 'd') { return { path: aPath, type: type, valid: false, msg: "Bad path: " + aPath + " must be a file", code: constant_1.errorCode.badPath }; } return { path: aPath, type: type, valid: true }; } exports.checkReadFile = checkReadFile; function checkReadDir(aPath, type) { if (!type) { return { path: aPath, type: type, valid: false, msg: "No such directory: " + aPath, code: constant_1.errorCode.notdir }; } else if (type !== 'd') { return { path: aPath, type: type, valid: false, msg: "Bad path: " + aPath + " must be a directory", code: constant_1.errorCode.badPath }; } return { path: aPath, type: type, valid: true }; } exports.checkReadDir = checkReadDir; function checkWriteFile(client, aPath, type) { return __awaiter(this, void 0, void 0, function () { var _a, root, dir, parentType; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(type && type === 'd')) return [3 /*break*/, 1]; return [2 /*return*/, { path: aPath, type: type, valid: false, msg: "Bad path: " + aPath + " must be a regular file", code: constant_1.errorCode.badPath }]; case 1: if (!!type) return [3 /*break*/, 3]; _a = path_1.default.parse(aPath), root = _a.root, dir = _a.dir; // let parentDir = path.parse(aPath).dir; if (!dir) { return [2 /*return*/, { path: aPath, type: false, valid: false, msg: "Bad path: " + aPath + " cannot determine parent directory", code: constant_1.errorCode.badPath }]; } if (root === dir) { return [2 /*return*/, { path: aPath, type: type, valid: true }]; } return [4 /*yield*/, client.exists(dir)]; case 2: parentType = _b.sent(); if (!parentType) { return [2 /*return*/, { path: aPath, type: type, valid: false, msg: "Bad path: " + dir + " parent not exist", code: constant_1.errorCode.badPath }]; } else if (parentType !== 'd') { return [2 /*return*/, { path: aPath, type: type, valid: false, msg: "Bad path: " + dir + " must be a directory", code: constant_1.errorCode.badPath }]; } return [2 /*return*/, { path: aPath, type: type, valid: true }]; case 3: return [2 /*return*/, { path: aPath, type: type, valid: true }]; } }); }); } exports.checkWriteFile = checkWriteFile; function checkWriteDir(client, aPath, type) { return __awaiter(this, void 0, void 0, function () { var _a, root, dir, parentType; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(type && type !== 'd')) return [3 /*break*/, 1]; return [2 /*return*/, { path: aPath, type: type, valid: false, msg: "Bad path: " + aPath + " must be a directory", code: constant_1.errorCode.badPath }]; case 1: if (!!type) return [3 /*break*/, 3]; _a = path_1.default.parse(aPath), root = _a.root, dir = _a.dir; if (root === dir) { return [2 /*return*/, { path: aPath, type: type, valid: true }]; } if (!dir) { return [2 /*return*/, { path: aPath, type: false, valid: false, msg: "Bad path: " + aPath + " cannot determine directory parent", code: constant_1.errorCode.badPath }]; } return [4 /*yield*/, client.exists(dir)]; case 2: parentType = _b.sent(); if (parentType && parentType !== 'd') { return [2 /*return*/, { path: aPath, type: type, valid: false, msg: 'Bad path: Parent Directory must be a directory', code: constant_1.errorCode.badPath }]; } _b.label = 3; case 3: // don't care if parent does not exist as it might be created // via recursive call to mkdir. return [2 /*return*/, { path: aPath, type: type, valid: true }]; } }); }); } exports.checkWriteDir = checkWriteDir; function checkWriteObject(aPath, type) { // for writeObj, not much error checking we can do // Just return path, type and valid indicator return { path: aPath, type: type, valid: true }; } exports.checkWriteObject = checkWriteObject; function checkRemotePath(client, rPath, target) { if (target === void 0) { target = constant_1.targetType.readFile; } return __awaiter(this, void 0, void 0, function () { var aPath, type; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, normalizeRemotePath(client, rPath)]; case 1: aPath = _a.sent(); return [4 /*yield*/, client.exists(aPath)]; case 2: type = _a.sent(); switch (target) { case constant_1.targetType.readObj: return [2 /*return*/, checkReadObject(aPath, type)]; case constant_1.targetType.readFile: return [2 /*return*/, checkReadFile(aPath, type)]; case constant_1.targetType.readDir: return [2 /*return*/, checkReadDir(aPath, type)]; case constant_1.targetType.writeFile: return [2 /*return*/, checkWriteFile(client, aPath, type)]; case constant_1.targetType.writeDir: return [2 /*return*/, checkWriteDir(client, aPath, type)]; case constant_1.targetType.writeObj: return [2 /*return*/, checkWriteObject(aPath, type)]; default: throw formatError("Unknown target type: " + target, 'checkRemotePath', constant_1.errorCode.generic); } return [2 /*return*/]; } }); }); } exports.checkRemotePath = checkRemotePath; /** * Check to see if there is an active sftp connection * * @param {Object} client - current sftp object * @param {String} name - name given to this connection * @param {Function} reject - if defined, call this rather than throw * an error * @returns {Boolean} True if connection OK * @throws {Error} */ function haveConnection(client, name, reject) { if (!client.sftpWrapper) { var newError = formatError('No SFTP connection available', name, constant_1.errorCode.connect); if (reject) { reject(newError); return false; } else { throw newError; } } return true; } exports.haveConnection = haveConnection; function dumpListeners(emitter) { var eventNames = emitter.eventNames(); if (eventNames.length) { console.log('Listener Data'); eventNames.map(function (n) { var listeners = emitter.listeners(n); console.log(n + ": " + emitter.listenerCount(n)); console.dir(listeners); listeners.map(function (l) { console.log("listener name = " + l.name); }); }); } } exports.dumpListeners = dumpListeners; function hasListener(emitter, eventName, listenerName) { var listeners = emitter.listeners(eventName); var matches = listeners.filter(function (l) { return l.name === listenerName; }); return matches.length === 0 ? false : true; } exports.hasListener = hasListener; function joinRemote(client) { var _a, _b; var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } if (client.remotePathSep === path_1.default.win32.sep) { return (_a = path_1.default.win32).join.apply(_a, args); } return (_b = path_1.default.posix).join.apply(_b, args); } exports.joinRemote = joinRemote;