UNPKG

filefive

Version:

SFTP/FTP/Amazon S3 client and dual-panel file manager for macOS and Linux

272 lines (271 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ATTRIBUTES = void 0; const node_path_1 = require("node:path"); const ssh2_1 = require("ssh2"); const FileSystem_1 = require("../FileSystem"); // https://github.com/mscdex/ssh2/blob/master/SFTP.md // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4.3 // see also https://github.com/theophilusx/ssh2-sftp-client exports.ATTRIBUTES = [ { name: "name", type: FileSystem_1.FileAttributeType.String, title: "Name" }, { name: "size", type: FileSystem_1.FileAttributeType.Number, title: "Size" }, { name: "modified", type: FileSystem_1.FileAttributeType.Date, title: "Last Modified" }, { name: "rights", type: FileSystem_1.FileAttributeType.String, title: "Rights" } ]; class SFtp extends FileSystem_1.FileSystem { constructor(host, user, password, privateKey = null, port = 22, onError, onClose = () => { }) { super(); this.host = host; this.user = user; this.password = password; this.privateKey = privateKey; this.port = port; this.onError = onError; this.onClose = onClose; this.connection = new ssh2_1.Client(); } async open() { if (this.connected === undefined) { let connecting = true; this.connected = new Promise((resolve, reject) => { this.connection .on('close', () => { if (this.connected) { if (connecting) { reject(new Error('Remote side unexpectedly closed network connection')); } else { this.onClose(); } } this.connected = undefined; connecting = false; }) .on('ready', () => { connecting = false; this.connection.sftp((e, sftp) => { if (e) { reject(new Error(this.decodeError(e))); return; } this.connection.on('error', this.onError); this.extensions = sftp._extensions; resolve(sftp); }); }) .on('error', err => reject(err)); }); this.connection.connect({ host: this.host, username: this.user, port: this.port, ...(this.privateKey ? { privateKey: this.privateKey } : { password: this.password }) // debug: s => console.log('DEBUG', s) }); } return this.connected; } close() { this.connection.end(); } opened() { return this.connected != undefined; } async pwd() { const sftp = await this.open(); return new Promise((resolve, reject) => sftp.realpath('.', (e, current) => e ? reject(new Error(this.decodeError(e))) : resolve(current))); } async ls(dir) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.readdir(dir, async (e, list) => { if (e) { reject(new Error(`LS: Can't get contents of ${dir} ` + this.decodeError(e))); return; } const files = []; for (let f of list) { if (f.attrs.isSymbolicLink()) { let target = await this.readlink((0, node_path_1.join)(dir, f.filename)); if (!(0, node_path_1.isAbsolute)(target)) { target = (0, node_path_1.normalize)((0, node_path_1.join)(dir, target)); } const targetStat = await this.stat(target); files.push({ path: (0, node_path_1.join)(dir, f.filename), name: f.filename, dir: f.attrs.isDirectory(), size: targetStat.size, modified: new Date(targetStat.mtime * 1000), owner: targetStat.uid, group: targetStat.gid, rights: targetStat.mode, target }); } else { files.push({ path: (0, node_path_1.join)(dir, f.filename), name: f.filename, dir: f.attrs.isDirectory(), size: f.attrs.size, modified: new Date(f.attrs.mtime * 1000), owner: f.attrs.uid, group: f.attrs.gid, rights: f.attrs.mode }); } } resolve(files); }); }); } async readlink(path) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.readlink(path, (e, target) => e ? reject(new Error(this.decodeError(e))) : resolve(target)); }); } async stat(path) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.stat(path, (e, stats) => e ? reject(new Error(this.decodeError(e))) : resolve(stats)); }); } async get(fromRemote, toLocal) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.fastGet(fromRemote, toLocal, (e) => e ? reject(new Error(this.decodeError(e))) : resolve()); }); } async put(fromLocal, toRemote) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.fastPut(fromLocal, toRemote, e => e ? reject(new Error(this.decodeError(e) + ` ${toRemote}`)) : resolve()); }); } async rm(path, recursive) { const sftp = await this.open(); return new Promise((resolve, reject) => { if (recursive) { sftp.rmdir(path, e => e ? reject(new Error(this.decodeError(e))) : resolve()); } else { sftp.unlink(path, e => e ? reject(new Error(this.decodeError(e))) : resolve()); } }); } async mkdir(path) { const sftp = await this.open(); return new Promise((resolve, reject) => sftp.mkdir(path, e => e ? reject(new Error(`MKDIR: Can't create directory ${path} ` + this.decodeError(e))) : resolve())); } async rename(from, to) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.rename(from, to, e => { if (e) { reject(new Error(this.decodeError(e))); return; } resolve(); }); }); } async mv(from, to) { await this.exec(`rm -Rf '${to}'`); await this.exec(`mkdir -p '${(0, node_path_1.dirname)(to)}'`); return this.exec(`mv -f '${from}' '${to}'`); } async cp(from, to, recursive) { await this.exec(`rm -Rf '${to}'`); if (recursive) { return this.exec(`cp -fR '${from}' '${to}'`); } await this.exec(`mkdir -p '${(0, node_path_1.dirname)(to)}'`); return this.exec(`cp -f '${from}' '${to}'`); } async write(path, s) { const sftp = await this.open(); return new Promise((resolve, reject) => { sftp.open(path, 'w', (e, h) => { if (e) { reject(e); return; } const data = Buffer.from(s); sftp.write(h, data, 0, data.length, 0, (e) => e ? reject(new Error(this.decodeError(e))) : resolve()); }); }); } async exec(cmd) { await this.open(); return new Promise((resolve, reject) => this.connection.exec(cmd, (e, stream) => { if (e) { reject(new Error(cmd + ': ' + this.decodeError(e))); return; } stream.on('exit', (code) => { if (code) { reject(new Error(`${cmd}: the process's return code is ${code}`)); } resolve(); }).on('data', (data) => { console.log(`${cmd}: ${data}`); }).stderr.on('data', data => { reject(new Error(`${cmd}: ${data}`)); }); })); } decodeError(e) { const STATUS_CODE = { OK: 0, EOF: 1, NO_SUCH_FILE: 2, PERMISSION_DENIED: 3, FAILURE: 4, BAD_MESSAGE: 5, NO_CONNECTION: 6, CONNECTION_LOST: 7, OP_UNSUPPORTED: 8 }; const STATUS_CODE_STR = { [STATUS_CODE.OK]: 'No error', [STATUS_CODE.EOF]: 'End of file', [STATUS_CODE.NO_SUCH_FILE]: 'No such file or directory', [STATUS_CODE.PERMISSION_DENIED]: 'Permission denied', [STATUS_CODE.FAILURE]: 'Failure', [STATUS_CODE.BAD_MESSAGE]: 'Bad message', [STATUS_CODE.NO_CONNECTION]: 'No connection', [STATUS_CODE.CONNECTION_LOST]: 'Connection lost', [STATUS_CODE.OP_UNSUPPORTED]: 'Operation unsupported', }; let msg = e.message || (('code' in e) ? (e['code'] in STATUS_CODE_STR ? STATUS_CODE_STR[e['code']] : `Code: ${e['code']}`) : 'Unknown error'); return msg; } } exports.default = SFtp;