mirror of
https://github.com/cdr/code-server.git
synced 2026-01-20 23:23:55 +01:00
201 lines
4.9 KiB
TypeScript
201 lines
4.9 KiB
TypeScript
/**
|
|
* Provides utilities for handling SSH connections
|
|
*/
|
|
import * as fs from "fs"
|
|
import * as path from "path"
|
|
import * as ssh from "ssh2"
|
|
import { FileEntry, SFTPStream } from "ssh2-streams"
|
|
|
|
/**
|
|
* Fills out all the functionality of SFTP using fs.
|
|
*/
|
|
export function fillSftpStream(accept: () => SFTPStream): void {
|
|
const sftp = accept()
|
|
|
|
let oid = 0
|
|
const fds: { [key: number]: boolean } = {}
|
|
const ods: {
|
|
[key: number]: {
|
|
path: string
|
|
read: boolean
|
|
}
|
|
} = {}
|
|
|
|
const sftpStatus = (reqID: number, err?: NodeJS.ErrnoException | null): boolean => {
|
|
let code = ssh.SFTP_STATUS_CODE.OK
|
|
if (err) {
|
|
if (err.code === "EACCES") {
|
|
code = ssh.SFTP_STATUS_CODE.PERMISSION_DENIED
|
|
} else if (err.code === "ENOENT") {
|
|
code = ssh.SFTP_STATUS_CODE.NO_SUCH_FILE
|
|
} else {
|
|
code = ssh.SFTP_STATUS_CODE.FAILURE
|
|
}
|
|
}
|
|
return sftp.status(reqID, code)
|
|
}
|
|
|
|
sftp.on("OPEN", (reqID, filename) => {
|
|
fs.open(filename, "w", (err, fd) => {
|
|
if (err) {
|
|
return sftpStatus(reqID, err)
|
|
}
|
|
fds[fd] = true
|
|
const buf = Buffer.alloc(4)
|
|
buf.writeUInt32BE(fd, 0)
|
|
return sftp.handle(reqID, buf)
|
|
})
|
|
})
|
|
|
|
sftp.on("OPENDIR", (reqID, path) => {
|
|
const buf = Buffer.alloc(4)
|
|
const id = oid++
|
|
buf.writeUInt32BE(id, 0)
|
|
ods[id] = {
|
|
path,
|
|
read: false,
|
|
}
|
|
sftp.handle(reqID, buf)
|
|
})
|
|
|
|
sftp.on("READDIR", (reqID, handle) => {
|
|
const od = handle.readUInt32BE(0)
|
|
if (!ods[od]) {
|
|
return sftp.status(reqID, ssh.SFTP_STATUS_CODE.NO_SUCH_FILE)
|
|
}
|
|
if (ods[od].read) {
|
|
sftp.status(reqID, ssh.SFTP_STATUS_CODE.EOF)
|
|
return
|
|
}
|
|
return fs.readdir(ods[od].path, (err, files) => {
|
|
if (err) {
|
|
return sftpStatus(reqID, err)
|
|
}
|
|
return Promise.all(
|
|
files.map((f) => {
|
|
return new Promise<FileEntry>((resolve, reject) => {
|
|
const fullPath = path.join(ods[od].path, f)
|
|
fs.stat(fullPath, (err, stats) => {
|
|
if (err) {
|
|
return reject(err)
|
|
}
|
|
|
|
resolve({
|
|
filename: f,
|
|
longname: fullPath,
|
|
attrs: {
|
|
atime: stats.atimeMs,
|
|
gid: stats.gid,
|
|
mode: stats.mode,
|
|
size: stats.size,
|
|
mtime: stats.mtimeMs,
|
|
uid: stats.uid,
|
|
},
|
|
})
|
|
})
|
|
})
|
|
}),
|
|
)
|
|
.then((files) => {
|
|
sftp.name(reqID, files)
|
|
ods[od].read = true
|
|
})
|
|
.catch(() => {
|
|
sftp.status(reqID, ssh.SFTP_STATUS_CODE.FAILURE)
|
|
})
|
|
})
|
|
})
|
|
|
|
sftp.on("WRITE", (reqID, handle, offset, data) => {
|
|
const fd = handle.readUInt32BE(0)
|
|
if (!fds[fd]) {
|
|
return sftp.status(reqID, ssh.SFTP_STATUS_CODE.NO_SUCH_FILE)
|
|
}
|
|
return fs.write(fd, data, offset, (err) => sftpStatus(reqID, err))
|
|
})
|
|
|
|
sftp.on("CLOSE", (reqID, handle) => {
|
|
const fd = handle.readUInt32BE(0)
|
|
if (!fds[fd]) {
|
|
if (ods[fd]) {
|
|
delete ods[fd]
|
|
return sftp.status(reqID, ssh.SFTP_STATUS_CODE.OK)
|
|
}
|
|
return sftp.status(reqID, ssh.SFTP_STATUS_CODE.NO_SUCH_FILE)
|
|
}
|
|
return fs.close(fd, (err) => sftpStatus(reqID, err))
|
|
})
|
|
|
|
sftp.on("STAT", (reqID, path) => {
|
|
fs.stat(path, (err, stats) => {
|
|
if (err) {
|
|
return sftpStatus(reqID, err)
|
|
}
|
|
return sftp.attrs(reqID, {
|
|
atime: stats.atime.getTime(),
|
|
gid: stats.gid,
|
|
mode: stats.mode,
|
|
mtime: stats.mtime.getTime(),
|
|
size: stats.size,
|
|
uid: stats.uid,
|
|
})
|
|
})
|
|
})
|
|
|
|
sftp.on("MKDIR", (reqID, path) => {
|
|
fs.mkdir(path, (err) => sftpStatus(reqID, err))
|
|
})
|
|
|
|
sftp.on("LSTAT", (reqID, path) => {
|
|
fs.lstat(path, (err, stats) => {
|
|
if (err) {
|
|
return sftpStatus(reqID, err)
|
|
}
|
|
return sftp.attrs(reqID, {
|
|
atime: stats.atimeMs,
|
|
gid: stats.gid,
|
|
mode: stats.mode,
|
|
mtime: stats.mtimeMs,
|
|
size: stats.size,
|
|
uid: stats.uid,
|
|
})
|
|
})
|
|
})
|
|
|
|
sftp.on("REMOVE", (reqID, path) => {
|
|
fs.unlink(path, (err) => sftpStatus(reqID, err))
|
|
})
|
|
|
|
sftp.on("RMDIR", (reqID, path) => {
|
|
fs.rmdir(path, (err) => sftpStatus(reqID, err))
|
|
})
|
|
|
|
sftp.on("REALPATH", (reqID, path) => {
|
|
fs.realpath(path, (pathErr, resolved) => {
|
|
if (pathErr) {
|
|
return sftpStatus(reqID, pathErr)
|
|
}
|
|
fs.stat(path, (statErr, stat) => {
|
|
if (statErr) {
|
|
return sftpStatus(reqID, statErr)
|
|
}
|
|
sftp.name(reqID, [
|
|
{
|
|
filename: resolved,
|
|
longname: resolved,
|
|
attrs: {
|
|
mode: stat.mode,
|
|
uid: stat.uid,
|
|
gid: stat.gid,
|
|
size: stat.size,
|
|
atime: stat.atime.getTime(),
|
|
mtime: stat.mtime.getTime(),
|
|
},
|
|
},
|
|
])
|
|
return
|
|
})
|
|
return
|
|
})
|
|
})
|
|
}
|