Files
inzerosight/content.test.js

90 lines
4.1 KiB
JavaScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import zwus from 'zwus';
import { makeSig } from './sig.js';
test('page overlay detects headers split by WBR and decodes across the split', async () => {
const buttons = [], inserted = [], fixtures = [], texts = [];
const rect = { top: 10, bottom: 20, left: 10, right: 20, width: 10, height: 10 };
const body = { tagName: 'BODY', appendChild() {} };
const parent = { tagName: 'DIV', parentElement: body, getBoundingClientRect: () => rect };
let observer;
const element = tag => ({
tagName: tag.toUpperCase(), nodeType: 1, style: {}, offsetWidth: 0, offsetHeight: 0,
appendChild(child) { child.parentElement = this; },
attachShadow() { return element('shadow'); },
getBoundingClientRect: () => rect,
remove() { this.removed = true; }
});
const addRun = (encoded, cut, cipher) => {
const parts = [encoded.slice(0, cut), encoded.slice(cut, 13), encoded.slice(13)];
const nodes = parts.flatMap((part, i) => i ? [element('wbr'), {
nodeType: 3, nodeValue: part, parentElement: parent, isConnected: true
}] : [{ nodeType: 3, nodeValue: part, parentElement: parent, isConnected: true }]);
nodes.forEach((node, i) => {
node.previousSibling = nodes[i - 1] || null;
node.nextSibling = nodes[i + 1] || null;
if (node.nodeType === 3) texts.push(node);
});
fixtures.push({ nodes, cipher });
};
for (const base of [3, 6, 7]) for (const cipher of
['PLAIN', 'SPECK48_96CTR', 'SPECK32_64ECB (insecure)']) {
const payload = cipher === 'PLAIN' ? zwus.encodeString('hello', base) :
zwus.encodeNumberArray([1, 2, 3], base);
for (const cut of [1, 2, 3, 5, 6, 10]) addRun(makeSig(base, cipher) + payload, cut, cipher);
}
globalThis.Node = { ELEMENT_NODE: 1, TEXT_NODE: 3 };
globalThis.NodeFilter = { SHOW_TEXT: 4, FILTER_ACCEPT: 1, FILTER_REJECT: 2 };
globalThis.document = {
body, documentElement: body,
createElement(tag) { const node = element(tag); if (tag === 'button') buttons.push(node); return node; },
createTreeWalker() {
let i = 0;
return { nextNode: () => texts[i++] || null };
},
createRange() {
return {
setStart(node, offset) { this.startNode = node; this.start = offset; },
setEnd(node, offset) { this.endNode = node; this.end = offset; },
getBoundingClientRect: () => rect,
toString() {
let value = '', node = this.startNode;
while (node) {
if (node.nodeType === 3) value += node.nodeValue.slice(
node === this.startNode ? this.start : 0,
node === this.endNode ? this.end : undefined
);
if (node === this.endNode) break;
node = node.nextSibling;
}
return value;
},
deleteContents() {},
insertNode(node) { inserted.push(node); }
};
}
};
globalThis.window = {
innerHeight: 100, innerWidth: 100, addEventListener() {},
getComputedStyle: () => ({ overflow: 'visible', overflowX: 'visible', overflowY: 'visible' })
};
globalThis.MutationObserver = class {
constructor(callback) { this.callback = callback; observer = this; }
observe() {}
};
globalThis.requestAnimationFrame = callback => { callback(); return 1; };
await import('./content.js');
assert.equal(buttons.length, fixtures.length);
fixtures.forEach(({ cipher }, i) =>
assert.equal(buttons[i].textContent, cipher === 'PLAIN' ? 'Decode' : 'Decrypt'));
observer.callback([{ type: 'childList', addedNodes: [] }]);
assert.equal(buttons.some(button => button.parentElement.removed), false);
buttons[0].onclick();
assert.equal(inserted[0].textContent, ' hello ');
});