From a93217d2da5d002c99528a6f0951c84c832935b9 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Apr 2024 13:24:41 +0200 Subject: [PATCH 1/4] Prefer innerText instead of innerHTML The previous implementation can lead to XSS injection --- client/src/app/modal/confirm.component.ts | 4 ++-- .../shared/control-bar/peertube-link-button.ts | 2 +- .../player/shared/dock/peertube-dock-component.ts | 4 ++-- .../player/shared/playlist/playlist-button.ts | 5 +---- .../player/shared/playlist/playlist-menu-item.ts | 13 ++++++------- .../assets/player/shared/playlist/playlist-menu.ts | 5 ++--- .../player/shared/settings/settings-dialog.ts | 1 - .../player/shared/settings/settings-panel-child.ts | 1 - .../player/shared/settings/settings-panel.ts | 1 - .../src/assets/player/shared/stats/stats-card.ts | 14 +++++++------- client/src/assets/player/shared/upnext/end-card.ts | 2 +- client/src/sass/player/dock.scss | 9 --------- client/src/standalone/videos/shared/player-html.ts | 8 ++++---- .../videos/shared/player-options-builder.ts | 2 +- 14 files changed, 27 insertions(+), 44 deletions(-) diff --git a/client/src/app/modal/confirm.component.ts b/client/src/app/modal/confirm.component.ts index 43369befa..0862d33d4 100644 --- a/client/src/app/modal/confirm.component.ts +++ b/client/src/app/modal/confirm.component.ts @@ -62,8 +62,8 @@ export class ConfirmComponent implements OnInit { this.confirmButtonText = confirmButtonText || $localize`Confirm` this.html.toSafeHtml(message) - .then(message => { - this.message = message + .then(html => { + this.message = html this.showModal() }) diff --git a/client/src/assets/player/shared/control-bar/peertube-link-button.ts b/client/src/assets/player/shared/control-bar/peertube-link-button.ts index f93c265d6..3f4a64236 100644 --- a/client/src/assets/player/shared/control-bar/peertube-link-button.ts +++ b/client/src/assets/player/shared/control-bar/peertube-link-button.ts @@ -29,7 +29,7 @@ class PeerTubeLinkButton extends Component { createEl () { const el = videojs.dom.createEl('a', { href: this.buildLink(), - innerHTML: this.options_.instanceName, + innerText: this.options_.instanceName, title: this.player().localize('Video page (new window)'), className: 'vjs-peertube-link', target: '_blank' diff --git a/client/src/assets/player/shared/dock/peertube-dock-component.ts b/client/src/assets/player/shared/dock/peertube-dock-component.ts index c13ca647b..973db2646 100644 --- a/client/src/assets/player/shared/dock/peertube-dock-component.ts +++ b/client/src/assets/player/shared/dock/peertube-dock-component.ts @@ -37,7 +37,7 @@ class PeerTubeDockComponent extends Component { const title = videojs.dom.createEl('div', { className: 'peertube-dock-title', title: this.options_.title, - innerHTML: this.options_.title + innerText: this.options_.title }) elWrapperTitleDescription.appendChild(title) @@ -47,7 +47,7 @@ class PeerTubeDockComponent extends Component { const description = videojs.dom.createEl('div', { className: 'peertube-dock-description', title: this.options_.description, - innerHTML: this.options_.description + innerText: this.options_.description }) elWrapperTitleDescription.appendChild(description) diff --git a/client/src/assets/player/shared/playlist/playlist-button.ts b/client/src/assets/player/shared/playlist/playlist-button.ts index 45cbb4899..fffa83c7a 100644 --- a/client/src/assets/player/shared/playlist/playlist-button.ts +++ b/client/src/assets/player/shared/playlist/playlist-button.ts @@ -22,19 +22,16 @@ class PlaylistButton extends ClickableComponent { createEl () { this.wrapper = super.createEl('div', { className: 'vjs-playlist-button', - innerHTML: '', tabIndex: -1 }) as HTMLElement const icon = super.createEl('div', { className: 'vjs-playlist-icon', - innerHTML: '', tabIndex: -1 }) this.playlistInfoElement = super.createEl('div', { className: 'vjs-playlist-info', - innerHTML: '', tabIndex: -1 }) as HTMLElement @@ -47,7 +44,7 @@ class PlaylistButton extends ClickableComponent { } update () { - this.playlistInfoElement.innerHTML = this.options_.getCurrentPosition() + '/' + this.options_.playlist.videosLength + this.playlistInfoElement.innerText = this.options_.getCurrentPosition() + '/' + this.options_.playlist.videosLength this.wrapper.title = this.player().localize('Playlist: {1}', [ this.options_.playlist.displayName ]) } diff --git a/client/src/assets/player/shared/playlist/playlist-menu-item.ts b/client/src/assets/player/shared/playlist/playlist-menu-item.ts index 66c92d9e5..dcb31f7f9 100644 --- a/client/src/assets/player/shared/playlist/playlist-menu-item.ts +++ b/client/src/assets/player/shared/playlist/playlist-menu-item.ts @@ -36,8 +36,7 @@ class PlaylistMenuItem extends Component { createEl () { const li = super.createEl('li', { - className: 'vjs-playlist-menu-item', - innerHTML: '' + className: 'vjs-playlist-menu-item' }) as HTMLElement if (!this.options_.element.video) { @@ -50,7 +49,7 @@ class PlaylistMenuItem extends Component { const position = super.createEl('div', { className: 'item-position', - innerHTML: this.options_.element.position + innerText: this.options_.element.position }) positionBlock.appendChild(position) @@ -92,12 +91,12 @@ class PlaylistMenuItem extends Component { }) const title = super.createEl('div', { - innerHTML: videoElement.video.name, + innerText: videoElement.video.name, className: 'title' }) const channel = super.createEl('div', { - innerHTML: videoElement.video.channel.displayName, + innerText: videoElement.video.channel.displayName, className: 'channel' }) @@ -111,7 +110,7 @@ class PlaylistMenuItem extends Component { if (videoElement.stopTimestamp) html += ' - ' + secondsToTime(videoElement.stopTimestamp) const timestamps = super.createEl('div', { - innerHTML: html, + innerText: html, className: 'timestamps' }) @@ -125,7 +124,7 @@ class PlaylistMenuItem extends Component { private buildUnavailableVideo (li: HTMLElement) { const block = super.createEl('div', { className: 'item-unavailable', - innerHTML: this.player().localize('Unavailable video') + innerTExt: this.player().localize('Unavailable video') }) li.appendChild(block) diff --git a/client/src/assets/player/shared/playlist/playlist-menu.ts b/client/src/assets/player/shared/playlist/playlist-menu.ts index f6795390c..437130115 100644 --- a/client/src/assets/player/shared/playlist/playlist-menu.ts +++ b/client/src/assets/player/shared/playlist/playlist-menu.ts @@ -73,7 +73,6 @@ class PlaylistMenu extends Component { const menu = super.createEl('div', { className: 'vjs-playlist-menu', - innerHTML: '', tabIndex: -1 }) @@ -84,13 +83,13 @@ class PlaylistMenu extends Component { const headerLeft = super.createEl('div') const leftTitle = super.createEl('div', { - innerHTML: this.options_.playlist.displayName, + innerText: this.options_.playlist.displayName, className: 'title' }) const playlistChannel = this.options_.playlist.videoChannel const leftSubtitle = super.createEl('div', { - innerHTML: playlistChannel + innerText: playlistChannel ? this.player().localize('By {1}', [ playlistChannel.displayName ]) : '', className: 'channel' diff --git a/client/src/assets/player/shared/settings/settings-dialog.ts b/client/src/assets/player/shared/settings/settings-dialog.ts index ba39d0f45..c0b1e0356 100644 --- a/client/src/assets/player/shared/settings/settings-dialog.ts +++ b/client/src/assets/player/shared/settings/settings-dialog.ts @@ -20,7 +20,6 @@ class SettingsDialog extends Component { return super.createEl('div', { className: 'vjs-settings-dialog vjs-modal-overlay', - innerHTML: '', tabIndex: -1 }, { 'role': 'dialog', diff --git a/client/src/assets/player/shared/settings/settings-panel-child.ts b/client/src/assets/player/shared/settings/settings-panel-child.ts index 161420c38..d4ce5ce3f 100644 --- a/client/src/assets/player/shared/settings/settings-panel-child.ts +++ b/client/src/assets/player/shared/settings/settings-panel-child.ts @@ -7,7 +7,6 @@ class SettingsPanelChild extends Component { createEl () { return super.createEl('div', { className: 'vjs-settings-panel-child', - innerHTML: '', tabIndex: -1 }) } diff --git a/client/src/assets/player/shared/settings/settings-panel.ts b/client/src/assets/player/shared/settings/settings-panel.ts index 28b579bdd..d7c198bbf 100644 --- a/client/src/assets/player/shared/settings/settings-panel.ts +++ b/client/src/assets/player/shared/settings/settings-panel.ts @@ -7,7 +7,6 @@ class SettingsPanel extends Component { createEl () { return super.createEl('div', { className: 'vjs-settings-panel', - innerHTML: '', tabIndex: -1 }) } diff --git a/client/src/assets/player/shared/stats/stats-card.ts b/client/src/assets/player/shared/stats/stats-card.ts index b04e91f99..6c16c6449 100644 --- a/client/src/assets/player/shared/stats/stats-card.ts +++ b/client/src/assets/player/shared/stats/stats-card.ts @@ -285,15 +285,15 @@ class StatsCard extends Component { if (player.muted()) volume += player.localize(' (muted)') const networkActivity = playerNetworkInfo.downloadSpeed - ? `${playerNetworkInfo.downloadSpeed} ⇓ / ${playerNetworkInfo.uploadSpeed} ⇑` + ? `${playerNetworkInfo.downloadSpeed} \u21D3 / ${playerNetworkInfo.uploadSpeed} \u21D1` : undefined let totalTransferred = playerNetworkInfo.totalDownloaded - ? `${playerNetworkInfo.totalDownloaded} ⇓` + ? `${playerNetworkInfo.totalDownloaded} \u21D3` : '' if (playerNetworkInfo.totalUploaded) { - totalTransferred += `/ ${playerNetworkInfo.totalUploaded} ⇑` + totalTransferred += `/ ${playerNetworkInfo.totalUploaded} \u21D1` } const downloadBreakdown = playerNetworkInfo.downloadedFromServer @@ -337,16 +337,16 @@ class StatsCard extends Component { el.root.style.display = 'block' - if (el.value.innerHTML === value) return - el.value.innerHTML = value + if (el.value.innerText === value) return + el.value.innerText = value } - private buildInfoRow (labelText: string, valueHTML?: string) { + private buildInfoRow (labelText: string) { const root = videojs.dom.createEl('div') as HTMLElement root.style.display = 'none' const label = videojs.dom.createEl('div', { innerText: labelText }) as HTMLElement - const value = videojs.dom.createEl('span', { innerHTML: valueHTML }) as HTMLElement + const value = videojs.dom.createEl('span') as HTMLElement root.appendChild(label) root.appendChild(value) diff --git a/client/src/assets/player/shared/upnext/end-card.ts b/client/src/assets/player/shared/upnext/end-card.ts index 798a88db1..268b28ea2 100644 --- a/client/src/assets/player/shared/upnext/end-card.ts +++ b/client/src/assets/player/shared/upnext/end-card.ts @@ -121,7 +121,7 @@ export class EndCard extends Component { this.autoplayRing.setAttribute('stroke-dasharray', `${this.dashOffsetStart}`) this.autoplayRing.setAttribute('stroke-dashoffset', `${-this.dashOffsetStart}`) - this.title.innerHTML = this.options_.getTitle() + this.title.innerText = this.options_.getTitle() if (this.totalTicks === 0) { return cb(false) diff --git a/client/src/sass/player/dock.scss b/client/src/sass/player/dock.scss index 1dce24d30..1c35b8af2 100644 --- a/client/src/sass/player/dock.scss +++ b/client/src/sass/player/dock.scss @@ -60,15 +60,6 @@ .peertube-dock-description { font-size: 11px; line-height: 1.5; - - .text::before { - @include margin-right(4px); - } - - .text::after { - @include margin-left(4px); - transform: scale(-1, 1); - } } @media screen and (max-width: $screen-width-750) { diff --git a/client/src/standalone/videos/shared/player-html.ts b/client/src/standalone/videos/shared/player-html.ts index ada2aaaf7..ffe0261d3 100644 --- a/client/src/standalone/videos/shared/player-html.ts +++ b/client/src/standalone/videos/shared/player-html.ts @@ -69,14 +69,14 @@ export class PlayerHTML { videoPasswordBlock.style.display = 'flex' const videoPasswordTitle = document.getElementById('video-password-title') - videoPasswordTitle.innerHTML = translatedTitle + videoPasswordTitle.innerText = translatedTitle const videoPasswordMessage = document.getElementById('video-password-content') - videoPasswordMessage.innerHTML = translatedMessage + videoPasswordMessage.innerText = translatedMessage if (incorrectPassword) { const videoPasswordError = document.getElementById('video-password-error') - videoPasswordError.innerHTML = peertubeTranslate('Incorrect password, please enter a correct password', translations) + videoPasswordError.innerText = peertubeTranslate('Incorrect password, please enter a correct password', translations) videoPasswordError.style.transform = 'scale(1.2)' setTimeout(() => { @@ -85,7 +85,7 @@ export class PlayerHTML { } const videoPasswordSubmitButton = document.getElementById('video-password-submit') - videoPasswordSubmitButton.innerHTML = peertubeTranslate('Watch Video', translations) + videoPasswordSubmitButton.innerText = peertubeTranslate('Watch Video', translations) const videoPasswordInput = document.getElementById('video-password-input') as HTMLInputElement videoPasswordInput.placeholder = peertubeTranslate('Password', translations) diff --git a/client/src/standalone/videos/shared/player-options-builder.ts b/client/src/standalone/videos/shared/player-options-builder.ts index a38895cd6..570465ad9 100644 --- a/client/src/standalone/videos/shared/player-options-builder.ts +++ b/client/src/standalone/videos/shared/player-options-builder.ts @@ -436,7 +436,7 @@ export class PlayerOptionsBuilder { : undefined const description = this.hasWarningTitle() && this.hasP2PEnabled() - ? '' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '' + ? peertubeTranslate('Watching this video may reveal your IP address to others.') : undefined if (!title && !description) return From 1c33c6a388323f241208c2b3bb763928a7cb1e94 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Apr 2024 14:12:44 +0200 Subject: [PATCH 2/4] Remove unused highlight pipe --- client/src/app/app.module.ts | 2 - client/src/app/header/highlight.pipe.ts | 54 ------------------- .../src/app/header/suggestion.component.html | 6 ++- 3 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 client/src/app/header/highlight.pipe.ts diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 6485c98c9..5887da61d 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -11,7 +11,6 @@ import { AppComponent } from './app.component' import { CoreModule, PluginService, RedirectService, ServerService } from './core' import { EmptyComponent } from './empty.component' import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header' -import { HighlightPipe } from './header/highlight.pipe' import { polyfillICU } from './helpers' import { LanguageChooserComponent, MenuComponent, NotificationComponent } from './menu' import { AccountSetupWarningModalComponent } from './modal/account-setup-warning-modal.component' @@ -58,7 +57,6 @@ export function loadConfigFactory (server: ServerService, pluginService: PluginS HeaderComponent, SearchTypeaheadComponent, SuggestionComponent, - HighlightPipe, AccountSetupWarningModalComponent, CustomModalComponent, diff --git a/client/src/app/header/highlight.pipe.ts b/client/src/app/header/highlight.pipe.ts deleted file mode 100644 index 50ee5c1bd..000000000 --- a/client/src/app/header/highlight.pipe.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { PipeTransform, Pipe } from '@angular/core' -import { SafeHtml } from '@angular/platform-browser' - -// Thanks https://gist.github.com/adamrecsko/0f28f474eca63e0279455476cc11eca7#gistcomment-2917369 -@Pipe({ name: 'highlight' }) -export class HighlightPipe implements PipeTransform { - /* use this for single match search */ - static SINGLE_MATCH = 'Single-Match' - /* use this for single match search with a restriction that target should start with search string */ - static SINGLE_AND_STARTS_WITH_MATCH = 'Single-And-StartsWith-Match' - /* use this for global search */ - static MULTI_MATCH = 'Multi-Match' - - transform ( - contentString: string = null, - stringToHighlight: string = null, - option = 'Single-And-StartsWith-Match', - caseSensitive = false, - highlightStyleName = 'search-highlight' - ): SafeHtml { - if (stringToHighlight && contentString && option) { - let regex: any = '' - const caseFlag: string = !caseSensitive ? 'i' : '' - - switch (option) { - case 'Single-Match': { - regex = new RegExp(stringToHighlight, caseFlag) - break - } - case 'Single-And-StartsWith-Match': { - regex = new RegExp('^' + stringToHighlight, caseFlag) - break - } - case 'Multi-Match': { - regex = new RegExp(stringToHighlight, 'g' + caseFlag) - break - } - default: { - // default will be a global case-insensitive match - regex = new RegExp(stringToHighlight, 'gi') - } - } - - const replaced = contentString.replace( - regex, - (match) => `${match}` - ) - - return replaced - } else { - return contentString - } - } -} diff --git a/client/src/app/header/suggestion.component.html b/client/src/app/header/suggestion.component.html index 3f85ed6ae..63737ada6 100644 --- a/client/src/app/header/suggestion.component.html +++ b/client/src/app/header/suggestion.component.html @@ -7,8 +7,10 @@
+ [attr.aria-label]="result.text" + > + {{ result.text }} +
In this instance's network From ff5a32ff3e31f81dc92d9fd1689c2f69c71970b8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Apr 2024 14:15:11 +0200 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d119d3d73..330f2dcfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v6.0.4 + +### IMPORTANT NOTES + + * If you upgrade from PeerTube **< v6.0.0**, please follow v6.0.0 IMPORTANT NOTES + * If you upgrade from PeerTube **v6.0.0**, please follow v6.0.1 IMPORTANT NOTES + +### SECURITY + + * **Important:** Prevent XSS injection in embed. Thanks [Syst3m0ver](https://www.linkedin.com/in/ahmed-hasnaoui-790618180)! + + ## v6.0.3 ### IMPORTANT NOTES From db69b78f1ba05415ae03a2e9bf1eff71cf9f8d00 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Apr 2024 14:35:00 +0200 Subject: [PATCH 4/4] Bumped to version v6.0.4 --- client/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index 2d695321b..23b1259cb 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "peertube-client", - "version": "6.0.3", + "version": "6.0.4", "private": true, "license": "AGPL-3.0", "author": { diff --git a/package.json b/package.json index decfc6529..2c29d3674 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "peertube", "description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.", - "version": "6.0.3", + "version": "6.0.4", "private": true, "licence": "AGPL-3.0", "engines": {