Fixed security issues

This commit is contained in:
dhoeben 2025-12-23 11:27:34 +01:00
parent 3ca2231e10
commit 8c508b53d0
2 changed files with 69 additions and 39 deletions

View File

@ -5,6 +5,7 @@ import copy
import json import json
import logging import logging
import os import os
import re
import traceback import traceback
import urllib import urllib
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -199,6 +200,9 @@ def config_themes():
if not name.lower().endswith(".css"): if not name.lower().endswith(".css"):
continue continue
if not re.fullmatch(r"[a-zA-Z0-9._-]+\.css", name):
continue
full_path = os.path.join(themes_dir, name) full_path = os.path.join(themes_dir, name)
if os.path.isfile(full_path): if os.path.isfile(full_path):
themes.append(name) themes.append(name)

View File

@ -1,4 +1,11 @@
import { createContext, useContext, useEffect, useMemo, useState } from "react"; import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import useSWR from "swr";
type Theme = "dark" | "light" | "system"; type Theme = "dark" | "light" | "system";
type ColorScheme = type ColorScheme =
@ -64,6 +71,9 @@ const initialState: ThemeProviderState = {
const ThemeProviderContext = createContext<ThemeProviderState>(initialState); const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
const fetcher = (url: string) =>
fetch(url).then((res) => (res.ok ? res.json() : []));
export function ThemeProvider({ export function ThemeProvider({
children, children,
defaultTheme = "system", defaultTheme = "system",
@ -105,48 +115,64 @@ export function ThemeProvider({
: "light"; : "light";
}, [theme]); }, [theme]);
const { data: customFiles } = useSWR<string[]>(
"/api/config/themes",
fetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
);
const allColorSchemes = useMemo(() => {
const customSchemes =
customFiles
?.filter((f) => /^[a-zA-Z0-9._-]+\.css$/.test(f))
.map((f) => {
const base = f.replace(/\.css$/, "");
return (base.startsWith("theme-")
? base
: `theme-${base}`) as ColorScheme;
}) ?? [];
return [...colorSchemes, ...customSchemes];
}, [customFiles]);
const [themesReady, setThemesReady] = useState(false);
useEffect(() => {
if (!customFiles) {
setThemesReady(true);
return;
}
const links = customFiles
.filter((f) => /^[a-zA-Z0-9._-]+\.css$/.test(f))
.map((file) => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `/config/themes/${file}`;
document.head.appendChild(link);
return new Promise<void>((resolve) => {
link.onload = () => resolve();
link.onerror = () => resolve();
});
});
Promise.all(links).then(() => setThemesReady(true));
}, [customFiles]);
useEffect(() => { useEffect(() => {
//localStorage.removeItem(storageKey); //localStorage.removeItem(storageKey);
//console.log(localStorage.getItem(storageKey)); //console.log(localStorage.getItem(storageKey));
const root = window.document.documentElement; if (!themesReady) {
return;
if (!(window as any).__frigateThemesLoaded) {
(window as any).__frigateThemesLoaded = true;
fetch("/api/config/themes")
.then((res) => (res.ok ? res.json() : []))
.then((files: string[]) => {
files.forEach((file) => {
if (!file.endsWith(".css")) {
return;
}
const baseName = file.replace(/\.css$/, "");
const className = baseName.startsWith("theme-")
? baseName
: `theme-${baseName}`;
if (!colorSchemes.includes(className as ColorScheme)) {
// runtime extension is intentional
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
colorSchemes.push(className);
}
if (!document.querySelector(`link[data-theme="${className}"]`)) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `/config/themes/${file}`;
link.dataset.theme = className;
document.head.appendChild(link);
}
});
})
.catch(() => {
});
} }
root.classList.remove("light", "dark", "system", ...colorSchemes); const root = window.document.documentElement;
root.classList.remove("light", "dark", "system", ...allColorSchemes);
root.classList.add(theme, colorScheme); root.classList.add(theme, colorScheme);
if (systemTheme) { if (systemTheme) {
@ -155,7 +181,7 @@ export function ThemeProvider({
} }
root.classList.add(theme); root.classList.add(theme);
}, [theme, colorScheme, systemTheme]); }, [theme, colorScheme, systemTheme, themesReady, allColorSchemes]);
const value = { const value = {
theme, theme,