Merge pull request #196 from php-flasher/issue-193

Introduce escapeHtml Option for Secure HTML Escaping in PHPFlasher
This commit is contained in:
Younes ENNAJI
2024-09-19 07:41:45 +01:00
committed by GitHub
6 changed files with 79 additions and 8 deletions
+28 -2
View File
@@ -19,6 +19,7 @@ export default class FlasherPlugin extends AbstractPlugin {
direction: 'top', direction: 'top',
rtl: false, rtl: false,
style: {} as Properties, style: {} as Properties,
escapeHtml: false,
} }
constructor(theme: Theme) { constructor(theme: Theme) {
@@ -31,11 +32,12 @@ export default class FlasherPlugin extends AbstractPlugin {
const render = () => const render = () =>
envelopes.forEach((envelope) => { envelopes.forEach((envelope) => {
// @ts-expect-error // @ts-expect-error
const typeTimeout = this.options.timeout ?? this.options.timeouts[envelope.type] ?? 5000; const typeTimeout = this.options.timeout ?? this.options.timeouts[envelope.type] ?? 5000
const options = { const options = {
...this.options, ...this.options,
...envelope.options, ...envelope.options,
timeout: envelope.options.timeout ?? typeTimeout, timeout: envelope.options.timeout ?? typeTimeout,
escapeHtml: (envelope.options.escapeHtml ?? this.options.escapeHtml) as boolean,
} }
this.addToContainer(this.createContainer(options), envelope, options) this.addToContainer(this.createContainer(options), envelope, options)
@@ -64,7 +66,12 @@ export default class FlasherPlugin extends AbstractPlugin {
return container return container
} }
private addToContainer(container: HTMLDivElement, envelope: Envelope, options: { direction: string, timeout: number, fps: number, rtl: boolean }): void { private addToContainer(container: HTMLDivElement, envelope: Envelope, options: { direction: string, timeout: number, fps: number, rtl: boolean, escapeHtml: boolean }): void {
if (options.escapeHtml) {
envelope.title = this.escapeHtml(envelope.title)
envelope.message = this.escapeHtml(envelope.message)
}
const notification = this.stringToHTML(this.theme.render(envelope)) const notification = this.stringToHTML(this.theme.render(envelope))
notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' ')) notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' '))
@@ -126,4 +133,23 @@ export default class FlasherPlugin extends AbstractPlugin {
template.innerHTML = str.trim() template.innerHTML = str.trim()
return template.content.firstElementChild as HTMLElement return template.content.firstElementChild as HTMLElement
} }
private escapeHtml(str: string | null | undefined): string {
if (str == null) {
return ''
}
return str.replace(/[&<>"'`=\/]/g, (char) => {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;',
'`': '&#96;',
'=': '&#61;',
'/': '&#47;',
}[char] as string
})
}
} }
+1
View File
@@ -15,4 +15,5 @@ export default class FlasherPlugin extends AbstractPlugin {
}): void; }): void;
private removeNotification; private removeNotification;
private stringToHTML; private stringToHTML;
private escapeHtml;
} }
+24 -2
View File
@@ -92,14 +92,15 @@ class FlasherPlugin extends AbstractPlugin {
direction: 'top', direction: 'top',
rtl: false, rtl: false,
style: {}, style: {},
escapeHtml: false,
}; };
this.theme = theme; this.theme = theme;
} }
renderEnvelopes(envelopes) { renderEnvelopes(envelopes) {
const render = () => envelopes.forEach((envelope) => { const render = () => envelopes.forEach((envelope) => {
var _a, _b, _c; var _a, _b, _c, _d;
const typeTimeout = (_b = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : this.options.timeouts[envelope.type]) !== null && _b !== void 0 ? _b : 5000; const typeTimeout = (_b = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : this.options.timeouts[envelope.type]) !== null && _b !== void 0 ? _b : 5000;
const options = Object.assign(Object.assign(Object.assign({}, this.options), envelope.options), { timeout: (_c = envelope.options.timeout) !== null && _c !== void 0 ? _c : typeTimeout }); const options = Object.assign(Object.assign(Object.assign({}, this.options), envelope.options), { timeout: (_c = envelope.options.timeout) !== null && _c !== void 0 ? _c : typeTimeout, escapeHtml: ((_d = envelope.options.escapeHtml) !== null && _d !== void 0 ? _d : this.options.escapeHtml) });
this.addToContainer(this.createContainer(options), envelope, options); this.addToContainer(this.createContainer(options), envelope, options);
}); });
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', render) : render(); document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', render) : render();
@@ -121,6 +122,10 @@ class FlasherPlugin extends AbstractPlugin {
} }
addToContainer(container, envelope, options) { addToContainer(container, envelope, options) {
var _a; var _a;
if (options.escapeHtml) {
envelope.title = this.escapeHtml(envelope.title);
envelope.message = this.escapeHtml(envelope.message);
}
const notification = this.stringToHTML(this.theme.render(envelope)); const notification = this.stringToHTML(this.theme.render(envelope));
notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' ')); notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' '));
options.direction === 'bottom' ? container.append(notification) : container.prepend(notification); options.direction === 'bottom' ? container.append(notification) : container.prepend(notification);
@@ -170,6 +175,23 @@ class FlasherPlugin extends AbstractPlugin {
template.innerHTML = str.trim(); template.innerHTML = str.trim();
return template.content.firstElementChild; return template.content.firstElementChild;
} }
escapeHtml(str) {
if (str == null) {
return '';
}
return str.replace(/[&<>"'`=\/]/g, (char) => {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;',
'`': '&#96;',
'=': '&#61;',
'/': '&#47;',
}[char];
});
}
} }
class Flasher extends AbstractPlugin { class Flasher extends AbstractPlugin {
+24 -2
View File
@@ -98,14 +98,15 @@
direction: 'top', direction: 'top',
rtl: false, rtl: false,
style: {}, style: {},
escapeHtml: false,
}; };
this.theme = theme; this.theme = theme;
} }
renderEnvelopes(envelopes) { renderEnvelopes(envelopes) {
const render = () => envelopes.forEach((envelope) => { const render = () => envelopes.forEach((envelope) => {
var _a, _b, _c; var _a, _b, _c, _d;
const typeTimeout = (_b = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : this.options.timeouts[envelope.type]) !== null && _b !== void 0 ? _b : 5000; const typeTimeout = (_b = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : this.options.timeouts[envelope.type]) !== null && _b !== void 0 ? _b : 5000;
const options = Object.assign(Object.assign(Object.assign({}, this.options), envelope.options), { timeout: (_c = envelope.options.timeout) !== null && _c !== void 0 ? _c : typeTimeout }); const options = Object.assign(Object.assign(Object.assign({}, this.options), envelope.options), { timeout: (_c = envelope.options.timeout) !== null && _c !== void 0 ? _c : typeTimeout, escapeHtml: ((_d = envelope.options.escapeHtml) !== null && _d !== void 0 ? _d : this.options.escapeHtml) });
this.addToContainer(this.createContainer(options), envelope, options); this.addToContainer(this.createContainer(options), envelope, options);
}); });
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', render) : render(); document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', render) : render();
@@ -127,6 +128,10 @@
} }
addToContainer(container, envelope, options) { addToContainer(container, envelope, options) {
var _a; var _a;
if (options.escapeHtml) {
envelope.title = this.escapeHtml(envelope.title);
envelope.message = this.escapeHtml(envelope.message);
}
const notification = this.stringToHTML(this.theme.render(envelope)); const notification = this.stringToHTML(this.theme.render(envelope));
notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' ')); notification.classList.add(...`fl-container${options.rtl ? ' fl-rtl' : ''}`.split(' '));
options.direction === 'bottom' ? container.append(notification) : container.prepend(notification); options.direction === 'bottom' ? container.append(notification) : container.prepend(notification);
@@ -176,6 +181,23 @@
template.innerHTML = str.trim(); template.innerHTML = str.trim();
return template.content.firstElementChild; return template.content.firstElementChild;
} }
escapeHtml(str) {
if (str == null) {
return '';
}
return str.replace(/[&<>"'`=\/]/g, (char) => {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;',
'`': '&#96;',
'=': '&#61;',
'/': '&#47;',
}[char];
});
}
} }
class Flasher extends AbstractPlugin { class Flasher extends AbstractPlugin {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long