Compare commits

..

No commits in common. "29f17aa874d0ea3343928897695859b5dadf7f07" and "2e9d5071d9c281d148f1cc192964b7ad8505591f" have entirely different histories.

8 changed files with 163 additions and 247 deletions

View file

@ -23,7 +23,7 @@ install: all
mkdir -p ${CHROOT}/var/www mkdir -p ${CHROOT}/var/www
cp ${WEBASSETS} ${CHROOT}/var/www cp ${WEBASSETS} ${CHROOT}/var/www
cp rlgwebd.service /usr/lib/systemd/system cp rlgwebd.service /usr/lib/systemd/system
if test ! -f /etc/rlgwebd.conf; then cp rlgwebd.conf /etc; fi if test ! -f /etc/rlgwebd.conf; cp rlgwebd.conf /etc; fi
# Libraries are not removed. Something else might be using them. # Libraries are not removed. Something else might be using them.
uninstall: uninstall:

View file

@ -5,11 +5,12 @@ browser. It is intended to be compatible with dgamelaunch.
Node Node
--- ---
RLGWebD is currently being updated to work with Node v10.x. RLGWebD currently works with Node v0.10.
It requires the 'posix', 'node-pty', and 'websocket' modules. Currently, It requires the 'posix', 'pty.js', and 'websocket' modules. Currently,
it expects them to be installed in "/var/local/lib/node_modules". It it expects them to be installed in the global location, which is
is not recommended to run npm as root when installing the modules. "/usr/lib/node_modules". It is planned to eventually use a different
location so that npm will not need to run as root.
init init
--- ---
@ -21,11 +22,14 @@ a proper initscript, but it could form the basis of one.
Configuration Configuration
--- ---
A configuration file is installed at /etc/rlgwebd.conf. It contains a You can set some options by changing some variables in the first few
list of options. lines of the rlgwebd script:
If the domain_name option and the SSL-related options are set, rlgwebd Option Variable Default
will use HTTPS instead of insecure HTTP.
Chroot path chrootDir /var/dgl
Username dropToUser rodney
Server port httpPort 8080
If you change the chroot location, change it in the first line of the If you change the chroot location, change it in the first line of the
Makefile too. Makefile too.
@ -55,7 +59,6 @@ Running "make install" will:
Copy the C programs and the libraries they need into the chroot Copy the C programs and the libraries they need into the chroot
Install the main RLGWebD script in /usr/local/bin Install the main RLGWebD script in /usr/local/bin
Place the systemd unit file in the proper directory Place the systemd unit file in the proper directory
Copy a configuration file into /etc
If you don't use systemd, or want to change the installation locations, If you don't use systemd, or want to change the installation locations,
you will have to edit the Makefile. you will have to edit the Makefile.

View file

@ -1,8 +1,8 @@
#!/bin/sh #!/bin/sh
NODE_PATH=/var/local/lib/node_modules NODE_PATH=/usr/lib/node_modules
LOGFILE=/var/log/rlgwebd.log LOGFILE=/var/log/rlgwebd.log
CTLSOCKET=/var/run/rlgwebd/rlgwebd.sock CTLSOCKET=/var/run/rlgwebd.sock
RLGWEBDJS=/usr/local/bin/rlgwebd RLGWEBDJS=/usr/local/bin/rlgwebd
export NODE_PATH export NODE_PATH

154
rlgwebd
View file

@ -1,38 +1,35 @@
#!/usr/bin/env node #!/usr/bin/env node
const http = require('http'); var http = require('http');
const https = require('https'); var https = require('https');
const net = require('net'); var net = require('net');
const url = require('url'); var url = require('url');
const path = require('path'); var path = require('path');
const fs = require('fs'); var fs = require('fs');
const events = require('events'); var events = require('events');
const child_process = require('child_process'); var child_process = require('child_process');
// Dependencies // Dependencies
const posix = require("posix"); var posix = require("posix");
const pty = require("node-pty"); var pty = require("pty.js");
const WebSocketServer = require("websocket").server; var WebSocketServer = require("websocket").server;
const errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
"Login failed", "Already playing", "Game launch failed",
"Server shutting down", "Game not in progress" ];
/* Default options */ /* Default options */
var rlgwebd_options = { var rlgwebd_options = {
control_socket: "/var/run/rlgwebd/rlgwebd.sock", control_socket: "/var/run/rlgwebd.sock",
port: 8080, http_port: 8080,
https_port: 8081,
chrootDir: "/var/dgl/", chrootDir: "/var/dgl/",
username: "rodney", username: "rodney",
static_root: "/var/www/" static_root: "/var/www/"
}; };
/* Read configuration from a file */ /* Read configuration from a file */
const config_file = "/etc/rlgwebd.conf"; var config_file = "/etc/rlgwebd.conf";
var config_lines = read_or_die(config_file, "Configuration file").toString().split('\n'); var config_lines = read_or_die(config_file, "Configuration file").toString().split('\n');
for (let conf_line of config_lines) { for (var i = 0; i < config_lines.length; i++) {
if (conf_line.length > 0 && conf_line[0] != '#') { if (config_lines[i].length > 0 && config_lines[i][0] != '#') {
var config_fields = conf_line.split('='); var config_fields = config_lines[i].split('=');
if (config_fields.length < 2) if (config_fields.length < 2)
continue; continue;
var option_name = config_fields[0].trim(); var option_name = config_fields[0].trim();
@ -47,13 +44,13 @@ if ("domain_name" in rlgwebd_options && "keyfile" in rlgwebd_options &&
"certfile" in rlgwebd_options) "certfile" in rlgwebd_options)
rlgwebd_options["use_https"] = true; rlgwebd_options["use_https"] = true;
const clearbufs = [ var clearbufs = [
Buffer.from([27, 91, 72, 27, 91, 50, 74]), // xterm: CSI H CSI 2J new Buffer([27, 91, 72, 27, 91, 50, 74]), // xterm: CSI H CSI 2J
Buffer.from([27, 91, 72, 27, 91, 74]) // screen: CSI H CSI J new Buffer([27, 91, 72, 27, 91, 74]) // screen: CSI H CSI J
]; ];
/* Data on the games available. */ /* Data on the games available. */
const games = { var games = {
"rogue3": { "rogue3": {
"name": "Rogue V3", "name": "Rogue V3",
"uname": "rogue3", "uname": "rogue3",
@ -113,7 +110,7 @@ function BaseGame() {
this.watchers = []; this.watchers = [];
/* replaybuf holds the output since the last screen clear, so watchers can /* replaybuf holds the output since the last screen clear, so watchers can
* begin with a complete screen. replaylen is the number of bytes stored. */ * begin with a complete screen. replaylen is the number of bytes stored. */
this.replaybuf = Buffer.alloc(1024); this.replaybuf = new Buffer(1024);
this.replaylen = 0; this.replaylen = 0;
/* Time of last activity. */ /* Time of last activity. */
this.lasttime = new Date(); this.lasttime = new Date();
@ -129,12 +126,12 @@ BaseGame.prototype.tag = function () {
BaseGame.prototype.framepush = function(chunk) { BaseGame.prototype.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */ /* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) { if (isclear(chunk)) {
this.replaybuf = Buffer.alloc(1024); this.replaybuf = new Buffer(1024);
this.replaylen = 0; this.replaylen = 0;
} }
/* Make sure there's space. */ /* Make sure there's space. */
while (this.replaybuf.length < chunk.length + this.replaylen) { while (this.replaybuf.length < chunk.length + this.replaylen) {
var nbuf = Buffer.alloc(this.replaybuf.length * 2); var nbuf = new Buffer(this.replaybuf.length * 2);
this.replaybuf.copy(nbuf, 0, 0, this.replaylen); this.replaybuf.copy(nbuf, 0, 0, this.replaylen);
this.replaybuf = nbuf; this.replaybuf = nbuf;
if (this.replaybuf.length > 65536) { if (this.replaybuf.length > 65536) {
@ -214,9 +211,7 @@ function TermSession(gname, pname, wsReq) {
var progressdir = path.join("/dgldir/inprogress", this.gname); var progressdir = path.join("/dgldir/inprogress", this.gname);
this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec"); this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n'; var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n';
fs.writeFile(this.lock, lmsg, "utf8", function (err) { fs.writeFile(this.lock, lmsg, "utf8");
if (err) tslog("Locking failed: %s", err);
});
var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname, var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname,
ts + ".ttyrec"); ts + ".ttyrec");
this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
@ -244,8 +239,8 @@ TermSession.prototype = new BaseGame();
/* Currently this also sends to the player and any watchers. */ /* Currently this also sends to the player and any watchers. */
TermSession.prototype.write_ttyrec = function (datastr) { TermSession.prototype.write_ttyrec = function (datastr) {
this.lasttime = new Date(); this.lasttime = new Date();
var buf = Buffer.from(datastr); var buf = new Buffer(datastr);
var chunk = Buffer.alloc(buf.length + 12); var chunk = new Buffer(buf.length + 12);
/* TTYREC headers */ /* TTYREC headers */
chunk.writeUInt32LE(Math.floor(this.lasttime.getTime() / 1000), 0); chunk.writeUInt32LE(Math.floor(this.lasttime.getTime() / 1000), 0);
chunk.writeUInt32LE(1000 * (this.lasttime.getTime() % 1000), 4); chunk.writeUInt32LE(1000 * (this.lasttime.getTime() % 1000), 4);
@ -279,7 +274,7 @@ TermSession.prototype.input_msg = function (message) {
if (hexstr.length % 2 != 0) { if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1); hexstr = hexstr.slice(0, -1);
} }
var keybuf = Buffer.from(hexstr, "hex"); var keybuf = new Buffer(hexstr, "hex");
this.write(keybuf); this.write(keybuf);
} }
}; };
@ -292,9 +287,7 @@ TermSession.prototype.close = function () {
TermSession.prototype.destroy = function () { TermSession.prototype.destroy = function () {
var tag = this.tag(); var tag = this.tag();
fs.unlink(this.lock, function (err) { fs.unlink(this.lock);
if (err) tslog("Lock removal failed: %s", err);
});
this.record.end(); this.record.end();
var watchsocks = this.watchers; var watchsocks = this.watchers;
this.watchers = []; this.watchers = [];
@ -363,7 +356,7 @@ DglSession.prototype.startchunk = function () {
if (this.reading) if (this.reading)
return; return;
this.reading = true; this.reading = true;
var header = Buffer.alloc(12); var header = new Buffer(12);
fs.read(this.fd, header, 0, 12, this.rpos, this.datachunk.bind(this)); fs.read(this.fd, header, 0, 12, this.rpos, this.datachunk.bind(this));
}; };
@ -384,7 +377,7 @@ DglSession.prototype.datachunk = function (err, n, buf) {
// Something is probably wrong... // Something is probably wrong...
tslog("DGL %s: looking for %d bytes", this.tag(), datalen); tslog("DGL %s: looking for %d bytes", this.tag(), datalen);
} }
var databuf = Buffer.alloc(datalen); var databuf = new Buffer(datalen);
fs.read(this.fd, databuf, 0, datalen, this.rpos, this.handledata.bind(this)); fs.read(this.fd, databuf, 0, datalen, this.rpos, this.handledata.bind(this));
}; };
@ -401,9 +394,9 @@ DglSession.prototype.handledata = function (err, n, buf) {
/* Process the data */ /* Process the data */
this.framepush(buf); this.framepush(buf);
var wmsg = JSON.stringify({"t": "d", "d": buf.toString("hex")}); var wmsg = JSON.stringify({"t": "d", "d": buf.toString("hex")});
for (let watcher of this.watchers) { for (var i = 0; i < this.watchers.length; i++) {
if (watcher.connected) if (this.watchers[i].connected)
watcher.sendUTF(wmsg); this.watchers[i].sendUTF(wmsg);
} }
this.emit("data", buf); this.emit("data", buf);
/* Recurse. */ /* Recurse. */
@ -418,8 +411,6 @@ DglSession.prototype.notifier = function (ev, finame) {
}; };
DglSession.prototype.close = function () { DglSession.prototype.close = function () {
/* The watcher might not be open yet. */
if ("recwatcher" in this)
this.recwatcher.close(); this.recwatcher.close();
/* Ensure all data is handled before quitting. */ /* Ensure all data is handled before quitting. */
this.startchunk(); this.startchunk();
@ -429,9 +420,7 @@ DglSession.prototype.close = function () {
if (connlist[i].connected) if (connlist[i].connected)
connlist[i].close(); connlist[i].close();
} }
fs.close(this.fd, function (err) { fs.close(this.fd);
if (err) tslog("PTY close failed: %s", err);
});
this.emit("close"); this.emit("close");
gamemux.emit('end', this.gname, this.pname); gamemux.emit('end', this.gname, this.pname);
tslog("DGL %s: closed", this.tag()); tslog("DGL %s: closed", this.tag());
@ -498,11 +487,8 @@ function checksaved(user, game, callback, args) {
var savedirc = game.uname + "save"; var savedirc = game.uname + "save";
var basename = String(pwent.uid) + "-" + user + game.suffix; var basename = String(pwent.uid) + "-" + user + game.suffix;
var savefile = path.join("/var/games/roguelike", savedirc, basename); var savefile = path.join("/var/games/roguelike", savedirc, basename);
fs.access(savefile, function (err) { fs.exists(savefile, function (exist) {
if (err) args.unshift(exist);
args.unshift(false);
else
args.unshift(true);
callback.apply(null, args); callback.apply(null, args);
}); });
} }
@ -581,8 +567,8 @@ function bufncmp(buf1, buf2, n) {
} }
function isclear(buf) { function isclear(buf) {
for (let clearer of clearbufs) { for (var i = 0; i < clearbufs.length; i++) {
if (bufncmp(buf, clearer, clearer.length)) if (bufncmp(buf, clearbufs[i], clearbufs[i].length))
return true; return true;
} }
return false; return false;
@ -702,15 +688,10 @@ function login(req, res, formdata) {
function regsetup(username) { function regsetup(username) {
function regsetup_l2(err) { function regsetup_l2(err) {
for (var g in games) { for (var g in games) {
fs.mkdir(path.join("/dgldir/ttyrec", username, games[g].uname), 0755, fs.mkdir(path.join("/dgldir/ttyrec", username, games[g].uname), 0755);
function (err) {
if (err) tslog("ttyrec mkdir failed: %s", err);
});
} }
} }
fs.mkdir(path.join("/dgldir/userdata", username), 0755, function (err) { fs.mkdir(path.join("/dgldir/userdata", username), 0755);
if (err) tslog("Userdata mkdir failed: %s", err);
});
fs.mkdir(path.join("/dgldir/ttyrec/", username), 0755, regsetup_l2); fs.mkdir(path.join("/dgldir/ttyrec/", username), 0755, regsetup_l2);
} }
@ -798,9 +779,7 @@ function stopgame(res, formdata) {
if (err.code == "ESRCH") { if (err.code == "ESRCH") {
var nodere = RegExp("^" + pname + ":node:"); var nodere = RegExp("^" + pname + ":node:");
if (fname.match(nodere)) { if (fname.match(nodere)) {
fs.unlink(fullfile, function (err) { fs.unlink(fullfile);
if (err) tslog("Stale lock removal failed: %s", err);
});
} }
} }
} }
@ -853,8 +832,6 @@ function startProgressWatcher() {
} }
function serveStatic(req, res, fname) { function serveStatic(req, res, fname) {
if (fname[0] !== "/")
fname = "/" + fname;
var nname = path.normalize(fname); var nname = path.normalize(fname);
if (nname == "" || nname == "/") if (nname == "" || nname == "/")
nname = "index.html"; nname = "index.html";
@ -862,9 +839,9 @@ function serveStatic(req, res, fname) {
path.join(nname, "index.html"); /* it was a directory */ path.join(nname, "index.html"); /* it was a directory */
var realname = path.join(rlgwebd_options.static_root, nname); var realname = path.join(rlgwebd_options.static_root, nname);
var extension = path.extname(realname); var extension = path.extname(realname);
fs.access(realname, function (access_err) { fs.exists(realname, function (exists) {
var resheaders = {}; var resheaders = {};
if (access_err || !extension || extension == ".html") if (!exists || !extension || extension == ".html")
resheaders["Content-Type"] = "text/html; charset=utf-8"; resheaders["Content-Type"] = "text/html; charset=utf-8";
else if (extension == ".png") else if (extension == ".png")
resheaders["Content-Type"] = "image/png"; resheaders["Content-Type"] = "image/png";
@ -876,7 +853,7 @@ function serveStatic(req, res, fname) {
resheaders["Content-Type"] = "image/svg+xml"; resheaders["Content-Type"] = "image/svg+xml";
else else
resheaders["Content-Type"] = "application/octet-stream"; resheaders["Content-Type"] = "application/octet-stream";
if (!access_err) { if (exists) {
fs.readFile(realname, function (error, data) { fs.readFile(realname, function (error, data) {
if (error) { if (error) {
res.writeHead(500, {}); res.writeHead(500, {});
@ -1047,6 +1024,10 @@ function setuinfo(req, res, postdata) {
} }
} }
var errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
"Login failed", "Already playing", "Game launch failed",
"Server shutting down", "Game not in progress" ];
function sendError(res, ecode, msg, box) { function sendError(res, ecode, msg, box) {
res.writeHead(200, { "Content-Type": "application/json" }); res.writeHead(200, { "Content-Type": "application/json" });
var edict = {"t": "E"}; var edict = {"t": "E"};
@ -1283,21 +1264,6 @@ if (rlgwebd_options.use_https) {
tls_options.ca = read_or_die(rlgwebd_options.cafile, "CA file"); tls_options.ca = read_or_die(rlgwebd_options.cafile, "CA file");
}; };
/* Make sure the socket directory is secure. */
var socket_dir = path.dirname(rlgwebd_options.control_socket);
try {
fs.mkdirSync(socket_dir, 0o700);
}
catch (err) {
if (err.code == "EEXIST") {
fs.chownSync(socket_dir, 0, 0);
fs.chmodSync(socket_dir, 0o700);
}
else {
throw err;
}
}
/* Open the control socket before chrooting where it can't be found */ /* Open the control socket before chrooting where it can't be found */
var ctlServer = net.createServer(function (sock) { var ctlServer = net.createServer(function (sock) {
sock.on('data', consoleHandler); sock.on('data', consoleHandler);
@ -1322,21 +1288,19 @@ ctlServer.listen(rlgwebd_options.control_socket, function () {
tslog("Could not drop permissions: %s", err); tslog("Could not drop permissions: %s", err);
process.exit(1); process.exit(1);
} }
if (rlgwebd_options.use_https) {
httpServer = https.createServer(tls_options, webHandler);
httpServer.listen(rlgwebd_options.port);
tslog('rlgwebd running on port %d (TLS)', rlgwebd_options.port);
wsServer = new WebSocketServer({"httpServer": httpServer});
wsServer.on("request", wsHandler);
tslog('Secure WebSockets are online');
}
else {
httpServer = http.createServer(webHandler); httpServer = http.createServer(webHandler);
httpServer.listen(rlgwebd_options.port); httpServer.listen(rlgwebd_options.http_port);
tslog('rlgwebd running on port %d', rlgwebd_options.port); tslog('rlgwebd running on port %d', rlgwebd_options.http_port);
wsServer = new WebSocketServer({"httpServer": httpServer}); wsServer = new WebSocketServer({"httpServer": httpServer});
wsServer.on("request", wsHandler); wsServer.on("request", wsHandler);
tslog('WebSockets are online'); tslog('WebSockets are online');
if (rlgwebd_options.use_https) {
var httpsServer = https.createServer(tls_options, webHandler);
httpsServer.listen(rlgwebd_options.https_port);
tslog('TLS running on port %d', rlgwebd_options.https_port);
var wssServer = new WebSocketServer({"httpServer": httpsServer});
wssServer.on("request", wsHandler);
tslog('Secure WebSockets are online');
} }
progressWatcher = startProgressWatcher(); progressWatcher = startProgressWatcher();
setInterval(pushStatus, 40000); setInterval(pushStatus, 40000);

View file

@ -1,19 +1,22 @@
#!/usr/bin/env node #!/usr/bin/env node
var net = require('net'); var net = require('net');
var sockpath = "/var/run/rlgwebd/rlgwebd.sock"; var domain = require('domain');
var sockpath = "/var/run/rlgwebd.sock";
var sock = new net.Socket(); var dom = domain.create();
sock.on('error', function (err) { dom.on('error', function (err) {
console.log("Cannot connect to " + sockpath + ", rlgwebd already stopped."); console.log("Cannot connect to " + sockpath + ", rlgwebd already stopped.");
process.exit(0); process.exit(0);
}); });
sock.connect(sockpath, function () { dom.run(function () {
sock.on('close', function (had_error) { var sock = net.connect(sockpath, function () {
sock.on('close', function () {
if (process.argv[2] == "debug") if (process.argv[2] == "debug")
console.log("Control socket closed"); console.log("Control socket closed");
}); });
sock.write("quit\n"); sock.write("quit\n");
}); });
});

View file

@ -3,9 +3,11 @@
# These values are set by default: # These values are set by default:
# Location of the socket for start/stop commands # Location of the socket for start/stop commands
#control_socket = /var/run/rlgwebd/rlgwebd.sock #control_socket = /var/run/rlgwebd.sock
# Port number to bind # Port number to bind
#port = 8080 #http_port = 8080
# Port number for HTTPS
#https_port = 8081
# Path to the dgamelaunch installation to chroot into # Path to the dgamelaunch installation to chroot into
# If you change this, change the Makefile too # If you change this, change the Makefile too
#chrootDir = /var/dgl/ #chrootDir = /var/dgl/

View file

@ -4,7 +4,7 @@ After=network.target syslog.target
[Service] [Service]
Type=simple Type=simple
Environment=NODE_PATH=/var/local/lib/node_modules Environment=NODE_PATH=/usr/lib/node_modules
ExecStart=/usr/local/bin/rlgwebd ExecStart=/usr/local/bin/rlgwebd
ExecStop=/usr/local/bin/rlgwebd-stop ExecStop=/usr/local/bin/rlgwebd-stop
Restart=on-failure Restart=on-failure

View file

@ -71,7 +71,6 @@ var termemu = {
scrB: 0, // init() will set this properly scrB: 0, // init() will set this properly
c: null, // Contains cursor position and text attributes c: null, // Contains cursor position and text attributes
offedge: false, // Going off the edge doesn't mean adding a new line offedge: false, // Going off the edge doesn't mean adding a new line
lastcode: 0, // Last printed character
clearAttrs: function () { clearAttrs: function () {
/* Make sure to reset ALL attribute properties and NOTHING else. */ /* Make sure to reset ALL attribute properties and NOTHING else. */
this.c.bold = false; this.c.bold = false;
@ -461,7 +460,6 @@ var termemu = {
this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]); this.screen.replaceChild(this.makeRow(), this.screen.childNodes[i]);
} }
this.flipCursor(); // make it appear in the new row this.flipCursor(); // make it appear in the new row
this.lastcode = 0;
return; return;
}, },
write: function (codes) { write: function (codes) {
@ -533,10 +531,6 @@ var termemu = {
debug(1, "Unrecognized sequence ESC " + codes[i].toString(16)); debug(1, "Unrecognized sequence ESC " + codes[i].toString(16));
this.comseq = []; this.comseq = [];
} }
if (this.comseq.length == 0) {
// A complete sequence was processed, clear lastcode.
this.lastcode = 0;
}
} }
else if (this.comseq.length == 2 && this.comseq[0] == 27) { else if (this.comseq.length == 2 && this.comseq[0] == 27) {
/* An ESC C N sequence. Not implemented. Doesn't check validity /* An ESC C N sequence. Not implemented. Doesn't check validity
@ -561,7 +555,6 @@ var termemu = {
String.fromCharCode(this.comseq[1]) + " 0x" + String.fromCharCode(this.comseq[1]) + " 0x" +
codes[i].toString(16)); codes[i].toString(16));
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
else if (this.comseq[0] == 157) { else if (this.comseq[0] == 157) {
/* Commands beginning with OSC */ /* Commands beginning with OSC */
@ -573,7 +566,6 @@ var termemu = {
debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence"); debug(0, "Got " + (this.comseq.length - 1) + "-byte OSC sequence");
this.oscProcess(); this.oscProcess();
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
else else
this.comseq.push(codes[i]); this.comseq.push(codes[i]);
@ -590,17 +582,15 @@ var termemu = {
/* Chars in csiPre can only occur right after the CSI */ /* Chars in csiPre can only occur right after the CSI */
debug(1, "Invalid CSI sequence: misplaced prefix"); debug(1, "Invalid CSI sequence: misplaced prefix");
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
else else
this.comseq.push(codes[i]); this.comseq.push(codes[i]);
} }
else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 && else if (csiPost.indexOf(this.comseq[this.comseq.length - 1]) >= 0 &&
!csiFinal(codes[i])) { !csiFinal(codes[i])) {
/* Chars in csiPost must come right before the final char */ /* Chars is csiPost must come right before the final char */
debug(1, "Invalid CSI sequence: misplaced postfix"); debug(1, "Invalid CSI sequence: misplaced postfix");
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 || else if ((codes[i] >= 48 && codes[i] <= 57) || codes[i] == 59 ||
csiPost.indexOf(codes[i]) >= 0) { csiPost.indexOf(codes[i]) >= 0) {
@ -615,49 +605,30 @@ var termemu = {
else { else {
debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16)); debug(1, "Invalid CSI sequence: unknown code " + codes[i].toString(16));
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
} }
else { else {
debug(1, "Unknown sequence with " + this.comseq[0].toString(16)); debug(1, "Unknown sequence with " + this.comseq[0].toString(16));
this.comseq = []; this.comseq = [];
this.lastcode = 0;
} }
continue;
} }
else if ((codes[i] >= 32 && codes[i] < 127) || codes[i] >= 160) { /* Treat it as a single character. */
/* If it's ASCII, it's printable; take a risk on anything higher */ if (codes[i] == 5) {
if ((this.c.cset == "0") && (codes[i] in decChars)) {
// DEC special character set
this.lastcode = decChars[codes[i]];
}
else {
this.lastcode = codes[i];
}
this.placechar(String.fromCharCode(this.lastcode));
}
else {
/* Treat it as a single control character. */
this.singleCtl(codes[i]);
}
}
return;
},
singleCtl: function (ctlcode) {
if (ctlcode == 5) {
sendback("06"); sendback("06");
} }
else if (ctlcode == 7) { else if (codes[i] == 7) {
/* bell */ /* bell */
bell(true); bell(true);
} }
else if (ctlcode == 8) { else if (codes[i] == 8) {
/* backspace */ /* backspace */
if (this.offedge) if (this.offedge)
this.offedge = false; this.offedge = false;
else if (this.c.x > 0) else if (this.c.x > 0)
this.cmove(null, this.c.x - 1); this.cmove(null, this.c.x - 1);
} }
else if (ctlcode == 9) { else if (codes[i] == 9) {
/* tab */ /* tab */
var xnew; var xnew;
if (this.c.x < this.w - 1) { if (this.c.x < this.w - 1) {
@ -670,47 +641,55 @@ var termemu = {
this.offedge = true; this.offedge = true;
} }
} }
else if (ctlcode >= 10 && ctlcode <= 12) { else if (codes[i] >= 10 && codes[i] <= 12) {
/* newline, vertical tab, form feed */ /* newline, vertical tab, form feed */
if (this.offedge) if (this.offedge)
this.newline(true); this.newline(true);
else else
this.newline(false); this.newline(false);
} }
else if (ctlcode == 13) { else if (codes[i] == 13) {
/* carriage return \r */ /* carriage return \r */
this.cmove(null, 0); this.cmove(null, 0);
} }
else if (ctlcode == 14) { else if (codes[i] == 14) {
/* shift out */ /* shift out */
// Currently assuming that G1 is DEC Special & Line Drawing // Currently assuming that G1 is DEC Special & Line Drawing
this.c.cset = "0"; this.c.cset = "0";
debug(0, "Using DEC graphics charset."); debug(0, "Using DEC graphics charset.");
} }
else if (ctlcode == 15) { else if (codes[i] == 15) {
/* shift in */ /* shift in */
// Currently assuming that G0 is ASCII // Currently assuming that G0 is ASCII
this.c.cset = "B"; this.c.cset = "B";
debug(0, "Using ASCII charset."); debug(0, "Using ASCII charset.");
} }
else if (ctlcode == 27) { else if (codes[i] == 27) {
/* escape */ /* escape */
this.comseq.push(27); this.comseq.push(codes[i]);
}
else if (codes[i] < 32 || (codes[i] >= 127 && codes[i] < 160)) {
/* Some kind of control character. */
debug(1, "Unprintable character 0x" + codes[i].toString(16));
} }
else { else {
debug(1, "Unprintable character 0x" + ctlcode.toString(16)); /* If it's ASCII, it's printable; take a risk on anything higher */
if ((this.c.cset == "0") && (codes[i] in decChars)) {
// DEC special character set
this.placechar(String.fromCharCode(decChars[codes[i]]));
} }
if (ctlcode != 27) { else {
// Sequences should preserve lastcode until they are completed this.placechar(String.fromCharCode(codes[i]));
this.lastcode = 0;
} }
}
}
return;
}, },
csiProcess: function () { csiProcess: function () {
/* Processes the CSI sequence in this.comseq */ /* Processes the CSI sequence in this.comseq */
var c = this.comseq[this.comseq.length - 1]; var c = this.comseq[this.comseq.length - 1];
if (this.comseq[0] != 155 || !csiFinal(c)) if (this.comseq[0] != 155 || !csiFinal(c))
return; return;
var printed = false;
var comstr = ""; var comstr = "";
for (var i = 1; i < this.comseq.length; i++) for (var i = 1; i < this.comseq.length; i++)
comstr += String.fromCharCode(this.comseq[i]); comstr += String.fromCharCode(this.comseq[i]);
@ -719,7 +698,6 @@ var termemu = {
var matchCSI = comstr.match(reCSI); var matchCSI = comstr.match(reCSI);
if (!matchCSI) { if (!matchCSI) {
debug(1, "Unrecognized CSI sequence: " + comstr); debug(1, "Unrecognized CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
var prefix = null; var prefix = null;
@ -747,7 +725,6 @@ var termemu = {
/* @ - insert spaces at cursor */ /* @ - insert spaces at cursor */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI @ sequence: " + comstr); debug(1, "Invalid CSI @ sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
/* The cursor stays still, but characters move out from under it. */ /* The cursor stays still, but characters move out from under it. */
@ -768,7 +745,6 @@ var termemu = {
/* E - next line, F - previous line, G - to column */ /* E - next line, F - previous line, G - to column */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr); debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
/* These may be out of range, but cmove will take care of that. */ /* These may be out of range, but cmove will take care of that. */
@ -793,7 +769,6 @@ var termemu = {
var y = 0; var y = 0;
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI H sequence: " + comstr); debug(1, "Invalid CSI H sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
if (params[0]) if (params[0])
@ -812,7 +787,6 @@ var termemu = {
var x = this.c.x; var x = this.c.x;
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI I sequence: " + comstr); debug(1, "Invalid CSI I sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
while (count > 0) { while (count > 0) {
@ -834,7 +808,6 @@ var termemu = {
debug(1, "Warning: CSI ?J not implemented"); debug(1, "Warning: CSI ?J not implemented");
else if (prefix || postfix) { else if (prefix || postfix) {
debug(1, "Invalid CSI J sequence: " + comstr); debug(1, "Invalid CSI J sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
if (!params[0]) { if (!params[0]) {
@ -855,7 +828,6 @@ var termemu = {
} }
else { else {
debug(1, "Unimplemented parameter in CSI J sequence: " + comstr); debug(1, "Unimplemented parameter in CSI J sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
for (var nrow = start; nrow <= end; nrow++) { for (var nrow = start; nrow <= end; nrow++) {
@ -883,7 +855,6 @@ var termemu = {
debug(1, "Warning: CSI ?K not implemented"); debug(1, "Warning: CSI ?K not implemented");
else if (prefix || postfix) { else if (prefix || postfix) {
debug(1, "Invalid CSI K sequence: " + comstr); debug(1, "Invalid CSI K sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
/* 0 (default): right, 1: left, 2: all. Include cursor position. */ /* 0 (default): right, 1: left, 2: all. Include cursor position. */
@ -914,14 +885,11 @@ var termemu = {
* M - delete current lines */ * M - delete current lines */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr); debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
/* CSI LM have no effect outside of the scrolling region */ /* CSI LM have no effect outside of the scrolling region */
if (this.c.y < this.scrT || this.c.y > this.scrB) { if (this.c.y < this.scrT || this.c.y > this.scrB)
this.lastcode = 0;
return; return;
}
this.flipCursor(); this.flipCursor();
while (count > 0) { while (count > 0) {
var blankrow = this.makeRow(); var blankrow = this.makeRow();
@ -947,7 +915,6 @@ var termemu = {
/* P - delete at active position, causing cells on the right to shift. */ /* P - delete at active position, causing cells on the right to shift. */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI P sequence: " + comstr); debug(1, "Invalid CSI P sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
var cursrow = this.screen.childNodes[this.c.y]; var cursrow = this.screen.childNodes[this.c.y];
@ -963,7 +930,6 @@ var termemu = {
/* S - scroll up, T - scroll down */ /* S - scroll up, T - scroll down */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr); debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
if (c == 83) if (c == 83)
@ -975,7 +941,6 @@ var termemu = {
/* X - erase characters */ /* X - erase characters */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI sequence: " + comstr); debug(1, "Invalid CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
var row = this.screen.childNodes[this.c.y]; var row = this.screen.childNodes[this.c.y];
@ -989,7 +954,6 @@ var termemu = {
var x = this.c.x; var x = this.c.x;
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI Z sequence: " + comstr); debug(1, "Invalid CSI Z sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
while (count > 0) { while (count > 0) {
@ -1006,26 +970,14 @@ var termemu = {
/* ` - go to col */ /* ` - go to col */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI ` sequence: " + comstr); debug(1, "Invalid CSI ` sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
this.cmove(null, count - 1); this.cmove(null, count - 1);
} }
else if (c == 98) {
/* b - repeat previous character */
if (this.lastcode !== 0) {
while (count > 0) {
this.placechar(String.fromCharCode(this.lastcode));
count--;
}
printed = true;
}
}
else if (c == 99) { else if (c == 99) {
/* c - query terminal attributes */ /* c - query terminal attributes */
if (prefix !== null) { if (prefix !== null) {
debug(1, "Unimplemented CSI sequence: " + comstr); debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
/* "CSI ? 1 ; 2 c" - VT100 */ /* "CSI ? 1 ; 2 c" - VT100 */
@ -1035,7 +987,6 @@ var termemu = {
/* d - go to row */ /* d - go to row */
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI d sequence: " + comstr); debug(1, "Invalid CSI d sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
this.cmove(count - 1, null); this.cmove(count - 1, null);
@ -1046,7 +997,6 @@ var termemu = {
var y = 0; var y = 0;
if (prefix || postfix) { if (prefix || postfix) {
debug(1, "Invalid CSI f sequence: " + comstr); debug(1, "Invalid CSI f sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
if (params[0]) if (params[0])
@ -1059,7 +1009,6 @@ var termemu = {
/* h - set modes */ /* h - set modes */
if (prefix != '?') { if (prefix != '?') {
debug(1, "Unimplemented CSI sequence: " + comstr); debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
for (var i = 0; i < params.length; i++) { for (var i = 0; i < params.length; i++) {
@ -1098,7 +1047,6 @@ var termemu = {
} }
else { else {
debug(1, "Unimplemented CSI sequence: " + comstr); debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
} }
@ -1132,7 +1080,6 @@ var termemu = {
/* m - character attributes */ /* m - character attributes */
if (prefix !== null) { if (prefix !== null) {
debug(1, "Unimplemented CSI sequence: " + comstr); debug(1, "Unimplemented CSI sequence: " + comstr);
this.lastcode = 0;
return; return;
} }
if (params.length == 0) if (params.length == 0)
@ -1186,18 +1133,15 @@ var termemu = {
t = params[0] - 1; t = params[0] - 1;
if (params[1] && params[1] <= this.h) if (params[1] && params[1] <= this.h)
b = params[1] - 1; b = params[1] - 1;
if (b > t) { if (b <= t)
return;
this.scrT = t; this.scrT = t;
this.scrB = b; this.scrB = b;
this.cmove(0, 0); this.cmove(0, 0);
} }
}
else { else {
debug(1, "Unimplemented CSI sequence: " + comstr); debug(1, "Unimplemented CSI sequence: " + comstr);
} }
if (!printed) {
this.lastcode = 0;
}
return; return;
}, },
oscProcess: function () { oscProcess: function () {