mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-12 05:08:18 +00:00
Escape toast text by default, add html option (#203)
Toast title, description and action labels were inserted into the page as HTML, so passing user input could inject markup or script. They are now escaped by default, and `html: true` keeps the old raw-HTML behaviour. The action href, onclick strings, category and duration are escaped as attribute values. `icon` stays raw markup. Documents the `html` and `icon` options, adds a warning about `html` and `onclick`, and notes that the Jinja and Nunjucks macros rely on template autoescaping. Co-authored-by: Maurits van Riezen <12109031+mousetail@users.noreply.github.com>
This commit is contained in:
co-authored by
Maurits van Riezen
parent
cafffa0597
commit
30e5bdcf3a
+18
-7
@@ -131,6 +131,13 @@
|
||||
element.addEventListener('transitionend', () => element.remove(), { once: true });
|
||||
}
|
||||
|
||||
const escapeHtml = (value) => String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
function createToast(config) {
|
||||
const {
|
||||
category = 'info',
|
||||
@@ -140,18 +147,22 @@
|
||||
cancel,
|
||||
duration,
|
||||
icon,
|
||||
html: allowHtml = false,
|
||||
} = config;
|
||||
|
||||
// Text is escaped unless `html: true`. `icon` is always raw markup (SVG).
|
||||
const text = (value) => (allowHtml ? String(value) : escapeHtml(value));
|
||||
|
||||
const iconHtml = icon || (category && ICONS[category]) || '';
|
||||
const titleHtml = title ? `<h2>${title}</h2>` : '';
|
||||
const descriptionHtml = description ? `<p>${description}</p>` : '';
|
||||
const titleHtml = title ? `<h2>${text(title)}</h2>` : '';
|
||||
const descriptionHtml = description ? `<p>${text(description)}</p>` : '';
|
||||
const actionHtml = action?.href
|
||||
? `<a href="${action.href}" class="btn" data-toast-action>${action.label}</a>`
|
||||
? `<a href="${escapeHtml(action.href)}" class="btn" data-toast-action>${text(action.label)}</a>`
|
||||
: action?.onclick
|
||||
? `<button type="button" class="btn" data-toast-action onclick="${action.onclick}">${action.label}</button>`
|
||||
? `<button type="button" class="btn" data-toast-action onclick="${escapeHtml(action.onclick)}">${text(action.label)}</button>`
|
||||
: '';
|
||||
const cancelHtml = cancel
|
||||
? `<button type="button" class="btn h-6 text-xs px-2.5 rounded-sm" data-variant="outline" data-toast-cancel onclick="${cancel?.onclick || ''}">${cancel.label}</button>`
|
||||
? `<button type="button" class="btn h-6 text-xs px-2.5 rounded-sm" data-variant="outline" data-toast-cancel onclick="${escapeHtml(cancel?.onclick || '')}">${text(cancel.label)}</button>`
|
||||
: '';
|
||||
|
||||
const footerHtml = actionHtml || cancelHtml ? `<footer>${actionHtml}${cancelHtml}</footer>` : '';
|
||||
@@ -161,8 +172,8 @@
|
||||
class="toast"
|
||||
role="${category === 'error' ? 'alert' : 'status'}"
|
||||
aria-atomic="true"
|
||||
${category ? `data-category="${category}"` : ''}
|
||||
${duration !== undefined ? `data-duration="${duration}"` : ''}
|
||||
${category ? `data-category="${escapeHtml(category)}"` : ''}
|
||||
${duration !== undefined ? `data-duration="${escapeHtml(duration)}"` : ''}
|
||||
>
|
||||
<div class="toast-content">
|
||||
${iconHtml}
|
||||
|
||||
Reference in New Issue
Block a user