chat: Allow chatNewMessage hook to modify more values

This commit is contained in:
Richard Hansen 2021-04-08 21:39:11 -04:00 committed by webzwo0i
parent 1ad134a538
commit 74554d36a5
2 changed files with 26 additions and 23 deletions

View file

@ -294,6 +294,11 @@ Things in context:
This hook is called on the client side whenever a chat message is received from This hook is called on the client side whenever a chat message is received from
the server. It can be used to create different notifications for chat messages. the server. It can be used to create different notifications for chat messages.
Hoook functions can modify the `author`, `authorName`, `duration`, `sticky`,
`text`, and `timeStr` context properties to change how the message is processed.
The `text` and `timeStr` properties may contain HTML, but plugins should be
careful to sanitize any added user input to avoid introducing an XSS
vulnerability.
## collectContentPre ## collectContentPre

View file

@ -109,14 +109,6 @@ exports.chat = (() => {
// correct the time // correct the time
msg.time += this._pad.clientTimeOffset; msg.time += this._pad.clientTimeOffset;
// create the time string
let minutes = `${new Date(msg.time).getMinutes()}`;
let hours = `${new Date(msg.time).getHours()}`;
if (minutes.length === 1) minutes = `0${minutes}`;
if (hours.length === 1) hours = `0${hours}`;
const timeStr = `${hours}:${minutes}`;
// create the authorclass
if (!msg.userId) { if (!msg.userId) {
/* /*
* If, for a bug or a database corruption, the message coming from the * If, for a bug or a database corruption, the message coming from the
@ -129,25 +121,25 @@ exports.chat = (() => {
'Replacing with "unknown". This may be a bug or a database corruption.'); 'Replacing with "unknown". This may be a bug or a database corruption.');
} }
msg.userId = padutils.escapeHtml(msg.userId); const authorClass = (authorId) => `author-${authorId.replace(/[^a-y0-9]/g, (c) => {
const authorClass = `author-${msg.userId.replace(/[^a-y0-9]/g, (c) => {
if (c === '.') return '-'; if (c === '.') return '-';
return `z${c.charCodeAt(0)}z`; return `z${c.charCodeAt(0)}z`;
})}`; })}`;
const text = padutils.escapeHtmlWithClickableLinks(msg.text, '_blank');
const authorName = msg.userName == null ? html10n.get('pad.userlist.unnamed')
: padutils.escapeHtml(msg.userName);
// the hook args // the hook args
const ctx = { const ctx = {
authorName, authorName: msg.userName != null ? msg.userName : html10n.get('pad.userlist.unnamed'),
author: msg.userId, author: msg.userId,
text, text: padutils.escapeHtmlWithClickableLinks(msg.text, '_blank'),
sticky: false, sticky: false,
timestamp: msg.time, timestamp: msg.time,
timeStr, timeStr: (() => {
let minutes = `${new Date(msg.time).getMinutes()}`;
let hours = `${new Date(msg.time).getHours()}`;
if (minutes.length === 1) minutes = `0${minutes}`;
if (hours.length === 1) hours = `0${hours}`;
return `${hours}:${minutes}`;
})(),
duration: 4000, duration: 4000,
}; };
@ -160,7 +152,7 @@ exports.chat = (() => {
// does this message contain this user's name? (is the curretn user mentioned?) // does this message contain this user's name? (is the curretn user mentioned?)
const myName = $('#myusernameedit').val(); const myName = $('#myusernameedit').val();
const wasMentioned = const wasMentioned =
text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName !== 'undefined'; ctx.text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName !== 'undefined';
// If the user was mentioned, make the message sticky // If the user was mentioned, make the message sticky
if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) {
@ -171,9 +163,14 @@ exports.chat = (() => {
// Call chat message hook // Call chat message hook
hooks.aCallAll('chatNewMessage', ctx, () => { hooks.aCallAll('chatNewMessage', ctx, () => {
const cls = authorClass(ctx.author);
const html = const html =
`<p data-authorId='${msg.userId}' class='${authorClass}'><b>${authorName}:</b>` + `<p data-authorId='${padutils.escapeHtml(ctx.author)}' class='${cls}'>` +
`<span class='time ${authorClass}'>${ctx.timeStr}</span> ${ctx.text}</p>`; `<b>${padutils.escapeHtml(ctx.authorName)}:</b>` +
// ctx.text was HTML-escaped before calling the hook, and ctx.timeStr couldn't have had
// any HTML. Hook functions are trusted to not introduce an XSS vulnerability by adding
// unescaped user input to either ctx.text or ctx.timeStr.
`<span class='time ${cls}'>${ctx.timeStr}</span> ${ctx.text}</p>`;
if (isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton'); if (isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton');
else $('#chattext').append(html); else $('#chattext').append(html);
@ -186,9 +183,10 @@ exports.chat = (() => {
if (!chatOpen && ctx.duration > 0) { if (!chatOpen && ctx.duration > 0) {
$.gritter.add({ $.gritter.add({
// Note: ctx.authorName and ctx.text are already HTML-escaped.
text: $('<p>') text: $('<p>')
.append($('<span>').addClass('author-name').html(ctx.authorName)) .append($('<span>').addClass('author-name').text(ctx.authorName))
// ctx.text was HTML-escaped before calling the hook. Hook functions are trusted
// to not introduce an XSS vulnerability by adding unescaped user input.
.append(ctx.text), .append(ctx.text),
sticky: ctx.sticky, sticky: ctx.sticky,
time: 5000, time: 5000,