fix: remove setting unsafe innerHTML

As it is vulnerable to stored Cross-Site Scripting.

Ref: PNX-3669
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2023-05-11 14:55:46 +02:00 committed by Akos Kitta
parent ee43a12eb7
commit e47fb2e651
3 changed files with 102 additions and 2 deletions

View File

@ -20,7 +20,7 @@ import { Installable } from '../../common/protocol';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
import { findChildTheiaButton } from '../utils/dom';
import { findChildTheiaButton, splitByBoldTag } from '../utils/dom';
@injectable()
export class LibraryListWidget extends ListWidget<
@ -81,7 +81,7 @@ export class LibraryListWidget extends ListWidget<
let installDependencies: boolean | undefined = undefined;
if (dependencies.length) {
const message = document.createElement('div');
message.innerHTML =
const textContent =
dependencies.length === 1
? nls.localize(
'arduino/library/needsOneDependency',
@ -95,6 +95,22 @@ export class LibraryListWidget extends ListWidget<
item.name,
version
);
const segments = splitByBoldTag(textContent);
if (!segments) {
message.textContent = textContent;
} else {
segments.map((segment) => {
const span = document.createElement('span');
if (typeof segment === 'string') {
span.textContent = segment;
} else {
const bold = document.createElement('b');
bold.textContent = segment.textContent;
span.appendChild(bold);
}
message.appendChild(span);
});
}
const listContainer = document.createElement('div');
listContainer.style.maxHeight = '300px';
listContainer.style.overflowY = 'auto';

View File

@ -35,3 +35,35 @@ export function findChildTheiaButton(
function isHTMLElement(element: Element): element is HTMLElement {
return element instanceof HTMLElement;
}
type Segment = string | { textContent: string; bold: true };
/**
* Returns with an array of `Segments` by splitting raw HTML text on the `<b></b>` groups. If splitting is not possible, returns `undefined`.
* Example: `one<b>two</b>three<b>four</b>five` will provide an five element length array. Where the 1<sup>st</sup> and 3<sup>rd</sup> elements are objects and the rest are string.
*/
export function splitByBoldTag(text: string): Segment[] | undefined {
const matches = text.matchAll(new RegExp(/<\s*b[^>]*>(.*?)<\s*\/\s*b>/gm));
if (!matches) {
return undefined;
}
const segments: Segment[] = [];
const textLength = text.length;
let processedLength = 0;
for (const match of matches) {
const { index } = match;
if (typeof index === 'number') {
if (!segments.length && index) {
segments.push(text.substring(0, index));
}
if (processedLength > 0) {
segments.push(text.substring(processedLength, index));
}
segments.push({ textContent: match[1], bold: true });
processedLength = index + match[0].length;
}
}
if (segments.length && textLength > processedLength) {
segments.push(text.substring(processedLength));
}
return segments.length ? segments : undefined;
}

View File

@ -0,0 +1,52 @@
import { splitByBoldTag } from '../../browser/utils/dom';
import { expect } from 'chai';
describe('dom', () => {
describe('splitByBoldTag', () => {
it('should split by bold tags', () => {
const actual = splitByBoldTag('one<b>matchOne</b>two');
const expected = ['one', { textContent: 'matchOne', bold: true }, 'two'];
expect(actual).to.be.deep.equal(expected);
});
it('should handle starting bold tags', () => {
const actual = splitByBoldTag(
'<b>matchOne</b>one<b>matchTwo</b> two <b>matchThree</b> three'
);
const expected = [
{ textContent: 'matchOne', bold: true },
'one',
{ textContent: 'matchTwo', bold: true },
' two ',
{ textContent: 'matchThree', bold: true },
' three',
];
expect(actual).to.be.deep.equal(expected);
});
it('should handle unclosed bold tags', () => {
const actual = splitByBoldTag(
'<b>matchOne</b>one<b>matchTwo</b> two <b>matchThree</b> three <b> '
);
const expected = [
{ textContent: 'matchOne', bold: true },
'one',
{ textContent: 'matchTwo', bold: true },
' two ',
{ textContent: 'matchThree', bold: true },
' three <b> ',
];
expect(actual).to.be.deep.equal(expected);
});
it('should handle no matches', () => {
const actual = splitByBoldTag('<b>alma');
expect(actual).to.be.undefined;
});
it('should handle empty strings', () => {
const actual = splitByBoldTag('');
expect(actual).to.be.undefined;
});
});
});