diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 82c371f4d..d6aca10e7 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts @@ -164,7 +164,8 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`, title: false, warningTitle: false - }) + }), + entry.video.name ) } diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 7a98cab3b..ce115dfab 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts @@ -815,6 +815,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { ? this.videoService.getVideoViewUrl(video.uuid) : null, embedUrl: video.embedUrl, + embedTitle: video.name, isLive: video.isLive, diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index e34836a18..eeb9f128b 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts @@ -117,7 +117,8 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV warningTitle: false, startTime: abuse.video.startAt, stopTime: abuse.video.endAt - }) + }), + abuse.video.name ) } diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts index 5b06c0bc7..4ca6f52ad 100644 --- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts @@ -61,7 +61,8 @@ export class VideoReportComponent extends FormReactive implements OnInit { baseUrl: this.video.embedUrl, title: false, warningTitle: false - }) + }), + this.video.name ) ) } diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts index b06ff3751..e8760bfcc 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.ts +++ b/client/src/app/shared/shared-share-modal/video-share.component.ts @@ -86,14 +86,14 @@ export class VideoShareComponent { const options = this.getVideoOptions(this.video.embedUrl) const embedUrl = buildVideoLink(options) - return buildVideoOrPlaylistEmbed(embedUrl) + return buildVideoOrPlaylistEmbed(embedUrl, this.video.name) } getPlaylistIframeCode () { const options = this.getPlaylistOptions(this.playlist.embedUrl) const embedUrl = buildPlaylistLink(options) - return buildVideoOrPlaylistEmbed(embedUrl) + return buildVideoOrPlaylistEmbed(embedUrl, this.playlist.displayName) } getVideoUrl () { diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index e6d614c47..1d335805b 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -98,6 +98,7 @@ export interface CommonOptions extends CustomizationOptions { videoViewUrl: string embedUrl: string + embedTitle: string isLive: boolean @@ -165,7 +166,7 @@ export class PeertubePlayerManager { PeertubePlayerManager.alreadyPlayed = true }) - self.addContextMenu(mode, player, options.common.embedUrl) + self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) player.bezels() @@ -203,7 +204,7 @@ export class PeertubePlayerManager { videojs(newVideoElement, videojsOptions, function (this: videojs.Player) { const player = this - self.addContextMenu(mode, player, options.common.embedUrl) + self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) PeertubePlayerManager.onPlayerChange(player) }) @@ -492,7 +493,7 @@ export class PeertubePlayerManager { return children } - private static addContextMenu (mode: PlayerMode, player: videojs.Player, videoEmbedUrl: string) { + private static addContextMenu (mode: PlayerMode, player: videojs.Player, videoEmbedUrl: string, videoEmbedTitle: string) { const content = [ { label: player.localize('Copy the video URL'), @@ -509,7 +510,7 @@ export class PeertubePlayerManager { { label: player.localize('Copy embed code'), listener: () => { - copyToClipboard(buildVideoOrPlaylistEmbed(videoEmbedUrl)) + copyToClipboard(buildVideoOrPlaylistEmbed(videoEmbedUrl, videoEmbedTitle)) } } ] diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index 6767459ce..d7451fa1d 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -1,4 +1,5 @@ import { VideoFile } from '@shared/models' +import { escapeHTML } from '@shared/core-utils/renderer' function toTitleCase (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) @@ -170,9 +171,11 @@ function secondsToTime (seconds: number, full = false, symbol?: string) { return time } -function buildVideoOrPlaylistEmbed (embedUrl: string) { +function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) { + const title = escapeHTML(embedTitle) return '' diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index c87270027..614a1cc0b 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -545,7 +545,8 @@ export class PeerTubeEmbed { serverUrl: window.location.origin, language: navigator.language, - embedUrl: window.location.origin + videoInfo.embedPath + embedUrl: window.location.origin + videoInfo.embedPath, + embedTitle: videoInfo.name }, webtorrent: { diff --git a/server/controllers/services.ts b/server/controllers/services.ts index d0217c30a..189e1651b 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts @@ -3,6 +3,7 @@ import { EMBED_SIZE, PREVIEWS_SIZE, WEBSERVER, THUMBNAILS_SIZE } from '../initia import { asyncMiddleware, oembedValidator } from '../middlewares' import { accountNameWithHostGetValidator } from '../middlewares/validators' import { MChannelSummary } from '@server/types/models' +import { escapeHTML } from '@shared/core-utils/renderer' const servicesRouter = express.Router() @@ -79,6 +80,7 @@ function buildOEmbed (options: { const embedUrl = webserverUrl + embedPath let embedWidth = EMBED_SIZE.width let embedHeight = EMBED_SIZE.height + const embedTitle = escapeHTML(title) let thumbnailUrl = previewPath ? webserverUrl + previewPath @@ -96,7 +98,7 @@ function buildOEmbed (options: { } const html = `` + `title="${embedTitle}" src="${embedUrl}" frameborder="0" allowfullscreen>` const json: any = { type: 'video', diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 0bd84ffaa..b93868c12 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -154,24 +154,6 @@ function root () { return rootPath } -// Thanks: https://stackoverflow.com/a/12034334 -function escapeHTML (stringParam) { - if (!stringParam) return '' - - const entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', - '/': '/', - '`': '`', - '=': '=' - } - - return String(stringParam).replace(/[&<>"'`=/]/g, s => entityMap[s]) -} - function pageToStartAndCount (page: number, itemsPerPage: number) { const start = (page - 1) * itemsPerPage @@ -278,7 +260,6 @@ export { objectConverter, root, - escapeHTML, pageToStartAndCount, sanitizeUrl, sanitizeHost, diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index f19ec7df0..fcc11c7b2 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts @@ -5,7 +5,8 @@ import validator from 'validator' import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' -import { escapeHTML, isTestInstance, sha256 } from '../helpers/core-utils' +import { isTestInstance, sha256 } from '../helpers/core-utils' +import { escapeHTML } from '@shared/core-utils/renderer' import { logger } from '../helpers/logger' import { CONFIG } from '../initializers/config' import { diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts index df910c111..6202eb66c 100644 --- a/server/tests/api/server/services.ts +++ b/server/tests/api/server/services.ts @@ -20,6 +20,7 @@ const expect = chai.expect describe('Test services', function () { let server: ServerInfo = null let playlistUUID: string + let playlistDisplayName: string let video: Video before(async function () { @@ -52,6 +53,7 @@ describe('Test services', function () { }) playlistUUID = res.body.videoPlaylist.uuid + playlistDisplayName = 'The Life and Times of Scrooge McDuck' await addVideoInPlaylist({ url: server.url, @@ -69,7 +71,7 @@ describe('Test services', function () { const res = await getOEmbed(server.url, oembedUrl) const expectedHtml = '' const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath @@ -88,7 +90,7 @@ describe('Test services', function () { const res = await getOEmbed(server.url, oembedUrl) const expectedHtml = '' expect(res.body.html).to.equal(expectedHtml) @@ -109,7 +111,7 @@ describe('Test services', function () { const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) const expectedHtml = '' expect(res.body.html).to.equal(expectedHtml) diff --git a/shared/core-utils/renderer/html.ts b/shared/core-utils/renderer/html.ts index 1220848a0..de4ad47ac 100644 --- a/shared/core-utils/renderer/html.ts +++ b/shared/core-utils/renderer/html.ts @@ -19,3 +19,21 @@ export const SANITIZE_OPTIONS = { } } } + +// Thanks: https://stackoverflow.com/a/12034334 +export function escapeHTML (stringParam: string) { + if (!stringParam) return '' + + const entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/', + '`': '`', + '=': '=' + } + + return String(stringParam).replace(/[&<>"'`=/]/g, s => entityMap[s]) +}