mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-07 07:36:37 +00:00
chore: remove dead code, deprecate unused plugin APIs (over-engineering audit)
Whole-tree audit. Every symbol was checked against core, the plugin monorepo and all eight third-party plugins in plugins.json first. - Deprecate (removal 3.10.0) plugin-facing methods nothing calls: LogoDownloader bulk download, ConfigManager backup/secret wrappers, APIHelper extras, BackgroundDataService poll API, PluginManager / PluginStateManager info readers, and a few CacheManager, FontManager, BaseOddsManager, DynamicTeamResolver methods and PluginTestCase. plugin_api_usage.py learns their receiver names; DEPRECATIONS doc regenerated. - Remove core-internal dead code: CacheMetrics, Vegas status/stats plumbing, sync "new cycle" message (followers ignore unknown types), unused operation types, test-only PluginCatalog readers, IPC to_dict and ping, _parse_form_value, CacheStrategyProtocol, ErrorAggregator callbacks, duplicate web response helpers. - Web UI: drop never-mounted json-file-manager.js, the example widget, utils/error_handler.js, four uncalled PluginAPI methods, and 29 escapeHtml shims (call window.LEDEscape directly). Public globals, BaseWidget and widget names unchanged. - Remove six one-off scripts (owner decision) and the unused markupsafe and pytest-mock pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,9 +12,10 @@
|
||||
//
|
||||
// CodeQL reported 83 js/incomplete-html-attribute-sanitization alerts for
|
||||
// exactly this. The web UI now has one implementation, window.LEDEscape in
|
||||
// app-early.js, and the old per-file escapers are one-line names for it. This
|
||||
// suite runs LEDEscape and every one of those names as shipped, and fails if a
|
||||
// hand-rolled escaper appears anywhere else in web_interface/.
|
||||
// app-early.js, which every page and widget calls directly (BaseWidget keeps
|
||||
// an escapeHtml method for plugin widgets). This suite runs LEDEscape and that
|
||||
// method as shipped, and fails if a hand-rolled escaper appears anywhere else
|
||||
// in web_interface/.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
@@ -85,30 +86,6 @@ const ESCAPERS = [
|
||||
['app-early.js (LEDEscape.attr)', null, null, 'attr'],
|
||||
['base-widget.js (BaseWidget.escapeHtml)',
|
||||
'static/v3/js/widgets/base-widget.js', 'escapeHtml(text) {', 'escapeHtml', true],
|
||||
['plugins_manager.js (top-level escapeHtml)',
|
||||
'static/v3/plugins_manager.js', 'function escapeHtml(text) {', 'escapeHtml', false],
|
||||
['plugins_manager.js (starlark escapeHtml)',
|
||||
'static/v3/plugins_manager.js', 'function escapeHtml(str) {', 'escapeHtml', false],
|
||||
['json-file-manager.js (_esc)',
|
||||
'static/v3/js/widgets/json-file-manager.js', '_esc(str) {', '_esc', true],
|
||||
['plugin-file-manager.js (escHtml)',
|
||||
'static/v3/js/widgets/plugin-file-manager.js', 'function escHtml(s) {', 'escHtml', false],
|
||||
['plugins_manager.js (escapeAttribute)',
|
||||
'static/v3/plugins_manager.js', 'function escapeAttribute(text) {', 'escapeAttribute', false],
|
||||
['notification.js (escapeHtml)',
|
||||
'static/v3/js/widgets/notification.js', 'function escapeHtml(text) {', 'escapeHtml', false],
|
||||
['google-calendar-picker.js (escapeHtml)',
|
||||
'static/v3/js/widgets/google-calendar-picker.js', 'function escapeHtml(str) {', 'escapeHtml', false],
|
||||
['text-input.js (escapeHtml)',
|
||||
'static/v3/js/widgets/text-input.js', 'function escapeHtml(text) {', 'escapeHtml', false],
|
||||
['slider.js (escapeAttr)',
|
||||
'static/v3/js/widgets/slider.js', 'function escapeAttr(text) {', 'escapeAttr', false],
|
||||
['tools.html (escHtml)',
|
||||
'templates/v3/partials/tools.html', 'function escHtml(s) {', 'escHtml', false],
|
||||
['tools.html (phEscape)',
|
||||
'templates/v3/partials/tools.html', 'function phEscape(s) {', 'phEscape', false],
|
||||
['logs.html (escapeHtml)',
|
||||
'templates/v3/partials/logs.html', 'function escapeHtml(text) {', 'escapeHtml', false],
|
||||
// cache.html, backup_restore.html, operation_history.html and display.html
|
||||
// have no script any more (display.html's two escapers were never called):
|
||||
// their js/pages/ modules draw server data with textContent, and each
|
||||
@@ -169,9 +146,7 @@ console.log('\n4b. LEDEscape.jsStringAttr: a JS string literal that survives an
|
||||
|
||||
console.log('\n4c. no hand-rolled escaper outside app-early.js');
|
||||
{
|
||||
const skip = new Set(['static/v3/js/app-early.js',
|
||||
// documentation example, kept self-contained on purpose
|
||||
'static/v3/js/widgets/example-color-picker.js']);
|
||||
const skip = new Set(['static/v3/js/app-early.js']);
|
||||
const found = [];
|
||||
const walk = dir => fs.readdirSync(dir, { withFileTypes: true }).forEach(e => {
|
||||
const p = path.join(dir, e.name);
|
||||
@@ -308,7 +283,7 @@ console.log('\n6. url-input onInput: previewLink.href is guarded at the sink');
|
||||
|
||||
// ── plugin-file-manager: cell edits travel via data-*, not inline handlers ──
|
||||
// A JSON key/day from an uploaded file used to be spliced, HTML-escaped,
|
||||
// into an oninput="...('${escHtml(col)}'...)" attribute. escHtml neutralises
|
||||
// into an oninput="...('${escHtml(col)}'...)" attribute. Escaping neutralises
|
||||
// a quote for an ordinary attribute, but here the value also has to survive
|
||||
// as a *JS string literal* -- the browser HTML-decodes the attribute before
|
||||
// running it as script, which turns the escaped quote back into a real one
|
||||
@@ -332,11 +307,10 @@ console.log("\n7. plugin-file-manager: cell edits never go through an inline han
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const escHtmlFn = loadFn('static/v3/js/widgets/plugin-file-manager.js', 'function escHtml(s) {', 'escHtml', false);
|
||||
const renderEntryTableSrc = extractFn('function renderEntryTable(fieldId, container, content) {');
|
||||
|
||||
const calls = [];
|
||||
const fakeWindow = { _pfmCellEdit: (fieldId, day, col, value) => calls.push({ fieldId, day, col, value }) };
|
||||
const fakeWindow = { LEDEscape, _pfmCellEdit: (fieldId, day, col, value) => calls.push({ fieldId, day, col, value }) };
|
||||
|
||||
class FakeContainer {
|
||||
constructor() { this._html = ''; this._listeners = {}; }
|
||||
@@ -354,12 +328,11 @@ console.log("\n7. plugin-file-manager: cell edits never go through an inline han
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
const renderEntryTable = eval(`(function(getState, escHtml, safeSetHTML, window){
|
||||
const renderEntryTable = eval(`(function(getState, safeSetHTML, window){
|
||||
${renderEntryTableSrc}
|
||||
return renderEntryTable;
|
||||
})`)(
|
||||
() => ({ entriesPerPage: 20, _tablePage: 1 }),
|
||||
escHtmlFn,
|
||||
(target, html) => { target.innerHTML = html; },
|
||||
fakeWindow
|
||||
);
|
||||
|
||||
@@ -62,12 +62,12 @@ global.setGridHtmlIfChanged = (container, html) => { container.innerHTML = html;
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
eval([
|
||||
'function escapeHtml(text) {', 'function escapeAttribute(text) {', 'function jsStringAttr(value) {',
|
||||
'function jsStringAttr(value) {',
|
||||
'function renderPluginStore(plugins) {', 'function renderSavedRepositories(repositories) {',
|
||||
'function renderCustomRegistryPlugins(plugins, registryUrl) {',
|
||||
].map(extract).join('\n') + '\nglobal.jsStringAttr = jsStringAttr; global.escapeHtml = escapeHtml;'
|
||||
].map(extract).join('\n') + '\nglobal.jsStringAttr = jsStringAttr;'
|
||||
+ '\nglobal.renderPluginStore = renderPluginStore; global.renderSavedRepositories = renderSavedRepositories;'
|
||||
+ '\nglobal.renderCustomRegistryPlugins = renderCustomRegistryPlugins; global.escapeAttribute = escapeAttribute;');
|
||||
+ '\nglobal.renderCustomRegistryPlugins = renderCustomRegistryPlugins;');
|
||||
|
||||
// ── minimal HTML start-tag tokenizer ───────────────────────────────────────
|
||||
function decodeEntities(s) {
|
||||
|
||||
@@ -16,7 +16,7 @@ const container = {
|
||||
innerHTML: '',
|
||||
querySelectorAll: () => [], // no skeletons in this harness
|
||||
};
|
||||
// escapeHtml() escapes via a detached element, so mirror what a browser does
|
||||
// LEDEscape.html() escapes via a detached element, so mirror what a browser does
|
||||
// when you read innerHTML back off textContent: & < > are escaped, quotes are not.
|
||||
class FakeEl {
|
||||
set textContent(v) { this._t = String(v == null ? '' : v); }
|
||||
@@ -36,7 +36,7 @@ global.PLUGIN_DEBUG = false;
|
||||
global.debugLog = () => {};
|
||||
function setupInstalledEventDelegation() {} // stubbed; tested separately
|
||||
|
||||
eval(slice('function escapeHtml(text)', '\nfunction isNewPlugin'));
|
||||
eval(slice('function jsStringAttr(value)', '\nfunction isNewPlugin'));
|
||||
eval(slice('function renderInstalledCards(plugins, total)',
|
||||
'// Set up event delegation for plugin action buttons'));
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ global.installedPlugins = [];
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
eval([
|
||||
'function escapeHtml(text) {', 'function escapeAttribute(text) {', 'function jsStringAttr(value) {',
|
||||
'function jsStringAttr(value) {',
|
||||
'function isStorePluginInstalled(pluginIdOrPlugin) {',
|
||||
'function findInstalledStorePlugin(pluginIdOrPlugin) {', 'function renderPluginStore(plugins) {',
|
||||
].map(extract).join('\n') + '\nglobal.renderPluginStore = renderPluginStore;'
|
||||
|
||||
@@ -253,9 +253,10 @@ const noSleep = { sleep: async () => {} };
|
||||
for (const endpoint of bad) codes.push(await refusal(endpoint));
|
||||
ok('an endpoint that could leave the API path is refused before fetch()',
|
||||
codes.every(c => c === 'INVALID_ENDPOINT') && urls.length === 0, { codes, urls });
|
||||
await PluginAPI.resetPluginConfig('a/../b&x=1');
|
||||
global.debugLog = () => {}; // GETs go through the throttler, which logs
|
||||
await PluginAPI.getPluginHealth('a/../b&x=1');
|
||||
ok('a plugin id is encoded into the URL, not spliced into it',
|
||||
urls[0] === '/api/v3/plugins/config/reset?plugin_id=a%2F..%2Fb%26x%3D1', urls);
|
||||
urls[0] === '/api/v3/plugins/health/a%2F..%2Fb%26x%3D1', urls);
|
||||
delete global.fetch;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user