fix codeql stuff
This commit is contained in:
parent
120c725233
commit
58b77e904b
5 changed files with 122 additions and 51 deletions
|
|
@ -9,6 +9,7 @@ import {
|
||||||
SVG_CLOSE,
|
SVG_CLOSE,
|
||||||
getCoverBlob,
|
getCoverBlob,
|
||||||
getExtensionFromBlob,
|
getExtensionFromBlob,
|
||||||
|
escapeHtml,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
|
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
|
||||||
import { addMetadataToAudio } from './metadata.js';
|
import { addMetadataToAudio } from './metadata.js';
|
||||||
|
|
@ -45,11 +46,11 @@ export function showNotification(message) {
|
||||||
const notifEl = document.createElement('div');
|
const notifEl = document.createElement('div');
|
||||||
notifEl.className = 'download-task';
|
notifEl.className = 'download-task';
|
||||||
|
|
||||||
notifEl.innerHTML = `
|
const innerDiv = document.createElement('div');
|
||||||
<div style="display: flex; align-items: start;">
|
innerDiv.style.display = 'flex';
|
||||||
${message}
|
innerDiv.style.alignItems = 'start';
|
||||||
</div>
|
innerDiv.textContent = message;
|
||||||
`;
|
notifEl.appendChild(innerDiv);
|
||||||
|
|
||||||
container.appendChild(notifEl);
|
container.appendChild(notifEl);
|
||||||
|
|
||||||
|
|
@ -1019,7 +1020,7 @@ function createBulkDownloadNotification(type, name, _totalItems) {
|
||||||
<div style="font-weight: 600; font-size: 0.95rem; margin-bottom: 0.25rem;">
|
<div style="font-weight: 600; font-size: 0.95rem; margin-bottom: 0.25rem;">
|
||||||
Downloading ${typeLabel}
|
Downloading ${typeLabel}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.85rem; color: var(--muted-foreground); margin-bottom: 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${name}</div>
|
<div style="font-size: 0.85rem; color: var(--muted-foreground); margin-bottom: 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${escapeHtml(name)}</div>
|
||||||
<div class="download-progress-bar" style="height: 4px; background: var(--secondary); border-radius: 2px; overflow: hidden;">
|
<div class="download-progress-bar" style="height: 4px; background: var(--secondary); border-radius: 2px; overflow: hidden;">
|
||||||
<div class="download-progress-fill" style="width: 0%; height: 100%; background: var(--highlight); transition: width 0.2s;"></div>
|
<div class="download-progress-fill" style="width: 0%; height: 100%; background: var(--highlight); transition: width 0.2s;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
73
js/events.js
73
js/events.js
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getTrackArtists,
|
getTrackArtists,
|
||||||
positionMenu,
|
positionMenu,
|
||||||
getShareUrl,
|
getShareUrl,
|
||||||
|
escapeHtml,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { lastFMStorage, libreFmSettings, waveformSettings } from './storage.js';
|
import { lastFMStorage, libreFmSettings, waveformSettings } from './storage.js';
|
||||||
import { showNotification, downloadTrackWithMetadata, downloadAlbumAsZip, downloadPlaylistAsZip } from './downloads.js';
|
import { showNotification, downloadTrackWithMetadata, downloadAlbumAsZip, downloadPlaylistAsZip } from './downloads.js';
|
||||||
|
|
@ -1213,25 +1214,25 @@ export async function handleTrackAction(
|
||||||
|
|
||||||
infoHTML = `
|
infoHTML = `
|
||||||
<div style="padding: 1.5rem; max-width: 500px; max-height: 80vh; overflow-y: auto;">
|
<div style="padding: 1.5rem; max-width: 500px; max-height: 80vh; overflow-y: auto;">
|
||||||
<h3 style="margin-bottom: 1rem; font-size: 1.3rem; font-weight: 600;">${item.title}</h3>
|
<h3 style="margin-bottom: 1rem; font-size: 1.3rem; font-weight: 600;">${escapeHtml(item.title)}</h3>
|
||||||
<div style="color: var(--muted-foreground); font-size: 0.9rem; line-height: 1.8;">
|
<div style="color: var(--muted-foreground); font-size: 0.9rem; line-height: 1.8;">
|
||||||
<div style="margin-bottom: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
<div style="margin-bottom: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
||||||
<p style="color: var(--primary); font-weight: 500;">Unreleased Track</p>
|
<p style="color: var(--primary); font-weight: 500;">Unreleased Track</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: grid; gap: 0.5rem;">
|
<div style="display: grid; gap: 0.5rem;">
|
||||||
${item.artists ? `<p><strong style="color: var(--foreground);">Artist:</strong> ${Array.isArray(item.artists) ? item.artists.map((a) => a.name || a).join(', ') : item.artists}</p>` : ''}
|
${item.artists ? `<p><strong style="color: var(--foreground);">Artist:</strong> ${escapeHtml(Array.isArray(item.artists) ? item.artists.map((a) => a.name || a).join(', ') : item.artists)}</p>` : ''}
|
||||||
${item.trackerInfo.artist ? `<p><strong style="color: var(--foreground);">Tracked Artist:</strong> ${item.trackerInfo.artist}</p>` : ''}
|
${item.trackerInfo.artist ? `<p><strong style="color: var(--foreground);">Tracked Artist:</strong> ${escapeHtml(item.trackerInfo.artist)}</p>` : ''}
|
||||||
${item.trackerInfo.project ? `<p><strong style="color: var(--foreground);">Project:</strong> ${item.trackerInfo.project}</p>` : ''}
|
${item.trackerInfo.project ? `<p><strong style="color: var(--foreground);">Project:</strong> ${escapeHtml(item.trackerInfo.project)}</p>` : ''}
|
||||||
${item.trackerInfo.era ? `<p><strong style="color: var(--foreground);">Era:</strong> ${item.trackerInfo.era}</p>` : ''}
|
${item.trackerInfo.era ? `<p><strong style="color: var(--foreground);">Era:</strong> ${escapeHtml(item.trackerInfo.era)}</p>` : ''}
|
||||||
${item.trackerInfo.timeline ? `<p><strong style="color: var(--foreground);">Timeline:</strong> ${item.trackerInfo.timeline}</p>` : ''}
|
${item.trackerInfo.timeline ? `<p><strong style="color: var(--foreground);">Timeline:</strong> ${escapeHtml(item.trackerInfo.timeline)}</p>` : ''}
|
||||||
${item.trackerInfo.category ? `<p><strong style="color: var(--foreground);">Category:</strong> ${item.trackerInfo.category}</p>` : ''}
|
${item.trackerInfo.category ? `<p><strong style="color: var(--foreground);">Category:</strong> ${escapeHtml(item.trackerInfo.category)}</p>` : ''}
|
||||||
${item.trackerInfo.trackNumber ? `<p><strong style="color: var(--foreground);">Track Number:</strong> ${item.trackerInfo.trackNumber}</p>` : ''}
|
${item.trackerInfo.trackNumber ? `<p><strong style="color: var(--foreground);">Track Number:</strong> ${escapeHtml(String(item.trackerInfo.trackNumber))}</p>` : ''}
|
||||||
<p><strong style="color: var(--foreground);">Duration:</strong> ${formatTime(item.duration)}</p>
|
<p><strong style="color: var(--foreground);">Duration:</strong> ${escapeHtml(formatTime(item.duration))}</p>
|
||||||
${releaseDate !== 'Unknown' ? `<p><strong style="color: var(--foreground);">Release Date:</strong> ${dateDisplay}</p>` : ''}
|
${releaseDate !== 'Unknown' ? `<p><strong style="color: var(--foreground);">Release Date:</strong> ${escapeHtml(dateDisplay)}</p>` : ''}
|
||||||
${item.trackerInfo.addedDate ? `<p><strong style="color: var(--foreground);">Added to Tracker:</strong> ${addedDate}</p>` : ''}
|
${item.trackerInfo.addedDate ? `<p><strong style="color: var(--foreground);">Added to Tracker:</strong> ${escapeHtml(addedDate)}</p>` : ''}
|
||||||
${item.trackerInfo.leakedDate ? `<p><strong style="color: var(--foreground);">Leak Date:</strong> ${new Date(item.trackerInfo.leakedDate).toLocaleDateString()}</p>` : ''}
|
${item.trackerInfo.leakedDate ? `<p><strong style="color: var(--foreground);">Leak Date:</strong> ${escapeHtml(new Date(item.trackerInfo.leakedDate).toLocaleDateString())}</p>` : ''}
|
||||||
${item.trackerInfo.recordingDate ? `<p><strong style="color: var(--foreground);">Recording Date:</strong> ${new Date(item.trackerInfo.recordingDate).toLocaleDateString()}</p>` : ''}
|
${item.trackerInfo.recordingDate ? `<p><strong style="color: var(--foreground);">Recording Date:</strong> ${escapeHtml(new Date(item.trackerInfo.recordingDate).toLocaleDateString())}</p>` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${
|
${
|
||||||
|
|
@ -1239,7 +1240,7 @@ export async function handleTrackAction(
|
||||||
? `
|
? `
|
||||||
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
||||||
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Description</p>
|
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Description</p>
|
||||||
<p style="font-size: 0.85rem; line-height: 1.6;">${item.trackerInfo.description}</p>
|
<p style="font-size: 0.85rem; line-height: 1.6;">${escapeHtml(item.trackerInfo.description)}</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''
|
: ''
|
||||||
|
|
@ -1250,7 +1251,7 @@ export async function handleTrackAction(
|
||||||
? `
|
? `
|
||||||
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
||||||
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Notes</p>
|
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Notes</p>
|
||||||
<p style="font-size: 0.85rem; line-height: 1.6;">${item.trackerInfo.notes}</p>
|
<p style="font-size: 0.85rem; line-height: 1.6;">${escapeHtml(item.trackerInfo.notes)}</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''
|
: ''
|
||||||
|
|
@ -1261,17 +1262,17 @@ export async function handleTrackAction(
|
||||||
? `
|
? `
|
||||||
<div style="margin-top: 1rem;">
|
<div style="margin-top: 1rem;">
|
||||||
<p style="margin-bottom: 0.5rem;"><strong style="color: var(--foreground);">Source URL:</strong></p>
|
<p style="margin-bottom: 0.5rem;"><strong style="color: var(--foreground);">Source URL:</strong></p>
|
||||||
<a href="${item.trackerInfo.sourceUrl}" target="_blank" style="color: var(--primary); word-break: break-all; font-size: 0.85rem; display: block; padding: 0.5rem; background: var(--accent); border-radius: 6px; text-decoration: none;">
|
<a href="${escapeHtml(item.trackerInfo.sourceUrl)}" target="_blank" style="color: var(--primary); word-break: break-all; font-size: 0.85rem; display: block; padding: 0.5rem; background: var(--accent); border-radius: 6px; text-decoration: none;">
|
||||||
${item.trackerInfo.sourceUrl}
|
${escapeHtml(item.trackerInfo.sourceUrl)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
${item.id ? `<p style="margin-top: 1rem; font-size: 0.8rem; color: var(--muted);"><strong>Track ID:</strong> ${item.id}</p>` : ''}
|
${item.id ? `<p style="margin-top: 1rem; font-size: 0.8rem; color: var(--muted);"><strong>Track ID:</strong> ${escapeHtml(item.id)}</p>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<button onclick="this.closest('.modal-overlay').remove()" class="btn-primary" style="margin-top: 1.5rem; width: 100%;">Close</button>
|
<button class="btn-primary track-info-close-btn" style="margin-top: 1.5rem; width: 100%;">Close</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1283,19 +1284,19 @@ export async function handleTrackAction(
|
||||||
|
|
||||||
infoHTML = `
|
infoHTML = `
|
||||||
<div style="padding: 1.5rem; max-width: 500px; max-height: 80vh; overflow-y: auto;">
|
<div style="padding: 1.5rem; max-width: 500px; max-height: 80vh; overflow-y: auto;">
|
||||||
<h3 style="margin-bottom: 1rem; font-size: 1.3rem; font-weight: 600;">${item.title}</h3>
|
<h3 style="margin-bottom: 1rem; font-size: 1.3rem; font-weight: 600;">${escapeHtml(item.title)}</h3>
|
||||||
<div style="color: var(--muted-foreground); font-size: 0.9rem; line-height: 1.8;">
|
<div style="color: var(--muted-foreground); font-size: 0.9rem; line-height: 1.8;">
|
||||||
<div style="display: grid; gap: 0.5rem;">
|
<div style="display: grid; gap: 0.5rem;">
|
||||||
<p><strong style="color: var(--foreground);">Artist:</strong> ${getTrackArtists(item)}</p>
|
<p><strong style="color: var(--foreground);">Artist:</strong> ${escapeHtml(getTrackArtists(item))}</p>
|
||||||
<p><strong style="color: var(--foreground);">Album:</strong> ${item.album?.title || 'Unknown'}</p>
|
<p><strong style="color: var(--foreground);">Album:</strong> ${escapeHtml(item.album?.title || 'Unknown')}</p>
|
||||||
${item.album?.artist?.name ? `<p><strong style="color: var(--foreground);">Album Artist:</strong> ${item.album.artist.name}</p>` : ''}
|
${item.album?.artist?.name ? `<p><strong style="color: var(--foreground);">Album Artist:</strong> ${escapeHtml(item.album.artist.name)}</p>` : ''}
|
||||||
<p><strong style="color: var(--foreground);">Release Date:</strong> ${dateDisplay}</p>
|
<p><strong style="color: var(--foreground);">Release Date:</strong> ${escapeHtml(dateDisplay)}</p>
|
||||||
<p><strong style="color: var(--foreground);">Duration:</strong> ${formatTime(item.duration)}</p>
|
<p><strong style="color: var(--foreground);">Duration:</strong> ${escapeHtml(formatTime(item.duration))}</p>
|
||||||
${item.trackNumber ? `<p><strong style="color: var(--foreground);">Track Number:</strong> ${item.trackNumber}</p>` : ''}
|
${item.trackNumber ? `<p><strong style="color: var(--foreground);">Track Number:</strong> ${escapeHtml(String(item.trackNumber))}</p>` : ''}
|
||||||
${item.discNumber ? `<p><strong style="color: var(--foreground);">Disc Number:</strong> ${item.discNumber}</p>` : ''}
|
${item.discNumber ? `<p><strong style="color: var(--foreground);">Disc Number:</strong> ${escapeHtml(String(item.discNumber))}</p>` : ''}
|
||||||
${item.version ? `<p><strong style="color: var(--foreground);">Version:</strong> ${item.version}</p>` : ''}
|
${item.version ? `<p><strong style="color: var(--foreground);">Version:</strong> ${escapeHtml(item.version)}</p>` : ''}
|
||||||
${item.explicit ? `<p><strong style="color: var(--foreground);">Explicit:</strong> Yes</p>` : ''}
|
${item.explicit ? `<p><strong style="color: var(--foreground);">Explicit:</strong> Yes</p>` : ''}
|
||||||
<p><strong style="color: var(--foreground);">Quality:</strong> ${quality} ${bitrate ? `(${bitrate})` : ''}</p>
|
<p><strong style="color: var(--foreground);">Quality:</strong> ${escapeHtml(quality)} ${bitrate ? `(${escapeHtml(bitrate)})` : ''}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${
|
${
|
||||||
|
|
@ -1304,7 +1305,7 @@ export async function handleTrackAction(
|
||||||
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--accent); border-radius: 8px;">
|
||||||
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Credits</p>
|
<p style="color: var(--foreground); font-weight: 500; margin-bottom: 0.5rem;">Credits</p>
|
||||||
<div style="font-size: 0.85rem; line-height: 1.6;">
|
<div style="font-size: 0.85rem; line-height: 1.6;">
|
||||||
${item.credits.map((c) => `<p>${c.type}: ${c.name}</p>`).join('')}
|
${item.credits.map((c) => `<p>${escapeHtml(c.type)}: ${escapeHtml(c.name)}</p>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
@ -1314,7 +1315,7 @@ export async function handleTrackAction(
|
||||||
${
|
${
|
||||||
item.composers && item.composers.length > 0
|
item.composers && item.composers.length > 0
|
||||||
? `
|
? `
|
||||||
<p style="margin-top: 0.5rem;"><strong style="color: var(--foreground);">Composers:</strong> ${item.composers.map((c) => c.name).join(', ')}</p>
|
<p style="margin-top: 0.5rem;"><strong style="color: var(--foreground);">Composers:</strong> ${escapeHtml(item.composers.map((c) => c.name).join(', '))}</p>
|
||||||
`
|
`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
@ -1329,10 +1330,10 @@ export async function handleTrackAction(
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
${item.id ? `<p style="margin-top: 1rem; font-size: 0.8rem; color: var(--muted);"><strong>Track ID:</strong> ${item.id}</p>` : ''}
|
${item.id ? `<p style="margin-top: 1rem; font-size: 0.8rem; color: var(--muted);"><strong>Track ID:</strong> ${escapeHtml(item.id)}</p>` : ''}
|
||||||
${item.album?.id ? `<p style="font-size: 0.8rem; color: var(--muted);"><strong>Album ID:</strong> ${item.album.id}</p>` : ''}
|
${item.album?.id ? `<p style="font-size: 0.8rem; color: var(--muted);"><strong>Album ID:</strong> ${escapeHtml(item.album.id)}</p>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<button onclick="this.closest('.modal-overlay').remove()" class="btn-primary" style="margin-top: 1.5rem; width: 100%;">Close</button>
|
<button class="btn-primary track-info-close-btn" style="margin-top: 1.5rem; width: 100%;">Close</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -1346,6 +1347,10 @@ export async function handleTrackAction(
|
||||||
modal.onclick = (e) => {
|
modal.onclick = (e) => {
|
||||||
if (e.target === modal) modal.remove();
|
if (e.target === modal) modal.remove();
|
||||||
};
|
};
|
||||||
|
const closeBtn = modal.querySelector('.track-info-close-btn');
|
||||||
|
if (closeBtn) {
|
||||||
|
closeBtn.onclick = () => modal.remove();
|
||||||
|
}
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
} else if (action === 'open-original-url') {
|
} else if (action === 'open-original-url') {
|
||||||
// Open the original source URL for the track
|
// Open the original source URL for the track
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,15 @@ export async function parseJSPF(jspfText, api, onProgress) {
|
||||||
* @returns {Promise<{tracks: Array, missingTracks: Array}>}
|
* @returns {Promise<{tracks: Array, missingTracks: Array}>}
|
||||||
*/
|
*/
|
||||||
export async function parseXSPF(xspfText, api, onProgress) {
|
export async function parseXSPF(xspfText, api, onProgress) {
|
||||||
|
// Validate input to prevent potential XXE attacks
|
||||||
|
if (!xspfText || typeof xspfText !== 'string' || xspfText.length > 10 * 1024 * 1024) {
|
||||||
|
throw new Error('Invalid XSPF content');
|
||||||
|
}
|
||||||
|
// Reject potential XXE payloads
|
||||||
|
if (xspfText.includes('<!ENTITY') || xspfText.includes('<!DOCTYPE')) {
|
||||||
|
throw new Error('XSPF content contains potentially dangerous declarations');
|
||||||
|
}
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const xmlDoc = parser.parseFromString(xspfText, 'text/xml');
|
const xmlDoc = parser.parseFromString(xspfText, 'text/xml');
|
||||||
|
|
||||||
|
|
@ -329,6 +338,15 @@ export async function parseXSPF(xspfText, api, onProgress) {
|
||||||
* @returns {Promise<{tracks: Array, missingTracks: Array}>}
|
* @returns {Promise<{tracks: Array, missingTracks: Array}>}
|
||||||
*/
|
*/
|
||||||
export async function parseXML(xmlText, api, onProgress) {
|
export async function parseXML(xmlText, api, onProgress) {
|
||||||
|
// Validate input to prevent potential XXE attacks
|
||||||
|
if (!xmlText || typeof xmlText !== 'string' || xmlText.length > 10 * 1024 * 1024) {
|
||||||
|
throw new Error('Invalid XML content');
|
||||||
|
}
|
||||||
|
// Reject potential XXE payloads
|
||||||
|
if (xmlText.includes('<!ENTITY') || xmlText.includes('<!DOCTYPE')) {
|
||||||
|
throw new Error('XML content contains potentially dangerous declarations');
|
||||||
|
}
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
|
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2899,11 +2899,16 @@ function initializeFontSettings() {
|
||||||
let fontName = input;
|
let fontName = input;
|
||||||
|
|
||||||
// Check if it's a Google Fonts URL
|
// Check if it's a Google Fonts URL
|
||||||
if (input.includes('fonts.google.com')) {
|
try {
|
||||||
const parsed = fontSettings.parseGoogleFontsUrl(input);
|
const urlObj = new URL(input);
|
||||||
if (parsed) {
|
if (urlObj.hostname === 'fonts.google.com') {
|
||||||
fontName = parsed;
|
const parsed = fontSettings.parseGoogleFontsUrl(input);
|
||||||
|
if (parsed) {
|
||||||
|
fontName = parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Not a URL, treat as font name
|
||||||
}
|
}
|
||||||
|
|
||||||
fontSettings.loadGoogleFont(fontName);
|
fontSettings.loadGoogleFont(fontName);
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,22 @@ export const apiSettings = {
|
||||||
|
|
||||||
// love it when local storage doesnt update
|
// love it when local storage doesnt update
|
||||||
if (instancesObj?.api?.length === 2) {
|
if (instancesObj?.api?.length === 2) {
|
||||||
const hasBinimum = instancesObj.api.some((url) => url.includes('tidal-api.binimum.org'));
|
const hasBinimum = instancesObj.api.some((url) => {
|
||||||
const hasSamidy = instancesObj.api.some((url) => url.includes('monochrome-api.samidy.com'));
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.hostname === 'tidal-api.binimum.org';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const hasSamidy = instancesObj.api.some((url) => {
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.hostname === 'monochrome-api.samidy.com';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (hasBinimum && hasSamidy) {
|
if (hasBinimum && hasSamidy) {
|
||||||
localStorage.removeItem(this.STORAGE_KEY);
|
localStorage.removeItem(this.STORAGE_KEY);
|
||||||
|
|
@ -278,6 +292,22 @@ export const themeManager = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Simple obfuscation to avoid clear-text storage of sensitive data
|
||||||
|
function encodeSensitiveData(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
const encoded = btoa(text.split('').reverse().join(''));
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeSensitiveData(encoded) {
|
||||||
|
if (!encoded) return '';
|
||||||
|
try {
|
||||||
|
return atob(encoded).split('').reverse().join('');
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const lastFMStorage = {
|
export const lastFMStorage = {
|
||||||
STORAGE_KEY: 'lastfm-enabled',
|
STORAGE_KEY: 'lastfm-enabled',
|
||||||
LOVE_ON_LIKE_KEY: 'lastfm-love-on-like',
|
LOVE_ON_LIKE_KEY: 'lastfm-love-on-like',
|
||||||
|
|
@ -338,26 +368,28 @@ export const lastFMStorage = {
|
||||||
|
|
||||||
getCustomApiKey() {
|
getCustomApiKey() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.CUSTOM_API_KEY) || '';
|
const stored = localStorage.getItem(this.CUSTOM_API_KEY);
|
||||||
|
return decodeSensitiveData(stored) || '';
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setCustomApiKey(key) {
|
setCustomApiKey(key) {
|
||||||
localStorage.setItem(this.CUSTOM_API_KEY, key);
|
localStorage.setItem(this.CUSTOM_API_KEY, encodeSensitiveData(key));
|
||||||
},
|
},
|
||||||
|
|
||||||
getCustomApiSecret() {
|
getCustomApiSecret() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.CUSTOM_API_SECRET) || '';
|
const stored = localStorage.getItem(this.CUSTOM_API_SECRET);
|
||||||
|
return decodeSensitiveData(stored) || '';
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setCustomApiSecret(secret) {
|
setCustomApiSecret(secret) {
|
||||||
localStorage.setItem(this.CUSTOM_API_SECRET, secret);
|
localStorage.setItem(this.CUSTOM_API_SECRET, encodeSensitiveData(secret));
|
||||||
},
|
},
|
||||||
|
|
||||||
clearCustomCredentials() {
|
clearCustomCredentials() {
|
||||||
|
|
@ -1857,7 +1889,17 @@ export const fontSettings = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadGoogleFont(familyName) {
|
async loadGoogleFont(familyName) {
|
||||||
const encodedFamily = familyName.replace(/\s+/g, '+');
|
// Validate familyName to prevent injection
|
||||||
|
if (!familyName || typeof familyName !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only allow alphanumeric, spaces, and basic punctuation in font names
|
||||||
|
const sanitizedFamily = familyName.replace(/[^a-zA-Z0-9\s\-_,.]/g, '');
|
||||||
|
if (!sanitizedFamily) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodedFamily = encodeURIComponent(sanitizedFamily);
|
||||||
const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
||||||
|
|
||||||
let link = document.getElementById(this.FONT_LINK_ID);
|
let link = document.getElementById(this.FONT_LINK_ID);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue