diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts
index e6b09b4f4f..f3b3c97c32 100644
--- a/packages/backend/src/server/web/feed.ts
+++ b/packages/backend/src/server/web/feed.ts
@@ -2,7 +2,16 @@ import { Feed } from "feed";
import { In, IsNull } from "typeorm";
import config from "@/config/index.js";
import type { User } from "@/models/entities/user.js";
+import type { Note } from "@/models/entities/note.js";
import { Notes, DriveFiles, UserProfiles, Users } from "@/models/index.js";
+import getNoteHtml from "@/remote/activitypub/misc/get-note-html.js";
+
+/**
+ * If there is this part in the note, it will cause CDATA to be terminated early.
+ */
+function escapeCDATA(str: string) {
+ return str.replaceAll("]]>", "]]]]>");
+}
export default async function (
user: User,
@@ -15,7 +24,7 @@ export default async function (
const author = {
link: `${config.url}/@${user.username}`,
email: `${user.username}@${config.host}`,
- name: user.name || user.username,
+ name: escapeCDATA(user.name || user.username),
};
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
@@ -44,11 +53,13 @@ export default async function (
title: `${author.name} (@${user.username}@${config.host})`,
updated: notes[0].createdAt,
generator: "Firefish",
- description: `${user.notesCount} Notes, ${
- profile.ffVisibility === "public" ? user.followingCount : "?"
- } Following, ${
- profile.ffVisibility === "public" ? user.followersCount : "?"
- } Followers${profile.description ? ` · ${profile.description}` : ""}`,
+ description: escapeCDATA(
+ `${user.notesCount} Notes, ${
+ profile.ffVisibility === "public" ? user.followingCount : "?"
+ } Following, ${
+ profile.ffVisibility === "public" ? user.followersCount : "?"
+ } Followers${profile.description ? ` · ${profile.description}` : ""}`,
+ ),
link: author.link,
image: await Users.getAvatarUrl(user),
feedLinks: {
@@ -88,19 +99,23 @@ export default async function (
}
feed.addItem({
- title: title
- .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "")
- .substring(0, 100),
+ title: escapeCDATA(
+ title
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "")
+ .substring(0, 100),
+ ),
link: `${config.url}/notes/${note.id}`,
date: note.createdAt,
description: note.cw
- ? note.cw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "")
+ ? escapeCDATA(note.cw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""))
: undefined,
- content: contentStr.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""),
+ content: escapeCDATA(
+ contentStr.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""),
+ ),
});
}
- async function noteToString(note, isTheNote = false) {
+ async function noteToString(note: Note, isTheNote = false) {
const author = isTheNote
? null
: await Users.findOneBy({ id: note.userId });
@@ -135,7 +150,10 @@ export default async function (
}">${file.name}`;
}
}
- outstr += `${note.cw ? note.cw + "
" : ""}${note.text || ""}${fileEle}`;
+
+ outstr += `${note.cw ? note.cw + "
" : ""}${
+ getNoteHtml(note) || ""
+ }${fileEle}`;
if (isTheNote) {
outstr += `