From 9608cce693d43579b54ef2500133f0630f7a96c3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 11 Feb 2026 11:50:06 -0500 Subject: [PATCH] refactor: emoji replacement code into helper function, remove use of regex on untrusted user input --- src/activitypub/helpers.js | 38 ++++++++++++++++++++++++++++++++++++++ src/activitypub/mocks.js | 25 ++----------------------- src/activitypub/notes.js | 8 +------- src/posts/create.js | 18 ++---------------- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index a966d26ac0..9a6577af41 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -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 ? + '' : + ``; + + 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; +}; diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 3e6e31d5df..c0fd614529 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -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'), ``); - }); - } + 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'), ``); - }); - } - 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, diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 56c1776318..d8fb26bfbb 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -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; diff --git a/src/posts/create.js b/src/posts/create.js index 044b07d699..1cce927c11 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -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'), ``); - }); - } + const property = postData.sourceContent && !postData.content ? 'sourceContent' : 'content'; + postData[property] = activitypub.helpers.renderEmoji(postData[property], _activitypub.tag); hasAttachment = _activitypub && _activitypub.attachment && _activitypub.attachment.length; }