/*!
 * Jodit Editor (https://xdsoft.net/jodit/)
 * Released under MIT see LICENSE.txt in the project root for license information.
 * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
 */
/**
 * @module helpers/html
 */
import { Dom } from "../../dom/dom.js";
import { attr } from "../utils/index.js";
/**
 * Removes dangerous constructs from HTML
 */
export function safeHTML(box, options) {
    var _a;
    if (!Dom.isElement(box) && !Dom.isFragment(box)) {
        return;
    }
    const removeEvents = (_a = options.removeEventAttributes) !== null && _a !== void 0 ? _a : options.removeOnError;
    // Single synchronous traversal of the subtree. Besides removing event
    // handlers and `javascript:` links, `sanitizeHTMLElement` neutralises
    // executable `iframe[srcdoc]`, `data:text/html` / SVG `data:` document
    // sources and dangerous schemes in every URL-bearing attribute.
    const process = (node) => {
        if (!Dom.isElement(node)) {
            return;
        }
        if (removeEvents) {
            removeAllEventAttributes(node);
        }
        sanitizeHTMLElement(node, options);
        if (options.safeLinksTarget &&
            node.nodeName === 'A' &&
            node.getAttribute('target') === '_blank') {
            const rel = node.getAttribute('rel') || '';
            const parts = rel.split(/\s+/).filter(Boolean);
            if (!parts.includes('noopener')) {
                parts.push('noopener');
            }
            if (!parts.includes('noreferrer')) {
                parts.push('noreferrer');
            }
            attr(node, 'rel', parts.join(' '));
        }
    };
    process(box);
    Dom.each(box, process);
}
/**
 * Remove all on* event handler attributes from an element
 */
function removeAllEventAttributes(elm) {
    if (!Dom.isElement(elm)) {
        return false;
    }
    let effected = false;
    const toRemove = [];
    for (let i = 0; i < elm.attributes.length; i++) {
        if (elm.attributes[i].name.toLowerCase().startsWith('on')) {
            toRemove.push(elm.attributes[i].name);
        }
    }
    for (const name of toRemove) {
        elm.removeAttribute(name);
        effected = true;
    }
    return effected;
}
/**
 * URL-bearing attributes (besides `href`) that can load or execute content.
 */
const URL_ATTRIBUTES = [
    'src',
    'data',
    'action',
    'formaction',
    'poster',
    'background',
    'xlink:href'
];
/**
 * Tags that load their URL as a *document* (scripts inside run). An SVG data
 * URL is only an XSS vector here — as an `<img>` source it renders inertly.
 */
const DOCUMENT_EMBED_TAGS = new Set(['iframe', 'frame', 'object', 'embed']);
/**
 * Detects executable / script-bearing URL schemes. The attribute value is
 * already HTML-entity-decoded by `getAttribute`, so only whitespace and
 * control characters (which browsers ignore inside a scheme) need stripping.
 */
function isDangerousUrl(value, tagName) {
    // eslint-disable-next-line no-control-regex
    const normalized = value.replace(/[\u0000-\u0020]+/g, '').toLowerCase();
    if (/^(?:javascript|vbscript|livescript|mocha):/.test(normalized)) {
        return true;
    }
    if (/^data:(?:text\/html|application\/xhtml)/.test(normalized)) {
        return true;
    }
    return (/^data:image\/svg/.test(normalized) && DOCUMENT_EMBED_TAGS.has(tagName));
}
export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
    safeJavaScriptLink: true,
    removeOnError: true
}) {
    if (!Dom.isElement(elm)) {
        return false;
    }
    let effected = false;
    if (removeOnError && elm.hasAttribute('onerror')) {
        attr(elm, 'onerror', null);
        effected = true;
    }
    const href = elm.getAttribute('href');
    if (safeJavaScriptLink && href && href.trim().indexOf('javascript') === 0) {
        attr(elm, 'href', location.protocol + '//' + href);
        effected = true;
    }
    if (safeJavaScriptLink) {
        // `srcdoc` runs its content as a full HTML document — drop it entirely.
        if (elm.hasAttribute('srcdoc')) {
            attr(elm, 'srcdoc', null);
            effected = true;
        }
        // Strip executable schemes from any other URL-bearing attribute.
        const tagName = elm.nodeName.toLowerCase();
        for (const name of URL_ATTRIBUTES) {
            const value = elm.getAttribute(name);
            if (value && isDangerousUrl(value, tagName)) {
                attr(elm, name, null);
                effected = true;
            }
        }
    }
    return effected;
}
