refactor: emoji replacement code into helper function, remove use of regex on untrusted user input

This commit is contained in:
Julian Lam
2026-02-11 11:50:06 -05:00
parent bb5e711802
commit 9608cce693
4 changed files with 43 additions and 46 deletions

View File

@@ -543,3 +543,41 @@ Helpers.addressed = (id, activity) => {
return combined.has(id);
};
Helpers.renderEmoji = (text, tags, strip = false) => {
if (!text || !tags) {
return text;
}
tags = Array.isArray(tags) ? tags : [tags];
let result = text;
tags.forEach((tag) => {
const isEmoji = tag.type === 'Emoji';
const hasUrl = tag.icon && tag.icon.url;
const isImage = !tag.icon?.mediaType || tag.icon.mediaType.startsWith('image/');
if (isEmoji && (strip || (hasUrl && isImage))) {
let { name } = tag;
if (!name.startsWith(':')) {
name = `:${name}`;
}
if (!name.endsWith(':')) {
name = `${name}:`;
}
const imgTag = strip ?
'' :
`<img class="not-responsive emoji" src="${tag.icon.url}" title="${name}" />`;
let index = result.indexOf(name);
while (index !== -1) {
result = result.substring(0, index) + imgTag + result.substring(index + name.length);
index = result.indexOf(name, index + imgTag.length);
}
}
});
return result;
};

View File

@@ -195,17 +195,7 @@ Mocks.profile = async (actors) => {
const iconBackgrounds = await user.getIconBackgrounds();
let bgColor = Array.prototype.reduce.call(preferredUsername, (cur, next) => cur + next.charCodeAt(), 0);
bgColor = iconBackgrounds[bgColor % iconBackgrounds.length];
summary = summary || '';
// Replace emoji in summary
if (tag && Array.isArray(tag)) {
tag
.filter(tag => tag.type === 'Emoji' &&
isEmojiShortcode.test(tag.name) &&
tag.icon && tag.icon.mediaType && tag.icon.mediaType.startsWith('image/'))
.forEach((tag) => {
summary = summary.replace(new RegExp(tag.name, 'g'), `<img class="not-responsive emoji" src="${tag.icon.url}" title="${tag.name}" />`);
});
}
summary = activitypub.helpers.renderEmoji(summary || '', tag);
// Add custom fields into user hash
const customFields = actor.attachment && Array.isArray(actor.attachment) && actor.attachment.length ?
@@ -308,24 +298,13 @@ Mocks.category = async (actors) => {
const backgroundImage = !icon || typeof icon === 'string' ? icon : icon.url;
// Replace emoji in summary
if (tag && Array.isArray(tag)) {
tag
.filter(tag => tag.type === 'Emoji' &&
isEmojiShortcode.test(tag.name) &&
tag.icon && tag.icon.mediaType && tag.icon.mediaType.startsWith('image/'))
.forEach((tag) => {
summary = summary.replace(new RegExp(tag.name, 'g'), `<img class="not-responsive emoji" src="${tag.icon.url}" title="${tag.name}" />`);
});
}
const payload = {
cid,
name,
handle: `${preferredUsername}@${hostname}`,
slug: `${preferredUsername}@${hostname}`,
description: summary,
descriptionParsed: posts.sanitize(summary),
descriptionParsed: posts.sanitize(activitypub.helpers.renderEmoji(summary || '', tag)),
icon: backgroundImage ? 'fa-none' : 'fa-comments',
color: '#fff',
bgColor,

View File

@@ -189,13 +189,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
}
// Remove any custom emoji from title
if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) {
_activitypub.tag
.filter(tag => tag.type === 'Emoji')
.forEach((tag) => {
title = title.replace(new RegExp(tag.name, 'g'), '');
});
}
title = activitypub.helpers.renderEmoji(title, _activitypub.tag, true);
}
mainPid = utils.isNumber(mainPid) ? parseInt(mainPid, 10) : mainPid;

View File

@@ -48,22 +48,8 @@ module.exports = function (Posts) {
}
// Rewrite emoji references to inline image assets
if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) {
_activitypub.tag
.filter(tag => tag.type === 'Emoji' &&
tag.icon && tag.icon.type === 'Image')
.forEach((tag) => {
if (!tag.name.startsWith(':')) {
tag.name = `:${tag.name}`;
}
if (!tag.name.endsWith(':')) {
tag.name = `${tag.name}:`;
}
const property = postData.sourceContent && !postData.content ? 'sourceContent' : 'content';
postData[property] = postData[property].replace(new RegExp(tag.name, 'g'), `<img class="not-responsive emoji" src="${tag.icon.url}" title="${tag.name}" />`);
});
}
const property = postData.sourceContent && !postData.content ? 'sourceContent' : 'content';
postData[property] = activitypub.helpers.renderEmoji(postData[property], _activitypub.tag);
hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length;
}