diff options
-rw-r--r-- | server/helpers/markdown.ts | 26 | ||||
-rw-r--r-- | server/lib/client-html.ts | 17 | ||||
-rw-r--r-- | server/lib/emailer.ts | 22 |
3 files changed, 38 insertions, 27 deletions
diff --git a/server/helpers/markdown.ts b/server/helpers/markdown.ts new file mode 100644 index 000000000..c8fb31c8c --- /dev/null +++ b/server/helpers/markdown.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import { SANITIZE_OPTIONS, TEXT_WITH_HTML_RULES } from '@shared/core-utils' | ||
2 | |||
3 | const sanitizeHtml = require('sanitize-html') | ||
4 | const markdownItEmoji = require('markdown-it-emoji/light') | ||
5 | const MarkdownItClass = require('markdown-it') | ||
6 | const markdownIt = new MarkdownItClass('default', { linkify: true, breaks: true, html: true }) | ||
7 | |||
8 | markdownIt.enable(TEXT_WITH_HTML_RULES) | ||
9 | markdownIt.use(markdownItEmoji) | ||
10 | |||
11 | const toSafeHtml = text => { | ||
12 | // Restore line feed | ||
13 | const textWithLineFeed = text.replace(/<br.?\/?>/g, '\r\n') | ||
14 | |||
15 | // Convert possible markdown (emojis, emphasis and lists) to html | ||
16 | const html = markdownIt.render(textWithLineFeed) | ||
17 | |||
18 | // Convert to safe Html | ||
19 | return sanitizeHtml(html, SANITIZE_OPTIONS) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | toSafeHtml | ||
26 | } | ||
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 6ddaa82c8..5485376d3 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -24,6 +24,7 @@ import { VideoChannelModel } from '../models/video/video-channel' | |||
24 | import { getActivityStreamDuration } from '../models/video/video-format-utils' | 24 | import { getActivityStreamDuration } from '../models/video/video-format-utils' |
25 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 25 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
26 | import { MAccountActor, MChannelActor } from '../types/models' | 26 | import { MAccountActor, MChannelActor } from '../types/models' |
27 | import { toSafeHtml } from '../helpers/markdown' | ||
27 | 28 | ||
28 | type Tags = { | 29 | type Tags = { |
29 | ogType: string | 30 | ogType: string |
@@ -54,6 +55,10 @@ type Tags = { | |||
54 | } | 55 | } |
55 | } | 56 | } |
56 | 57 | ||
58 | const toPlainText = (content: string) => { | ||
59 | return toSafeHtml(content).replace(/<[^>]+>/g, '') | ||
60 | } | ||
61 | |||
57 | class ClientHtml { | 62 | class ClientHtml { |
58 | 63 | ||
59 | private static htmlCache: { [path: string]: string } = {} | 64 | private static htmlCache: { [path: string]: string } = {} |
@@ -94,13 +99,13 @@ class ClientHtml { | |||
94 | } | 99 | } |
95 | 100 | ||
96 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) | 101 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) |
97 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) | 102 | customHtml = ClientHtml.addDescriptionTag(customHtml, toPlainText(video.description)) |
98 | 103 | ||
99 | const url = WEBSERVER.URL + video.getWatchStaticPath() | 104 | const url = WEBSERVER.URL + video.getWatchStaticPath() |
100 | const originUrl = video.url | 105 | const originUrl = video.url |
101 | const title = escapeHTML(video.name) | 106 | const title = escapeHTML(video.name) |
102 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) | 107 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) |
103 | const description = escapeHTML(video.description) | 108 | const description = toPlainText(video.description) |
104 | 109 | ||
105 | const image = { | 110 | const image = { |
106 | url: WEBSERVER.URL + video.getPreviewStaticPath() | 111 | url: WEBSERVER.URL + video.getPreviewStaticPath() |
@@ -152,13 +157,13 @@ class ClientHtml { | |||
152 | } | 157 | } |
153 | 158 | ||
154 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name)) | 159 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name)) |
155 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(videoPlaylist.description)) | 160 | customHtml = ClientHtml.addDescriptionTag(customHtml, toPlainText(videoPlaylist.description)) |
156 | 161 | ||
157 | const url = videoPlaylist.getWatchUrl() | 162 | const url = videoPlaylist.getWatchUrl() |
158 | const originUrl = videoPlaylist.url | 163 | const originUrl = videoPlaylist.url |
159 | const title = escapeHTML(videoPlaylist.name) | 164 | const title = escapeHTML(videoPlaylist.name) |
160 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) | 165 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) |
161 | const description = escapeHTML(videoPlaylist.description) | 166 | const description = toPlainText(videoPlaylist.description) |
162 | 167 | ||
163 | const image = { | 168 | const image = { |
164 | url: videoPlaylist.getThumbnailUrl() | 169 | url: videoPlaylist.getThumbnailUrl() |
@@ -236,13 +241,13 @@ class ClientHtml { | |||
236 | } | 241 | } |
237 | 242 | ||
238 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) | 243 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) |
239 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description)) | 244 | customHtml = ClientHtml.addDescriptionTag(customHtml, toPlainText(entity.description)) |
240 | 245 | ||
241 | const url = entity.getLocalUrl() | 246 | const url = entity.getLocalUrl() |
242 | const originUrl = entity.Actor.url | 247 | const originUrl = entity.Actor.url |
243 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) | 248 | const siteName = escapeHTML(CONFIG.INSTANCE.NAME) |
244 | const title = escapeHTML(entity.getDisplayName()) | 249 | const title = escapeHTML(entity.getDisplayName()) |
245 | const description = escapeHTML(entity.description) | 250 | const description = toPlainText(entity.description) |
246 | 251 | ||
247 | const image = { | 252 | const image = { |
248 | url: entity.Actor.getAvatarUrl(), | 253 | url: entity.Actor.getAvatarUrl(), |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 9ca0d5d5b..2fad82bcc 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -5,7 +5,6 @@ import { join } from 'path' | |||
5 | import { VideoChannelModel } from '@server/models/video/video-channel' | 5 | import { VideoChannelModel } from '@server/models/video/video-channel' |
6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | 6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' |
7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | 7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' |
8 | import { SANITIZE_OPTIONS, TEXT_WITH_HTML_RULES } from '@shared/core-utils' | ||
9 | import { AbuseState, EmailPayload, UserAbuse } from '@shared/models' | 8 | import { AbuseState, EmailPayload, UserAbuse } from '@shared/models' |
10 | import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model' | 9 | import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model' |
11 | import { isTestInstance, root } from '../helpers/core-utils' | 10 | import { isTestInstance, root } from '../helpers/core-utils' |
@@ -15,26 +14,7 @@ import { WEBSERVER } from '../initializers/constants' | |||
15 | import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MPlugin, MUser } from '../types/models' | 14 | import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MPlugin, MUser } from '../types/models' |
16 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' | 15 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' |
17 | import { JobQueue } from './job-queue' | 16 | import { JobQueue } from './job-queue' |
18 | 17 | import { toSafeHtml } from '../helpers/markdown' | |
19 | const sanitizeHtml = require('sanitize-html') | ||
20 | const markdownItEmoji = require('markdown-it-emoji/light') | ||
21 | const MarkdownItClass = require('markdown-it') | ||
22 | const markdownIt = new MarkdownItClass('default', { linkify: true, breaks: true, html: true }) | ||
23 | |||
24 | markdownIt.enable(TEXT_WITH_HTML_RULES) | ||
25 | |||
26 | markdownIt.use(markdownItEmoji) | ||
27 | |||
28 | const toSafeHtml = text => { | ||
29 | // Restore line feed | ||
30 | const textWithLineFeed = text.replace(/<br.?\/?>/g, '\r\n') | ||
31 | |||
32 | // Convert possible markdown (emojis, emphasis and lists) to html | ||
33 | const html = markdownIt.render(textWithLineFeed) | ||
34 | |||
35 | // Convert to safe Html | ||
36 | return sanitizeHtml(html, SANITIZE_OPTIONS) | ||
37 | } | ||
38 | 18 | ||
39 | const Email = require('email-templates') | 19 | const Email = require('email-templates') |
40 | 20 | ||