ignore invalid user mentions

pull/319/head
Sven Sauleau 2023-02-20 12:21:02 +00:00
rodzic 08a5bc4fa4
commit 5e77067324
5 zmienionych plików z 61 dodań i 26 usunięć

Wyświetl plik

@ -1,4 +1,6 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse' import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
function tag(name: string, content: string, attrs: Record<string, string> = {}): string { function tag(name: string, content: string, attrs: Record<string, string> = {}): string {
let htmlAttrs = '' let htmlAttrs = ''
@ -13,18 +15,24 @@ const linkRegex = /(^|\s|\b)(https?:\/\/[-\w@:%._+~#=]{2,256}\.[a-z]{2,6}\b(?:[-
const mentionedEmailRegex = /(^|\s|\b|\W)@(\w+(?:[.-]?\w+)+@\w+(?:[.-]?\w+)+(?:\.\w{2,63})+)(\b|\s|$)/g const mentionedEmailRegex = /(^|\s|\b|\W)@(\w+(?:[.-]?\w+)+@\w+(?:[.-]?\w+)+(?:\.\w{2,63})+)(\b|\s|$)/g
/// Transform a text status into a HTML status; enriching it with links / mentions. /// Transform a text status into a HTML status; enriching it with links / mentions.
export function enrichStatus(status: string): string { export function enrichStatus(status: string, mentions: Array<Actor>): string {
const enrichedStatus = status const enrichedStatus = status
.replace( .replace(
linkRegex, linkRegex,
(_, matchPrefix: string, link: string, matchSuffix: string) => (_, matchPrefix: string, link: string, matchSuffix: string) =>
`${matchPrefix}${getLinkAnchor(link)}${matchSuffix}` `${matchPrefix}${getLinkAnchor(link)}${matchSuffix}`
) )
.replace( .replace(mentionedEmailRegex, (_, matchPrefix: string, email: string, matchSuffix: string) => {
mentionedEmailRegex, // ensure that the match is part of the mentions array
(_, matchPrefix: string, email: string, matchSuffix: string) => for (let i = 0, len = mentions.length; i < len; i++) {
`${matchPrefix}${getMentionSpan(email)}${matchSuffix}` if (email === urlToHandle(mentions[i].id)) {
) return `${matchPrefix}${getMentionSpan(email)}${matchSuffix}`
}
}
// otherwise the match isn't valid and we don't add HTML
return `${matchPrefix}${email}${matchSuffix}`
})
return tag('p', enrichedStatus) return tag('p', enrichedStatus)
} }

Wyświetl plik

@ -21,20 +21,21 @@ export async function queryAcct(domain: string, acct: string): Promise<Actor | n
export async function queryAcctLink(domain: string, acct: string): Promise<URL | null> { export async function queryAcctLink(domain: string, acct: string): Promise<URL | null> {
const params = new URLSearchParams({ resource: `acct:${acct}` }) const params = new URLSearchParams({ resource: `acct:${acct}` })
let res let data: WebFingerResponse
try { try {
const url = new URL('/.well-known/webfinger?' + params, 'https://' + domain) const url = new URL('/.well-known/webfinger?' + params, 'https://' + domain)
console.log('query', url.href) console.log('query', url.href)
res = await fetch(url, { headers }) const res = await fetch(url, { headers })
if (!res.ok) { if (!res.ok) {
throw new Error(`WebFinger API returned: ${res.status}`) throw new Error(`WebFinger API returned: ${res.status}`)
} }
data = await res.json<WebFingerResponse>()
} catch (err) { } catch (err) {
console.warn('failed to query WebFinger:', err) console.warn('failed to query WebFinger:', err)
return null return null
} }
const data = await res.json<WebFingerResponse>()
for (let i = 0, len = data.links.length; i < len; i++) { for (let i = 0, len = data.links.length; i < len; i++) {
const link = data.links[i] const link = data.links[i]
if (link.rel === 'self' && link.type === 'application/activity+json') { if (link.rel === 'self' && link.type === 'application/activity+json') {

Wyświetl plik

@ -167,7 +167,7 @@ describe('Mastodon APIs', () => {
}) })
describe('Microformats', () => { describe('Microformats', () => {
test('convert mentions to HTML', () => { test('convert mentions to HTML', async () => {
const mentionsToTest = [ const mentionsToTest = [
{ {
mention: '@sven2@example.com', mention: '@sven2@example.com',
@ -195,18 +195,34 @@ describe('Mastodon APIs', () => {
'<span class="h-card"><a href="https://123456.test.testey.abcdef/@testey" class="u-url mention">@<span>testey</span></a></span>', '<span class="h-card"><a href="https://123456.test.testey.abcdef/@testey" class="u-url mention">@<span>testey</span></a></span>',
}, },
] ]
mentionsToTest.forEach(({ mention, expectedMentionSpan }) => {
assert.equal(enrichStatus(`hey ${mention} hi`), `<p>hey ${expectedMentionSpan} hi</p>`) for (let i = 0, len = mentionsToTest.length; i < len; i++) {
assert.equal(enrichStatus(`${mention} hi`), `<p>${expectedMentionSpan} hi</p>`) const { mention, expectedMentionSpan } = mentionsToTest[i]
assert.equal(enrichStatus(`${mention}\n\thein`), `<p>${expectedMentionSpan}\n\thein</p>`)
assert.equal(enrichStatus(`hey ${mention}`), `<p>hey ${expectedMentionSpan}</p>`) // List of mentioned actors, only the `id` is required so we can hack together an Actor
assert.equal(enrichStatus(`${mention}`), `<p>${expectedMentionSpan}</p>`) const mentions: any = [
assert.equal(enrichStatus(`@!@£${mention}!!!`), `<p>@!@£${expectedMentionSpan}!!!</p>`) { id: new URL('https://example.com/sven2') },
}) { id: new URL('https://example.eng.com/test') },
{ id: new URL('https://example.eng.co.uk/test.a.b.c-d') },
{ id: new URL('https://123456.abcdef/testey') },
{ id: new URL('https://123456.test.testey.abcdef/testey') },
]
assert.equal(enrichStatus(`hey ${mention} hi`, mentions), `<p>hey ${expectedMentionSpan} hi</p>`)
assert.equal(enrichStatus(`${mention} hi`, mentions), `<p>${expectedMentionSpan} hi</p>`)
assert.equal(enrichStatus(`${mention}\n\thein`, mentions), `<p>${expectedMentionSpan}\n\thein</p>`)
assert.equal(enrichStatus(`hey ${mention}`, mentions), `<p>hey ${expectedMentionSpan}</p>`)
assert.equal(enrichStatus(`${mention}`, mentions), `<p>${expectedMentionSpan}</p>`)
assert.equal(enrichStatus(`@!@£${mention}!!!`, mentions), `<p>@!@£${expectedMentionSpan}!!!</p>`)
}
}) })
test('handle invalid mention', () => { test('handle invalid mention', () => {
assert.equal(enrichStatus('hey @#-...@example.com'), '<p>hey @#-...@example.com</p>') assert.equal(enrichStatus('hey @#-...@example.com', []), '<p>hey @#-...@example.com</p>')
})
test('mention to invalid user', () => {
assert.equal(enrichStatus('hey test@example.com', []), '<p>hey test@example.com</p>')
}) })
test('convert links to HTML', () => { test('convert links to HTML', () => {
@ -222,11 +238,11 @@ describe('Mastodon APIs', () => {
linksToTest.forEach((link) => { linksToTest.forEach((link) => {
const url = new URL(link) const url = new URL(link)
const urlDisplayText = `${url.hostname}${url.pathname}` const urlDisplayText = `${url.hostname}${url.pathname}`
assert.equal(enrichStatus(`hey ${link} hi`), `<p>hey <a href="${link}">${urlDisplayText}</a> hi</p>`) assert.equal(enrichStatus(`hey ${link} hi`, []), `<p>hey <a href="${link}">${urlDisplayText}</a> hi</p>`)
assert.equal(enrichStatus(`${link} hi`), `<p><a href="${link}">${urlDisplayText}</a> hi</p>`) assert.equal(enrichStatus(`${link} hi`, []), `<p><a href="${link}">${urlDisplayText}</a> hi</p>`)
assert.equal(enrichStatus(`hey ${link}`), `<p>hey <a href="${link}">${urlDisplayText}</a></p>`) assert.equal(enrichStatus(`hey ${link}`, []), `<p>hey <a href="${link}">${urlDisplayText}</a></p>`)
assert.equal(enrichStatus(`${link}`), `<p><a href="${link}">${urlDisplayText}</a></p>`) assert.equal(enrichStatus(`${link}`, []), `<p><a href="${link}">${urlDisplayText}</a></p>`)
assert.equal(enrichStatus(`@!@£${link}!!!`), `<p>@!@£<a href="${link}">${urlDisplayText}</a>!!!</p>`) assert.equal(enrichStatus(`@!@£${link}!!!`, []), `<p>@!@£<a href="${link}">${urlDisplayText}</a>!!!</p>`)
}) })
}) })
}) })

Wyświetl plik

@ -457,6 +457,11 @@ describe('Mastodon APIs', () => {
}) })
) )
} }
if (
input.toString() === 'https://cloudflare.com/.well-known/webfinger?resource=acct%3Ano-json%40cloudflare.com'
) {
return new Response('not json', { status: 200 })
}
if (input.toString() === 'https://instance.horse/users/sven') { if (input.toString() === 'https://instance.horse/users/sven') {
return new Response( return new Response(
@ -496,7 +501,7 @@ describe('Mastodon APIs', () => {
} }
{ {
const mentions = await getMentions('unknown@actor.com', domain) const mentions = await getMentions('no-json@actor.com', domain)
assert.equal(mentions.length, 0) assert.equal(mentions.length, 0)
} }
@ -524,6 +529,11 @@ describe('Mastodon APIs', () => {
assert.equal(mentions.length, 1) assert.equal(mentions.length, 1)
assert.equal(mentions[0].id.toString(), 'https://' + domain + '/users/sven') assert.equal(mentions[0].id.toString(), 'https://' + domain + '/users/sven')
} }
{
const mentions = await getMentions('<p>@unknown</p>', domain)
assert.equal(mentions.length, 0)
}
}) })
test('get status count likes', async () => { test('get status count likes', async () => {

Wyświetl plik

@ -107,12 +107,12 @@ export async function handleRequest(
const hashtags = getHashtags(body.status) const hashtags = getHashtags(body.status)
const content = enrichStatus(body.status)
const mentions = await getMentions(body.status, domain) const mentions = await getMentions(body.status, domain)
if (mentions.length > 0) { if (mentions.length > 0) {
extraProperties.tag = mentions.map(newMention) extraProperties.tag = mentions.map(newMention)
} }
const content = enrichStatus(body.status, mentions)
const note = await createStatus(domain, db, connectedActor, content, mediaAttachments, extraProperties) const note = await createStatus(domain, db, connectedActor, content, mediaAttachments, extraProperties)
if (hashtags.length > 0) { if (hashtags.length > 0) {