kopia lustrzana https://github.com/cloudflare/wildebeest
				
				
				
			ignore invalid user mentions
							rodzic
							
								
									08a5bc4fa4
								
							
						
					
					
						commit
						5e77067324
					
				|  | @ -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) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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') { | ||||||
|  |  | ||||||
|  | @ -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>`) | ||||||
| 			}) | 			}) | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -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 () => { | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
		Ładowanie…
	
		Reference in New Issue
	
	 Sven Sauleau
						Sven Sauleau