176 lines
5.1 KiB
JavaScript
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) => ({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
}[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);
|
|
};
|
|
})();
|