From de3a37d7fb266f2e9e6719eb966fe5a5d8e6c9bb Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sun, 7 Jul 2024 02:02:41 +0100 Subject: [PATCH] HTML Reporter: Fix encoding of label for urlConfig multi-value item * Add test coverage for QUnit.config.urlConfig items with object as value. The urlConfig feature is mostly covered by `/test/reporter-urlparams.html` already, but what isn't covered yet is an item with an object value, where option labels can be customised. * In adding tests I noticed that, when urlConfig is used to create a multi-value option (rather than string, as is more common), then `val.label` was not escaped, which meant that if labels were to contain mention of an HTML tag or otherwise contain "<" and ">", these could glitch and break part of the toolbar rendering. This is unlikely to be exploitable, e.g. not controlled by URL parameters, and generally populated with literals. Even dynamic menus that feed dropdown contents from external input are fine, since this affects the top-level label only. Improve docs: * Avoid the overloaded word "option". Instead using the word "item" consistently when referring to urlConfig items, to avoid confusion with the `"; diff --git a/test/main/HtmlReporter.js b/test/main/HtmlReporter.js index 6291a6d14..23daf76cb 100644 --- a/test/main/HtmlReporter.js +++ b/test/main/HtmlReporter.js @@ -3,6 +3,18 @@ // Skip in non-browser environment (typeof document !== 'undefined' ? QUnit.module : QUnit.module.skip)('HtmlReporter', { beforeEach: function () { + this.normHTML = function (html) { + var div = document.createElement('div'); + // For example: + // * Firefox 45 (Win8.1) normalizes attribute order differently from Firefox 127 (macOS). + // * IE 11 (Win10) will try to minimise escaping by using single quotes for values + // that contain double quotes. + // * Safari 9.1 will serialize "" tag as escaped entities in attributes, whereas other + // browsers specifically normalize in the other direction where "" is a literal inside + // quoted attributes where escaping for "<" is redundant. + div.innerHTML = html; + return div.innerHTML; + }; this.MockQUnit = { on: function (event, fn) { this.on[event] = fn; @@ -215,6 +227,47 @@ QUnit.test('hidepassed', function (assert) { assert.strictEqual(node.checked, true); }); +QUnit.test('urlConfig', function (assert) { + var element = document.createElement('div'); + new QUnit.reporters.html(this.MockQUnit, { + element: element, + config: { + urlConfig: [ + 'xid', + { + id: 'xmulti', + label: 'Multi', + tooltip: 'Escaped ">' + }, + { + id: 'xmenu', + label: 'Menu', + tooltip: 'My tooltip', + value: ['a', 'b'] + }, + { + id: 'xmenulabel', + label: 'Menu', + value: { a: 'AA', bbb: 'B' } + }, + // Create UI for a built-in option + 'altertitle' + ] + } + }); + this.MockQUnit._do_start_empty(); + + var node = element.querySelector('.qunit-url-config'); + assert.strictEqual(node.innerHTML, this.normHTML( + '' + + '' + + '' + + '' + + '', + 'qunit-url-config HTML' + )); +}); + QUnit.test('filter', function (assert) { var element = document.createElement('div'); new QUnit.reporters.html(this.MockQUnit, { @@ -251,7 +304,24 @@ QUnit.test('overall escaping', function (assert) { new QUnit.reporters.html(this.MockQUnit, { element: element, config: { - urlConfig: [] + urlConfig: [ + { + id: 'x', + label: '', + tooltip: '', + value: { + '': '' + } + }, + { + id: 'y', + label: '', + tooltip: '', + value: [ + '' + ] + } + ] } }); @@ -295,9 +365,14 @@ QUnit.test('overall escaping', function (assert) { runtime: 2 }); - var scriptTagPos = element.innerHTML.indexOf('