bethaus-app/static/functions.js
2026-02-28 09:08:48 +00:00

176 lines
5.1 KiB
JavaScript

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(`<!doctype html>
<html>
<head>
<title>Token PDF</title>
${styles}
<style>
body { padding: 24px; }
.token-action-buttons { display: none !important; }
.card { box-shadow: none !important; }
</style>
</head>
<body>${clone.outerHTML}</body>
</html>`);
popup.document.close();
popup.focus();
popup.onload = () => {
popup.print();
popup.close();
};
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, (char) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[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);
};
})();