function toClipboard(url) { if (navigator.clipboard && window.isSecureContext) { // Modern approach navigator.clipboard.writeText(url) .then(() => { alert('Link in die Zwischenablage kopiert!'); }) .catch(err => { console.error('Fehler beim Kopieren: ', err); }); } else { // Fallback for older browsers const textarea = document.createElement('textarea'); textarea.value = url; textarea.style.position = 'fixed'; // Verhindert Scrollen textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); alert('Token-URL in die Zwischenablage kopiert!'); } catch (err) { console.error('Fallback: Kopieren fehlgeschlagen', err); } document.body.removeChild(textarea); } } function printToken() { const printable = document.getElementById('tokenPrintable'); if (!printable) { console.error('Token printable content not found.'); return; } const clone = printable.cloneNode(true); clone.querySelectorAll('.token-action-buttons').forEach(el => el.remove()); const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style')) .map(node => node.outerHTML) .join('\n'); const popup = window.open('', '_blank', 'width=900,height=1200'); if (!popup) { alert('Bitte Popups erlauben, um das PDF zu speichern.'); return; } popup.document.write(` Token PDF ${styles} ${clone.outerHTML} `); popup.document.close(); popup.focus(); popup.onload = () => { popup.print(); popup.close(); }; } function escapeHtml(value) { return String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); } function sanitizeHtml(dirtyHtml) { const template = document.createElement('template'); template.innerHTML = String(dirtyHtml ?? ''); // Remove high-risk elements entirely. template.content.querySelectorAll('script, iframe, object, embed, link, meta, style, base, form').forEach((el) => { el.remove(); }); const hasUnsafeScheme = (raw) => /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(raw); const isAllowedScheme = (raw) => /^(https?|mailto|tel):/i.test(raw); const isAllowedDataImage = (raw) => /^data:image\/(png|gif|jpe?g|webp);base64,/i.test(raw); const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_ELEMENT); let node = walker.nextNode(); while (node) { [...node.attributes].forEach((attr) => { const name = attr.name.toLowerCase(); const value = String(attr.value || '').trim(); if (name.startsWith('on') || name === 'srcdoc') { node.removeAttribute(attr.name); return; } if (name === 'href' || name === 'src' || name === 'xlink:href' || name === 'formaction') { if (!value) return; if (hasUnsafeScheme(value) && !isAllowedScheme(value) && !isAllowedDataImage(value)) { node.removeAttribute(attr.name); return; } if (name === 'href' && /^https?:/i.test(value)) { node.setAttribute('rel', 'noopener noreferrer'); } } }); node = walker.nextNode(); } return template.innerHTML; } window.escapeHtml = escapeHtml; window.sanitizeHtml = sanitizeHtml; // Attach CSRF token automatically to same-origin mutating fetch requests. (() => { if (window.__csrfFetchPatched) return; window.__csrfFetchPatched = true; const nativeFetch = window.fetch.bind(window); const protectedMethods = new Set(['POST', 'PUT', 'PATCH', 'DELETE']); function getMethod(input, init) { if (init && init.method) return String(init.method).toUpperCase(); if (input instanceof Request && input.method) return String(input.method).toUpperCase(); return 'GET'; } function getUrl(input) { if (typeof input === 'string') return input; if (input instanceof URL) return input.toString(); if (input instanceof Request) return input.url; return ''; } window.fetch = function patchedFetch(input, init = {}) { const method = getMethod(input, init); const token = window.CSRF_TOKEN; if (!token || !protectedMethods.has(method)) { return nativeFetch(input, init); } let targetUrl; try { targetUrl = new URL(getUrl(input), window.location.href); } catch (err) { return nativeFetch(input, init); } if (targetUrl.origin !== window.location.origin) { return nativeFetch(input, init); } const headers = new Headers( (init && init.headers) || (input instanceof Request ? input.headers : undefined) ); if (!headers.has('X-CSRF-Token')) { headers.set('X-CSRF-Token', token); } const nextInit = { ...init, method, headers }; return nativeFetch(input, nextInit); }; })();