diff --git a/backend/package.json b/backend/package.json
index 031a43df05..954f40c700 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -87,6 +87,7 @@
"metascraper-url": "^5.6.5",
"metascraper-video": "^5.6.5",
"metascraper-youtube": "^5.6.3",
+ "minimatch": "^3.0.4",
"neo4j-driver": "~1.7.5",
"neo4j-graphql-js": "^2.6.3",
"neode": "^0.3.0",
diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
index f2e3c23035..6fe5d3891c 100644
--- a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
+++ b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
@@ -64,7 +64,7 @@ describe('currentUser { notifications }', () => {
let post
const title = 'Mentioning Al Capone'
const content =
- 'Hey @al-capone how do you do?'
+ 'Hey @al-capone how do you do?'
beforeEach(async () => {
const createPostMutation = gql`
@@ -88,7 +88,7 @@ describe('currentUser { notifications }', () => {
it('sends you a notification', async () => {
const expectedContent =
- 'Hey @al-capone how do you do?'
+ 'Hey @al-capone how do you do?'
const expected = {
currentUser: {
notifications: [
@@ -108,14 +108,22 @@ describe('currentUser { notifications }', () => {
).resolves.toEqual(expected)
})
- describe('who mentions me again', () => {
+ describe('who mentions me many times', () => {
beforeEach(async () => {
- const updatedContent = `${post.content} One more mention to @al-capone`
- // The response `post.content` contains a link but the XSSmiddleware
- // should have the `mention` CSS class removed. I discovered this
- // during development and thought: A feature not a bug! This way we
- // can encode a re-mentioning of users when you edit your post or
- // comment.
+ const updatedContent = `
+ One more mention to
+
+ @al-capone
+
+ and again:
+
+ @al-capone
+
+ and again
+
+ @al-capone
+
+ `
const updatePostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!) {
UpdatePost(id: $id, content: $content, title: $title) {
@@ -136,7 +144,7 @@ describe('currentUser { notifications }', () => {
it('creates exactly one more notification', async () => {
const expectedContent =
- 'Hey @al-capone how do you do? One more mention to @al-capone'
+ '
One more mention to
@al-capone
and again:
@al-capone
and again
@al-capone
'
const expected = {
currentUser: {
notifications: [
diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
index c2fcf169c8..d08309f0ba 100644
--- a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
+++ b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
@@ -1,20 +1,13 @@
import cheerio from 'cheerio'
-const ID_REGEX = /\/profile\/([\w\-.!~*'"(),]+)/g
export default function(content) {
if (!content) return []
const $ = cheerio.load(content)
- const urls = $('.mention')
+ let userIds = $('a.mention[data-mention-id]')
.map((_, el) => {
- return $(el).attr('href')
+ return $(el).attr('data-mention-id')
})
.get()
- const ids = []
- urls.forEach(url => {
- let match
- while ((match = ID_REGEX.exec(url)) != null) {
- ids.push(match[1])
- }
- })
- return ids
+ userIds = userIds.map(id => id.trim()).filter(id => !!id)
+ return userIds
}
diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
index f39fbc859d..a631b64a32 100644
--- a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
+++ b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
@@ -1,5 +1,12 @@
import extractMentionedUsers from './extractMentionedUsers'
+const contentWithMentions =
+ '
Something inspirational about @bob-der-baumeister and @jenny-rostock.
' +const contentEmptyMentions = + 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' +const contentWithPlainLinks = + 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' + describe('extractMentionedUsers', () => { describe('content undefined', () => { it('returns empty array', () => { @@ -7,53 +14,17 @@ describe('extractMentionedUsers', () => { }) }) - describe('searches through links', () => { - it('ignores links without .mention class', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual([]) - }) - - describe('given a link with .mention class', () => { - it('extracts ids', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) - }) - - describe('handles links', () => { - it('with slug and id', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) - }) - - it('with domains', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) - }) - - it('special characters', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual(['u!*(),2', 'u.~-3']) - }) - }) + it('ignores links without .mention class', () => { + expect(extractMentionedUsers(contentWithPlainLinks)).toEqual([]) + }) - describe('does not crash if', () => { - it('`href` contains no user id', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual([]) - }) + describe('given a link with .mention class and `data-mention-id` attribute ', () => { + it('extracts ids', () => { + expect(extractMentionedUsers(contentWithMentions)).toEqual(['u3']) + }) - it('`href` is empty or invalid', () => { - const content = - 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' - expect(extractMentionedUsers(content)).toEqual([]) - }) - }) + it('ignores empty `data-mention-id` attributes', () => { + expect(extractMentionedUsers(contentEmptyMentions)).toEqual([]) }) }) }) diff --git a/backend/src/middleware/xssMiddleware.js b/backend/src/middleware/xssMiddleware.js index 6894e86015..f98ab9d619 100644 --- a/backend/src/middleware/xssMiddleware.js +++ b/backend/src/middleware/xssMiddleware.js @@ -2,30 +2,16 @@ import walkRecursive from '../helpers/walkRecursive' // import { getByDot, setByDot, getItems, replaceItems } from 'feathers-hooks-common' import sanitizeHtml from 'sanitize-html' // import { isEmpty, intersection } from 'lodash' -import cheerio from 'cheerio' import linkifyHtml from 'linkifyjs/html' -const embedToAnchor = content => { - const $ = cheerio.load(content) - $('div[data-url-embed]').each((i, el) => { - const url = el.attribs['data-url-embed'] - const aTag = $(`${url}`) - $(el).replaceWith(aTag) - }) - return $('body').html() -} - function clean(dirty) { if (!dirty) { return dirty } - // Convert embeds to a-tags - dirty = embedToAnchor(dirty) dirty = linkifyHtml(dirty) dirty = sanitizeHtml(dirty, { allowedTags: [ - 'iframe', 'img', 'p', 'h3', @@ -50,35 +36,24 @@ function clean(dirty) { a: ['href', 'class', 'target', 'data-*', 'contenteditable'], span: ['contenteditable', 'class', 'data-*'], img: ['src'], - iframe: ['src', 'class', 'frameborder', 'allowfullscreen'], }, allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'], parser: { lowerCaseTags: true, }, transformTags: { - iframe: function(tagName, attribs) { - return { - tagName: 'a', - text: attribs.src, - attribs: { - href: attribs.src, - target: '_blank', - 'data-url-embed': '', - }, - } - }, h1: 'h3', h2: 'h3', h3: 'h3', h4: 'h4', h5: 'strong', i: 'em', - a: function(tagName, attribs) { + a: (tagName, attribs) => { return { tagName: 'a', attribs: { - href: attribs.href, + ...attribs, + href: attribs.href || '', target: '_blank', rel: 'noopener noreferrer nofollow', }, @@ -86,33 +61,6 @@ function clean(dirty) { }, b: 'strong', s: 'strike', - img: function(tagName, attribs) { - const src = attribs.src - - if (!src) { - // remove broken images - return {} - } - - // if (isEmpty(hook.result)) { - // const config = hook.app.get('thumbor') - // if (config && src.indexOf(config < 0)) { - // // download image - // // const ThumborUrlHelper = require('../helper/thumbor-helper') - // // const Thumbor = new ThumborUrlHelper(config.key || null, config.url || null) - // // src = Thumbor - // // .setImagePath(src) - // // .buildUrl('740x0') - // } - // } - return { - tagName: 'img', - attribs: { - // TODO: use environment variables - src: `http://localhost:3050/images?url=${src}`, - }, - } - }, }, }) @@ -120,8 +68,6 @@ function clean(dirty) { dirty = dirty // remove all tags with "space only" .replace(/<[a-z-]+>[\s]+<\/[a-z-]+>/gim, '') - // remove all iframes - .replace(/(