mirror of
https://github.com/planetrenox/inzerosight.git
synced 2026-09-18 18:15:42 +00:00
Organize extension source and tests into folders
This commit is contained in:
89
tests/content.test.js
Normal file
89
tests/content.test.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import zwus from 'zwus';
|
||||
import { makeSig } from '../src/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('../src/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 ');
|
||||
});
|
||||
133
tests/sig.test.js
Normal file
133
tests/sig.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import zwus from 'zwus';
|
||||
import * as chunked from '../src/chunked.js';
|
||||
import * as ctr from '../src/speck48_96ctr.js';
|
||||
import * as ecb from '../src/speck32_64ecb.js';
|
||||
import { makeSig, parseSig, parseModernSig, parseLegacySig, getPayloadEnd } from '../src/sig.js';
|
||||
|
||||
// Frozen wire-format fixtures, independent of the signature registry.
|
||||
const HEADERS = {
|
||||
3: '\u200D\u200B\u00AD\u180E\u200D',
|
||||
6: '\u200D\u200B\u00AD\u200C\u200D',
|
||||
7: '\u200D\u200B\u00AD\u200C\u200C'
|
||||
};
|
||||
const MODES = ['PLAIN', 'SPECK48_96CTR', 'SPECK32_64ECB (insecure)'];
|
||||
const MESSAGE = 'tt\0 Hello, 世界 🌍\n';
|
||||
const key = 'signature regression';
|
||||
|
||||
for (const base of [3, 6, 7]) {
|
||||
const z = zwus[base], header = HEADERS[base];
|
||||
const extended = digits => header + z.unifier + z[0] +
|
||||
Array.from(digits, d => z[d]).join('');
|
||||
|
||||
test(`ZWUS-${base}: exact 11-character headers and round trips for every mode`, () => {
|
||||
for (const [id, cipher] of MODES.entries()) {
|
||||
const expected = extended('000' + id);
|
||||
assert.equal(makeSig(base, cipher), expected);
|
||||
assert.equal(makeSig(String(base), cipher).length, 11);
|
||||
const engine = [null, ctr, ecb][id];
|
||||
const payload = engine ? chunked.encodeNumberArray(engine.encrypt(MESSAGE, engine.getKey(key)), base) :
|
||||
chunked.encodeString(MESSAGE, base);
|
||||
const parsed = parseSig('visible ' + expected + payload + ' suffix');
|
||||
assert.deepEqual(parsed, {
|
||||
base: String(base), cipher, payload: payload + ' suffix', sigIdx: 8, sigLen: 11, legacy: false
|
||||
});
|
||||
const start = parsed.sigIdx + parsed.sigLen;
|
||||
const source = 'visible ' + expected + payload + ' suffix';
|
||||
const end = getPayloadEnd(source, base, start);
|
||||
assert.equal(source.slice(start, end), payload);
|
||||
const decoded = engine ? engine.decrypt(chunked.decodeToNumberArray(parsed.payload, base), engine.getKey(key)) :
|
||||
chunked.decodeToString(parsed.payload, base);
|
||||
assert.equal(decoded, MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: modern reader rejects every unregistered four-digit ID`, () => {
|
||||
for (let id = 3; id < base ** 4; id++)
|
||||
assert.equal(parseModernSig(extended(id.toString(base).padStart(4, '0'))), null, `ID ${id}`);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: modern reader requires every character to match`, () => {
|
||||
const sig = extended('0000');
|
||||
for (let end = 0; end < sig.length; end++)
|
||||
assert.equal(parseModernSig(sig.slice(0, end)), null);
|
||||
for (let i = 0; i < sig.length; i++)
|
||||
assert.equal(parseModernSig(sig.slice(0, i) + 'x' + sig.slice(i + 1)), null);
|
||||
assert.equal(parseModernSig(extended('10000')), null);
|
||||
assert.equal(parseSig(extended('0000') + z[2]).sigLen, 11);
|
||||
assert.equal(parseSig(extended('0000') + z[2]).payload, z[2]);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: malformed extensions do not fall back to legacy PLAIN`, () => {
|
||||
for (const tail of [z.unifier, z.unifier + z[0], z.unifier + z[0].repeat(4),
|
||||
z.unifier + z[0] + 'xxx', z.unifier + z[0] + z[2].repeat(4)])
|
||||
assert.equal(parseSig(header + tail), null);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: legacy five-character PLAIN stays readable`, () => {
|
||||
const payload = zwus.encodeString(MESSAGE, base);
|
||||
const parsed = parseSig(header + payload);
|
||||
assert.deepEqual(parsed, {
|
||||
base: String(base), cipher: 'PLAIN', payload, sigIdx: 0, sigLen: 5, legacy: true
|
||||
});
|
||||
assert.equal(zwus.decodeToString(parsed.payload, base), MESSAGE);
|
||||
assert.equal(parseModernSig(header + payload), null);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: legacy ten-character encrypted signatures stay readable`, () => {
|
||||
for (const [id, engine] of [[1, ctr], [2, ecb]]) {
|
||||
const sig = extended('00' + id);
|
||||
const payload = zwus.encodeNumberArray(engine.encrypt(MESSAGE, engine.getKey(key)), base);
|
||||
const parsed = parseSig(sig + payload);
|
||||
assert.equal(parsed.cipher, MODES[id]);
|
||||
assert.equal(parsed.sigLen, 10);
|
||||
assert.equal(parsed.legacy, true);
|
||||
assert.equal(parsed.payload, payload);
|
||||
assert.equal(engine.decrypt(zwus.decodeToNumberArray(parsed.payload, base), engine.getKey(key)), MESSAGE);
|
||||
// A legacy header's next digit is payload, even if all 11 characters resemble a future ID.
|
||||
assert.equal(parseLegacySig(sig + z[0]).payload, z[0]);
|
||||
}
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: complete modern signatures take priority and preserve offsets`, () => {
|
||||
const sig = extended('0000'), payload = zwus.encodeString('hello', base);
|
||||
const prefix = header + z.unifier + 'broken ';
|
||||
const parsed = parseSig(prefix + sig + payload);
|
||||
assert.equal(parsed.sigIdx, prefix.length);
|
||||
assert.equal(parsed.legacy, false);
|
||||
assert.equal(parsed.payload, payload);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: legacy and modern messages separated by visible text remain discoverable`, () => {
|
||||
const payload = zwus.encodeString('hello', base);
|
||||
const legacy = header + payload, modern = extended('0000') + payload;
|
||||
const text = legacy + ' and ' + modern;
|
||||
const first = parseSig(text);
|
||||
assert.equal(first.legacy, true);
|
||||
assert.equal(first.sigIdx, 0);
|
||||
const end = getPayloadEnd(text, base, first.sigLen);
|
||||
assert.equal(end, legacy.length);
|
||||
assert.equal(parseSig(text.slice(end)).legacy, false);
|
||||
});
|
||||
|
||||
test(`ZWUS-${base}: empty and chunked payloads preserve their contents`, () => {
|
||||
const sig = makeSig(base, 'PLAIN');
|
||||
assert.equal(parseSig(sig).payload, '');
|
||||
const text = 't'.repeat(65535) + '🌍\0';
|
||||
assert.equal(chunked.decodeToString(parseSig(sig + chunked.encodeString(text, base)).payload, base), text);
|
||||
});
|
||||
}
|
||||
|
||||
test('unknown standards and modes cannot generate signatures', () => {
|
||||
assert.throws(() => makeSig(8, 'PLAIN'), RangeError);
|
||||
assert.throws(() => makeSig(7, 'unknown'), RangeError);
|
||||
assert.throws(() => makeSig(7), RangeError);
|
||||
});
|
||||
|
||||
test('legacy cross-standard collision does not match a modern signature', () => {
|
||||
const text = zwus.encodeString('ŵt', 7);
|
||||
assert.equal(parseModernSig(text), null);
|
||||
assert.equal(parseLegacySig(text).base, '6');
|
||||
assert.equal(parseSig(text + makeSig(7, 'PLAIN')).legacy, false);
|
||||
});
|
||||
Reference in New Issue
Block a user