Sync up with master

This commit is contained in:
Bryan Ashby 2023-10-12 20:40:57 -06:00
commit 19b6f93be4
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
9 changed files with 258 additions and 109 deletions

16
.vscode/launch.json vendored
View File

@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"runtimeExecutable": "/home/nuskooler/.local/share/rtx/installs/nodejs/16.20.2/bin/node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/main.js"
}
]
}

View File

@ -24,7 +24,7 @@ function ANSIEscapeParser(options) {
this.graphicRendition = {}; this.graphicRendition = {};
this.parseState = { this.parseState = {
re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex
}; };
options = miscUtil.valueWithDefault(options, { options = miscUtil.valueWithDefault(options, {
@ -77,10 +77,24 @@ function ANSIEscapeParser(options) {
self.clearScreen = function () { self.clearScreen = function () {
self.column = 1; self.column = 1;
self.row = 1; self.row = 1;
self.positionUpdated();
self.emit('clear screen'); self.emit('clear screen');
}; };
self.positionUpdated = function () { self.positionUpdated = function () {
if (self.row > self.termHeight) {
if (this.savedPosition) {
this.savedPosition.row -= self.row - self.termHeight;
}
self.emit('scroll', self.row - self.termHeight);
self.row = self.termHeight;
} else if (self.row < 1) {
if (this.savedPosition) {
this.savedPosition.row -= self.row - 1;
}
self.emit('scroll', -(self.row - 1));
self.row = 1;
}
self.emit('position update', self.row, self.column); self.emit('position update', self.row, self.column);
}; };
@ -231,7 +245,7 @@ function ANSIEscapeParser(options) {
self.parseState = { self.parseState = {
// ignore anything past EOF marker, if any // ignore anything past EOF marker, if any
buffer: input.split(String.fromCharCode(0x1a), 1)[0], buffer: input.split(String.fromCharCode(0x1a), 1)[0],
re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex
stop: false, stop: false,
}; };
}; };
@ -271,9 +285,46 @@ function ANSIEscapeParser(options) {
opCode = match[2]; opCode = match[2];
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
escape(opCode, args); // Handle the case where there is no bracket
if (!_.isNil(match[3])) {
opCode = match[3];
args = [];
// no bracket
switch (opCode) {
// save cursor position
case '7':
escape('s', args);
break;
// restore cursor position
case '8':
escape('u', args);
break;
// scroll up
case 'D':
escape('S', args);
break;
// move to next line
case 'E':
// functonality is the same as ESC [ E
escape(opCode, args);
break;
// create a tab at current cursor position
case 'H':
literal('\t');
break;
// scroll down
case 'M':
escape('T', args);
break;
}
} else {
escape(opCode, args);
}
//self.emit('chunk', match[0]);
self.emit('control', match[0], opCode, args); self.emit('control', match[0], opCode, args);
} }
} while (0 !== re.lastIndex); } while (0 !== re.lastIndex);
@ -281,8 +332,8 @@ function ANSIEscapeParser(options) {
if (pos < buffer.length) { if (pos < buffer.length) {
var lastBit = buffer.slice(pos); var lastBit = buffer.slice(pos);
// :TODO: check for various ending LF's, not just DOS \r\n // handles either \r\n or \n
if ('\r\n' === lastBit.slice(-2).toString()) { if ('\n' === lastBit.slice(-1).toString()) {
switch (self.trailingLF) { switch (self.trailingLF) {
case 'default': case 'default':
// //
@ -290,14 +341,14 @@ function ANSIEscapeParser(options) {
// if we're going to end on termHeight // if we're going to end on termHeight
// //
if (this.termHeight === self.row) { if (this.termHeight === self.row) {
lastBit = lastBit.slice(0, -2); lastBit = lastBit.slice(0, -1);
} }
break; break;
case 'omit': case 'omit':
case 'no': case 'no':
case false: case false:
lastBit = lastBit.slice(0, -2); lastBit = lastBit.slice(0, -1);
break; break;
} }
} }
@ -308,48 +359,6 @@ function ANSIEscapeParser(options) {
self.emit('complete'); self.emit('complete');
}; };
/*
self.parse = function(buffer, savedRe) {
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
// :TODO: move this to "constants" section @ top
var re = /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g;
var pos = 0;
var match;
var opCode;
var args;
// ignore anything past EOF marker, if any
buffer = buffer.split(String.fromCharCode(0x1a), 1)[0];
do {
pos = re.lastIndex;
match = re.exec(buffer);
if(null !== match) {
if(match.index > pos) {
parseMCI(buffer.slice(pos, match.index));
}
opCode = match[2];
args = getArgArray(match[1].split(';'));
escape(opCode, args);
self.emit('chunk', match[0]);
}
} while(0 !== re.lastIndex);
if(pos < buffer.length) {
parseMCI(buffer.slice(pos));
}
self.emit('complete');
};
*/
function escape(opCode, args) { function escape(opCode, args) {
let arg; let arg;
@ -382,6 +391,35 @@ function ANSIEscapeParser(options) {
self.moveCursor(-arg, 0); self.moveCursor(-arg, 0);
break; break;
// line feed
case 'E':
arg = isNaN(args[0]) ? 1 : args[0];
if (this.row + arg > this.termHeight) {
this.emit('scroll', arg - (this.termHeight - this.row));
self.moveCursor(0, this.termHeight);
} else {
self.moveCursor(0, arg);
}
break;
// reverse line feed
case 'F':
arg = isNaN(args[0]) ? 1 : args[0];
if (this.row - arg < 1) {
this.emit('scroll', -(arg - this.row));
self.moveCursor(0, 1 - this.row);
} else {
self.moveCursor(0, -arg);
}
break;
// absolute horizontal cursor position
case 'G':
arg = isNaN(args[0]) ? 1 : args[0];
self.column = Math.max(1, arg);
self.positionUpdated();
break;
case 'f': // horiz & vertical case 'f': // horiz & vertical
case 'H': // cursor position case 'H': // cursor position
//self.row = args[0] || 1; //self.row = args[0] || 1;
@ -392,14 +430,32 @@ function ANSIEscapeParser(options) {
self.positionUpdated(); self.positionUpdated();
break; break;
// save position // erase display/screen
case 's': case 'J':
self.saveCursorPosition(); if (isNaN(args[0]) || 0 === args[0]) {
self.emit('erase rows', self.row, self.termHeight);
} else if (1 === args[0]) {
self.emit('erase rows', 1, self.row);
} else if (2 === args[0]) {
self.clearScreen();
}
break; break;
// restore position // erase text in line
case 'u': case 'K':
self.restoreCursorPosition(); if (isNaN(args[0]) || 0 === args[0]) {
self.emit('erase columns', self.row, self.column, self.termWidth);
} else if (1 === args[0]) {
self.emit('erase columns', self.row, 1, self.column);
} else if (2 === args[0]) {
self.emit('erase columns', self.row, 1, self.termWidth);
}
break;
// insert line
case 'L':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('insert line', self.row, arg);
break; break;
// set graphic rendition // set graphic rendition
@ -471,14 +527,50 @@ function ANSIEscapeParser(options) {
self.emit('sgr update', self.graphicRendition); self.emit('sgr update', self.graphicRendition);
break; // m break; // m
// :TODO: s, u, K // save position
case 's':
self.saveCursorPosition();
break;
// erase display/screen // Scroll up
case 'J': case 'S':
// :TODO: Handle other 'J' types! arg = isNaN(args[0]) ? 1 : args[0];
if (2 === args[0]) { self.emit('scroll', arg);
self.clearScreen(); break;
}
// Scroll down
case 'T':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('scroll', -arg);
break;
// restore position
case 'u':
self.restoreCursorPosition();
break;
// clear
case 'U':
self.clearScreen();
break;
// delete line
// TODO: how should we handle 'M'?
case 'Y':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('delete line', self.row, arg);
break;
// back tab
case 'Z':
// calculate previous tabstop
self.column = Math.max(1, self.column - (self.column % 8 || 8));
self.positionUpdated();
break;
case '@':
// insert column(s)
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('insert columns', self.row, self.column, arg);
break; break;
} }
} }

View File

@ -208,17 +208,17 @@ module.exports = class ArchiveUtil {
// pty.js doesn't currently give us a error when things fail, // pty.js doesn't currently give us a error when things fail,
// so we have this horrible, horrible hack: // so we have this horrible, horrible hack:
let err; let err;
proc.once('data', d => { proc.onData(d => {
if (_.isString(d) && d.startsWith('execvp(3) failed.')) { if (_.isString(d) && d.startsWith('execvp(3) failed.')) {
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`); err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
} }
}); });
proc.once('exit', exitCode => { proc.onExit(exitEvent => {
return cb( return cb(
exitCode exitEvent.exitCode
? Errors.ExternalProcess( ? Errors.ExternalProcess(
`${action} failed with exit code: ${exitCode}` `${action} failed with exit code: ${exitEvent.exitCode}`
) )
: err : err
); );
@ -358,10 +358,12 @@ module.exports = class ArchiveUtil {
output += data; output += data;
}); });
proc.once('exit', exitCode => { proc.onExit(exitEvent => {
if (exitCode) { if (exitEvent.exitCode) {
return cb( return cb(
Errors.ExternalProcess(`List failed with exit code: ${exitCode}`) Errors.ExternalProcess(
`List failed with exit code: ${exitEvent.exitCode}`
)
); );
} }

View File

@ -316,6 +316,74 @@ function display(client, art, options, cb) {
} }
}); });
// Remove any MCI's that are in erased rows
ansiParser.on('erase row', (startRow, endRow) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] >= startRow && mciInfo.position[0] <= endRow) {
delete mciMap[mapKey];
}
});
});
// Remove any MCI's that are in erased columns
ansiParser.on('erase columns', (row, startCol, endCol) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (
mciInfo.position[0] === row &&
mciInfo.position[1] >= startCol &&
mciInfo.position[1] <= endCol
) {
delete mciMap[mapKey];
}
});
});
ansiParser.on('insert columns', (row, startCol, numCols) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] === row && mciInfo.position[1] >= startCol) {
mciInfo.position[1] += numCols;
if (mciInfo.position[1] > client.term.termWidth) {
delete mciMap[mapKey];
}
}
});
});
// Clear the screen, removing any MCI's
ansiParser.on('clear screen', () => {
_.forEach(mciMap, (mciInfo, mapKey) => {
delete mciMap[mapKey];
});
});
ansiParser.on('scroll', scrollY => {
_.forEach(mciMap, mciInfo => {
mciInfo.position[0] -= scrollY;
});
});
ansiParser.on('insert line', (row, numLines) => {
_.forEach(mciMap, mciInfo => {
if (mciInfo.position[0] >= row) {
mciInfo.position[0] += numLines;
}
});
});
ansiParser.on('delete line', (row, numLines) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] >= row) {
if (mciInfo.position[0] < row + numLines) {
// unlike scrolling, the rows are actually gone,
// so we need to delete any MCI's that are in them
delete mciMap[mapKey];
} else {
mciInfo.position[0] -= numLines;
}
}
});
});
ansiParser.on('literal', literal => client.term.write(literal, false)); ansiParser.on('literal', literal => client.term.write(literal, false));
ansiParser.on('control', control => client.term.rawWrite(control)); ansiParser.on('control', control => client.term.rawWrite(control));

View File

@ -115,9 +115,10 @@ module.exports = class Door {
spawnOptions spawnOptions
); );
prePty.once('exit', exitCode => { prePty.onExit(exitEvent => {
const { exitCode, signal } = exitEvent;
this.client.log.info( this.client.log.info(
{ exitCode: exitCode }, { exitCode, signal },
'Door pre-command exited' 'Door pre-command exited'
); );
return callback(null); return callback(null);
@ -167,7 +168,7 @@ module.exports = class Door {
this.doorPty.onData(this.doorDataHandler.bind(this)); this.doorPty.onData(this.doorDataHandler.bind(this));
this.doorPty.once('close', () => { this.doorPty.onExit((/*exitEvent*/) => {
return this.restoreIo(this.doorPty); return this.restoreIo(this.doorPty);
}); });
} else if ('socket' === this.io) { } else if ('socket' === this.io) {
@ -180,8 +181,9 @@ module.exports = class Door {
); );
} }
this.doorPty.once('exit', exitCode => { this.doorPty.onExit(exitEvent => {
this.client.log.info({ exitCode: exitCode }, 'Door exited'); const { exitCode, signal } = exitEvent;
this.client.log.info({ exitCode, signal }, 'Door exited');
if (this.sockServer) { if (this.sockServer) {
this.sockServer.close(); this.sockServer.close();

View File

@ -167,17 +167,21 @@ class ScheduledEvent {
return cb(e); return cb(e);
} }
proc.once('exit', exitCode => { proc.onExit(exitEvent => {
if (exitCode) { if (exitEvent.exitCode) {
Log.warn( Log.warn(
{ eventName: this.name, action: this.action, exitCode: exitCode }, {
eventName: this.name,
action: this.action,
exitCode: exitEvent.exitCode,
},
'Bad exit code while performing scheduled event action' 'Bad exit code while performing scheduled event action'
); );
} }
return cb( return cb(
exitCode exitEvent.exitCode
? Errors.ExternalProcess( ? Errors.ExternalProcess(
`Bad exit code while performing scheduled event action: ${exitCode}` `Bad exit code while performing scheduled event action: ${exitEvent.exitCode}`
) )
: null : null
); );

View File

@ -485,13 +485,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
}); });
externalProc.once('close', () => { externalProc.onExit(exitEvent => {
return this.restorePipeAfterExternalProc(); const { exitCode, signal } = exitEvent;
});
externalProc.once('exit', exitCode => {
this.client.log.debug( this.client.log.debug(
{ cmd: cmd, args: args, exitCode: exitCode }, { cmd: cmd, args: args, exitCode, signal },
'Process exited' 'Process exited'
); );

View File

@ -56,7 +56,7 @@
"minimist": "^1.2.6", "minimist": "^1.2.6",
"moment": "2.29.4", "moment": "2.29.4",
"nntp-server": "3.1.0", "nntp-server": "3.1.0",
"node-pty": "0.10.1", "node-pty": "1.0.0",
"nodemailer": "6.7.7", "nodemailer": "6.7.7",
"otplib": "11.0.1", "otplib": "11.0.1",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
@ -65,8 +65,8 @@
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"sqlite3": "5.1.6", "sqlite3": "5.1.6",
"sqlite3-trans": "1.3.0", "sqlite3-trans": "1.3.0",
"string-strip-html": "8.4.0",
"ssh2": "1.14.0", "ssh2": "1.14.0",
"string-strip-html": "8.4.0",
"systeminformation": "5.21.7", "systeminformation": "5.21.7",
"telnet-socket": "0.2.4", "telnet-socket": "0.2.4",
"temptmp": "^1.1.0", "temptmp": "^1.1.0",

View File

@ -1921,12 +1921,12 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
node-pty@0.10.1: node-pty@1.0.0:
version "0.10.1" version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg== integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
dependencies: dependencies:
nan "^2.14.0" nan "^2.17.0"
nodemailer@6.7.7: nodemailer@6.7.7:
version "6.7.7" version "6.7.7"