fix(mindspace): canonicalize public page links without MindSpace segment

This commit is contained in:
john
2026-07-10 11:39:21 +08:00
parent aede1e6fcb
commit adc13a167e
2 changed files with 72 additions and 1 deletions
+41 -1
View File
@@ -444,6 +444,8 @@ const PUBLIC_HTML_LINK_PATTERN =
/https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi;
const PUBLIC_HTML_MARKDOWN_LINK_PATTERN =
/\[([^\]\n]*)\]\((https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html))\)/gi;
const MARKDOWN_LINK_PATTERN = /\[([^\]\n]*)\]\((https?:\/\/[^\s<>"')\]]+)\)/g;
const PLAIN_HTML_URL_PATTERN = /https?:\/\/[^\s<>"')\]]+\.html/gi;
const USER_IDENTITY_BLOCK_PATTERN = /^\[用户身份\][\s\S]*?(?:\n{2,}|$)/;
const IMAGE_URL_LINES_PATTERN = /\n*\[图片\d+]: [^\n]+/g;
const TKMIND_VISION_NOTE_PATTERN = /\n*【TKMind 图片分析结果[\s\S]*$/;
@@ -515,6 +517,36 @@ function sanitizeOwnPublicHtmlUrl(publicUrl, owner, rawRelativePath, currentUser
};
}
function isCurrentUserPublicOwner(owner, currentUser) {
const normalizedOwner = String(owner ?? '').trim().toLowerCase();
const normalizedUserId = String(currentUser?.id ?? '').trim().toLowerCase();
const normalizedUsername = String(currentUser?.username ?? '').trim().toLowerCase();
return Boolean(normalizedOwner && (
normalizedOwner === normalizedUserId ||
normalizedOwner === normalizedUsername
));
}
function sanitizeMissingMindSpacePublicHtmlUrl(url, currentUser) {
let parsed;
try {
parsed = new URL(url);
} catch {
return null;
}
const parts = parsed.pathname
.split('/')
.filter(Boolean)
.map((part) => decodePathSegment(part));
if (parts[0]?.toLowerCase() === 'mindspace') return null;
const owner = parts[0];
if (!isCurrentUserPublicOwner(owner, currentUser)) return null;
const rawRelativePath = parts.slice(1).join('/');
if (!rawRelativePath.toLowerCase().endsWith('.html')) return null;
const result = sanitizeOwnPublicHtmlUrl(url, owner, rawRelativePath, currentUser);
return result.ok ? result.url : result.notice;
}
export function sanitizePublicHtmlLinksInText(text, currentUser) {
let next = String(text ?? '').replace(
PUBLIC_HTML_MARKDOWN_LINK_PATTERN,
@@ -524,10 +556,18 @@ export function sanitizePublicHtmlLinksInText(text, currentUser) {
return label ? `[${label}](${result.url})` : result.url;
},
);
return next.replace(PUBLIC_HTML_LINK_PATTERN, (match, owner, rawRelativePath) => {
next = next.replace(PUBLIC_HTML_LINK_PATTERN, (match, owner, rawRelativePath) => {
const result = sanitizeOwnPublicHtmlUrl(match, owner, rawRelativePath, currentUser);
return result.ok ? result.url : result.notice;
});
next = next.replace(MARKDOWN_LINK_PATTERN, (match, label, url) => {
const sanitizedUrl = sanitizeMissingMindSpacePublicHtmlUrl(url, currentUser);
if (!sanitizedUrl) return match;
return label ? `[${label}](${sanitizedUrl})` : sanitizedUrl;
});
return next.replace(PLAIN_HTML_URL_PATTERN, (match) => (
sanitizeMissingMindSpacePublicHtmlUrl(match, currentUser) ?? match
));
}
export function sanitizeUserVisibleMessageText(text, currentUser) {