Sanitize HTML strings passed to buefy.toast().
The buefy toast component does not sanitize HTML leaving it open to XSS. This patch centralised all toast calls in the app to a util function which sanitizes HTML strings before passing to toast(). Closes #357.
This commit is contained in:
parent
cf0c8f3855
commit
ed57ecca99
8 changed files with 34 additions and 90 deletions
|
@ -9,6 +9,17 @@ dayjs.extend(relativeTime);
|
|||
|
||||
const reEmail = /(.+?)@(.+?)/ig;
|
||||
|
||||
const htmlEntities = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '=',
|
||||
};
|
||||
|
||||
export default class Utils {
|
||||
constructor(i18n) {
|
||||
this.i18n = i18n;
|
||||
|
@ -67,6 +78,9 @@ export default class Utils {
|
|||
return out.toFixed(2) + pfx;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/12034334
|
||||
escapeHTML = (html) => html.replace(/[&<>"'`=/]/g, (s) => htmlEntities[s]);
|
||||
|
||||
// UI shortcuts.
|
||||
confirm = (msg, onConfirm, onCancel) => {
|
||||
Dialog.confirm({
|
||||
|
@ -98,7 +112,7 @@ export default class Utils {
|
|||
|
||||
toast = (msg, typ, duration) => {
|
||||
Toast.open({
|
||||
message: msg,
|
||||
message: this.escapeHTML(msg),
|
||||
type: !typ ? 'is-success' : typ,
|
||||
queue: false,
|
||||
duration: duration || 2000,
|
||||
|
|
|
@ -278,11 +278,7 @@ export default Vue.extend({
|
|||
// Post.
|
||||
this.$api.importSubscribers(params).then(() => {
|
||||
// On file upload, show a confirmation.
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('import.importStarted'),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('import.importStarted'));
|
||||
|
||||
// Start polling status.
|
||||
this.pollStatus();
|
||||
|
|
|
@ -84,11 +84,7 @@ export default Vue.extend({
|
|||
this.$api.createList(this.form).then((data) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: data.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: data.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -96,11 +92,7 @@ export default Vue.extend({
|
|||
this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.updated', { name: data.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.updated', { name: data.name }));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -181,11 +181,7 @@ export default Vue.extend({
|
|||
this.$api.deleteList(list.id).then(() => {
|
||||
this.getLists();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: list.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: list.name }));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -119,11 +119,7 @@ export default Vue.extend({
|
|||
this.$api.createSubscriber(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -150,11 +146,7 @@ export default Vue.extend({
|
|||
this.$api.updateSubscriber(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.updated', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.updated', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -164,21 +156,12 @@ export default Vue.extend({
|
|||
try {
|
||||
attribs = JSON.parse(str);
|
||||
} catch (e) {
|
||||
this.$buefy.toast.open({
|
||||
message: `${this.$t('subscribers.invalidJSON')}: ${e.toString()}`,
|
||||
type: 'is-danger',
|
||||
duration: 3000,
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`${this.$t('subscribers.invalidJSON')}: ${e.toString()}`,
|
||||
'is-danger', 3000);
|
||||
return null;
|
||||
}
|
||||
if (attribs instanceof Array) {
|
||||
this.$buefy.toast.open({
|
||||
message: 'Attributes should be a map {} and not an array []',
|
||||
type: 'is-danger',
|
||||
duration: 3000,
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast('Attributes should be a map {} and not an array []', 'is-danger', 3000);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -355,11 +355,7 @@ export default Vue.extend({
|
|||
this.$api.deleteSubscriber(sub.id).then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: sub.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: sub.name }));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -406,11 +402,7 @@ export default Vue.extend({
|
|||
.then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }));
|
||||
});
|
||||
};
|
||||
} else {
|
||||
|
@ -422,11 +414,8 @@ export default Vue.extend({
|
|||
}).then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.subscribersDeleted',
|
||||
{ num: this.numSelectedSubscribers }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -454,11 +443,7 @@ export default Vue.extend({
|
|||
|
||||
fn(data).then(() => {
|
||||
this.querySubscribers();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.listChangeApplied'),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.listChangeApplied'));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -98,11 +98,7 @@ export default Vue.extend({
|
|||
this.$api.createTemplate(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -116,11 +112,7 @@ export default Vue.extend({
|
|||
this.$api.updateTemplate(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: `'${d.name}' updated`,
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`'${d.name}' updated`);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -143,35 +143,21 @@ export default Vue.extend({
|
|||
this.$api.createTemplate(data).then((d) => {
|
||||
this.$api.getTemplates();
|
||||
this.$emit('finished');
|
||||
this.$buefy.toast.open({
|
||||
message: `'${d.name}' created`,
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`'${d.name}' created`);
|
||||
});
|
||||
},
|
||||
|
||||
makeTemplateDefault(tpl) {
|
||||
this.$api.makeTemplateDefault(tpl.id).then(() => {
|
||||
this.$api.getTemplates();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: tpl.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: tpl.name }));
|
||||
});
|
||||
},
|
||||
|
||||
deleteTemplate(tpl) {
|
||||
this.$api.deleteTemplate(tpl.id).then(() => {
|
||||
this.$api.getTemplates();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: tpl.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: tpl.name }));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue