mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-09 13:46:33 +00:00
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:
parent
ee43a12eb7
commit
e47fb2e651
@ -20,7 +20,7 @@ import { Installable } from '../../common/protocol';
|
|||||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
|
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||||
import { findChildTheiaButton } from '../utils/dom';
|
import { findChildTheiaButton, splitByBoldTag } from '../utils/dom';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LibraryListWidget extends ListWidget<
|
export class LibraryListWidget extends ListWidget<
|
||||||
@ -81,7 +81,7 @@ export class LibraryListWidget extends ListWidget<
|
|||||||
let installDependencies: boolean | undefined = undefined;
|
let installDependencies: boolean | undefined = undefined;
|
||||||
if (dependencies.length) {
|
if (dependencies.length) {
|
||||||
const message = document.createElement('div');
|
const message = document.createElement('div');
|
||||||
message.innerHTML =
|
const textContent =
|
||||||
dependencies.length === 1
|
dependencies.length === 1
|
||||||
? nls.localize(
|
? nls.localize(
|
||||||
'arduino/library/needsOneDependency',
|
'arduino/library/needsOneDependency',
|
||||||
@ -95,6 +95,22 @@ export class LibraryListWidget extends ListWidget<
|
|||||||
item.name,
|
item.name,
|
||||||
version
|
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');
|
const listContainer = document.createElement('div');
|
||||||
listContainer.style.maxHeight = '300px';
|
listContainer.style.maxHeight = '300px';
|
||||||
listContainer.style.overflowY = 'auto';
|
listContainer.style.overflowY = 'auto';
|
||||||
|
@ -35,3 +35,35 @@ export function findChildTheiaButton(
|
|||||||
function isHTMLElement(element: Element): element is HTMLElement {
|
function isHTMLElement(element: Element): element is HTMLElement {
|
||||||
return element instanceof 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;
|
||||||
|
}
|
||||||
|
52
arduino-ide-extension/src/test/browser/dom.test.ts
Normal file
52
arduino-ide-extension/src/test/browser/dom.test.ts
Normal 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user