0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Improved verification logic for Mentions

refs https://github.com/TryGhost/Team/issues/2550

By using cheerio to parse the HTML we can correctly look for elements
which use the target URL as the href attribute, rather than doing a
plaintext search. This closer to what the spec says.
This commit is contained in:
Fabien "egg" O'Carroll 2023-02-17 18:56:44 +07:00 committed by Fabien 'egg' O'Carroll
parent e14d2e662b
commit 8908a51547
3 changed files with 124 additions and 7 deletions

View file

@ -153,8 +153,7 @@ describe('Webmentions (receiving)', function () {
`;
nock(sourceUrl.origin)
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'})
.persist();
.reply(200, html, {'Content-Type': 'text/html'});
testCreatingTheMention: {
const processWebmentionJob = jobsService.awaitCompletion('processWebmention');
@ -352,4 +351,97 @@ describe('Webmentions (receiving)', function () {
})
.expectStatus(429);
});
it('can verify a webmention <a> link', async function () {
const processWebmentionJob = jobsService.awaitCompletion('processWebmention');
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><a href="${urlUtils.getSiteUrl()}">your cool website mentioned</a></body></html>
`;
nock(targetUrl.origin)
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await processWebmentionJob;
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('can verifiy a webmention <img> link', async function () {
const processWebmentionJob = jobsService.awaitCompletion('processWebmention');
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><img src="${urlUtils.getSiteUrl()}"></body></html>
`;
nock(targetUrl.origin)
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await processWebmentionJob;
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('can verify a webmention <video> link', async function () {
const processWebmentionJob = jobsService.awaitCompletion('processWebmention');
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><video src="${urlUtils.getSiteUrl()}"></body></html>
`;
nock(targetUrl.origin)
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await processWebmentionJob;
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
});

View file

@ -1,6 +1,7 @@
const ObjectID = require('bson-objectid').default;
const {ValidationError} = require('@tryghost/errors');
const MentionCreatedEvent = require('./MentionCreatedEvent');
const cheerio = require('cheerio');
module.exports = class Mention {
/** @type {Array} */
@ -22,11 +23,9 @@ module.exports = class Mention {
* @param {string} html
*/
verify(html) {
if (html.includes(this.target.href)) {
this.#verified = true;
} else {
this.#verified = false;
}
const $ = cheerio.load(html);
const hasTargetUrl = $('a[href*="' + this.target.href + '"], img[src*="' + this.target.href + '"], video[src*="' + this.target.href + '"]').length > 0;
this.#verified = hasTargetUrl;
}
/** @type {URL} */

View file

@ -44,6 +44,32 @@ describe('Mention', function () {
mention.verify('<a href="https://not-da-target.com">');
assert(!mention.verified);
});
it('Does check for Image targets', async function () {
const mention = await Mention.create({
...validInput,
target: 'https://target.com/image.jpg'
});
assert(!mention.verified);
mention.verify('<img src="https://target.com/image.jpg">');
assert(mention.verified);
mention.verify('<img src="https://not-da-target.com/image.jpg">');
assert(!mention.verified);
});
it('Does check for Video targets', async function () {
const mention = await Mention.create({
...validInput,
target: 'https://target.com/video.mp4'
});
assert(!mention.verified);
mention.verify('<video src="https://target.com/video.mp4">');
assert(mention.verified);
mention.verify('<video src="https://not-da-target.com/video.mp4">');
assert(!mention.verified);
});
});
describe('create', function () {