fix: bound browser input and resource policies
This commit is contained in:
+3
-2
@@ -1,10 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<link rel="preload" href="./fonts/font.otf" as="font" type="font/otf" crossorigin>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'none'; frame-src 'none'; img-src 'self' data:; media-src 'none'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'none'" />
|
||||
<link rel="preload" href="./fonts/font.otf" as="font" type="font/otf" crossorigin />
|
||||
<title>Mode 7. A horror story.</title>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
</head>
|
||||
|
||||
@@ -80,6 +80,7 @@ export class TeletextTerminal { // Export the terminal class
|
||||
|
||||
this._keyQueue = []; // Key event queue
|
||||
this._keyWaiters = []; // Promises waiting for a key
|
||||
this._maxKeyQueue = 64; // Bound buffered input so key repeat cannot grow memory indefinitely
|
||||
|
||||
this._audioCtx = null; // AudioContext (lazy)
|
||||
this._muted = false; // Mute flag
|
||||
@@ -806,6 +807,9 @@ this.setInputMode(false); // Exit input mode after input is captured
|
||||
|
||||
_pushKey(evt) { // Push a key event into the queue or resolve a waiter
|
||||
|
||||
if (!evt || !["enter", "backspace", "char"].includes(evt.type)) return; // Reject malformed events
|
||||
if (evt.type === "char" && (typeof evt.ch !== "string" || evt.ch.length !== 1)) return; // Require one character
|
||||
|
||||
if (evt && evt.type === "enter") { // Enter triggers fast-forward
|
||||
this._fastForward = true; // Enable fast-forward
|
||||
this._fastForwardUntil = Date.now() + this._fastForwardMs; // Set expiry
|
||||
@@ -821,6 +825,7 @@ if (evt && evt.type === "enter") { // Enter triggers fast-forward
|
||||
return; // Done
|
||||
} // End waiter path
|
||||
|
||||
if (this._keyQueue.length >= this._maxKeyQueue) this._keyQueue.shift(); // Discard oldest buffered input
|
||||
this._keyQueue.push(evt); // Otherwise queue it for later
|
||||
} // End _pushKey
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import { TeletextTerminal } from "../js/core/teletext.js";
|
||||
|
||||
function makeInputTarget() {
|
||||
const term = Object.create(TeletextTerminal.prototype);
|
||||
term._keyQueue = [];
|
||||
term._keyWaiters = [];
|
||||
term._maxKeyQueue = 64;
|
||||
term._fastForward = false;
|
||||
term._fastForwardAnyKey = false;
|
||||
term._fastForwardMs = 1200;
|
||||
term._fastForwardUntil = 0;
|
||||
return term;
|
||||
}
|
||||
|
||||
test("keyboard input queue remains bounded", () => {
|
||||
const term = makeInputTarget();
|
||||
|
||||
for (let i = 0; i < 1000; i += 1) {
|
||||
term._pushKey({ type: "char", ch: String(i % 10) });
|
||||
}
|
||||
|
||||
assert.equal(term._keyQueue.length, 64);
|
||||
});
|
||||
|
||||
test("malformed synthetic key events are rejected", () => {
|
||||
const term = makeInputTarget();
|
||||
|
||||
term._pushKey(null);
|
||||
term._pushKey({ type: "unknown" });
|
||||
term._pushKey({ type: "char", ch: "too long" });
|
||||
|
||||
assert.deepEqual(term._keyQueue, []);
|
||||
});
|
||||
|
||||
test("entry point defines restrictive browser policies", async () => {
|
||||
const html = await readFile(new URL("../index.html", import.meta.url), "utf8");
|
||||
|
||||
assert.match(html, /name="referrer" content="no-referrer"/);
|
||||
assert.match(html, /http-equiv="Content-Security-Policy"/);
|
||||
assert.match(html, /script-src 'self'/);
|
||||
assert.match(html, /object-src 'none'/);
|
||||
assert.match(html, /base-uri 'none'/);
|
||||
});
|
||||
Reference in New Issue
Block a user