diff --git a/src/lib/components/views/PlaylistDetailView.svelte b/src/lib/components/views/PlaylistDetailView.svelte index ac3434aee..fd0364c42 100644 --- a/src/lib/components/views/PlaylistDetailView.svelte +++ b/src/lib/components/views/PlaylistDetailView.svelte @@ -22,6 +22,7 @@ import { isTrackUnavailable, clearTrackUnavailable, subscribe as subscribeUnavailable } from '$lib/stores/unavailableTracksStore'; import { isBlacklisted as isArtistBlacklisted } from '$lib/stores/artistBlacklistStore'; import { showToast } from '$lib/stores/toastStore'; + import { sanitizeHtml } from '$lib/utils/sanitize'; import { get } from 'svelte/store'; import { t } from '$lib/i18n'; import { onMount, tick } from 'svelte'; @@ -2020,7 +2021,7 @@ Playlist

{playlist.name}

{#if playlist.description} -

{playlist.description}

+

{@html sanitizeHtml(playlist.description)}

{/if}
{playlist.owner.name} @@ -2603,6 +2604,15 @@ line-height: 1.4; } + .playlist-description :global(a) { + color: var(--accent-primary); + text-decoration: none; + } + + .playlist-description :global(a:hover) { + text-decoration: underline; + } + .playlist-info { display: flex; align-items: center; diff --git a/src/lib/utils/sanitize.ts b/src/lib/utils/sanitize.ts new file mode 100644 index 000000000..b73e4b351 --- /dev/null +++ b/src/lib/utils/sanitize.ts @@ -0,0 +1,37 @@ +/** + * Lightweight HTML sanitizer for rendering API content (e.g. Qobuz playlist descriptions). + * Allows safe tags and strips dangerous elements/attributes. + */ + +const ALLOWED_TAGS = new Set([ + 'b', 'i', 'em', 'strong', 'br', 'a', 'p', 'span', 'ul', 'ol', 'li', +]); + +/** Strip event handler attributes and javascript: URLs */ +function stripDangerousAttrs(tag: string): string { + // Remove on* event attributes + tag = tag.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, ''); + // Remove javascript: URLs from href/src + tag = tag.replace(/(href|src)\s*=\s*(?:"javascript:[^"]*"|'javascript:[^']*')/gi, '$1=""'); + return tag; +} + +export function sanitizeHtml(html: string): string { + if (!html) return ''; + + // Remove