fix missing title attribute on <iframe> tag suggested for embedding (#3901)
* title attribute is missing on <iframe> tag suggested for embedding #3861 * fix #3901 * fix: escapeHTML #3901 * fix: playlist title instead of video title #3901 * fix #3901 * assign title directly #3901
This commit is contained in:
parent
47099aba46
commit
4097c6d66c
13 changed files with 48 additions and 35 deletions
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
|||
baseUrl: this.video.embedUrl,
|
||||
title: false,
|
||||
warningTitle: false
|
||||
})
|
||||
}),
|
||||
this.video.name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 '<iframe width="560" height="315" ' +
|
||||
'sandbox="allow-same-origin allow-scripts allow-popups" ' +
|
||||
'title="' + title + '" ' +
|
||||
'src="' + embedUrl + '" ' +
|
||||
'frameborder="0" allowfullscreen>' +
|
||||
'</iframe>'
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 = `<iframe width="${embedWidth}" height="${embedHeight}" sandbox="allow-same-origin allow-scripts" ` +
|
||||
`src="${embedUrl}" frameborder="0" allowfullscreen></iframe>`
|
||||
`title="${embedTitle}" src="${embedUrl}" frameborder="0" allowfullscreen></iframe>`
|
||||
|
||||
const json: any = {
|
||||
type: 'video',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
|
||||
`src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
'frameborder="0" allowfullscreen></iframe>'
|
||||
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 = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
|
||||
`src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
|
||||
`title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
|
||||
'frameborder="0" allowfullscreen></iframe>'
|
||||
|
||||
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 = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
|
||||
`src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
'frameborder="0" allowfullscreen></iframe>'
|
||||
|
||||
expect(res.body.html).to.equal(expectedHtml)
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue