init f0ckm

This commit is contained in:
2026-04-25 19:51:52 +02:00
commit b646107eb7
241 changed files with 70364 additions and 0 deletions

93
public/s/js/sanitizer.js Normal file
View File

@@ -0,0 +1,93 @@
/**
* Simple Whitelist-based HTML Sanitizer
* Protects against XSS by stripping disallowed tags and attributes.
*/
class Sanitizer {
static ALLOWED_TAGS = ['span', 'img', 'a', 'br', 'b', 'i', 'strong', 'em', 'blockquote', 'pre', 'code', 'div', 'p', 'hr', 'ul', 'ol', 'li', 'textarea', 'button', 'input', 'label', 'select', 'option', 'svg', 'polyline', 'path', 'line', 'rect', 'circle', 'g', 'defs', 'symbol', 'use', 'polygon', 'ellipse', 'lineargradient', 'radialgradient', 'stop', 'clippath', 'mask', 'iframe', 'video', 'audio'];
static ALLOWED_ATTRS = ['class', 'style', 'src', 'href', 'alt', 'title', 'target', 'width', 'height', 'placeholder', 'readonly', 'disabled', 'value', 'name', 'id', 'type', 'data-parent', 'data-id', 'data-username', 'xmlns', 'viewbox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'points', 'x1', 'y1', 'x2', 'y2', 'd', 'transform', 'rx', 'ry', 'x', 'y', 'offset', 'stop-color', 'stop-opacity', 'fill-rule', 'clip-rule', 'cx', 'cy', 'r', 'fill-opacity', 'stroke-opacity', 'preserveaspectratio', 'vector-effect', 'pointer-events', 'allowfullscreen', 'frameborder', 'allow', 'referrerpolicy', 'rel', 'controls', 'loop', 'muted', 'playsinline', 'preload', 'tooltip', 'flow'];
static DISALLOWED_URL_SCHEMES = ['javascript:', 'data:', 'vbscript:'];
/**
* Clean an HTML string
* @param {string} html
* @returns {string} Sanitized HTML string
*/
static clean(html) {
if (!html) return '';
const template = document.createElement('template');
template.innerHTML = html;
this.sanitizeNode(template.content);
return template.innerHTML;
}
/**
* Iteratively sanitize DOM nodes (prevents stack overflow)
* @param {Node} root
*/
static sanitizeNode(root) {
const stack = [root];
while (stack.length > 0) {
const current = stack.pop();
const nodes = Array.from(current.childNodes);
for (const node of nodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName.toLowerCase();
if (!this.ALLOWED_TAGS.includes(tagName)) {
// If tag is not allowed, replace it with its text content
const text = document.createTextNode(node.textContent);
node.parentNode.replaceChild(text, node);
} else {
// Sanitize attributes
const attrs = Array.from(node.attributes);
for (const attr of attrs) {
const attrName = attr.name.toLowerCase();
// Check if attribute is on whitelist or is a data- attribute
if (!this.ALLOWED_ATTRS.includes(attrName) && !attrName.startsWith('data-')) {
node.removeAttribute(attr.name);
continue;
}
// Special handling for URLs
if (attrName === 'href' || attrName === 'src') {
const val = attr.value.trim().toLowerCase();
if (this.DISALLOWED_URL_SCHEMES.some(scheme => val.startsWith(scheme))) {
node.removeAttribute(attr.name);
}
// Iframes: only allow YouTube embed URLs
if (attrName === 'src' && tagName === 'iframe') {
if (!val.startsWith('https://www.youtube.com/embed/')) {
node.removeAttribute(attr.name);
}
}
}
// Special handling for style (extremely restrictive)
if (attrName === 'style') {
// Only allow specific safe CSS properties
const safeStyles = ['color', 'background', 'background-color', 'background-image', 'font-weight', 'font-style', 'text-decoration', 'vertical-align', 'height', 'width', 'display', 'fill', 'stroke', 'stroke-width', 'opacity', 'cursor', 'border', 'border-radius', 'padding', 'padding-left', 'padding-right', 'padding-top', 'padding-bottom', 'margin', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom', 'position', 'top', 'left', 'right', 'bottom', 'z-index', 'flex', 'flex-direction', 'justify-content', 'align-items', 'gap'];
const styleParts = attr.value.split(';').filter(p => p.trim().length > 0);
const cleanStyles = styleParts.filter(part => {
const prop = part.split(':')[0].trim().toLowerCase();
return safeStyles.includes(prop);
});
if (cleanStyles.length > 0) {
node.setAttribute(attr.name, cleanStyles.join('; '));
} else {
node.removeAttribute(attr.name);
}
}
}
// Push to stack for iterative processing of children
stack.push(node);
}
}
}
}
}
}
// Global export
window.Sanitizer = Sanitizer;