aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--server/lib/client-html.ts105
2 files changed, 55 insertions, 51 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e980654a6..8ffb00d23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -117,6 +117,7 @@
117 * Fix player settings menu keyboard navigation 117 * Fix player settings menu keyboard navigation
118 * Fix player placeholder width 118 * Fix player placeholder width
119 * Fix playlist miniature size with big description 119 * Fix playlist miniature size with big description
120 * Correctly escape meta tags
120 121
121 122
122## v3.4.1 123## v3.4.1
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 84eb33348..b2948254b 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -2,6 +2,7 @@ import express from 'express'
2import { readFile } from 'fs-extra' 2import { readFile } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import validator from 'validator' 4import validator from 'validator'
5import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
5import { escapeHTML } from '@shared/core-utils/renderer' 6import { escapeHTML } from '@shared/core-utils/renderer'
6import { HTMLServerConfig } from '@shared/models' 7import { HTMLServerConfig } from '@shared/models'
7import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' 8import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n'
@@ -27,7 +28,6 @@ import { VideoChannelModel } from '../models/video/video-channel'
27import { VideoPlaylistModel } from '../models/video/video-playlist' 28import { VideoPlaylistModel } from '../models/video/video-playlist'
28import { MAccountActor, MChannelActor } from '../types/models' 29import { MAccountActor, MChannelActor } from '../types/models'
29import { ServerConfigManager } from './server-config-manager' 30import { ServerConfigManager } from './server-config-manager'
30import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
31 31
32type Tags = { 32type Tags = {
33 ogType: string 33 ogType: string
@@ -38,11 +38,12 @@ type Tags = {
38 numberOfItems: number 38 numberOfItems: number
39 } 39 }
40 40
41 siteName: string 41 escapedSiteName: string
42 title: string 42 escapedTitle: string
43 escapedDescription: string
44
43 url: string 45 url: string
44 originUrl: string 46 originUrl: string
45 description: string
46 47
47 disallowIndexation?: boolean 48 disallowIndexation?: boolean
48 49
@@ -100,15 +101,15 @@ class ClientHtml {
100 res.status(HttpStatusCode.NOT_FOUND_404) 101 res.status(HttpStatusCode.NOT_FOUND_404)
101 return html 102 return html
102 } 103 }
104 const description = mdToPlainText(video.description)
103 105
104 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) 106 let customHtml = ClientHtml.addTitleTag(html, video.name)
105 customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(video.description)) 107 customHtml = ClientHtml.addDescriptionTag(customHtml, description)
106 108
107 const url = WEBSERVER.URL + video.getWatchStaticPath() 109 const url = WEBSERVER.URL + video.getWatchStaticPath()
108 const originUrl = video.url 110 const originUrl = video.url
109 const title = escapeHTML(video.name) 111 const title = video.name
110 const siteName = escapeHTML(CONFIG.INSTANCE.NAME) 112 const siteName = CONFIG.INSTANCE.NAME
111 const description = mdToPlainText(video.description)
112 113
113 const image = { 114 const image = {
114 url: WEBSERVER.URL + video.getPreviewStaticPath() 115 url: WEBSERVER.URL + video.getPreviewStaticPath()
@@ -128,9 +129,9 @@ class ClientHtml {
128 customHtml = ClientHtml.addTags(customHtml, { 129 customHtml = ClientHtml.addTags(customHtml, {
129 url, 130 url,
130 originUrl, 131 originUrl,
131 siteName, 132 escapedSiteName: escapeHTML(siteName),
132 title, 133 escapedTitle: escapeHTML(title),
133 description, 134 escapedDescription: escapeHTML(description),
134 image, 135 image,
135 embed, 136 embed,
136 ogType, 137 ogType,
@@ -161,14 +162,15 @@ class ClientHtml {
161 return html 162 return html
162 } 163 }
163 164
164 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name)) 165 const description = mdToPlainText(videoPlaylist.description)
165 customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(videoPlaylist.description)) 166
167 let customHtml = ClientHtml.addTitleTag(html, videoPlaylist.name)
168 customHtml = ClientHtml.addDescriptionTag(customHtml, description)
166 169
167 const url = WEBSERVER.URL + videoPlaylist.getWatchStaticPath() 170 const url = WEBSERVER.URL + videoPlaylist.getWatchStaticPath()
168 const originUrl = videoPlaylist.url 171 const originUrl = videoPlaylist.url
169 const title = escapeHTML(videoPlaylist.name) 172 const title = videoPlaylist.name
170 const siteName = escapeHTML(CONFIG.INSTANCE.NAME) 173 const siteName = CONFIG.INSTANCE.NAME
171 const description = mdToPlainText(videoPlaylist.description)
172 174
173 const image = { 175 const image = {
174 url: videoPlaylist.getThumbnailUrl() 176 url: videoPlaylist.getThumbnailUrl()
@@ -190,10 +192,10 @@ class ClientHtml {
190 customHtml = ClientHtml.addTags(customHtml, { 192 customHtml = ClientHtml.addTags(customHtml, {
191 url, 193 url,
192 originUrl, 194 originUrl,
193 siteName, 195 escapedSiteName: escapeHTML(siteName),
196 escapedTitle: escapeHTML(title),
197 escapedDescription: escapeHTML(description),
194 embed, 198 embed,
195 title,
196 description,
197 image, 199 image,
198 list, 200 list,
199 ogType, 201 ogType,
@@ -259,14 +261,15 @@ class ClientHtml {
259 return ClientHtml.getIndexHTML(req, res) 261 return ClientHtml.getIndexHTML(req, res)
260 } 262 }
261 263
262 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) 264 const description = mdToPlainText(entity.description)
263 customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(entity.description)) 265
266 let customHtml = ClientHtml.addTitleTag(html, entity.getDisplayName())
267 customHtml = ClientHtml.addDescriptionTag(customHtml, description)
264 268
265 const url = entity.getLocalUrl() 269 const url = entity.getLocalUrl()
266 const originUrl = entity.Actor.url 270 const originUrl = entity.Actor.url
267 const siteName = escapeHTML(CONFIG.INSTANCE.NAME) 271 const siteName = CONFIG.INSTANCE.NAME
268 const title = escapeHTML(entity.getDisplayName()) 272 const title = entity.getDisplayName()
269 const description = mdToPlainText(entity.description)
270 273
271 const image = { 274 const image = {
272 url: entity.Actor.getAvatarUrl(), 275 url: entity.Actor.getAvatarUrl(),
@@ -281,9 +284,9 @@ class ClientHtml {
281 customHtml = ClientHtml.addTags(customHtml, { 284 customHtml = ClientHtml.addTags(customHtml, {
282 url, 285 url,
283 originUrl, 286 originUrl,
284 title, 287 escapedTitle: escapeHTML(title),
285 siteName, 288 escapedSiteName: escapeHTML(siteName),
286 description, 289 escapedDescription: escapeHTML(description),
287 image, 290 image,
288 ogType, 291 ogType,
289 twitterCard, 292 twitterCard,
@@ -367,14 +370,14 @@ class ClientHtml {
367 let text = title || CONFIG.INSTANCE.NAME 370 let text = title || CONFIG.INSTANCE.NAME
368 if (title) text += ` - ${CONFIG.INSTANCE.NAME}` 371 if (title) text += ` - ${CONFIG.INSTANCE.NAME}`
369 372
370 const titleTag = `<title>${text}</title>` 373 const titleTag = `<title>${escapeHTML(text)}</title>`
371 374
372 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.TITLE, titleTag) 375 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.TITLE, titleTag)
373 } 376 }
374 377
375 private static addDescriptionTag (htmlStringPage: string, description?: string) { 378 private static addDescriptionTag (htmlStringPage: string, description?: string) {
376 const content = description || CONFIG.INSTANCE.SHORT_DESCRIPTION 379 const content = description || CONFIG.INSTANCE.SHORT_DESCRIPTION
377 const descriptionTag = `<meta name="description" content="${content}" />` 380 const descriptionTag = `<meta name="description" content="${escapeHTML(content)}" />`
378 381
379 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.DESCRIPTION, descriptionTag) 382 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.DESCRIPTION, descriptionTag)
380 } 383 }
@@ -406,8 +409,8 @@ class ClientHtml {
406 private static generateOpenGraphMetaTags (tags: Tags) { 409 private static generateOpenGraphMetaTags (tags: Tags) {
407 const metaTags = { 410 const metaTags = {
408 'og:type': tags.ogType, 411 'og:type': tags.ogType,
409 'og:site_name': tags.siteName, 412 'og:site_name': tags.escapedSiteName,
410 'og:title': tags.title, 413 'og:title': tags.escapedTitle,
411 'og:image': tags.image.url 414 'og:image': tags.image.url
412 } 415 }
413 416
@@ -417,7 +420,7 @@ class ClientHtml {
417 } 420 }
418 421
419 metaTags['og:url'] = tags.url 422 metaTags['og:url'] = tags.url
420 metaTags['og:description'] = mdToPlainText(tags.description) 423 metaTags['og:description'] = tags.escapedDescription
421 424
422 if (tags.embed) { 425 if (tags.embed) {
423 metaTags['og:video:url'] = tags.embed.url 426 metaTags['og:video:url'] = tags.embed.url
@@ -432,8 +435,8 @@ class ClientHtml {
432 435
433 private static generateStandardMetaTags (tags: Tags) { 436 private static generateStandardMetaTags (tags: Tags) {
434 return { 437 return {
435 name: tags.title, 438 name: tags.escapedTitle,
436 description: mdToPlainText(tags.description), 439 description: tags.escapedDescription,
437 image: tags.image.url 440 image: tags.image.url
438 } 441 }
439 } 442 }
@@ -442,8 +445,8 @@ class ClientHtml {
442 const metaTags = { 445 const metaTags = {
443 'twitter:card': tags.twitterCard, 446 'twitter:card': tags.twitterCard,
444 'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME, 447 'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME,
445 'twitter:title': tags.title, 448 'twitter:title': tags.escapedTitle,
446 'twitter:description': tags.description, 449 'twitter:description': tags.escapedDescription,
447 'twitter:image': tags.image.url 450 'twitter:image': tags.image.url
448 } 451 }
449 452
@@ -465,8 +468,8 @@ class ClientHtml {
465 const schema = { 468 const schema = {
466 '@context': 'http://schema.org', 469 '@context': 'http://schema.org',
467 '@type': tags.schemaType, 470 '@type': tags.schemaType,
468 'name': tags.title, 471 'name': tags.escapedTitle,
469 'description': tags.description, 472 'description': tags.escapedDescription,
470 'image': tags.image.url, 473 'image': tags.image.url,
471 'url': tags.url 474 'url': tags.url
472 } 475 }
@@ -496,59 +499,59 @@ class ClientHtml {
496 const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues) 499 const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues)
497 const schemaTags = this.generateSchemaTags(tagsValues) 500 const schemaTags = this.generateSchemaTags(tagsValues)
498 501
499 const { url, title, embed, originUrl, disallowIndexation } = tagsValues 502 const { url, escapedTitle, embed, originUrl, disallowIndexation } = tagsValues
500 503
501 const oembedLinkTags: { type: string, href: string, title: string }[] = [] 504 const oembedLinkTags: { type: string, href: string, escapedTitle: string }[] = []
502 505
503 if (embed) { 506 if (embed) {
504 oembedLinkTags.push({ 507 oembedLinkTags.push({
505 type: 'application/json+oembed', 508 type: 'application/json+oembed',
506 href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(url), 509 href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(url),
507 title 510 escapedTitle
508 }) 511 })
509 } 512 }
510 513
511 let tagsString = '' 514 let tagsStr = ''
512 515
513 // Opengraph 516 // Opengraph
514 Object.keys(openGraphMetaTags).forEach(tagName => { 517 Object.keys(openGraphMetaTags).forEach(tagName => {
515 const tagValue = openGraphMetaTags[tagName] 518 const tagValue = openGraphMetaTags[tagName]
516 519
517 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 520 tagsStr += `<meta property="${tagName}" content="${tagValue}" />`
518 }) 521 })
519 522
520 // Standard 523 // Standard
521 Object.keys(standardMetaTags).forEach(tagName => { 524 Object.keys(standardMetaTags).forEach(tagName => {
522 const tagValue = standardMetaTags[tagName] 525 const tagValue = standardMetaTags[tagName]
523 526
524 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 527 tagsStr += `<meta property="${tagName}" content="${tagValue}" />`
525 }) 528 })
526 529
527 // Twitter card 530 // Twitter card
528 Object.keys(twitterCardMetaTags).forEach(tagName => { 531 Object.keys(twitterCardMetaTags).forEach(tagName => {
529 const tagValue = twitterCardMetaTags[tagName] 532 const tagValue = twitterCardMetaTags[tagName]
530 533
531 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 534 tagsStr += `<meta property="${tagName}" content="${tagValue}" />`
532 }) 535 })
533 536
534 // OEmbed 537 // OEmbed
535 for (const oembedLinkTag of oembedLinkTags) { 538 for (const oembedLinkTag of oembedLinkTags) {
536 tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />` 539 tagsStr += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.escapedTitle}" />`
537 } 540 }
538 541
539 // Schema.org 542 // Schema.org
540 if (schemaTags) { 543 if (schemaTags) {
541 tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>` 544 tagsStr += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>`
542 } 545 }
543 546
544 // SEO, use origin URL 547 // SEO, use origin URL
545 tagsString += `<link rel="canonical" href="${originUrl}" />` 548 tagsStr += `<link rel="canonical" href="${originUrl}" />`
546 549
547 if (disallowIndexation) { 550 if (disallowIndexation) {
548 tagsString += `<meta name="robots" content="noindex" />` 551 tagsStr += `<meta name="robots" content="noindex" />`
549 } 552 }
550 553
551 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, tagsString) 554 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, tagsStr)
552 } 555 }
553} 556}
554 557