INSERT INTO `axme_templates` (`id`, `uid`, `customer_id`, `name`, `content`, `created_at`, `updated_at`, `builder`, `is_default`, `theme`, `type`) VALUES
(27, '64ff570a7dba2', 2, 'Plantilla de prueba', '<!DOCTYPE html><html lang=\"en\"><head><style id=\"mceDefaultStyles\" type=\"text/css\">img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}\r\n</style>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"Mark Otto, Jacob Thornton, and Bootstrap contributors\">\n    <meta name=\"generator\" content=\"AcelleSystemLayouts\">\n    <title>Blank</title>\n\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1NzBhN2RiYTIvY3Nz/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1NzBhN2RiYTIvY3Nz/custom.css\" rel=\"stylesheet\">\n\n    <style>\n        @media (min-width: 768px) {\n            .container {\n                max-width: 650px;\n            }\n        }\n        @media (min-width: 992px) {\n            .container {\n                max-width: 760px;\n            }\n        }\n        @media (min-width: 769px) {\n            [data-hide-on=\"desktop\"] {\n                display: none!important;\n            }\n        }\n        @media (max-width: 600px) {\n            [data-hide-on=\"mobile\"] {\n                display: none!important;\n            }\n        }\n        @media (max-width: 600px) {\n            [builder-element=CellContainerElement].cell-responsive {\n                display: block!important;\n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] {\n                width: 100%!important;\n                max-width: none!important;        \n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"],\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"BlockElement\"] {\n                padding-left: 0px!important;\n                padding-right: 0px!important;\n            }\n            /* EmailMode */\n            [builder-element=CellContainerElement].cell-responsive tbody,\n            [builder-element=CellContainerElement].cell-responsive tr,\n            [builder-element=CellContainerElement].cell-responsive td {\n                display: block!important;\n                width: 100%;\n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] td {\n                padding-left: 0px!important;\n                padding-right: 0px!important;\n            }\n        }\n        @media (min-width: 600px) {\n            [builder-element=PricingTableElement] .col-12 {\n                flex-basis: 0;\n                flex-grow: 1;\n            }\n        }\n    </style>\n  <script>/*\n * This entire block is wrapped in an IIFE to prevent polluting the scope of the web page with\n * functions created by this extension.\n */\n(function(realOpen, realSend, realFetch) {\n    /*\n     * Sometimes pages overwrite standard objects with global\n     * variables. Where possible, we extract fresh versions from an\n     * iframe.\n     */\n    const iframe = document.createElement(\'iframe\');\n    document.documentElement.appendChild(iframe);\n\n    // Initialise with global values\n    let URL = window.URL;\n    let WeakMap = window.WeakMap;\n    let WeakSet = window.WeakSet;\n    let TextDecoder = window.TextDecoder;\n    let Uint8Array = window.Uint8Array;\n    let decodeURIComponent = window.decodeURIComponent;\n    let URLSearchParams = window.URLSearchParams;\n    let Array = window.Array;\n    let Object = window.Object;\n    let atob = window.atob;\n    let CustomEvent = window.CustomEvent;\n    let performance = window.performance;\n    let JSON = window.JSON;\n\n    /*\n     * Under some circumstances the iframe isn\'t added\n     * successfully. If it has a contentWindow then we can extract the\n     * values from it, but otherwise we fall back on the global\n     * values.\n     */\n    if (iframe.contentWindow) {\n        // Try block because sometimes the CORS prevents access to iframe\n        try {\n            URL = iframe.contentWindow.URL;\n            WeakMap = iframe.contentWindow.WeakMap;\n            WeakSet = iframe.contentWindow.WeakSet;\n            TextDecoder = iframe.contentWindow.TextDecoder;\n            Uint8Array = iframe.contentWindow.Uint8Array;\n            decodeURIComponent = iframe.contentWindow.decodeURIComponent;\n            URLSearchParams = iframe.contentWindow.URLSearchParams;\n            Array = iframe.contentWindow.Array;\n            Object = iframe.contentWindow.Object;\n            atob = iframe.contentWindow.atob;\n            CustomEvent = iframe.contentWindow.CustomEvent;\n            performance = iframe.contentWindow.performance;\n            JSON = iframe.contentWindow.JSON;\n        } catch (error) {\n            // Empty catch block to prevent errors from showing in page logs\n        }\n    }\n\n    // If iframe was added successfully, remove it\n    if (iframe.parentNode) {\n        iframe.parentNode.removeChild(iframe);\n    }\n\n    /*\n     * If any of the values don\'t have the correct type, return and\n     * don\'t attempt any monkey patching. This avoids breaking web\n     * pages.\n     */\n    for (f of [\n        URL,\n        WeakMap,\n        WeakSet,\n        TextDecoder,\n        Uint8Array,\n        decodeURIComponent,\n        URLSearchParams,\n        Array,\n        Object,\n        atob,\n        CustomEvent,\n    ]) {\n        if (typeof f !== \'function\') {\n            return;\n        }\n    }\n\n    for (o of [performance, JSON]) {\n        if (typeof o !== \'object\') {\n            return;\n        }\n    }\n\n    /*\n     * Override common funcions that accept callbacks, such as\n     * setInterval, to keep track of what script each callback comes\n     * from. Skimmers commonly have the actual skimming code in a\n     * callback, so most of the time when we intercept a request,\n     * document.currentScript is null. To solve this, the script is\n     * stored in a closure when the callback is passed, and assigned\n     * to callbackCurrentscript when the callback is run.\n     */\n\n    let callbackCurrentScript = null;\n\n    // Maps callbacks originally passed to patched versions\n    const patchedCallbackMap = new WeakMap();\n\n    // Maps patched callbacks to original versions\n    const originalCallbackMap = new WeakMap();\n\n    /**\n     *  Generates the function with which to override the\n     *  callback-accepting functions.\n     *  @param realFn The real function, e.g. setInterval.\n     *  @param callbackIndex The index of the callback in the\n     *  arguments of realFn. For example, the callback is the first\n     *  argument in setInterval, so callbackIndex should be 0.\n     */\n    function generateAddCallbackFunction(realFn, callbackIndex) {\n        const patched = function(...args) {\n            const script = document.currentScript || callbackCurrentScript;\n\n            const callback = args[callbackIndex];\n\n            // If the callback isn\'t a function, there\'s nothing to patch\n            if (typeof callback !== \'function\') {\n                return realFn.apply(this, args);\n            }\n\n            // If a patch exists, use it instead of creating new function\n            let patchedCallback = patchedCallbackMap.get(callback);\n            if (!patchedCallback) {\n                patchedCallback = function() {\n                    callbackCurrentScript = script;\n                    const retVal = callback.apply(this, [...arguments]);\n                    callbackCurrentScript = null;\n                    return retVal;\n                };\n                patchedCallbackMap.set(callback, patchedCallback);\n                originalCallbackMap.set(patchedCallback, callback);\n            }\n\n            args[callbackIndex] = patchedCallback;\n            return realFn.apply(this, args);\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    const realSetInterval = window.setInterval;\n\n    window.setInterval = generateAddCallbackFunction(realSetInterval, 0);\n\n    const realSetTimeout = window.setTimeout;\n\n    window.setTimeout = generateAddCallbackFunction(realSetTimeout, 0);\n\n    const realPromiseThen = Promise.prototype.then;\n    Promise.prototype.then = generateAddCallbackFunction(realPromiseThen, 0);\n\n    const realPromiseCatch = Promise.prototype.catch;\n    Promise.prototype.catch = generateAddCallbackFunction(realPromiseCatch, 0);\n\n    const realPromiseFinally = Promise.prototype.finally;\n    if (realPromiseFinally) {\n        Promise.prototype.finally = generateAddCallbackFunction(\n            realPromiseFinally,\n            0\n        );\n    }\n\n    const realAddEventListener = EventTarget.prototype.addEventListener;\n\n    EventTarget.prototype.addEventListener = generateAddCallbackFunction(\n        realAddEventListener,\n        1\n    );\n\n    /*\n     * removeEventListener needs to be patched to map the passed\n     * listener to the patched listener.\n     */\n    const realRemoveEventListener = EventTarget.prototype.removeEventListener;\n    EventTarget.prototype.removeEventListener = function(...args) {\n        args[1] = patchedCallbackMap.get(args[1]) || args[1];\n\n        return realRemoveEventListener.apply(this, args);\n    };\n    EventTarget.prototype.removeEventListener.toString = () =>\n        realRemoveEventListener.toString();\n\n    /*\n     * Event listeners are sometimes set by directly assigning to a\n     * property from the GlobalEventHandlers mixin,\n     * e.g. \'submitBtn.onclick\'. For each of the GlobalEventHandlers\n     * implementations that are likely to be relevant, the event\n     * properties likely to be used by skimmers are overridden with\n     * our version that tracks currentScript when assigned.\n     */\n    for (const eventTarget of [\n        HTMLElement.prototype,\n        Document.prototype,\n        window,\n    ]) {\n        for (const eventName of [\n            \'onclick\',\n            \'onblur\',\n            \'onfocus\',\n            \'onchange\',\n            \'oninput\',\n            \'onkeydown\',\n            \'onkeypress\',\n            \'onkeyup\',\n            \'onload\',\n            \'onloadend\',\n            \'onmousedown\',\n            \'onmouseenter\',\n            \'onmouseleave\',\n            \'onmousemove\',\n            \'onmouseout\',\n            \'onmouseover\',\n            \'onmouseup\',\n            \'onpointerdown\',\n            \'onpointermove\',\n            \'onpointerup\',\n            \'onpointerover\',\n            \'onpointerout\',\n            \'onpointerenter\',\n            \'onpointerleave\',\n            \'onsubmit\',\n        ]) {\n            const descriptor = Object.getOwnPropertyDescriptor(\n                eventTarget,\n                eventName\n            );\n\n            if (!descriptor) {\n                continue;\n            }\n\n            const realGetter = descriptor.get;\n            const realSetter = descriptor.set;\n\n            const patchedSetter = generateAddCallbackFunction(realSetter, 0);\n\n            /*\n             * In case a script checks the value of the property after\n             * setting it, the getter has to be patched to return the\n             * original callback.\n             */\n            const patchedGetter = function() {\n                const realValue = realGetter.call(this);\n\n                const originalCallback = originalCallbackMap.get(realValue);\n                if (originalCallback) {\n                    return originalCallback;\n                } else {\n                    return realValue;\n                }\n            };\n\n            patchedGetter.toString = () => realGetter.toString();\n\n            Object.defineProperty(eventTarget, eventName, {\n                get: patchedGetter,\n                set: patchedSetter,\n            });\n        }\n    }\n\n    /*\n     * This script has some dependencies that need to be included\n     * without polluting the global namespace. This is achieved by\n     * inserting them into the source at build time in the\n     * makefile. This is preferable to loading the dependencies via\n     * AJAX, because the latter doesn\'t work if the CSP disallows\n     * unsafe-eval. The following tag tells the makefile where to\n     * insert the dependencies.\n     */\n    // <DEPENDENCIES>\nclass InputAnalyser {\n    constructor(_inputs) {\n        this._inputs = _inputs;\n        this._irrelevantInputs = new WeakSet();\n\n        /*\n         * Not currently using Password because it was causing too\n         * many false positives. Some sites use external auth domains,\n         * and many have misconfigured analytics that collect\n         * passwords.\n         */\n        this.relevantInputs = [new CardNumber()];\n    }\n\n    get relevantInputValues() {\n        const inputs = {};\n        this._inputs.forEach(\n            input => (inputs[input.id || input.name] = input.value)\n        );\n\n        return inputs;\n    }\n\n    get relevantFilledInputs() {\n        return this.allInputs\n            .filter(input => this.isRelevantElement(input))\n            .filter(input => input.value.length >= cons.minInputSize)\n            .map(input => ({\n                id: input.id,\n                name: input.name,\n                value: input.value,\n            }));\n    }\n\n    get allInputs() {\n        this._inputs = [\n            ...document.querySelectorAll(\n                this.relevantInputs.map(i => i.relevantInputTypes).join(\', \')\n            ),\n        ];\n\n        return this._inputs;\n    }\n\n    static getLabelText(input) {\n        if (!input.labels.length) {\n            return \'\';\n        }\n        return stripSpecialChars(input.labels[0].textContent).trim();\n    }\n\n    /**\n     * Takes an HTMLElement and determines whether or not it could be relevant for the purposes of\n     * skimmer detection.\n     * @param {HTMLElement} input The element which is to be checked for relevancy.\n     * @returns {boolean} Returns true if the element is potentially relevant (e.g. a credit card\n     * field or other such sensitive input).\n     */\n    isRelevantElement(input) {\n        // If the element has been identified as irrelevant, return false\n        if (this._irrelevantInputs.has(input)) {\n            return false;\n        }\n\n        // Check the input against all the relevant input types\n        const relevances = this.relevantInputs.map(i => i.test(input));\n\n        /*\n         *  If they all agree that this input is unlikely to become\n         *  relevant, exclude it from further checks and return false\n         */\n        if (relevances.every(i => i === RelevantInput.relevance.NOT_RELEVANT)) {\n            this._irrelevantInputs.add(input);\n            return false;\n        }\n\n        // Return true if any consider the input to be relevant, otherwise false\n        return relevances.some(i => i === RelevantInput.relevance.RELEVANT);\n    }\n}\n/*\n * Input Matcher represents a class that can have matches with relevant inputs.\n * Each class that inherits must implement the findInputMatches method.\n */\nclass InputMatcher {\n    /*\n     * Finds any matches with the list of inputs.\n     * @param inputs. An object whose values represent relevant inputs.\n     * @return {[String]} The keys of the inputs that were found.\n     */\n    findInputMatches(inputs) {\n        throw new Error(\'findInputMatches has not been implemented.\');\n    }\n\n    /**\n     * Determines whether one or more values in needles are present in any of\n     * the keys of the haystacks.\n     * Checks both raw input and base64 encoded versions of the needles.\n     * @param needles An object. The haystacks are searched for the values of\n     * this object.\n     * @param haystacks The array of strings in which the needle(s) may reside.\n     * A string may optionally be supplied if only one value is present.\n     * @returns {[String]} The keys of the needles that were found in one or\n     * more haystacks in either raw or base64 encoded form.\n     */\n    static atLeastOneNeedleInHaystack(needles, haystacks) {\n        if (\n            needles === undefined ||\n            needles.length === 0 ||\n            haystacks === undefined\n        ) {\n            return [];\n        }\n\n        const searchArray = Array.isArray(haystacks) ? haystacks : [haystacks];\n\n        return Object.entries(needles)\n            .filter(([_, needle]) =>\n                searchArray.some(haystack => {\n                    if (isBase64Encoded(haystack)) {\n                        if (atob(haystack).indexOf(needle) !== -1) {\n                            return true;\n                        }\n                    }\n                    return haystack.indexOf(needle) !== -1;\n                })\n            )\n            .map(([input, _]) => input);\n    }\n}\n/*\n * Matcher for an object that cannot contain any inputs.\n */\nclass EmptyMatcher extends InputMatcher {\n    findInputMatches(inputs) {\n        return [];\n    }\n}\n\nclass URLParamsMatcher extends InputMatcher {\n    constructor(url) {\n        super();\n        this._url = url;\n    }\n\n    findInputMatches(inputs) {\n        try {\n            const parsedUrl = new URL(this._url);\n            return InputMatcher.atLeastOneNeedleInHaystack(inputs, [\n                ...parsedUrl.searchParams.values(),\n            ]);\n        } catch (e) {\n            return [];\n        }\n    }\n}\n\nclass StringBodyMatcher extends InputMatcher {\n    constructor(str) {\n        super();\n        this._str = str;\n    }\n\n    findInputMatches(inputs) {\n        let decoded;\n        try {\n            decoded = decodeURIComponent(this._str);\n        } catch (e) {\n            decoded = this._str;\n        }\n\n        const searchParams = new URLSearchParams(decoded);\n        const parsedQuery = {};\n        const parsedValues = [];\n\n        for (const [key, value] of searchParams.entries()) {\n            parsedQuery[key] = value;\n            parsedValues.push(value);\n        }\n\n        if (parsedQuery !== {} && parsedValues.indexOf(null) === -1) {\n            return InputMatcher.atLeastOneNeedleInHaystack(\n                inputs,\n                parsedValues\n            );\n        }\n        return InputMatcher.atLeastOneNeedleInHaystack(inputs, decoded);\n    }\n}\n\nclass FormBodyMatcher extends InputMatcher {\n    constructor(formData) {\n        super();\n        this._formData = formData;\n    }\n\n    findInputMatches(inputs) {\n        return InputMatcher.atLeastOneNeedleInHaystack(\n            inputs,\n            Object.values(this._formData).flat()\n        );\n    }\n}\n/**\r\n * Takes a string and removes a variety of \'special\' characters like punctuation.\r\n * @param str The string to be modified.\r\n * @returns {string} The resulting string without any special characters.\r\n */\r\nfunction stripSpecialChars(str) {\r\n    return str.replace(/[`~!@#$%^&*()_|+\\-=?;:\'\",.<>}{\\][\\\\/]/gi, \'\');\r\n}\r\n\r\n/**\r\n * Attempts to identify where a string is base-64 encoded or not.\r\n * @param string The raw string to be examined.\r\n * @returns {boolean} Returns true if the string matches a base-64 string. False otherwise.\r\n */\r\nfunction isBase64Encoded(string) {\r\n    return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(\r\n        string\r\n    );\r\n}\r\n\r\n// Computes the base64 sha512/sha224 hash of some text, removing trailing ==\r\nfunction base64hash(hashFunction, text) {\r\n    return btoa(\r\n        hashFunction\r\n            .array(text)\r\n            .map(char => String.fromCharCode(char))\r\n            .join(\'\')\r\n    ).slice(0, -2);\r\n}\r\n\r\nfunction normaliseUrl(str) {\r\n    const link = document.createElement(\'a\');\r\n    link.href = str;\r\n    return link.href;\r\n}\r\n\r\nfunction stripCredentials(urlstr) {\r\n    try {\r\n        const url = new URL(urlstr);\r\n        const params = url.searchParams;\r\n        params.forEach((_, key) => params.set(key, \'\'));\r\n        return url.toString();\r\n    } catch (e) {\r\n        return urlstr;\r\n    }\r\n}\r\n\r\n/**\r\n * Check if two URLs match regardless of any partial encoding.\r\n *\r\n * Uses decodeURI which can throw a URIError when url is malformed.\r\n */\r\nfunction urlsMatch(first, second) {\r\n    try {\r\n        return decodeURI(first) === decodeURI(second);\r\n    } catch (e) {\r\n        return false;\r\n    }\r\n}\r\n\r\nfunction sendMessage(type, data, tabId, options) {\r\n    if (tabId) {\r\n        chrome.tabs.sendMessage(tabId, { data, type }, options);\r\n    } else {\r\n        chrome.runtime.sendMessage({ data, type });\r\n    }\r\n}\r\n\r\n// Encodes the given input using base64url as defined by RFC4648 section 5.\r\nfunction base64Url(input) {\r\n    return btoa(input)\r\n        .replace(\'+\', \'-\')\r\n        .replace(\'/\', \'_\');\r\n}\r\n/*\n * Netcraft Extension\n * constants JavaScript\n */\n\n/* eslint sort-keys: \'off\' */\n/* sort-keys is disabled as the constants are separated by type */\n\n// Aligned values here make for easier reading, so we disable prettier\n\n// prettier-ignore\nconst cons = (function() {\n    const cryptojackersUrl = \'https://www.netcraft.com/apps/help/glossary/#crypto-miners\';\n    const skimmersUrl = \'https://www.netcraft.com/apps/help/glossary/#credit-card-skimmers\';\n    const evilResourceCrime = \'attempted to load\';\n\n    return {\n        fields: [\n            \'topsites\',\n            \'country\',\n            \'netblock\',\n            \'hoster\',\n            \'firstseen\',\n            \'rank\',\n            \'risk\',\n            \'uses_sslv3\',\n            \'pfs\',\n            \'heartbleed\',\n            \'heartbleed_message\',\n        ],\n\n        // options that can be toggle in options.js and installed flag\n\n        toggleOptions: [\n            \'installed\',\n            \'block\',\n            \'xss\',\n            \'suspicious\',\n            \'other-malicious-scripts\',\n            \'credential-leaks\',\n            \'analytics\',\n            \'cryptojackers\',\n            \'skimmers\',\n        ],\n\n        // domains\n        mirror:  \'https://mirror2.extension.netcraft.com/\',\n        toolbar: \'https://mirror.toolbar.netcraft.com/\',\n\n        // links and faqs\n        blogArchive:      \'https://news.netcraft.com/archives/\',\n        browserApp:       \'https://www.netcraft.com/apps/browser/\',\n        cryptojackersUrl,\n        extensionStore:   \'https://addons.mozilla.org/en-us/firefox/addon/netcraft-toolbar?src=external-blocked_visit-rating\',\n        heartbleedUrl:    \'https://www.netcraft.com/apps/help/glossary/#heartbleed\',\n        logBlockedVisit:  \'https://toolbar.netcraft.com/blocked_visit\',\n        netblock:         \'https://sitereport.netcraft.com/netblock?q=\',\n        pfsUrl:           \'https://www.netcraft.com/apps/help/glossary/#pfs\',\n        report:           \'https://sitereport.netcraft.com/?url=\',\n        reportAPI:        \'https://report.netcraft.com/api/v2/report/urls\',\n        reportMistake:    \'https://report.netcraft.com/report/mistake?url=\',\n        skimmersUrl,\n        sslv3Url:         \'https://www.netcraft.com/apps/help/glossary/#sslv3\',\n        submissionURL:    \'https://report.netcraft.com/submission/\',\n        topsites:         \'https://trends.netcraft.com/topsites?s=\',\n\n        // paths\n        block:              \'blocked.html\',\n        check:              \'check_url/v4/\',\n        danger:             \'Images/danger.png\',\n        embed:              \'/embedded\',\n        evilJSPatternsFeed: \'eviljspatterns\',\n        skimmerDomainWhitelistFeed: \'eviljsskimmerwhitelist\',\n        evilResourcesFeed:  \'blockdb/netcraft_extension_evil_resource_encrypted-yppL7bVh/\',\n        flags:              \'Images/flags/\',\n        force:              \'/dodns\',\n        heartbleed:         \'Images/heartbleed.png\',\n        info:               \'/info\',\n        poodlePost:         \'2014/10/15/googles-poodle-affects-oodles.html\',\n        warning:            \'Images/warning.png\',\n\n        // feed constants\n        evilResourceTypes: [\'cryptojacker\', \'skimmer\'],\n        evilResourcesSalt: \'C4jhYHxW\',\n        maxRevisions:      5,\n        randomSaltLength:  8,\n\n        // mappings of url_type => information\n        urlTypeInformationMappings: {\n            \'malicious_javascript/cryptojacker\': [\'web miners\',             cryptojackersUrl],\n            \'malicious_javascript/skimmer\':      [\'shopping site skimmers\', skimmersUrl],\n        },\n\n        // mappings of evilResourceTypes => user options\n        evilResourceOptionMappings: {\n            \'cryptojacker\': \'cryptojackers\',\n            \'skimmer\':      \'skimmers\'\n        },\n\n        // mappings of user options => url_type\n        optionUrlTypeMappings: {\n            \'block\':                   /^(?!malicious_javascript:)/,\n            \'cryptojackers\':           /^malicious_javascript\\/cryptojacker$/,\n            \'other-malicious-scripts\': /^malicious_javascript\\/(?!(skimmer|cryptojacker)$)/,\n            \'skimmers\':                /^malicious_javascript\\/skimmer$/,\n        },\n\n        // report constants\n        malwareSourceEmail:  \'extension-reported-malware@netcraft.com\',\n        malwareSourceUUID:   \'mwcCcApi89cobY9loJ9rWJ9HuOhlpwMj\',\n        extensionSourceUUID: \'qBEQ3NhEnwK4sypmuSxBJzRqpxZLRXxe\',\n\n        // report error default message\n        reportErrorMessage: \'An error occured with your submission. Please try again.\',\n\n        // mapping from report API errors to displayed messages\n        reportErrorMappings: {\n            \'Duplicate of a recent submission\': \'This URL has already been reported.\',\n            \'Supplied email is not valid\': \'Invalid email address. Please correct it and try again.\',\n        },\n\n        whitelistErrorMessages: {\n            invalidHostname: \'Invalid hostname, please correct it and try again\',\n            duplicate: \'Protection is already disabled for this site\',\n        },\n\n        // browserAction icon paths\n        iconNormal: {\n            16: \'Images/action-normal-16.png\',\n            24: \'Images/action-normal-24.png\',\n            32: \'Images/action-normal-32.png\'\n        },\n        iconDanger: {\n            16: \'Images/action-danger-16.png\',\n            24: \'Images/action-danger-24.png\',\n            32: \'Images/action-danger-32.png\'\n        },\n        iconWarning: {\n            16: \'Images/action-warning-16.png\',\n            24: \'Images/action-warning-24.png\',\n            32: \'Images/action-warning-32.png\'\n        },\n        iconDisabled: {\n            16: \'Images/action-disabled-16.png\',\n            24: \'Images/action-disabled-24.png\',\n            32: \'Images/action-disabled-32.png\'\n        },\n\n        // Extension internal message types\n        capturedIntercept:  \'interceptDetailsCaptured\',\n        confirmedIntercept: \'interceptDetailsConfirmed\',\n        contentReady:       \'contentScriptReady\',\n        leaksEnabled:       \'credentialLeaksEnabled\',\n        reportURL:          \'report\',\n        reqBlock:           \'block\',\n        reqDetails:         \'details\',\n        reqForce:           \'force\',\n        reqPopUp:           \'popup\',\n        reqPrintTimingData: \'time\', //debug\n        reqInputs:          \'requestInputValues\',\n        sendInputs:         \'sendInputs\',\n        sendIntercept:      \'sendInterceptDetails\',\n        updateOption:       \'updateOption\',\n        updateWhitelist:    \'whitelist\', // disable button whitelist\n\n        // Status codes\n        errEvilResource: \'EVILRESOURCE\',\n        errNoDetails:    \'NODETAILS\',\n        errNoInfo:       \'NOINFO\',\n        errBlocked:      \'BLOCKED\',\n        errPending:      \'PENDING\',\n        errServer:       \'ERROR\',\n        errSuspicious:   \'SUSPICIOUS\',\n        errTimeout:      \'TIMEOUT\',\n        errXSS:          \'XSS\',\n        noError:         \'OK\',\n\n        // Valid protocols (it is a regex match so ^ matches start of string)\n        protocols:    [\'^http\', \'^https\', \'^ftp\'],\n\n        // Human readable messages\n        msgNa:           \'NA\',\n        msgNewSite:      \'New Site\',\n        msgNoInfo:       \'Site information not available\',\n        msgNoServer:     \'Unable to contact Netcraft servers\',\n        msgSuspicious:   \'Suspicious URL Detected\',\n        msgXSS:          \'Suspected XSS Attack\',\n        msgBlocked:      {\n            \'malicious_javascript/skimmer\': {\n                title: \'Shopping Site Skimmer Detected\',\n                description: \'shopping site skimmer\'\n            },\n            \'malicious_javascript/dropsite\': {\n                title: \'Potential Credential Leak Detected\',\n                description: \'potential credential leak\'\n            },\n            \'malicious_javascript/cryptojacker\': {\n                title: \'Web Miner Detected\',\n                description: \'web miner\'\n            },\n            \'malware_c2/infrastructure\': {\n                title: \'Malicious URL\',\n                description: \'malicious page\'\n            },\n            \'default\': {\n                title: \'Suspected Phishing\',\n                description: \'suspected phishing site\'\n            },\n        },\n\n        // Human readable reasons\n        urlTypeCrimeMappings: {\n            \'malicious_javascript/skimmer\':      evilResourceCrime,\n            \'malicious_javascript/cryptojacker\': evilResourceCrime,\n            \'malicious_javascript/dropsite\':     \' is blocked because we detected that sensitive information was leaked from\',\n        },\n\n        // Timeout function value to clear cache etc.\n        timeout:      300000,\n\n        // Skimmer whitelist update interval (1 day)\n        skimmerWhitelistInterval: 1000 * 60 * 60 * 24,\n\n        // Google Analytics account\n        account:      \'UA-2150242-8\',\n        gaCategory:   \'Popup load times\',\n\n        // InputAnalyser constants\n        minInputSize: 5,\n\n        // First addresses of the private IPv4 netblocks\n        privateIPv4Blocks: [\n            \'10.0.0.0\',\n            \'172.16.0.0\',\n            \'192.168.0.0\'\n        ],\n\n        // Sync storage key for disable for this site button whitelist\n        disableButtonWhitelistKey: \'disableButtonWhitelist\',\n\n        // Local storage key for local credential leak detection whitelist\n        localCredentialLeakWhitelistKey: \'localCredentialLeakWhitelist\',\n\n        // Whitelisted fileName regexes for credential leak detection\n        credentialLeakFileNameWhitelist: [\n            /^https?:\\/\\/googleads.g.doubleclick.net\\//,\n        ],\n    };\n})();\n\n/*\n * recursively prevents any property or sub-property in cons from:\n *   - being added;\n *   - being removed; or\n *   - being modified.\n */\nfunction deepFreeze(object) {\n    Object.freeze(object);\n\n    for (const name of Object.getOwnPropertyNames(object)) {\n        if (typeof object[name] === \'object\') {\n            deepFreeze(object[name]);\n        }\n    }\n}\n\ndeepFreeze(cons);\n/*\r\n * RelevantInput objects check whether given form inputs should be\r\n * treated as sensitive data.\r\n *\r\n * This should be treated as an abstract class. Only the subclasses\r\n * are intended to be instantiated.\r\n */\r\nclass RelevantInput {\r\n    /*\r\n     * Enum of the possible return values of test():\r\n     * RELEVANT:\r\n     *     Input is relevant now, outgoing requests should be\r\n     *     checked for the contained value (subject to minimum length).\r\n     * POTENTIALLY RELEVANT:\r\n     *     Input should not be considered relevant now, but may be in\r\n     *     future so should continue to be checked.\r\n     * NOT_RELEVANT:\r\n     *     Input is not relevant and will not become relevant, so can\r\n     *     be ommitted from further checks to optimise performance.\r\n     */\r\n    static get relevance() {\r\n        return {\r\n            NOT_RELEVANT: 1,\r\n            POTENTIALLY_RELEVANT: 2,\r\n            RELEVANT: 3,\r\n        };\r\n    }\r\n\r\n    /*\r\n     * relevantInputTypes (string) - a comma separated list of\r\n     * selectors that describe the types of input that should be\r\n     * considered relevant.\r\n     */\r\n    constructor(relevantInputTypes) {\r\n        this.relevantInputTypes = relevantInputTypes;\r\n    }\r\n\r\n    /*\r\n     * Checks whether any of the given input\'s \'labels\' (the ID, name,\r\n     * or actual label) match the given regex.\r\n     */\r\n    static testInputLabel(regex, input) {\r\n        const labels = [\r\n            input.id,\r\n            input.name,\r\n            InputAnalyser.getLabelText(input),\r\n        ];\r\n        return labels.some(label => regex.test(label));\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for passwords.\r\n */\r\nclass Password extends RelevantInput {\r\n    constructor() {\r\n        super(\'input[type=password]\');\r\n    }\r\n\r\n    /*\r\n     * Passwords are verified purely by their input type.\r\n     */\r\n    test(input) {\r\n        if (input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for card numbers.\r\n */\r\nclass CardNumber extends RelevantInput {\r\n    constructor() {\r\n        // Inputs with no type attribute act like text inputs\r\n        super(\r\n            \'input[type=text], input[type=tel], input[type=number], input:not([type])\'\r\n        );\r\n\r\n        // Definitely card number labels\r\n        this.inputLabelRegexes = [\r\n            /(cc|card).?(num|no)/i,\r\n            /カード番号/,\r\n            /Номер.*карты/i,\r\n            /信用卡卡號/i,\r\n            /信用卡号码/i,\r\n        ];\r\n\r\n        /*\r\n         * Matches incomplete card numbers, because ideally the\r\n         * skimmer should be detected before the victim gives away\r\n         * most of their card number.\r\n         */\r\n        this.inputValueRegexes = [/^(\\d{4}[-\\s]?){0,4}\\d{1,4}$/];\r\n\r\n        // Probably card number labels\r\n        this.suspectInputLabelRegexes = [\r\n            /card/i,\r\n            /tarjeta/i,\r\n            /carte/i,\r\n            /карты/i,\r\n            /卡/i,\r\n            /カード/i,\r\n            /카드/i,\r\n            /credit[^s]/i,\r\n        ];\r\n\r\n        // If it matches one of these, probably not a card number\r\n        this.inputLabelExceptionRegexes = [\r\n            /post/i,\r\n            /code/i,\r\n            /zip/i,\r\n            /acc(oun)?t?[-_]?n(um|o)/i,\r\n            /credit[-_]?(sum|limit)/i,\r\n            /loyalty/i,\r\n            /gift/i,\r\n            /idcard/i,\r\n            /cardid/i,\r\n            /club/i,\r\n            /sku/i,\r\n            /(\\b|_)otp/i,\r\n            /price/i,\r\n            /amount/i,\r\n            /service/i,\r\n            /picard/i,\r\n            /search/i,\r\n            /contact/i,\r\n            /callme/i,\r\n            /chat/i,\r\n            /activate/i,\r\n            /msisdn/i,\r\n        ];\r\n    }\r\n\r\n    test(input) {\r\n        if (!input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return NOT_RELEVANT if label matches an exception regex\r\n        if (\r\n            this.inputLabelExceptionRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return RELEVANT if label clearly states card number\r\n        if (\r\n            this.inputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        }\r\n\r\n        /*\r\n         * If label suggests card number, return RELEVANT if value\r\n         * also suggests card number or POTENTIALLY_RELEVANT if\r\n         * not. Otherwise, return NOT_RELEVANT as this element will\r\n         * not become relevant unless a label changes (unlikely).\r\n         */\r\n        if (\r\n            this.suspectInputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            if (this.inputValueRegexes.some(r => r.test(input.value))) {\r\n                return RelevantInput.relevance.RELEVANT;\r\n            } else {\r\n                return RelevantInput.relevance.POTENTIALLY_RELEVANT;\r\n            }\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\n    const inputAnalyser = new InputAnalyser();\n\n    const resourceBlockList = new Set([\n                            \"6C590F97DDB1A11AE795B3050E4C3866\", \"D285BE79786E2ECE396941331C5BA65E\", \"59DF8523520DE7F08FB53D3D12BD94F5\", \"1FB784EBAFDA1618023756BF5B6D7E46\", \"0D6E1B5F03B81F5C37813B22E29C4FE2\", \"EFAB0ECE9383D50C555B8168C3E2A4DD\", \"AC8A4B13A411A4E43E61D0A034FA4A55\", \"25A781F6D1F94EEACCF1E00E844D9E14\", \"3920B04ED0D215EA738F524F60485F23\", \"05F4A2D3161A92EB82A44F15FC2A3AEC\", \"C5A1BE7B3855B0A25275B2877E005CB9\", \"00069CEC347076721EBE2257978A0A33\", \"9A513130D02F360BBDC16D8AC0C579D8\", \"7CDB2A3A29E50F90C1000252EF523AD6\", \"25CE912FAF2376026AAD0174AD5A0329\", \"739E1F67AA5F64A06FC469F6030802D4\", \"F49368C9A05C29EF49411999C5987F2C\", \"AB2DD89FB892155275D8780CF527BC93\", \"CFE758EB18FBFE70B1F690241B75D2B1\", \"E43C3463B40A790545F4149FF06B419E\", \"8B6D4C76A7CCF5EB720660B8B2D3F5EB\", \"45573B3E2F613AF5BF2145DF004BABD1\", \"E3DB4E968B371DE94CE607199A062F39\", \"A4EFF1240C5DE614B394FB738CBD28E2\", \"F42D10E84B9A69A8A24062319F425A83\", \"90646BE39982594A58EDF5D2630CCFFD\", \"669853BEA9B84EA51D7302A7FBFF5BC3\", \"0BA566B4777526F451108100107DC5ED\", \"AB19E12BFFBC47244BDCC2403EAA9037\", \"61DAF8537240639277FD1A5908A131A4\", \"6F1DC1309CFE734C038685D8F17A3B6B\", \"DCC3DB001334F29C1C0208D15E9EAB49\", \"2DBD7D54022C28273ADE3147B8EEB076\", \"AE3760E93412C65EC5BF63537B037019\", \"4CD96A830DAC0E0B5EBE578B0DD870AE\", \"4C70569BBE1821F5655382660807FA77\", \"05ECA17FF0EA78669ED43B2BD7B63571\", \"44E503B3621513B9D3666F284E4DF47A\", \"8F1C92E7665860AE70CBB228DED86111\", \"D69552885B55C4097E5725F805815639\", \"B4357A9D889E974CDF3F9740E48F9A1B\", \"5709DC60CFC365B7064B06C709A9D32B\", \"14E8729CBB16AC3625EAA0D7A679E56E\", \"793D4DABAAA377A12F2B6CE88C5FB389\", \"C5D1DCE2AE0D8CE79054FC16CB13D65A\", \"89F91750BD4A9397D1F8CE183757FF70\", \"50B8BC213FB188A687173C1A0A77EA57\"\n                        ]);\n    const evilJSPatterns = new Set([\n                            \n                        ]);\n\n    // Extract body object\n    function extractBody(data) {\n        if (!data) {\n            return Promise.resolve({});\n        }\n        if (FormData.prototype.isPrototypeOf(data)) {\n            const formData = {};\n            for (const [key, value] of data.entries()) {\n                formData[key] = value;\n            }\n            return Promise.resolve({ formData });\n        }\n        // Create a temporary Request object to parse the body\n        const tempRequest = new Request(\'\', { body: data, method: \'POST\' });\n        return extractBodyFromRequest(tempRequest);\n    }\n\n    function extractBodyFromRequest(request) {\n        // Try to extract as text\n        return request\n            .text()\n            .then(text => {\n                return { text };\n            })\n            .catch(e => {\n                return {};\n            });\n    }\n\n    /**\n     * This function checks if the request hostname/URL is in the blocklist.\n     * Used when identifying whether or not to forward request to background\n     * script.\n     * @param hostname Hostname to check against blocklist.\n     * @param url Request url to check against evil JS patterns\n     * @returns boolean true if hostname is in the blocklist, false if\n     * it is not in blocklist or if blocklist is not provided.\n     */\n    function requestInBlocklist(hostname, url) {\n        if (resourceBlockList === undefined || evilJSPatterns === undefined) {\n            return false;\n        }\n        if (testEvilJS(url.replace(/^(https?:)?\\/\\//i, \'\'))) {\n            return true;\n        }\n        while (!testEvilJS(hostname) && !testResource(hostname)) {\n            const newHostname = hostname.replace(/^.+?\\./g, \'\');\n\n            if (hostname === newHostname) return false;\n\n            hostname = newHostname;\n        }\n        return true;\n    }\n\n    function testEvilJS(url) {\n        return evilJSPatterns.has(base64hash(sha512, url));\n    }\n\n    function testResource(hostname) {\n        return resourceBlockList.has(\n            md5(cons.evilResourcesSalt + hostname).toUpperCase()\n        );\n    }\n\n    /*\n     * Implementations to overwrite window.XMLHttpRequest.prototype.open,\n     * window.XMLHttpRequest.prototype.send and window.fetch. These are (to my knowledge) the two\n     * APIs that can be used to make requests within Javascript. If there are more, then they will\n     * also need to be overridden and intercepted like below.\n     */\n\n    /**\n     * This function generates functions to be used for wrapping and intercepting network\n     * requests, namely from XMLHttpRequest and the Fetch API. It works by taking the real\n     * function we want to intercept the request for, and then uses document.currentScript to work\n     * out which file initiated the request. It sends those details to the content script which\n     * forwards it to the background script, and then we await a confirmation of receipt from the\n     * background page. Finally, the real request is sent by forwarding parameters to the original\n     * function passed in.\n     * @param realFn A reference to the real function that is being intercepted.\n     * @param getRequestUrlFn A function used to get the request URL from the object. This is\n     * necessary as the mechanisms for different objects differs depending on implementation.\n     * @param getRequestBodyPr A function used to get the request body from the object. Returns\n     * a promise. It should be null if it\'s not applicable (e.g. if it sets the src of an image or script).\n     * @param type The type of request, which will be a ResourceType:\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType\n     * @returns {function(...[*]=): PromiseLike<any | never>}\n     */\n    function generateRequestInterceptFn(\n        realFn,\n        getRequestUrlFn,\n        getRequestBodyPr,\n        type\n    ) {\n        const patched = function(...args) {\n            /*\n             * We create a link DOM element in order to correctly generate the\n             * absolute path of the request, automatically transforming from a\n             * relative path if necessary.\n             */\n            const requestUrl = normaliseUrl(\n                getRequestUrlFn.bind(this)(...args)\n            );\n\n            let requestHostname;\n            let pageHostname;\n\n            try {\n                requestHostname = new URL(requestUrl).hostname;\n                pageHostname = new URL(location.href).hostname;\n            } catch (e) {\n                return realFn.apply(this, args);\n            }\n\n            /*\n             * If hostname is in the blocklist, the request should always be\n             * sent to the background script.\n             */\n            // eslint-disable-next-line prefer-const\n            let forceStall = false;\n\n            if (!forceStall) {\n                /*\n                 * Delaying outgoing requests sometimes causes problems\n                 * with elements such as video players and dynamically\n                 * loaded images, so it should only be done when\n                 * necessary. Before inputAnalyser is defined and when the\n                 * request is to the same hostname as the page, this can\n                 * be skipped.\n                 */\n                if (\n                    requestHostname === pageHostname ||\n                    inputAnalyser === undefined\n                ) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * It can also be skipped if there is no sensitive\n                 * information entered into a form on the page yet.\n                 */\n                const relevantFilledInputs = inputAnalyser.relevantFilledInputs;\n                if (!relevantFilledInputs.length) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * For requests with no body, it\'s only necessary to delay\n                 * them when the sensitive information from the form\n                 * inputs is present in the request URL.\n                 */\n                const ia = new InputAnalyser(relevantFilledInputs);\n                const relevantInputValues = ia.relevantInputValues;\n                const urlMatcher = new URLParamsMatcher(requestUrl);\n\n                if (\n                    getRequestBodyPr === null &&\n                    !urlMatcher.findInputMatches(relevantInputValues).length\n                ) {\n                    return realFn.apply(this, args);\n                }\n            }\n\n            /*\n             * Create a promise that resolves immediately if the request body\n             * does not apply to this request.\n             */\n            if (getRequestBodyPr === null) {\n                getRequestBodyPr = function() {\n                    return Promise.resolve({});\n                };\n            }\n\n            let fileName;\n            if (document.currentScript && document.currentScript.src) {\n                fileName = document.currentScript.src;\n            } else if (callbackCurrentScript && callbackCurrentScript.src) {\n                fileName = callbackCurrentScript.src;\n            } else {\n                fileName = location.href;\n            }\n\n            return getRequestBodyPr\n                .bind(this)(...args)\n                .then(requestBody => {\n                    /*\n                     * Generate the information object to send through to the\n                     * content script including the request URL, the initiator\n                     * filename and a timestamp relative to the page load.\n                     */\n                    const detail = {\n                        fileName,\n                        requestBody,\n                        requestUrl,\n                        timestamp: performance.now(),\n                        type,\n                    };\n\n                    document.dispatchEvent(\n                        new CustomEvent(cons.capturedIntercept, {\n                            detail,\n                        })\n                    );\n\n                    return new Promise(resolve => {\n                        /*\n                         * Send the initiator\'s file name and other info to the\n                         * background script, then wait for confirmation that the\n                         * details made it to the background page before calling\n                         * the real function. This is to avoid any race conditions\n                         * whereby the details haven\'t been added to the tab data\n                         * before onBeforeRequest/onBeforeSendHeaders gets called.\n                         */\n                        document.addEventListener(\n                            cons.confirmedIntercept,\n                            function compareData(customEvent) {\n                                const receivedData = JSON.parse(\n                                    customEvent.detail\n                                );\n                                if (detailsMatch(detail, receivedData)) {\n                                    /*\n                                     * If it\'s the correct confirmation, then we\n                                     * can resolve the promise and call the real\n                                     * function in the finally block.\n                                     */\n                                    document.removeEventListener(\n                                        cons.confirmedIntercept,\n                                        compareData,\n                                        false\n                                    );\n                                    resolve();\n                                }\n                            }\n                        );\n                    }).then(() => realFn.apply(this, args));\n                });\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    function detailsMatch(first, second) {\n        const comparisonKeys = [\'fileName\', \'requestUrl\', \'timestamp\', \'type\'];\n        for (const key of comparisonKeys) {\n            if (first[key] !== second[key]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    window.fetch = generateRequestInterceptFn(\n        realFetch,\n        request => (typeof request === \'string\' ? request : request.url),\n        (request, options) => {\n            // Check if argument is a Request object.\n            if (request instanceof Request) {\n                return extractBodyFromRequest(request);\n            }\n            return options ? extractBody(options.body) : Promise.resolve({});\n        },\n        \'xmlhttprequest\'\n    );\n\n    window.XMLHttpRequest.prototype.send = generateRequestInterceptFn(\n        realSend,\n        function getRequestUrl() {\n            return this._requestUrl;\n        },\n        extractBody,\n        \'xmlhttprequest\'\n    );\n\n    /*\n     * This function is intercepted so that we can capture the value of the\n     * request URL, since it isn\'t present in the arguments for\n     * XMLHttpRequest.send (above).\n     */\n    window.XMLHttpRequest.prototype.open = function open(...args) {\n        [, this._requestUrl] = args;\n        realOpen.apply(this, args);\n    };\n    window.XMLHttpRequest.prototype.open.toString = () => realOpen.toString();\n\n    /*\n     * Monkeypatches for setting the src of images and scripts, an\n     * indirect way of sending requests that is often employed by\n     * skimmers. The getters are also overridden to make the behaviour\n     * seem identical to the original implementation. Otherwise, if a\n     * script were to set an src and immediately read it, it would\n     * come back as the old value.\n     */\n\n    /*\n     * WeakMap maps an object to a value. This is used to store the\n     * new src of an object when its true src property may not yet\n     * have been set.\n     */\n    const srcMap = new WeakMap();\n    const requestElements = [\n        {\n            srcElement: HTMLImageElement.prototype,\n            type: \'image\',\n        },\n        {\n            srcElement: HTMLScriptElement.prototype,\n            type: \'script\',\n        },\n    ];\n\n    for (const { srcElement, type } of requestElements) {\n        const realGetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .get;\n        const realSetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .set;\n        const realGetAttribute = srcElement.getAttribute;\n        const realSetAttribute = srcElement.setAttribute;\n        const realRemoveAttribute = srcElement.removeAttribute;\n\n        const interceptFn = generateRequestInterceptFn(\n            realSetSrc,\n            src => `${src}`,\n            null,\n            type\n        );\n\n        function setSrc(url) {\n            /*\n             * It\'s valid to provide a TrustedScriptURL wrapping the URL but we need to\n             * return a string in the getter, so we convert the url to a string here.\n             *\n             * The url can also be undefined/null so we can\'t just call `.toString()`.\n             *\n             * See #56.\n             */\n            srcMap.set(this, `${url}`);\n            interceptFn.call(this, url);\n            return url;\n        }\n\n        function getSrc() {\n            if (srcMap.get(this) === undefined) {\n                return realGetSrc.call(this);\n            } else {\n                return normaliseUrl(srcMap.get(this));\n            }\n        }\n\n        getSrc.toString = () => realGetSrc.toString();\n        setSrc.toString = () => realSetSrc.toString();\n\n        Object.defineProperty(srcElement, \'src\', {\n            get: getSrc,\n            set: setSrc,\n        });\n\n        srcElement.setAttribute = function(attributeName, value) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                setSrc.call(this, value);\n            } else {\n                realSetAttribute.call(this, attributeName, value);\n            }\n        };\n        srcElement.setAttribute.toString = () => realSetAttribute.toString();\n\n        srcElement.getAttribute = function(attributeName) {\n            if (\n                attributeName.toUpperCase() === \'SRC\' &&\n                srcMap.get(this) !== undefined\n            ) {\n                return srcMap.get(this);\n            } else {\n                return realGetAttribute.call(this, attributeName);\n            }\n        };\n        srcElement.getAttribute.toString = () => realGetAttribute.toString();\n\n        srcElement.removeAttribute = function(attributeName) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                srcMap.delete(this);\n            }\n\n            return realRemoveAttribute.call(this, attributeName);\n        };\n        srcElement.removeAttribute.toString = () =>\n            realRemoveAttribute.toString();\n    }\n})(\n    window.XMLHttpRequest.prototype.open,\n    window.XMLHttpRequest.prototype.send,\n    window.fetch\n);\n</script>\n                    <style font-name=\"MS PMincho\">\n                        @font-face {\n                            font-family: \"MS PMincho\";\n                            src: url(https://sys.emarketing.pe/builder/font/ms-pmincho.ttf);\n                        }\n                    </style>\n                \n                    <style font-name=\"MS Mincho\">\n                        @font-face {\n                            font-family: \"MS Mincho\";\n                            src: url(https://sys.emarketing.pe/builder/font/ms-mincho.ttf);\n                        }\n                    </style>\n                <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"><link href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Outlined\" rel=\"stylesheet\">\n                \n            </head>\n  <body class=\"builderjs-layout\" spellcheck=\"false\">\n    <!--[if mso]>\n        <center>\n        <table><tr><td width=\"760\">\n    <![endif]-->\n        <div builder-element=\"PageElement\" style=\"padding-top:50px;padding-bottom:50px;\" class=\"\"><table width=\"100%\" builder-element=\"BlockElement\" style=\"opacity: 1;\" class=\"\">\n                    <tbody><tr><td style=\"padding-top:15px;padding-bottom:15px\">\n                        <div class=\"container\">\n                        <div style=\"text-align:center;margin:0\">\n            <img builder-element=\"\" src=\"data:image/png;base64,[encoded data, 4956 characters]\" width=\"200px\" class=\"\">\n        </div></div>\n                    </td></tr></tbody></table><table width=\"100%\" builder-element=\"BlockElement\" style=\"opacity: 1;\" class=\"\">\n                    <tbody><tr><td style=\"padding-top:15px;padding-bottom:15px\">\n                        <div class=\"container\">\n                        <div builder-element=\"TextElement\" builder-inline-edit=\"\" id=\"mce_0\" style=\"position: relative; text-align: center;\" spellcheck=\"false\">I\'m a new Text block ready for your content.</div></div>\n                    </td></tr></tbody></table></div>\n    <!--[if mso]>\n        </td></tr></table>\n        </center>\n    <![endif]--> \n  \n</body></html>', '2023-09-11 18:06:02', '2023-09-11 18:06:24', 1, 0, NULL, 'email');
INSERT INTO `axme_templates` (`id`, `uid`, `customer_id`, `name`, `content`, `created_at`, `updated_at`, `builder`, `is_default`, `theme`, `type`) VALUES
(29, '64ff588915ff0', 2, 'Plantilla Demo', '<!DOCTYPE html><html lang=\"en\"><head><style id=\"mceDefaultStyles\" type=\"text/css\">img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}\r\n</style>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\r\n    <meta name=\"description\" content=\"\">\r\n    <meta name=\"author\" content=\"Mark Otto, Jacob Thornton, and Bootstrap contributors\">\r\n    <meta name=\"generator\" content=\"AcelleSystemLayouts\">\r\n    <title>Blank</title>\r\n\r\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1ODg5MTVmZjAvY3Nz/bootstrap.min.css\" rel=\"stylesheet\">\r\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1ODg5MTVmZjAvY3Nz/custom.css\" rel=\"stylesheet\">\r\n\r\n    <style>\r\n        @media (min-width: 768px) {\r\n            .container {\r\n                max-width: 650px;\r\n            }\r\n        }\r\n        @media (min-width: 992px) {\r\n            .container {\r\n                max-width: 760px;\r\n            }\r\n        }\r\n        @media (min-width: 769px) {\r\n            [data-hide-on=\"desktop\"] {\r\n                display: none!important;\r\n            }\r\n        }\r\n        @media (max-width: 600px) {\r\n            [data-hide-on=\"mobile\"] {\r\n                display: none!important;\r\n            }\r\n        }\r\n        @media (max-width: 600px) {\r\n            [builder-element=CellContainerElement].cell-responsive {\r\n                display: block!important;\r\n            }\r\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] {\r\n                width: 100%!important;\r\n                max-width: none!important;        \r\n            }\r\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"],\r\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"BlockElement\"] {\r\n                padding-left: 0px!important;\r\n                padding-right: 0px!important;\r\n            }\r\n            /* EmailMode */\r\n            [builder-element=CellContainerElement].cell-responsive tbody,\r\n            [builder-element=CellContainerElement].cell-responsive tr,\r\n            [builder-element=CellContainerElement].cell-responsive td {\r\n                display: block!important;\r\n                width: 100%;\r\n            }\r\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] td {\r\n                padding-left: 0px!important;\r\n                padding-right: 0px!important;\r\n            }\r\n        }\r\n        @media (min-width: 600px) {\r\n            [builder-element=PricingTableElement] .col-12 {\r\n                flex-basis: 0;\r\n                flex-grow: 1;\r\n            }\r\n        }\r\n    </style>\r\n  <script>/*\r\n * This entire block is wrapped in an IIFE to prevent polluting the scope of the web page with\r\n * functions created by this extension.\r\n */\r\n(function(realOpen, realSend, realFetch) {\r\n    /*\r\n     * Sometimes pages overwrite standard objects with global\r\n     * variables. Where possible, we extract fresh versions from an\r\n     * iframe.\r\n     */\r\n    const iframe = document.createElement(\'iframe\');\r\n    document.documentElement.appendChild(iframe);\r\n\r\n    // Initialise with global values\r\n    let URL = window.URL;\r\n    let WeakMap = window.WeakMap;\r\n    let WeakSet = window.WeakSet;\r\n    let TextDecoder = window.TextDecoder;\r\n    let Uint8Array = window.Uint8Array;\r\n    let decodeURIComponent = window.decodeURIComponent;\r\n    let URLSearchParams = window.URLSearchParams;\r\n    let Array = window.Array;\r\n    let Object = window.Object;\r\n    let atob = window.atob;\r\n    let CustomEvent = window.CustomEvent;\r\n    let performance = window.performance;\r\n    let JSON = window.JSON;\r\n\r\n    /*\r\n     * Under some circumstances the iframe isn\'t added\r\n     * successfully. If it has a contentWindow then we can extract the\r\n     * values from it, but otherwise we fall back on the global\r\n     * values.\r\n     */\r\n    if (iframe.contentWindow) {\r\n        // Try block because sometimes the CORS prevents access to iframe\r\n        try {\r\n            URL = iframe.contentWindow.URL;\r\n            WeakMap = iframe.contentWindow.WeakMap;\r\n            WeakSet = iframe.contentWindow.WeakSet;\r\n            TextDecoder = iframe.contentWindow.TextDecoder;\r\n            Uint8Array = iframe.contentWindow.Uint8Array;\r\n            decodeURIComponent = iframe.contentWindow.decodeURIComponent;\r\n            URLSearchParams = iframe.contentWindow.URLSearchParams;\r\n            Array = iframe.contentWindow.Array;\r\n            Object = iframe.contentWindow.Object;\r\n            atob = iframe.contentWindow.atob;\r\n            CustomEvent = iframe.contentWindow.CustomEvent;\r\n            performance = iframe.contentWindow.performance;\r\n            JSON = iframe.contentWindow.JSON;\r\n        } catch (error) {\r\n            // Empty catch block to prevent errors from showing in page logs\r\n        }\r\n    }\r\n\r\n    // If iframe was added successfully, remove it\r\n    if (iframe.parentNode) {\r\n        iframe.parentNode.removeChild(iframe);\r\n    }\r\n\r\n    /*\r\n     * If any of the values don\'t have the correct type, return and\r\n     * don\'t attempt any monkey patching. This avoids breaking web\r\n     * pages.\r\n     */\r\n    for (f of [\r\n        URL,\r\n        WeakMap,\r\n        WeakSet,\r\n        TextDecoder,\r\n        Uint8Array,\r\n        decodeURIComponent,\r\n        URLSearchParams,\r\n        Array,\r\n        Object,\r\n        atob,\r\n        CustomEvent,\r\n    ]) {\r\n        if (typeof f !== \'function\') {\r\n            return;\r\n        }\r\n    }\r\n\r\n    for (o of [performance, JSON]) {\r\n        if (typeof o !== \'object\') {\r\n            return;\r\n        }\r\n    }\r\n\r\n    /*\r\n     * Override common funcions that accept callbacks, such as\r\n     * setInterval, to keep track of what script each callback comes\r\n     * from. Skimmers commonly have the actual skimming code in a\r\n     * callback, so most of the time when we intercept a request,\r\n     * document.currentScript is null. To solve this, the script is\r\n     * stored in a closure when the callback is passed, and assigned\r\n     * to callbackCurrentscript when the callback is run.\r\n     */\r\n\r\n    let callbackCurrentScript = null;\r\n\r\n    // Maps callbacks originally passed to patched versions\r\n    const patchedCallbackMap = new WeakMap();\r\n\r\n    // Maps patched callbacks to original versions\r\n    const originalCallbackMap = new WeakMap();\r\n\r\n    /**\r\n     *  Generates the function with which to override the\r\n     *  callback-accepting functions.\r\n     *  @param realFn The real function, e.g. setInterval.\r\n     *  @param callbackIndex The index of the callback in the\r\n     *  arguments of realFn. For example, the callback is the first\r\n     *  argument in setInterval, so callbackIndex should be 0.\r\n     */\r\n    function generateAddCallbackFunction(realFn, callbackIndex) {\r\n        const patched = function(...args) {\r\n            const script = document.currentScript || callbackCurrentScript;\r\n\r\n            const callback = args[callbackIndex];\r\n\r\n            // If the callback isn\'t a function, there\'s nothing to patch\r\n            if (typeof callback !== \'function\') {\r\n                return realFn.apply(this, args);\r\n            }\r\n\r\n            // If a patch exists, use it instead of creating new function\r\n            let patchedCallback = patchedCallbackMap.get(callback);\r\n            if (!patchedCallback) {\r\n                patchedCallback = function() {\r\n                    callbackCurrentScript = script;\r\n                    const retVal = callback.apply(this, [...arguments]);\r\n                    callbackCurrentScript = null;\r\n                    return retVal;\r\n                };\r\n                patchedCallbackMap.set(callback, patchedCallback);\r\n                originalCallbackMap.set(patchedCallback, callback);\r\n            }\r\n\r\n            args[callbackIndex] = patchedCallback;\r\n            return realFn.apply(this, args);\r\n        };\r\n        // make toString() look native, #34\r\n        patched.toString = () => realFn.toString();\r\n        return patched;\r\n    }\r\n\r\n    const realSetInterval = window.setInterval;\r\n\r\n    window.setInterval = generateAddCallbackFunction(realSetInterval, 0);\r\n\r\n    const realSetTimeout = window.setTimeout;\r\n\r\n    window.setTimeout = generateAddCallbackFunction(realSetTimeout, 0);\r\n\r\n    const realPromiseThen = Promise.prototype.then;\r\n    Promise.prototype.then = generateAddCallbackFunction(realPromiseThen, 0);\r\n\r\n    const realPromiseCatch = Promise.prototype.catch;\r\n    Promise.prototype.catch = generateAddCallbackFunction(realPromiseCatch, 0);\r\n\r\n    const realPromiseFinally = Promise.prototype.finally;\r\n    if (realPromiseFinally) {\r\n        Promise.prototype.finally = generateAddCallbackFunction(\r\n            realPromiseFinally,\r\n            0\r\n        );\r\n    }\r\n\r\n    const realAddEventListener = EventTarget.prototype.addEventListener;\r\n\r\n    EventTarget.prototype.addEventListener = generateAddCallbackFunction(\r\n        realAddEventListener,\r\n        1\r\n    );\r\n\r\n    /*\r\n     * removeEventListener needs to be patched to map the passed\r\n     * listener to the patched listener.\r\n     */\r\n    const realRemoveEventListener = EventTarget.prototype.removeEventListener;\r\n    EventTarget.prototype.removeEventListener = function(...args) {\r\n        args[1] = patchedCallbackMap.get(args[1]) || args[1];\r\n\r\n        return realRemoveEventListener.apply(this, args);\r\n    };\r\n    EventTarget.prototype.removeEventListener.toString = () =>\r\n        realRemoveEventListener.toString();\r\n\r\n    /*\r\n     * Event listeners are sometimes set by directly assigning to a\r\n     * property from the GlobalEventHandlers mixin,\r\n     * e.g. \'submitBtn.onclick\'. For each of the GlobalEventHandlers\r\n     * implementations that are likely to be relevant, the event\r\n     * properties likely to be used by skimmers are overridden with\r\n     * our version that tracks currentScript when assigned.\r\n     */\r\n    for (const eventTarget of [\r\n        HTMLElement.prototype,\r\n        Document.prototype,\r\n        window,\r\n    ]) {\r\n        for (const eventName of [\r\n            \'onclick\',\r\n            \'onblur\',\r\n            \'onfocus\',\r\n            \'onchange\',\r\n            \'oninput\',\r\n            \'onkeydown\',\r\n            \'onkeypress\',\r\n            \'onkeyup\',\r\n            \'onload\',\r\n            \'onloadend\',\r\n            \'onmousedown\',\r\n            \'onmouseenter\',\r\n            \'onmouseleave\',\r\n            \'onmousemove\',\r\n            \'onmouseout\',\r\n            \'onmouseover\',\r\n            \'onmouseup\',\r\n            \'onpointerdown\',\r\n            \'onpointermove\',\r\n            \'onpointerup\',\r\n            \'onpointerover\',\r\n            \'onpointerout\',\r\n            \'onpointerenter\',\r\n            \'onpointerleave\',\r\n            \'onsubmit\',\r\n        ]) {\r\n            const descriptor = Object.getOwnPropertyDescriptor(\r\n                eventTarget,\r\n                eventName\r\n            );\r\n\r\n            if (!descriptor) {\r\n                continue;\r\n            }\r\n\r\n            const realGetter = descriptor.get;\r\n            const realSetter = descriptor.set;\r\n\r\n            const patchedSetter = generateAddCallbackFunction(realSetter, 0);\r\n\r\n            /*\r\n             * In case a script checks the value of the property after\r\n             * setting it, the getter has to be patched to return the\r\n             * original callback.\r\n             */\r\n            const patchedGetter = function() {\r\n                const realValue = realGetter.call(this);\r\n\r\n                const originalCallback = originalCallbackMap.get(realValue);\r\n                if (originalCallback) {\r\n                    return originalCallback;\r\n                } else {\r\n                    return realValue;\r\n                }\r\n            };\r\n\r\n            patchedGetter.toString = () => realGetter.toString();\r\n\r\n            Object.defineProperty(eventTarget, eventName, {\r\n                get: patchedGetter,\r\n                set: patchedSetter,\r\n            });\r\n        }\r\n    }\r\n\r\n    /*\r\n     * This script has some dependencies that need to be included\r\n     * without polluting the global namespace. This is achieved by\r\n     * inserting them into the source at build time in the\r\n     * makefile. This is preferable to loading the dependencies via\r\n     * AJAX, because the latter doesn\'t work if the CSP disallows\r\n     * unsafe-eval. The following tag tells the makefile where to\r\n     * insert the dependencies.\r\n     */\r\n    // <dependencies>\r\nclass InputAnalyser {\r\n    constructor(_inputs) {\r\n        this._inputs = _inputs;\r\n        this._irrelevantInputs = new WeakSet();\r\n\r\n        /*\r\n         * Not currently using Password because it was causing too\r\n         * many false positives. Some sites use external auth domains,\r\n         * and many have misconfigured analytics that collect\r\n         * passwords.\r\n         */\r\n        this.relevantInputs = [new CardNumber()];\r\n    }\r\n\r\n    get relevantInputValues() {\r\n        const inputs = {};\r\n        this._inputs.forEach(\r\n            input => (inputs[input.id || input.name] = input.value)\r\n        );\r\n\r\n        return inputs;\r\n    }\r\n\r\n    get relevantFilledInputs() {\r\n        return this.allInputs\r\n            .filter(input => this.isRelevantElement(input))\r\n            .filter(input => input.value.length >= cons.minInputSize)\r\n            .map(input => ({\r\n                id: input.id,\r\n                name: input.name,\r\n                value: input.value,\r\n            }));\r\n    }\r\n\r\n    get allInputs() {\r\n        this._inputs = [\r\n            ...document.querySelectorAll(\r\n                this.relevantInputs.map(i => i.relevantInputTypes).join(\', \')\r\n            ),\r\n        ];\r\n\r\n        return this._inputs;\r\n    }\r\n\r\n    static getLabelText(input) {\r\n        if (!input.labels.length) {\r\n            return \'\';\r\n        }\r\n        return stripSpecialChars(input.labels[0].textContent).trim();\r\n    }\r\n\r\n    /**\r\n     * Takes an HTMLElement and determines whether or not it could be relevant for the purposes of\r\n     * skimmer detection.\r\n     * @param {HTMLElement} input The element which is to be checked for relevancy.\r\n     * @returns {boolean} Returns true if the element is potentially relevant (e.g. a credit card\r\n     * field or other such sensitive input).\r\n     */\r\n    isRelevantElement(input) {\r\n        // If the element has been identified as irrelevant, return false\r\n        if (this._irrelevantInputs.has(input)) {\r\n            return false;\r\n        }\r\n\r\n        // Check the input against all the relevant input types\r\n        const relevances = this.relevantInputs.map(i => i.test(input));\r\n\r\n        /*\r\n         *  If they all agree that this input is unlikely to become\r\n         *  relevant, exclude it from further checks and return false\r\n         */\r\n        if (relevances.every(i => i === RelevantInput.relevance.NOT_RELEVANT)) {\r\n            this._irrelevantInputs.add(input);\r\n            return false;\r\n        }\r\n\r\n        // Return true if any consider the input to be relevant, otherwise false\r\n        return relevances.some(i => i === RelevantInput.relevance.RELEVANT);\r\n    }\r\n}\r\n/*\r\n * Input Matcher represents a class that can have matches with relevant inputs.\r\n * Each class that inherits must implement the findInputMatches method.\r\n */\r\nclass InputMatcher {\r\n    /*\r\n     * Finds any matches with the list of inputs.\r\n     * @param inputs. An object whose values represent relevant inputs.\r\n     * @return {[String]} The keys of the inputs that were found.\r\n     */\r\n    findInputMatches(inputs) {\r\n        throw new Error(\'findInputMatches has not been implemented.\');\r\n    }\r\n\r\n    /**\r\n     * Determines whether one or more values in needles are present in any of\r\n     * the keys of the haystacks.\r\n     * Checks both raw input and base64 encoded versions of the needles.\r\n     * @param needles An object. The haystacks are searched for the values of\r\n     * this object.\r\n     * @param haystacks The array of strings in which the needle(s) may reside.\r\n     * A string may optionally be supplied if only one value is present.\r\n     * @returns {[String]} The keys of the needles that were found in one or\r\n     * more haystacks in either raw or base64 encoded form.\r\n     */\r\n    static atLeastOneNeedleInHaystack(needles, haystacks) {\r\n        if (\r\n            needles === undefined ||\r\n            needles.length === 0 ||\r\n            haystacks === undefined\r\n        ) {\r\n            return [];\r\n        }\r\n\r\n        const searchArray = Array.isArray(haystacks) ? haystacks : [haystacks];\r\n\r\n        return Object.entries(needles)\r\n            .filter(([_, needle]) =>\r\n                searchArray.some(haystack => {\r\n                    if (isBase64Encoded(haystack)) {\r\n                        if (atob(haystack).indexOf(needle) !== -1) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                    return haystack.indexOf(needle) !== -1;\r\n                })\r\n            )\r\n            .map(([input, _]) => input);\r\n    }\r\n}\r\n/*\r\n * Matcher for an object that cannot contain any inputs.\r\n */\r\nclass EmptyMatcher extends InputMatcher {\r\n    findInputMatches(inputs) {\r\n        return [];\r\n    }\r\n}\r\n\r\nclass URLParamsMatcher extends InputMatcher {\r\n    constructor(url) {\r\n        super();\r\n        this._url = url;\r\n    }\r\n\r\n    findInputMatches(inputs) {\r\n        try {\r\n            const parsedUrl = new URL(this._url);\r\n            return InputMatcher.atLeastOneNeedleInHaystack(inputs, [\r\n                ...parsedUrl.searchParams.values(),\r\n            ]);\r\n        } catch (e) {\r\n            return [];\r\n        }\r\n    }\r\n}\r\n\r\nclass StringBodyMatcher extends InputMatcher {\r\n    constructor(str) {\r\n        super();\r\n        this._str = str;\r\n    }\r\n\r\n    findInputMatches(inputs) {\r\n        let decoded;\r\n        try {\r\n            decoded = decodeURIComponent(this._str);\r\n        } catch (e) {\r\n            decoded = this._str;\r\n        }\r\n\r\n        const searchParams = new URLSearchParams(decoded);\r\n        const parsedQuery = {};\r\n        const parsedValues = [];\r\n\r\n        for (const [key, value] of searchParams.entries()) {\r\n            parsedQuery[key] = value;\r\n            parsedValues.push(value);\r\n        }\r\n\r\n        if (parsedQuery !== {} && parsedValues.indexOf(null) === -1) {\r\n            return InputMatcher.atLeastOneNeedleInHaystack(\r\n                inputs,\r\n                parsedValues\r\n            );\r\n        }\r\n        return InputMatcher.atLeastOneNeedleInHaystack(inputs, decoded);\r\n    }\r\n}\r\n\r\nclass FormBodyMatcher extends InputMatcher {\r\n    constructor(formData) {\r\n        super();\r\n        this._formData = formData;\r\n    }\r\n\r\n    findInputMatches(inputs) {\r\n        return InputMatcher.atLeastOneNeedleInHaystack(\r\n            inputs,\r\n            Object.values(this._formData).flat()\r\n        );\r\n    }\r\n}\r\n/**\r\n * Takes a string and removes a variety of \'special\' characters like punctuation.\r\n * @param str The string to be modified.\r\n * @returns {string} The resulting string without any special characters.\r\n */\r\nfunction stripSpecialChars(str) {\r\n    return str.replace(/[`~!@#$%^&*()_|+\\-=?;:\'\",.<>}{\\][\\\\/]/gi, \'\');\r\n}\r\n\r\n/**\r\n * Attempts to identify where a string is base-64 encoded or not.\r\n * @param string The raw string to be examined.\r\n * @returns {boolean} Returns true if the string matches a base-64 string. False otherwise.\r\n */\r\nfunction isBase64Encoded(string) {\r\n    return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(\r\n        string\r\n    );\r\n}\r\n\r\n// Computes the base64 sha512/sha224 hash of some text, removing trailing ==\r\nfunction base64hash(hashFunction, text) {\r\n    return btoa(\r\n        hashFunction\r\n            .array(text)\r\n            .map(char => String.fromCharCode(char))\r\n            .join(\'\')\r\n    ).slice(0, -2);\r\n}\r\n\r\nfunction normaliseUrl(str) {\r\n    const link = document.createElement(\'a\');\r\n    link.href = str;\r\n    return link.href;\r\n}\r\n\r\nfunction stripCredentials(urlstr) {\r\n    try {\r\n        const url = new URL(urlstr);\r\n        const params = url.searchParams;\r\n        params.forEach((_, key) => params.set(key, \'\'));\r\n        return url.toString();\r\n    } catch (e) {\r\n        return urlstr;\r\n    }\r\n}\r\n\r\n/**\r\n * Check if two URLs match regardless of any partial encoding.\r\n *\r\n * Uses decodeURI which can throw a URIError when url is malformed.\r\n */\r\nfunction urlsMatch(first, second) {\r\n    try {\r\n        return decodeURI(first) === decodeURI(second);\r\n    } catch (e) {\r\n        return false;\r\n    }\r\n}\r\n\r\nfunction sendMessage(type, data, tabId, options) {\r\n    if (tabId) {\r\n        chrome.tabs.sendMessage(tabId, { data, type }, options);\r\n    } else {\r\n        chrome.runtime.sendMessage({ data, type });\r\n    }\r\n}\r\n\r\n// Encodes the given input using base64url as defined by RFC4648 section 5.\r\nfunction base64Url(input) {\r\n    return btoa(input)\r\n        .replace(\'+\', \'-\')\r\n        .replace(\'/\', \'_\');\r\n}\r\n/*\r\n * Netcraft Extension\r\n * constants JavaScript\r\n */\r\n\r\n/* eslint sort-keys: \'off\' */\r\n/* sort-keys is disabled as the constants are separated by type */\r\n\r\n// Aligned values here make for easier reading, so we disable prettier\r\n\r\n// prettier-ignore\r\nconst cons = (function() {\r\n    const cryptojackersUrl = \'https://www.netcraft.com/apps/help/glossary/#crypto-miners\';\r\n    const skimmersUrl = \'https://www.netcraft.com/apps/help/glossary/#credit-card-skimmers\';\r\n    const evilResourceCrime = \'attempted to load\';\r\n\r\n    return {\r\n        fields: [\r\n            \'topsites\',\r\n            \'country\',\r\n            \'netblock\',\r\n            \'hoster\',\r\n            \'firstseen\',\r\n            \'rank\',\r\n            \'risk\',\r\n            \'uses_sslv3\',\r\n            \'pfs\',\r\n            \'heartbleed\',\r\n            \'heartbleed_message\',\r\n        ],\r\n\r\n        // options that can be toggle in options.js and installed flag\r\n\r\n        toggleOptions: [\r\n            \'installed\',\r\n            \'block\',\r\n            \'xss\',\r\n            \'suspicious\',\r\n            \'other-malicious-scripts\',\r\n            \'credential-leaks\',\r\n            \'analytics\',\r\n            \'cryptojackers\',\r\n            \'skimmers\',\r\n        ],\r\n\r\n        // domains\r\n        mirror:  \'https://mirror2.extension.netcraft.com/\',\r\n        toolbar: \'https://mirror.toolbar.netcraft.com/\',\r\n\r\n        // links and faqs\r\n        blogArchive:      \'https://news.netcraft.com/archives/\',\r\n        browserApp:       \'https://www.netcraft.com/apps/browser/\',\r\n        cryptojackersUrl,\r\n        extensionStore:   \'https://addons.mozilla.org/en-us/firefox/addon/netcraft-toolbar?src=external-blocked_visit-rating\',\r\n        heartbleedUrl:    \'https://www.netcraft.com/apps/help/glossary/#heartbleed\',\r\n        logBlockedVisit:  \'https://toolbar.netcraft.com/blocked_visit\',\r\n        netblock:         \'https://sitereport.netcraft.com/netblock?q=\',\r\n        pfsUrl:           \'https://www.netcraft.com/apps/help/glossary/#pfs\',\r\n        report:           \'https://sitereport.netcraft.com/?url=\',\r\n        reportAPI:        \'https://report.netcraft.com/api/v2/report/urls\',\r\n        reportMistake:    \'https://report.netcraft.com/report/mistake?url=\',\r\n        skimmersUrl,\r\n        sslv3Url:         \'https://www.netcraft.com/apps/help/glossary/#sslv3\',\r\n        submissionURL:    \'https://report.netcraft.com/submission/\',\r\n        topsites:         \'https://trends.netcraft.com/topsites?s=\',\r\n\r\n        // paths\r\n        block:              \'blocked.html\',\r\n        check:              \'check_url/v4/\',\r\n        danger:             \'Images/danger.png\',\r\n        embed:              \'/embedded\',\r\n        evilJSPatternsFeed: \'eviljspatterns\',\r\n        skimmerDomainWhitelistFeed: \'eviljsskimmerwhitelist\',\r\n        evilResourcesFeed:  \'blockdb/netcraft_extension_evil_resource_encrypted-yppL7bVh/\',\r\n        flags:              \'Images/flags/\',\r\n        force:              \'/dodns\',\r\n        heartbleed:         \'Images/heartbleed.png\',\r\n        info:               \'/info\',\r\n        poodlePost:         \'2014/10/15/googles-poodle-affects-oodles.html\',\r\n        warning:            \'Images/warning.png\',\r\n\r\n        // feed constants\r\n        evilResourceTypes: [\'cryptojacker\', \'skimmer\'],\r\n        evilResourcesSalt: \'C4jhYHxW\',\r\n        maxRevisions:      5,\r\n        randomSaltLength:  8,\r\n\r\n        // mappings of url_type => information\r\n        urlTypeInformationMappings: {\r\n            \'malicious_javascript/cryptojacker\': [\'web miners\',             cryptojackersUrl],\r\n            \'malicious_javascript/skimmer\':      [\'shopping site skimmers\', skimmersUrl],\r\n        },\r\n\r\n        // mappings of evilResourceTypes => user options\r\n        evilResourceOptionMappings: {\r\n            \'cryptojacker\': \'cryptojackers\',\r\n            \'skimmer\':      \'skimmers\'\r\n        },\r\n\r\n        // mappings of user options => url_type\r\n        optionUrlTypeMappings: {\r\n            \'block\':                   /^(?!malicious_javascript:)/,\r\n            \'cryptojackers\':           /^malicious_javascript\\/cryptojacker$/,\r\n            \'other-malicious-scripts\': /^malicious_javascript\\/(?!(skimmer|cryptojacker)$)/,\r\n            \'skimmers\':                /^malicious_javascript\\/skimmer$/,\r\n        },\r\n\r\n        // report constants\r\n        malwareSourceEmail:  \'extension-reported-malware@netcraft.com\',\r\n        malwareSourceUUID:   \'mwcCcApi89cobY9loJ9rWJ9HuOhlpwMj\',\r\n        extensionSourceUUID: \'qBEQ3NhEnwK4sypmuSxBJzRqpxZLRXxe\',\r\n\r\n        // report error default message\r\n        reportErrorMessage: \'An error occured with your submission. Please try again.\',\r\n\r\n        // mapping from report API errors to displayed messages\r\n        reportErrorMappings: {\r\n            \'Duplicate of a recent submission\': \'This URL has already been reported.\',\r\n            \'Supplied email is not valid\': \'Invalid email address. Please correct it and try again.\',\r\n        },\r\n\r\n        whitelistErrorMessages: {\r\n            invalidHostname: \'Invalid hostname, please correct it and try again\',\r\n            duplicate: \'Protection is already disabled for this site\',\r\n        },\r\n\r\n        // browserAction icon paths\r\n        iconNormal: {\r\n            16: \'Images/action-normal-16.png\',\r\n            24: \'Images/action-normal-24.png\',\r\n            32: \'Images/action-normal-32.png\'\r\n        },\r\n        iconDanger: {\r\n            16: \'Images/action-danger-16.png\',\r\n            24: \'Images/action-danger-24.png\',\r\n            32: \'Images/action-danger-32.png\'\r\n        },\r\n        iconWarning: {\r\n            16: \'Images/action-warning-16.png\',\r\n            24: \'Images/action-warning-24.png\',\r\n            32: \'Images/action-warning-32.png\'\r\n        },\r\n        iconDisabled: {\r\n            16: \'Images/action-disabled-16.png\',\r\n            24: \'Images/action-disabled-24.png\',\r\n            32: \'Images/action-disabled-32.png\'\r\n        },\r\n\r\n        // Extension internal message types\r\n        capturedIntercept:  \'interceptDetailsCaptured\',\r\n        confirmedIntercept: \'interceptDetailsConfirmed\',\r\n        contentReady:       \'contentScriptReady\',\r\n        leaksEnabled:       \'credentialLeaksEnabled\',\r\n        reportURL:          \'report\',\r\n        reqBlock:           \'block\',\r\n        reqDetails:         \'details\',\r\n        reqForce:           \'force\',\r\n        reqPopUp:           \'popup\',\r\n        reqPrintTimingData: \'time\', //debug\r\n        reqInputs:          \'requestInputValues\',\r\n        sendInputs:         \'sendInputs\',\r\n        sendIntercept:      \'sendInterceptDetails\',\r\n        updateOption:       \'updateOption\',\r\n        updateWhitelist:    \'whitelist\', // disable button whitelist\r\n\r\n        // Status codes\r\n        errEvilResource: \'EVILRESOURCE\',\r\n        errNoDetails:    \'NODETAILS\',\r\n        errNoInfo:       \'NOINFO\',\r\n        errBlocked:      \'BLOCKED\',\r\n        errPending:      \'PENDING\',\r\n        errServer:       \'ERROR\',\r\n        errSuspicious:   \'SUSPICIOUS\',\r\n        errTimeout:      \'TIMEOUT\',\r\n        errXSS:          \'XSS\',\r\n        noError:         \'OK\',\r\n\r\n        // Valid protocols (it is a regex match so ^ matches start of string)\r\n        protocols:    [\'^http\', \'^https\', \'^ftp\'],\r\n\r\n        // Human readable messages\r\n        msgNa:           \'NA\',\r\n        msgNewSite:      \'New Site\',\r\n        msgNoInfo:       \'Site information not available\',\r\n        msgNoServer:     \'Unable to contact Netcraft servers\',\r\n        msgSuspicious:   \'Suspicious URL Detected\',\r\n        msgXSS:          \'Suspected XSS Attack\',\r\n        msgBlocked:      {\r\n            \'malicious_javascript/skimmer\': {\r\n                title: \'Shopping Site Skimmer Detected\',\r\n                description: \'shopping site skimmer\'\r\n            },\r\n            \'malicious_javascript/dropsite\': {\r\n                title: \'Potential Credential Leak Detected\',\r\n                description: \'potential credential leak\'\r\n            },\r\n            \'malicious_javascript/cryptojacker\': {\r\n                title: \'Web Miner Detected\',\r\n                description: \'web miner\'\r\n            },\r\n            \'malware_c2/infrastructure\': {\r\n                title: \'Malicious URL\',\r\n                description: \'malicious page\'\r\n            },\r\n            \'default\': {\r\n                title: \'Suspected Phishing\',\r\n                description: \'suspected phishing site\'\r\n            },\r\n        },\r\n\r\n        // Human readable reasons\r\n        urlTypeCrimeMappings: {\r\n            \'malicious_javascript/skimmer\':      evilResourceCrime,\r\n            \'malicious_javascript/cryptojacker\': evilResourceCrime,\r\n            \'malicious_javascript/dropsite\':     \' is blocked because we detected that sensitive information was leaked from\',\r\n        },\r\n\r\n        // Timeout function value to clear cache etc.\r\n        timeout:      300000,\r\n\r\n        // Skimmer whitelist update interval (1 day)\r\n        skimmerWhitelistInterval: 1000 * 60 * 60 * 24,\r\n\r\n        // Google Analytics account\r\n        account:      \'UA-2150242-8\',\r\n        gaCategory:   \'Popup load times\',\r\n\r\n        // InputAnalyser constants\r\n        minInputSize: 5,\r\n\r\n        // First addresses of the private IPv4 netblocks\r\n        privateIPv4Blocks: [\r\n            \'10.0.0.0\',\r\n            \'172.16.0.0\',\r\n            \'192.168.0.0\'\r\n        ],\r\n\r\n        // Sync storage key for disable for this site button whitelist\r\n        disableButtonWhitelistKey: \'disableButtonWhitelist\',\r\n\r\n        // Local storage key for local credential leak detection whitelist\r\n        localCredentialLeakWhitelistKey: \'localCredentialLeakWhitelist\',\r\n\r\n        // Whitelisted fileName regexes for credential leak detection\r\n        credentialLeakFileNameWhitelist: [\r\n            /^https?:\\/\\/googleads.g.doubleclick.net\\//,\r\n        ],\r\n    };\r\n})();\r\n\r\n/*\r\n * recursively prevents any property or sub-property in cons from:\r\n *   - being added;\r\n *   - being removed; or\r\n *   - being modified.\r\n */\r\nfunction deepFreeze(object) {\r\n    Object.freeze(object);\r\n\r\n    for (const name of Object.getOwnPropertyNames(object)) {\r\n        if (typeof object[name] === \'object\') {\r\n            deepFreeze(object[name]);\r\n        }\r\n    }\r\n}\r\n\r\ndeepFreeze(cons);\r\n/*\r\n * RelevantInput objects check whether given form inputs should be\r\n * treated as sensitive data.\r\n *\r\n * This should be treated as an abstract class. Only the subclasses\r\n * are intended to be instantiated.\r\n */\r\nclass RelevantInput {\r\n    /*\r\n     * Enum of the possible return values of test():\r\n     * RELEVANT:\r\n     *     Input is relevant now, outgoing requests should be\r\n     *     checked for the contained value (subject to minimum length).\r\n     * POTENTIALLY RELEVANT:\r\n     *     Input should not be considered relevant now, but may be in\r\n     *     future so should continue to be checked.\r\n     * NOT_RELEVANT:\r\n     *     Input is not relevant and will not become relevant, so can\r\n     *     be ommitted from further checks to optimise performance.\r\n     */\r\n    static get relevance() {\r\n        return {\r\n            NOT_RELEVANT: 1,\r\n            POTENTIALLY_RELEVANT: 2,\r\n            RELEVANT: 3,\r\n        };\r\n    }\r\n\r\n    /*\r\n     * relevantInputTypes (string) - a comma separated list of\r\n     * selectors that describe the types of input that should be\r\n     * considered relevant.\r\n     */\r\n    constructor(relevantInputTypes) {\r\n        this.relevantInputTypes = relevantInputTypes;\r\n    }\r\n\r\n    /*\r\n     * Checks whether any of the given input\'s \'labels\' (the ID, name,\r\n     * or actual label) match the given regex.\r\n     */\r\n    static testInputLabel(regex, input) {\r\n        const labels = [\r\n            input.id,\r\n            input.name,\r\n            InputAnalyser.getLabelText(input),\r\n        ];\r\n        return labels.some(label => regex.test(label));\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for passwords.\r\n */\r\nclass Password extends RelevantInput {\r\n    constructor() {\r\n        super(\'input[type=password]\');\r\n    }\r\n\r\n    /*\r\n     * Passwords are verified purely by their input type.\r\n     */\r\n    test(input) {\r\n        if (input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for card numbers.\r\n */\r\nclass CardNumber extends RelevantInput {\r\n    constructor() {\r\n        // Inputs with no type attribute act like text inputs\r\n        super(\r\n            \'input[type=text], input[type=tel], input[type=number], input:not([type])\'\r\n        );\r\n\r\n        // Definitely card number labels\r\n        this.inputLabelRegexes = [\r\n            /(cc|card).?(num|no)/i,\r\n            /カード番号/,\r\n            /Номер.*карты/i,\r\n            /信用卡卡號/i,\r\n            /信用卡号码/i,\r\n        ];\r\n\r\n        /*\r\n         * Matches incomplete card numbers, because ideally the\r\n         * skimmer should be detected before the victim gives away\r\n         * most of their card number.\r\n         */\r\n        this.inputValueRegexes = [/^(\\d{4}[-\\s]?){0,4}\\d{1,4}$/];\r\n\r\n        // Probably card number labels\r\n        this.suspectInputLabelRegexes = [\r\n            /card/i,\r\n            /tarjeta/i,\r\n            /carte/i,\r\n            /карты/i,\r\n            /卡/i,\r\n            /カード/i,\r\n            /카드/i,\r\n            /credit[^s]/i,\r\n        ];\r\n\r\n        // If it matches one of these, probably not a card number\r\n        this.inputLabelExceptionRegexes = [\r\n            /post/i,\r\n            /code/i,\r\n            /zip/i,\r\n            /acc(oun)?t?[-_]?n(um|o)/i,\r\n            /credit[-_]?(sum|limit)/i,\r\n            /loyalty/i,\r\n            /gift/i,\r\n            /idcard/i,\r\n            /cardid/i,\r\n            /club/i,\r\n            /sku/i,\r\n            /(\\b|_)otp/i,\r\n            /price/i,\r\n            /amount/i,\r\n            /service/i,\r\n            /picard/i,\r\n            /search/i,\r\n            /contact/i,\r\n            /callme/i,\r\n            /chat/i,\r\n            /activate/i,\r\n            /msisdn/i,\r\n        ];\r\n    }\r\n\r\n    test(input) {\r\n        if (!input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return NOT_RELEVANT if label matches an exception regex\r\n        if (\r\n            this.inputLabelExceptionRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return RELEVANT if label clearly states card number\r\n        if (\r\n            this.inputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        }\r\n\r\n        /*\r\n         * If label suggests card number, return RELEVANT if value\r\n         * also suggests card number or POTENTIALLY_RELEVANT if\r\n         * not. Otherwise, return NOT_RELEVANT as this element will\r\n         * not become relevant unless a label changes (unlikely).\r\n         */\r\n        if (\r\n            this.suspectInputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            if (this.inputValueRegexes.some(r => r.test(input.value))) {\r\n                return RelevantInput.relevance.RELEVANT;\r\n            } else {\r\n                return RelevantInput.relevance.POTENTIALLY_RELEVANT;\r\n            }\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\r\n    const inputAnalyser = new InputAnalyser();\r\n\r\n    const resourceBlockList = new Set([\r\n                            \"6C590F97DDB1A11AE795B3050E4C3866\", \"D285BE79786E2ECE396941331C5BA65E\", \"59DF8523520DE7F08FB53D3D12BD94F5\", \"1FB784EBAFDA1618023756BF5B6D7E46\", \"0D6E1B5F03B81F5C37813B22E29C4FE2\", \"EFAB0ECE9383D50C555B8168C3E2A4DD\", \"AC8A4B13A411A4E43E61D0A034FA4A55\", \"25A781F6D1F94EEACCF1E00E844D9E14\", \"3920B04ED0D215EA738F524F60485F23\", \"05F4A2D3161A92EB82A44F15FC2A3AEC\", \"C5A1BE7B3855B0A25275B2877E005CB9\", \"00069CEC347076721EBE2257978A0A33\", \"9A513130D02F360BBDC16D8AC0C579D8\", \"7CDB2A3A29E50F90C1000252EF523AD6\", \"25CE912FAF2376026AAD0174AD5A0329\", \"739E1F67AA5F64A06FC469F6030802D4\", \"F49368C9A05C29EF49411999C5987F2C\", \"AB2DD89FB892155275D8780CF527BC93\", \"CFE758EB18FBFE70B1F690241B75D2B1\", \"E43C3463B40A790545F4149FF06B419E\", \"8B6D4C76A7CCF5EB720660B8B2D3F5EB\", \"45573B3E2F613AF5BF2145DF004BABD1\", \"E3DB4E968B371DE94CE607199A062F39\", \"A4EFF1240C5DE614B394FB738CBD28E2\", \"F42D10E84B9A69A8A24062319F425A83\", \"90646BE39982594A58EDF5D2630CCFFD\", \"669853BEA9B84EA51D7302A7FBFF5BC3\", \"0BA566B4777526F451108100107DC5ED\", \"AB19E12BFFBC47244BDCC2403EAA9037\", \"61DAF8537240639277FD1A5908A131A4\", \"6F1DC1309CFE734C038685D8F17A3B6B\", \"DCC3DB001334F29C1C0208D15E9EAB49\", \"2DBD7D54022C28273ADE3147B8EEB076\", \"AE3760E93412C65EC5BF63537B037019\", \"4CD96A830DAC0E0B5EBE578B0DD870AE\", \"4C70569BBE1821F5655382660807FA77\", \"05ECA17FF0EA78669ED43B2BD7B63571\", \"44E503B3621513B9D3666F284E4DF47A\", \"8F1C92E7665860AE70CBB228DED86111\", \"D69552885B55C4097E5725F805815639\", \"B4357A9D889E974CDF3F9740E48F9A1B\", \"5709DC60CFC365B7064B06C709A9D32B\", \"14E8729CBB16AC3625EAA0D7A679E56E\", \"793D4DABAAA377A12F2B6CE88C5FB389\", \"C5D1DCE2AE0D8CE79054FC16CB13D65A\", \"89F91750BD4A9397D1F8CE183757FF70\", \"50B8BC213FB188A687173C1A0A77EA57\"\r\n                        ]);\r\n    const evilJSPatterns = new Set([\r\n                            \r\n                        ]);\r\n\r\n    // Extract body object\r\n    function extractBody(data) {\r\n        if (!data) {\r\n            return Promise.resolve({});\r\n        }\r\n        if (FormData.prototype.isPrototypeOf(data)) {\r\n            const formData = {};\r\n            for (const [key, value] of data.entries()) {\r\n                formData[key] = value;\r\n            }\r\n            return Promise.resolve({ formData });\r\n        }\r\n        // Create a temporary Request object to parse the body\r\n        const tempRequest = new Request(\'\', { body: data, method: \'POST\' });\r\n        return extractBodyFromRequest(tempRequest);\r\n    }\r\n\r\n    function extractBodyFromRequest(request) {\r\n        // Try to extract as text\r\n        return request\r\n            .text()\r\n            .then(text => {\r\n                return { text };\r\n            })\r\n            .catch(e => {\r\n                return {};\r\n            });\r\n    }\r\n\r\n    /**\r\n     * This function checks if the request hostname/URL is in the blocklist.\r\n     * Used when identifying whether or not to forward request to background\r\n     * script.\r\n     * @param hostname Hostname to check against blocklist.\r\n     * @param url Request url to check against evil JS patterns\r\n     * @returns boolean true if hostname is in the blocklist, false if\r\n     * it is not in blocklist or if blocklist is not provided.\r\n     */\r\n    function requestInBlocklist(hostname, url) {\r\n        if (resourceBlockList === undefined || evilJSPatterns === undefined) {\r\n            return false;\r\n        }\r\n        if (testEvilJS(url.replace(/^(https?:)?\\/\\//i, \'\'))) {\r\n            return true;\r\n        }\r\n        while (!testEvilJS(hostname) && !testResource(hostname)) {\r\n            const newHostname = hostname.replace(/^.+?\\./g, \'\');\r\n\r\n            if (hostname === newHostname) return false;\r\n\r\n            hostname = newHostname;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    function testEvilJS(url) {\r\n        return evilJSPatterns.has(base64hash(sha512, url));\r\n    }\r\n\r\n    function testResource(hostname) {\r\n        return resourceBlockList.has(\r\n            md5(cons.evilResourcesSalt + hostname).toUpperCase()\r\n        );\r\n    }\r\n\r\n    /*\r\n     * Implementations to overwrite window.XMLHttpRequest.prototype.open,\r\n     * window.XMLHttpRequest.prototype.send and window.fetch. These are (to my knowledge) the two\r\n     * APIs that can be used to make requests within Javascript. If there are more, then they will\r\n     * also need to be overridden and intercepted like below.\r\n     */\r\n\r\n    /**\r\n     * This function generates functions to be used for wrapping and intercepting network\r\n     * requests, namely from XMLHttpRequest and the Fetch API. It works by taking the real\r\n     * function we want to intercept the request for, and then uses document.currentScript to work\r\n     * out which file initiated the request. It sends those details to the content script which\r\n     * forwards it to the background script, and then we await a confirmation of receipt from the\r\n     * background page. Finally, the real request is sent by forwarding parameters to the original\r\n     * function passed in.\r\n     * @param realFn A reference to the real function that is being intercepted.\r\n     * @param getRequestUrlFn A function used to get the request URL from the object. This is\r\n     * necessary as the mechanisms for different objects differs depending on implementation.\r\n     * @param getRequestBodyPr A function used to get the request body from the object. Returns\r\n     * a promise. It should be null if it\'s not applicable (e.g. if it sets the src of an image or script).\r\n     * @param type The type of request, which will be a ResourceType:\r\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType\r\n     * @returns {function(...[*]=): PromiseLike<any | never>}\r\n     */\r\n    function generateRequestInterceptFn(\r\n        realFn,\r\n        getRequestUrlFn,\r\n        getRequestBodyPr,\r\n        type\r\n    ) {\r\n        const patched = function(...args) {\r\n            /*\r\n             * We create a link DOM element in order to correctly generate the\r\n             * absolute path of the request, automatically transforming from a\r\n             * relative path if necessary.\r\n             */\r\n            const requestUrl = normaliseUrl(\r\n                getRequestUrlFn.bind(this)(...args)\r\n            );\r\n\r\n            let requestHostname;\r\n            let pageHostname;\r\n\r\n            try {\r\n                requestHostname = new URL(requestUrl).hostname;\r\n                pageHostname = new URL(location.href).hostname;\r\n            } catch (e) {\r\n                return realFn.apply(this, args);\r\n            }\r\n\r\n            /*\r\n             * If hostname is in the blocklist, the request should always be\r\n             * sent to the background script.\r\n             */\r\n            // eslint-disable-next-line prefer-const\r\n            let forceStall = false;\r\n\r\n            if (!forceStall) {\r\n                /*\r\n                 * Delaying outgoing requests sometimes causes problems\r\n                 * with elements such as video players and dynamically\r\n                 * loaded images, so it should only be done when\r\n                 * necessary. Before inputAnalyser is defined and when the\r\n                 * request is to the same hostname as the page, this can\r\n                 * be skipped.\r\n                 */\r\n                if (\r\n                    requestHostname === pageHostname ||\r\n                    inputAnalyser === undefined\r\n                ) {\r\n                    return realFn.apply(this, args);\r\n                }\r\n\r\n                /*\r\n                 * It can also be skipped if there is no sensitive\r\n                 * information entered into a form on the page yet.\r\n                 */\r\n                const relevantFilledInputs = inputAnalyser.relevantFilledInputs;\r\n                if (!relevantFilledInputs.length) {\r\n                    return realFn.apply(this, args);\r\n                }\r\n\r\n                /*\r\n                 * For requests with no body, it\'s only necessary to delay\r\n                 * them when the sensitive information from the form\r\n                 * inputs is present in the request URL.\r\n                 */\r\n                const ia = new InputAnalyser(relevantFilledInputs);\r\n                const relevantInputValues = ia.relevantInputValues;\r\n                const urlMatcher = new URLParamsMatcher(requestUrl);\r\n\r\n                if (\r\n                    getRequestBodyPr === null &&\r\n                    !urlMatcher.findInputMatches(relevantInputValues).length\r\n                ) {\r\n                    return realFn.apply(this, args);\r\n                }\r\n            }\r\n\r\n            /*\r\n             * Create a promise that resolves immediately if the request body\r\n             * does not apply to this request.\r\n             */\r\n            if (getRequestBodyPr === null) {\r\n                getRequestBodyPr = function() {\r\n                    return Promise.resolve({});\r\n                };\r\n            }\r\n\r\n            let fileName;\r\n            if (document.currentScript && document.currentScript.src) {\r\n                fileName = document.currentScript.src;\r\n            } else if (callbackCurrentScript && callbackCurrentScript.src) {\r\n                fileName = callbackCurrentScript.src;\r\n            } else {\r\n                fileName = location.href;\r\n            }\r\n\r\n            return getRequestBodyPr\r\n                .bind(this)(...args)\r\n                .then(requestBody => {\r\n                    /*\r\n                     * Generate the information object to send through to the\r\n                     * content script including the request URL, the initiator\r\n                     * filename and a timestamp relative to the page load.\r\n                     */\r\n                    const detail = {\r\n                        fileName,\r\n                        requestBody,\r\n                        requestUrl,\r\n                        timestamp: performance.now(),\r\n                        type,\r\n                    };\r\n\r\n                    document.dispatchEvent(\r\n                        new CustomEvent(cons.capturedIntercept, {\r\n                            detail,\r\n                        })\r\n                    );\r\n\r\n                    return new Promise(resolve => {\r\n                        /*\r\n                         * Send the initiator\'s file name and other info to the\r\n                         * background script, then wait for confirmation that the\r\n                         * details made it to the background page before calling\r\n                         * the real function. This is to avoid any race conditions\r\n                         * whereby the details haven\'t been added to the tab data\r\n                         * before onBeforeRequest/onBeforeSendHeaders gets called.\r\n                         */\r\n                        document.addEventListener(\r\n                            cons.confirmedIntercept,\r\n                            function compareData(customEvent) {\r\n                                const receivedData = JSON.parse(\r\n                                    customEvent.detail\r\n                                );\r\n                                if (detailsMatch(detail, receivedData)) {\r\n                                    /*\r\n                                     * If it\'s the correct confirmation, then we\r\n                                     * can resolve the promise and call the real\r\n                                     * function in the finally block.\r\n                                     */\r\n                                    document.removeEventListener(\r\n                                        cons.confirmedIntercept,\r\n                                        compareData,\r\n                                        false\r\n                                    );\r\n                                    resolve();\r\n                                }\r\n                            }\r\n                        );\r\n                    }).then(() => realFn.apply(this, args));\r\n                });\r\n        };\r\n        // make toString() look native, #34\r\n        patched.toString = () => realFn.toString();\r\n        return patched;\r\n    }\r\n\r\n    function detailsMatch(first, second) {\r\n        const comparisonKeys = [\'fileName\', \'requestUrl\', \'timestamp\', \'type\'];\r\n        for (const key of comparisonKeys) {\r\n            if (first[key] !== second[key]) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    window.fetch = generateRequestInterceptFn(\r\n        realFetch,\r\n        request => (typeof request === \'string\' ? request : request.url),\r\n        (request, options) => {\r\n            // Check if argument is a Request object.\r\n            if (request instanceof Request) {\r\n                return extractBodyFromRequest(request);\r\n            }\r\n            return options ? extractBody(options.body) : Promise.resolve({});\r\n        },\r\n        \'xmlhttprequest\'\r\n    );\r\n\r\n    window.XMLHttpRequest.prototype.send = generateRequestInterceptFn(\r\n        realSend,\r\n        function getRequestUrl() {\r\n            return this._requestUrl;\r\n        },\r\n        extractBody,\r\n        \'xmlhttprequest\'\r\n    );\r\n\r\n    /*\r\n     * This function is intercepted so that we can capture the value of the\r\n     * request URL, since it isn\'t present in the arguments for\r\n     * XMLHttpRequest.send (above).\r\n     */\r\n    window.XMLHttpRequest.prototype.open = function open(...args) {\r\n        [, this._requestUrl] = args;\r\n        realOpen.apply(this, args);\r\n    };\r\n    window.XMLHttpRequest.prototype.open.toString = () => realOpen.toString();\r\n\r\n    /*\r\n     * Monkeypatches for setting the src of images and scripts, an\r\n     * indirect way of sending requests that is often employed by\r\n     * skimmers. The getters are also overridden to make the behaviour\r\n     * seem identical to the original implementation. Otherwise, if a\r\n     * script were to set an src and immediately read it, it would\r\n     * come back as the old value.\r\n     */\r\n\r\n    /*\r\n     * WeakMap maps an object to a value. This is used to store the\r\n     * new src of an object when its true src property may not yet\r\n     * have been set.\r\n     */\r\n    const srcMap = new WeakMap();\r\n    const requestElements = [\r\n        {\r\n            srcElement: HTMLImageElement.prototype,\r\n            type: \'image\',\r\n        },\r\n        {\r\n            srcElement: HTMLScriptElement.prototype,\r\n            type: \'script\',\r\n        },\r\n    ];\r\n\r\n    for (const { srcElement, type } of requestElements) {\r\n        const realGetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\r\n            .get;\r\n        const realSetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\r\n            .set;\r\n        const realGetAttribute = srcElement.getAttribute;\r\n        const realSetAttribute = srcElement.setAttribute;\r\n        const realRemoveAttribute = srcElement.removeAttribute;\r\n\r\n        const interceptFn = generateRequestInterceptFn(\r\n            realSetSrc,\r\n            src => `${src}`,\r\n            null,\r\n            type\r\n        );\r\n\r\n        function setSrc(url) {\r\n            /*\r\n             * It\'s valid to provide a TrustedScriptURL wrapping the URL but we need to\r\n             * return a string in the getter, so we convert the url to a string here.\r\n             *\r\n             * The url can also be undefined/null so we can\'t just call `.toString()`.\r\n             *\r\n             * See #56.\r\n             */\r\n            srcMap.set(this, `${url}`);\r\n            interceptFn.call(this, url);\r\n            return url;\r\n        }\r\n\r\n        function getSrc() {\r\n            if (srcMap.get(this) === undefined) {\r\n                return realGetSrc.call(this);\r\n            } else {\r\n                return normaliseUrl(srcMap.get(this));\r\n            }\r\n        }\r\n\r\n        getSrc.toString = () => realGetSrc.toString();\r\n        setSrc.toString = () => realSetSrc.toString();\r\n\r\n        Object.defineProperty(srcElement, \'src\', {\r\n            get: getSrc,\r\n            set: setSrc,\r\n        });\r\n\r\n        srcElement.setAttribute = function(attributeName, value) {\r\n            if (attributeName.toUpperCase() === \'SRC\') {\r\n                setSrc.call(this, value);\r\n            } else {\r\n                realSetAttribute.call(this, attributeName, value);\r\n            }\r\n        };\r\n        srcElement.setAttribute.toString = () => realSetAttribute.toString();\r\n\r\n        srcElement.getAttribute = function(attributeName) {\r\n            if (\r\n                attributeName.toUpperCase() === \'SRC\' &&\r\n                srcMap.get(this) !== undefined\r\n            ) {\r\n                return srcMap.get(this);\r\n            } else {\r\n                return realGetAttribute.call(this, attributeName);\r\n            }\r\n        };\r\n        srcElement.getAttribute.toString = () => realGetAttribute.toString();\r\n\r\n        srcElement.removeAttribute = function(attributeName) {\r\n            if (attributeName.toUpperCase() === \'SRC\') {\r\n                srcMap.delete(this);\r\n            }\r\n\r\n            return realRemoveAttribute.call(this, attributeName);\r\n        };\r\n        srcElement.removeAttribute.toString = () =>\r\n            realRemoveAttribute.toString();\r\n    }\r\n})(\r\n    window.XMLHttpRequest.prototype.open,\r\n    window.XMLHttpRequest.prototype.send,\r\n    window.fetch\r\n);\r\n</script>\r\n                    <style font-name=\"MS PMincho\">\r\n                        @font-face {\r\n                            font-family: \"MS PMincho\";\r\n                            src: url(https://sys.emarketing.pe/builder/font/ms-pmincho.ttf);\r\n                        }\r\n                    </style>\r\n                \r\n                    <style font-name=\"MS Mincho\">\r\n                        @font-face {\r\n                            font-family: \"MS Mincho\";\r\n                            src: url(https://sys.emarketing.pe/builder/font/ms-mincho.ttf);\r\n                        }\r\n                    </style>\r\n                <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"><link href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Outlined\" rel=\"stylesheet\">\r\n                \r\n            </head>\r\n  <body class=\"builderjs-layout\" spellcheck=\"false\">\r\nMensaje de prueba<br /><!-- [if mso]>\r\n        <center>\r\n        <table><tr><td width=\"760\">\r\n    <![endif]--><!-- [if mso]>\r\n        </td></tr></table>\r\n        </center>\r\n    <![endif]-->\r\n</body></html>', '2023-09-11 18:12:25', '2023-09-11 18:14:15', 1, 0, NULL, 'email');
INSERT INTO `axme_templates` (`id`, `uid`, `customer_id`, `name`, `content`, `created_at`, `updated_at`, `builder`, `is_default`, `theme`, `type`) VALUES
(31, '64ff5aa2a154b', 2, '[Campaign: \"Ejemplo23\"]', '<!DOCTYPE html><html lang=\"en\"><head><style id=\"mceDefaultStyles\" type=\"text/css\">img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}\n</style>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"Mark Otto, Jacob Thornton, and Bootstrap contributors\">\n    <meta name=\"generator\" content=\"AcelleSystemLayouts\">\n    <title>Blank</title>\n\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1YWEyYTE1NGIvY3Nz/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"/assets/YXBwL2N1c3RvbWVycy82NGZmNTU5NGExODhjL2hvbWUvdGVtcGxhdGVzLzY0ZmY1YWEyYTE1NGIvY3Nz/custom.css\" rel=\"stylesheet\">\n\n    <style>\n        @media (min-width: 768px) {\n            .container {\n                max-width: 650px;\n            }\n        }\n        @media (min-width: 992px) {\n            .container {\n                max-width: 760px;\n            }\n        }\n        @media (min-width: 769px) {\n            [data-hide-on=\"desktop\"] {\n                display: none!important;\n            }\n        }\n        @media (max-width: 600px) {\n            [data-hide-on=\"mobile\"] {\n                display: none!important;\n            }\n        }\n        @media (max-width: 600px) {\n            [builder-element=CellContainerElement].cell-responsive {\n                display: block!important;\n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] {\n                width: 100%!important;\n                max-width: none!important;        \n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"],\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"BlockElement\"] {\n                padding-left: 0px!important;\n                padding-right: 0px!important;\n            }\n            /* EmailMode */\n            [builder-element=CellContainerElement].cell-responsive tbody,\n            [builder-element=CellContainerElement].cell-responsive tr,\n            [builder-element=CellContainerElement].cell-responsive td {\n                display: block!important;\n                width: 100%;\n            }\n            [builder-element=CellContainerElement].cell-responsive [builder-element=\"CellElement\"] td {\n                padding-left: 0px!important;\n                padding-right: 0px!important;\n            }\n        }\n        @media (min-width: 600px) {\n            [builder-element=PricingTableElement] .col-12 {\n                flex-basis: 0;\n                flex-grow: 1;\n            }\n        }\n    </style>\n  <script>/*\n * This entire block is wrapped in an IIFE to prevent polluting the scope of the web page with\n * functions created by this extension.\n */\n(function(realOpen, realSend, realFetch) {\n    /*\n     * Sometimes pages overwrite standard objects with global\n     * variables. Where possible, we extract fresh versions from an\n     * iframe.\n     */\n    const iframe = document.createElement(\'iframe\');\n    document.documentElement.appendChild(iframe);\n\n    // Initialise with global values\n    let URL = window.URL;\n    let WeakMap = window.WeakMap;\n    let WeakSet = window.WeakSet;\n    let TextDecoder = window.TextDecoder;\n    let Uint8Array = window.Uint8Array;\n    let decodeURIComponent = window.decodeURIComponent;\n    let URLSearchParams = window.URLSearchParams;\n    let Array = window.Array;\n    let Object = window.Object;\n    let atob = window.atob;\n    let CustomEvent = window.CustomEvent;\n    let performance = window.performance;\n    let JSON = window.JSON;\n\n    /*\n     * Under some circumstances the iframe isn\'t added\n     * successfully. If it has a contentWindow then we can extract the\n     * values from it, but otherwise we fall back on the global\n     * values.\n     */\n    if (iframe.contentWindow) {\n        // Try block because sometimes the CORS prevents access to iframe\n        try {\n            URL = iframe.contentWindow.URL;\n            WeakMap = iframe.contentWindow.WeakMap;\n            WeakSet = iframe.contentWindow.WeakSet;\n            TextDecoder = iframe.contentWindow.TextDecoder;\n            Uint8Array = iframe.contentWindow.Uint8Array;\n            decodeURIComponent = iframe.contentWindow.decodeURIComponent;\n            URLSearchParams = iframe.contentWindow.URLSearchParams;\n            Array = iframe.contentWindow.Array;\n            Object = iframe.contentWindow.Object;\n            atob = iframe.contentWindow.atob;\n            CustomEvent = iframe.contentWindow.CustomEvent;\n            performance = iframe.contentWindow.performance;\n            JSON = iframe.contentWindow.JSON;\n        } catch (error) {\n            // Empty catch block to prevent errors from showing in page logs\n        }\n    }\n\n    // If iframe was added successfully, remove it\n    if (iframe.parentNode) {\n        iframe.parentNode.removeChild(iframe);\n    }\n\n    /*\n     * If any of the values don\'t have the correct type, return and\n     * don\'t attempt any monkey patching. This avoids breaking web\n     * pages.\n     */\n    for (f of [\n        URL,\n        WeakMap,\n        WeakSet,\n        TextDecoder,\n        Uint8Array,\n        decodeURIComponent,\n        URLSearchParams,\n        Array,\n        Object,\n        atob,\n        CustomEvent,\n    ]) {\n        if (typeof f !== \'function\') {\n            return;\n        }\n    }\n\n    for (o of [performance, JSON]) {\n        if (typeof o !== \'object\') {\n            return;\n        }\n    }\n\n    /*\n     * Override common funcions that accept callbacks, such as\n     * setInterval, to keep track of what script each callback comes\n     * from. Skimmers commonly have the actual skimming code in a\n     * callback, so most of the time when we intercept a request,\n     * document.currentScript is null. To solve this, the script is\n     * stored in a closure when the callback is passed, and assigned\n     * to callbackCurrentscript when the callback is run.\n     */\n\n    let callbackCurrentScript = null;\n\n    // Maps callbacks originally passed to patched versions\n    const patchedCallbackMap = new WeakMap();\n\n    // Maps patched callbacks to original versions\n    const originalCallbackMap = new WeakMap();\n\n    /**\n     *  Generates the function with which to override the\n     *  callback-accepting functions.\n     *  @param realFn The real function, e.g. setInterval.\n     *  @param callbackIndex The index of the callback in the\n     *  arguments of realFn. For example, the callback is the first\n     *  argument in setInterval, so callbackIndex should be 0.\n     */\n    function generateAddCallbackFunction(realFn, callbackIndex) {\n        const patched = function(...args) {\n            const script = document.currentScript || callbackCurrentScript;\n\n            const callback = args[callbackIndex];\n\n            // If the callback isn\'t a function, there\'s nothing to patch\n            if (typeof callback !== \'function\') {\n                return realFn.apply(this, args);\n            }\n\n            // If a patch exists, use it instead of creating new function\n            let patchedCallback = patchedCallbackMap.get(callback);\n            if (!patchedCallback) {\n                patchedCallback = function() {\n                    callbackCurrentScript = script;\n                    const retVal = callback.apply(this, [...arguments]);\n                    callbackCurrentScript = null;\n                    return retVal;\n                };\n                patchedCallbackMap.set(callback, patchedCallback);\n                originalCallbackMap.set(patchedCallback, callback);\n            }\n\n            args[callbackIndex] = patchedCallback;\n            return realFn.apply(this, args);\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    const realSetInterval = window.setInterval;\n\n    window.setInterval = generateAddCallbackFunction(realSetInterval, 0);\n\n    const realSetTimeout = window.setTimeout;\n\n    window.setTimeout = generateAddCallbackFunction(realSetTimeout, 0);\n\n    const realPromiseThen = Promise.prototype.then;\n    Promise.prototype.then = generateAddCallbackFunction(realPromiseThen, 0);\n\n    const realPromiseCatch = Promise.prototype.catch;\n    Promise.prototype.catch = generateAddCallbackFunction(realPromiseCatch, 0);\n\n    const realPromiseFinally = Promise.prototype.finally;\n    if (realPromiseFinally) {\n        Promise.prototype.finally = generateAddCallbackFunction(\n            realPromiseFinally,\n            0\n        );\n    }\n\n    const realAddEventListener = EventTarget.prototype.addEventListener;\n\n    EventTarget.prototype.addEventListener = generateAddCallbackFunction(\n        realAddEventListener,\n        1\n    );\n\n    /*\n     * removeEventListener needs to be patched to map the passed\n     * listener to the patched listener.\n     */\n    const realRemoveEventListener = EventTarget.prototype.removeEventListener;\n    EventTarget.prototype.removeEventListener = function(...args) {\n        args[1] = patchedCallbackMap.get(args[1]) || args[1];\n\n        return realRemoveEventListener.apply(this, args);\n    };\n    EventTarget.prototype.removeEventListener.toString = () =>\n        realRemoveEventListener.toString();\n\n    /*\n     * Event listeners are sometimes set by directly assigning to a\n     * property from the GlobalEventHandlers mixin,\n     * e.g. \'submitBtn.onclick\'. For each of the GlobalEventHandlers\n     * implementations that are likely to be relevant, the event\n     * properties likely to be used by skimmers are overridden with\n     * our version that tracks currentScript when assigned.\n     */\n    for (const eventTarget of [\n        HTMLElement.prototype,\n        Document.prototype,\n        window,\n    ]) {\n        for (const eventName of [\n            \'onclick\',\n            \'onblur\',\n            \'onfocus\',\n            \'onchange\',\n            \'oninput\',\n            \'onkeydown\',\n            \'onkeypress\',\n            \'onkeyup\',\n            \'onload\',\n            \'onloadend\',\n            \'onmousedown\',\n            \'onmouseenter\',\n            \'onmouseleave\',\n            \'onmousemove\',\n            \'onmouseout\',\n            \'onmouseover\',\n            \'onmouseup\',\n            \'onpointerdown\',\n            \'onpointermove\',\n            \'onpointerup\',\n            \'onpointerover\',\n            \'onpointerout\',\n            \'onpointerenter\',\n            \'onpointerleave\',\n            \'onsubmit\',\n        ]) {\n            const descriptor = Object.getOwnPropertyDescriptor(\n                eventTarget,\n                eventName\n            );\n\n            if (!descriptor) {\n                continue;\n            }\n\n            const realGetter = descriptor.get;\n            const realSetter = descriptor.set;\n\n            const patchedSetter = generateAddCallbackFunction(realSetter, 0);\n\n            /*\n             * In case a script checks the value of the property after\n             * setting it, the getter has to be patched to return the\n             * original callback.\n             */\n            const patchedGetter = function() {\n                const realValue = realGetter.call(this);\n\n                const originalCallback = originalCallbackMap.get(realValue);\n                if (originalCallback) {\n                    return originalCallback;\n                } else {\n                    return realValue;\n                }\n            };\n\n            patchedGetter.toString = () => realGetter.toString();\n\n            Object.defineProperty(eventTarget, eventName, {\n                get: patchedGetter,\n                set: patchedSetter,\n            });\n        }\n    }\n\n    /*\n     * This script has some dependencies that need to be included\n     * without polluting the global namespace. This is achieved by\n     * inserting them into the source at build time in the\n     * makefile. This is preferable to loading the dependencies via\n     * AJAX, because the latter doesn\'t work if the CSP disallows\n     * unsafe-eval. The following tag tells the makefile where to\n     * insert the dependencies.\n     */\n    // <dependencies>\nclass InputAnalyser {\n    constructor(_inputs) {\n        this._inputs = _inputs;\n        this._irrelevantInputs = new WeakSet();\n\n        /*\n         * Not currently using Password because it was causing too\n         * many false positives. Some sites use external auth domains,\n         * and many have misconfigured analytics that collect\n         * passwords.\n         */\n        this.relevantInputs = [new CardNumber()];\n    }\n\n    get relevantInputValues() {\n        const inputs = {};\n        this._inputs.forEach(\n            input => (inputs[input.id || input.name] = input.value)\n        );\n\n        return inputs;\n    }\n\n    get relevantFilledInputs() {\n        return this.allInputs\n            .filter(input => this.isRelevantElement(input))\n            .filter(input => input.value.length >= cons.minInputSize)\n            .map(input => ({\n                id: input.id,\n                name: input.name,\n                value: input.value,\n            }));\n    }\n\n    get allInputs() {\n        this._inputs = [\n            ...document.querySelectorAll(\n                this.relevantInputs.map(i => i.relevantInputTypes).join(\', \')\n            ),\n        ];\n\n        return this._inputs;\n    }\n\n    static getLabelText(input) {\n        if (!input.labels.length) {\n            return \'\';\n        }\n        return stripSpecialChars(input.labels[0].textContent).trim();\n    }\n\n    /**\n     * Takes an HTMLElement and determines whether or not it could be relevant for the purposes of\n     * skimmer detection.\n     * @param {HTMLElement} input The element which is to be checked for relevancy.\n     * @returns {boolean} Returns true if the element is potentially relevant (e.g. a credit card\n     * field or other such sensitive input).\n     */\n    isRelevantElement(input) {\n        // If the element has been identified as irrelevant, return false\n        if (this._irrelevantInputs.has(input)) {\n            return false;\n        }\n\n        // Check the input against all the relevant input types\n        const relevances = this.relevantInputs.map(i => i.test(input));\n\n        /*\n         *  If they all agree that this input is unlikely to become\n         *  relevant, exclude it from further checks and return false\n         */\n        if (relevances.every(i => i === RelevantInput.relevance.NOT_RELEVANT)) {\n            this._irrelevantInputs.add(input);\n            return false;\n        }\n\n        // Return true if any consider the input to be relevant, otherwise false\n        return relevances.some(i => i === RelevantInput.relevance.RELEVANT);\n    }\n}\n/*\n * Input Matcher represents a class that can have matches with relevant inputs.\n * Each class that inherits must implement the findInputMatches method.\n */\nclass InputMatcher {\n    /*\n     * Finds any matches with the list of inputs.\n     * @param inputs. An object whose values represent relevant inputs.\n     * @return {[String]} The keys of the inputs that were found.\n     */\n    findInputMatches(inputs) {\n        throw new Error(\'findInputMatches has not been implemented.\');\n    }\n\n    /**\n     * Determines whether one or more values in needles are present in any of\n     * the keys of the haystacks.\n     * Checks both raw input and base64 encoded versions of the needles.\n     * @param needles An object. The haystacks are searched for the values of\n     * this object.\n     * @param haystacks The array of strings in which the needle(s) may reside.\n     * A string may optionally be supplied if only one value is present.\n     * @returns {[String]} The keys of the needles that were found in one or\n     * more haystacks in either raw or base64 encoded form.\n     */\n    static atLeastOneNeedleInHaystack(needles, haystacks) {\n        if (\n            needles === undefined ||\n            needles.length === 0 ||\n            haystacks === undefined\n        ) {\n            return [];\n        }\n\n        const searchArray = Array.isArray(haystacks) ? haystacks : [haystacks];\n\n        return Object.entries(needles)\n            .filter(([_, needle]) =>\n                searchArray.some(haystack => {\n                    if (isBase64Encoded(haystack)) {\n                        if (atob(haystack).indexOf(needle) !== -1) {\n                            return true;\n                        }\n                    }\n                    return haystack.indexOf(needle) !== -1;\n                })\n            )\n            .map(([input, _]) => input);\n    }\n}\n/*\n * Matcher for an object that cannot contain any inputs.\n */\nclass EmptyMatcher extends InputMatcher {\n    findInputMatches(inputs) {\n        return [];\n    }\n}\n\nclass URLParamsMatcher extends InputMatcher {\n    constructor(url) {\n        super();\n        this._url = url;\n    }\n\n    findInputMatches(inputs) {\n        try {\n            const parsedUrl = new URL(this._url);\n            return InputMatcher.atLeastOneNeedleInHaystack(inputs, [\n                ...parsedUrl.searchParams.values(),\n            ]);\n        } catch (e) {\n            return [];\n        }\n    }\n}\n\nclass StringBodyMatcher extends InputMatcher {\n    constructor(str) {\n        super();\n        this._str = str;\n    }\n\n    findInputMatches(inputs) {\n        let decoded;\n        try {\n            decoded = decodeURIComponent(this._str);\n        } catch (e) {\n            decoded = this._str;\n        }\n\n        const searchParams = new URLSearchParams(decoded);\n        const parsedQuery = {};\n        const parsedValues = [];\n\n        for (const [key, value] of searchParams.entries()) {\n            parsedQuery[key] = value;\n            parsedValues.push(value);\n        }\n\n        if (parsedQuery !== {} && parsedValues.indexOf(null) === -1) {\n            return InputMatcher.atLeastOneNeedleInHaystack(\n                inputs,\n                parsedValues\n            );\n        }\n        return InputMatcher.atLeastOneNeedleInHaystack(inputs, decoded);\n    }\n}\n\nclass FormBodyMatcher extends InputMatcher {\n    constructor(formData) {\n        super();\n        this._formData = formData;\n    }\n\n    findInputMatches(inputs) {\n        return InputMatcher.atLeastOneNeedleInHaystack(\n            inputs,\n            Object.values(this._formData).flat()\n        );\n    }\n}\n/**\n * Takes a string and removes a variety of \'special\' characters like punctuation.\n * @param str The string to be modified.\n * @returns {string} The resulting string without any special characters.\n */\nfunction stripSpecialChars(str) {\n    return str.replace(/[`~!@#$%^&*()_|+\\-=?;:\'\",.<>}{\\][\\\\/]/gi, \'\');\n}\n\n/**\n * Attempts to identify where a string is base-64 encoded or not.\n * @param string The raw string to be examined.\n * @returns {boolean} Returns true if the string matches a base-64 string. False otherwise.\n */\nfunction isBase64Encoded(string) {\n    return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(\n        string\n    );\n}\n\n// Computes the base64 sha512/sha224 hash of some text, removing trailing ==\nfunction base64hash(hashFunction, text) {\n    return btoa(\n        hashFunction\n            .array(text)\n            .map(char => String.fromCharCode(char))\n            .join(\'\')\n    ).slice(0, -2);\n}\n\nfunction normaliseUrl(str) {\n    const link = document.createElement(\'a\');\n    link.href = str;\n    return link.href;\n}\n\nfunction stripCredentials(urlstr) {\n    try {\n        const url = new URL(urlstr);\n        const params = url.searchParams;\n        params.forEach((_, key) => params.set(key, \'\'));\n        return url.toString();\n    } catch (e) {\n        return urlstr;\n    }\n}\n\n/**\n * Check if two URLs match regardless of any partial encoding.\n *\n * Uses decodeURI which can throw a URIError when url is malformed.\n */\nfunction urlsMatch(first, second) {\n    try {\n        return decodeURI(first) === decodeURI(second);\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction sendMessage(type, data, tabId, options) {\n    if (tabId) {\n        chrome.tabs.sendMessage(tabId, { data, type }, options);\n    } else {\n        chrome.runtime.sendMessage({ data, type });\n    }\n}\n\n// Encodes the given input using base64url as defined by RFC4648 section 5.\nfunction base64Url(input) {\n    return btoa(input)\n        .replace(\'+\', \'-\')\n        .replace(\'/\', \'_\');\n}\n/*\n * Netcraft Extension\n * constants JavaScript\n */\n\n/* eslint sort-keys: \'off\' */\n/* sort-keys is disabled as the constants are separated by type */\n\n// Aligned values here make for easier reading, so we disable prettier\n\n// prettier-ignore\nconst cons = (function() {\n    const cryptojackersUrl = \'https://www.netcraft.com/apps/help/glossary/#crypto-miners\';\n    const skimmersUrl = \'https://www.netcraft.com/apps/help/glossary/#credit-card-skimmers\';\n    const evilResourceCrime = \'attempted to load\';\n\n    return {\n        fields: [\n            \'topsites\',\n            \'country\',\n            \'netblock\',\n            \'hoster\',\n            \'firstseen\',\n            \'rank\',\n            \'risk\',\n            \'uses_sslv3\',\n            \'pfs\',\n            \'heartbleed\',\n            \'heartbleed_message\',\n        ],\n\n        // options that can be toggle in options.js and installed flag\n\n        toggleOptions: [\n            \'installed\',\n            \'block\',\n            \'xss\',\n            \'suspicious\',\n            \'other-malicious-scripts\',\n            \'credential-leaks\',\n            \'analytics\',\n            \'cryptojackers\',\n            \'skimmers\',\n        ],\n\n        // domains\n        mirror:  \'https://mirror2.extension.netcraft.com/\',\n        toolbar: \'https://mirror.toolbar.netcraft.com/\',\n\n        // links and faqs\n        blogArchive:      \'https://news.netcraft.com/archives/\',\n        browserApp:       \'https://www.netcraft.com/apps/browser/\',\n        cryptojackersUrl,\n        extensionStore:   \'https://addons.mozilla.org/en-us/firefox/addon/netcraft-toolbar?src=external-blocked_visit-rating\',\n        heartbleedUrl:    \'https://www.netcraft.com/apps/help/glossary/#heartbleed\',\n        logBlockedVisit:  \'https://toolbar.netcraft.com/blocked_visit\',\n        netblock:         \'https://sitereport.netcraft.com/netblock?q=\',\n        pfsUrl:           \'https://www.netcraft.com/apps/help/glossary/#pfs\',\n        report:           \'https://sitereport.netcraft.com/?url=\',\n        reportAPI:        \'https://report.netcraft.com/api/v2/report/urls\',\n        reportMistake:    \'https://report.netcraft.com/report/mistake?url=\',\n        skimmersUrl,\n        sslv3Url:         \'https://www.netcraft.com/apps/help/glossary/#sslv3\',\n        submissionURL:    \'https://report.netcraft.com/submission/\',\n        topsites:         \'https://trends.netcraft.com/topsites?s=\',\n\n        // paths\n        block:              \'blocked.html\',\n        check:              \'check_url/v4/\',\n        danger:             \'Images/danger.png\',\n        embed:              \'/embedded\',\n        evilJSPatternsFeed: \'eviljspatterns\',\n        skimmerDomainWhitelistFeed: \'eviljsskimmerwhitelist\',\n        evilResourcesFeed:  \'blockdb/netcraft_extension_evil_resource_encrypted-yppL7bVh/\',\n        flags:              \'Images/flags/\',\n        force:              \'/dodns\',\n        heartbleed:         \'Images/heartbleed.png\',\n        info:               \'/info\',\n        poodlePost:         \'2014/10/15/googles-poodle-affects-oodles.html\',\n        warning:            \'Images/warning.png\',\n\n        // feed constants\n        evilResourceTypes: [\'cryptojacker\', \'skimmer\'],\n        evilResourcesSalt: \'C4jhYHxW\',\n        maxRevisions:      5,\n        randomSaltLength:  8,\n\n        // mappings of url_type => information\n        urlTypeInformationMappings: {\n            \'malicious_javascript/cryptojacker\': [\'web miners\',             cryptojackersUrl],\n            \'malicious_javascript/skimmer\':      [\'shopping site skimmers\', skimmersUrl],\n        },\n\n        // mappings of evilResourceTypes => user options\n        evilResourceOptionMappings: {\n            \'cryptojacker\': \'cryptojackers\',\n            \'skimmer\':      \'skimmers\'\n        },\n\n        // mappings of user options => url_type\n        optionUrlTypeMappings: {\n            \'block\':                   /^(?!malicious_javascript:)/,\n            \'cryptojackers\':           /^malicious_javascript\\/cryptojacker$/,\n            \'other-malicious-scripts\': /^malicious_javascript\\/(?!(skimmer|cryptojacker)$)/,\n            \'skimmers\':                /^malicious_javascript\\/skimmer$/,\n        },\n\n        // report constants\n        malwareSourceEmail:  \'extension-reported-malware@netcraft.com\',\n        malwareSourceUUID:   \'mwcCcApi89cobY9loJ9rWJ9HuOhlpwMj\',\n        extensionSourceUUID: \'qBEQ3NhEnwK4sypmuSxBJzRqpxZLRXxe\',\n\n        // report error default message\n        reportErrorMessage: \'An error occured with your submission. Please try again.\',\n\n        // mapping from report API errors to displayed messages\n        reportErrorMappings: {\n            \'Duplicate of a recent submission\': \'This URL has already been reported.\',\n            \'Supplied email is not valid\': \'Invalid email address. Please correct it and try again.\',\n        },\n\n        whitelistErrorMessages: {\n            invalidHostname: \'Invalid hostname, please correct it and try again\',\n            duplicate: \'Protection is already disabled for this site\',\n        },\n\n        // browserAction icon paths\n        iconNormal: {\n            16: \'Images/action-normal-16.png\',\n            24: \'Images/action-normal-24.png\',\n            32: \'Images/action-normal-32.png\'\n        },\n        iconDanger: {\n            16: \'Images/action-danger-16.png\',\n            24: \'Images/action-danger-24.png\',\n            32: \'Images/action-danger-32.png\'\n        },\n        iconWarning: {\n            16: \'Images/action-warning-16.png\',\n            24: \'Images/action-warning-24.png\',\n            32: \'Images/action-warning-32.png\'\n        },\n        iconDisabled: {\n            16: \'Images/action-disabled-16.png\',\n            24: \'Images/action-disabled-24.png\',\n            32: \'Images/action-disabled-32.png\'\n        },\n\n        // Extension internal message types\n        capturedIntercept:  \'interceptDetailsCaptured\',\n        confirmedIntercept: \'interceptDetailsConfirmed\',\n        contentReady:       \'contentScriptReady\',\n        leaksEnabled:       \'credentialLeaksEnabled\',\n        reportURL:          \'report\',\n        reqBlock:           \'block\',\n        reqDetails:         \'details\',\n        reqForce:           \'force\',\n        reqPopUp:           \'popup\',\n        reqPrintTimingData: \'time\', //debug\n        reqInputs:          \'requestInputValues\',\n        sendInputs:         \'sendInputs\',\n        sendIntercept:      \'sendInterceptDetails\',\n        updateOption:       \'updateOption\',\n        updateWhitelist:    \'whitelist\', // disable button whitelist\n\n        // Status codes\n        errEvilResource: \'EVILRESOURCE\',\n        errNoDetails:    \'NODETAILS\',\n        errNoInfo:       \'NOINFO\',\n        errBlocked:      \'BLOCKED\',\n        errPending:      \'PENDING\',\n        errServer:       \'ERROR\',\n        errSuspicious:   \'SUSPICIOUS\',\n        errTimeout:      \'TIMEOUT\',\n        errXSS:          \'XSS\',\n        noError:         \'OK\',\n\n        // Valid protocols (it is a regex match so ^ matches start of string)\n        protocols:    [\'^http\', \'^https\', \'^ftp\'],\n\n        // Human readable messages\n        msgNa:           \'NA\',\n        msgNewSite:      \'New Site\',\n        msgNoInfo:       \'Site information not available\',\n        msgNoServer:     \'Unable to contact Netcraft servers\',\n        msgSuspicious:   \'Suspicious URL Detected\',\n        msgXSS:          \'Suspected XSS Attack\',\n        msgBlocked:      {\n            \'malicious_javascript/skimmer\': {\n                title: \'Shopping Site Skimmer Detected\',\n                description: \'shopping site skimmer\'\n            },\n            \'malicious_javascript/dropsite\': {\n                title: \'Potential Credential Leak Detected\',\n                description: \'potential credential leak\'\n            },\n            \'malicious_javascript/cryptojacker\': {\n                title: \'Web Miner Detected\',\n                description: \'web miner\'\n            },\n            \'malware_c2/infrastructure\': {\n                title: \'Malicious URL\',\n                description: \'malicious page\'\n            },\n            \'default\': {\n                title: \'Suspected Phishing\',\n                description: \'suspected phishing site\'\n            },\n        },\n\n        // Human readable reasons\n        urlTypeCrimeMappings: {\n            \'malicious_javascript/skimmer\':      evilResourceCrime,\n            \'malicious_javascript/cryptojacker\': evilResourceCrime,\n            \'malicious_javascript/dropsite\':     \' is blocked because we detected that sensitive information was leaked from\',\n        },\n\n        // Timeout function value to clear cache etc.\n        timeout:      300000,\n\n        // Skimmer whitelist update interval (1 day)\n        skimmerWhitelistInterval: 1000 * 60 * 60 * 24,\n\n        // Google Analytics account\n        account:      \'UA-2150242-8\',\n        gaCategory:   \'Popup load times\',\n\n        // InputAnalyser constants\n        minInputSize: 5,\n\n        // First addresses of the private IPv4 netblocks\n        privateIPv4Blocks: [\n            \'10.0.0.0\',\n            \'172.16.0.0\',\n            \'192.168.0.0\'\n        ],\n\n        // Sync storage key for disable for this site button whitelist\n        disableButtonWhitelistKey: \'disableButtonWhitelist\',\n\n        // Local storage key for local credential leak detection whitelist\n        localCredentialLeakWhitelistKey: \'localCredentialLeakWhitelist\',\n\n        // Whitelisted fileName regexes for credential leak detection\n        credentialLeakFileNameWhitelist: [\n            /^https?:\\/\\/googleads.g.doubleclick.net\\//,\n        ],\n    };\n})();\n\n/*\n * recursively prevents any property or sub-property in cons from:\n *   - being added;\n *   - being removed; or\n *   - being modified.\n */\nfunction deepFreeze(object) {\n    Object.freeze(object);\n\n    for (const name of Object.getOwnPropertyNames(object)) {\n        if (typeof object[name] === \'object\') {\n            deepFreeze(object[name]);\n        }\n    }\n}\n\ndeepFreeze(cons);\n/*\n * RelevantInput objects check whether given form inputs should be\n * treated as sensitive data.\n *\n * This should be treated as an abstract class. Only the subclasses\n * are intended to be instantiated.\n */\nclass RelevantInput {\n    /*\n     * Enum of the possible return values of test():\n     * RELEVANT:\n     *     Input is relevant now, outgoing requests should be\n     *     checked for the contained value (subject to minimum length).\n     * POTENTIALLY RELEVANT:\n     *     Input should not be considered relevant now, but may be in\n     *     future so should continue to be checked.\n     * NOT_RELEVANT:\n     *     Input is not relevant and will not become relevant, so can\n     *     be ommitted from further checks to optimise performance.\n     */\n    static get relevance() {\n        return {\n            NOT_RELEVANT: 1,\n            POTENTIALLY_RELEVANT: 2,\n            RELEVANT: 3,\n        };\n    }\n\n    /*\n     * relevantInputTypes (string) - a comma separated list of\n     * selectors that describe the types of input that should be\n     * considered relevant.\n     */\n    constructor(relevantInputTypes) {\n        this.relevantInputTypes = relevantInputTypes;\n    }\n\n    /*\n     * Checks whether any of the given input\'s \'labels\' (the ID, name,\n     * or actual label) match the given regex.\n     */\n    static testInputLabel(regex, input) {\n        const labels = [\n            input.id,\n            input.name,\n            InputAnalyser.getLabelText(input),\n        ];\n        return labels.some(label => regex.test(label));\n    }\n}\n\n/*\n * RelevantInput for passwords.\n */\nclass Password extends RelevantInput {\n    constructor() {\n        super(\'input[type=password]\');\n    }\n\n    /*\n     * Passwords are verified purely by their input type.\n     */\n    test(input) {\n        if (input.matches(this.relevantInputTypes)) {\n            return RelevantInput.relevance.RELEVANT;\n        } else {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n    }\n}\n\n/*\n * RelevantInput for card numbers.\n */\nclass CardNumber extends RelevantInput {\n    constructor() {\n        // Inputs with no type attribute act like text inputs\n        super(\n            \'input[type=text], input[type=tel], input[type=number], input:not([type])\'\n        );\n\n        // Definitely card number labels\n        this.inputLabelRegexes = [\n            /(cc|card).?(num|no)/i,\n            /&#12459;&#12540;&#12489;&#30058;&#21495;/,\n            /&#1053;&#1086;&#1084;&#1077;&#1088;.*&#1082;&#1072;&#1088;&#1090;&#1099;/i,\n            /&#20449;&#29992;&#21345;&#21345;&#34399;/i,\n            /&#20449;&#29992;&#21345;&#21495;&#30721;/i,\n        ];\n\n        /*\n         * Matches incomplete card numbers, because ideally the\n         * skimmer should be detected before the victim gives away\n         * most of their card number.\n         */\n        this.inputValueRegexes = [/^(\\d{4}[-\\s]?){0,4}\\d{1,4}$/];\n\n        // Probably card number labels\n        this.suspectInputLabelRegexes = [\n            /card/i,\n            /tarjeta/i,\n            /carte/i,\n            /&#1082;&#1072;&#1088;&#1090;&#1099;/i,\n            /&#21345;/i,\n            /&#12459;&#12540;&#12489;/i,\n            /&#52852;&#46300;/i,\n            /credit[^s]/i,\n        ];\n\n        // If it matches one of these, probably not a card number\n        this.inputLabelExceptionRegexes = [\n            /post/i,\n            /code/i,\n            /zip/i,\n            /acc(oun)?t?[-_]?n(um|o)/i,\n            /credit[-_]?(sum|limit)/i,\n            /loyalty/i,\n            /gift/i,\n            /idcard/i,\n            /cardid/i,\n            /club/i,\n            /sku/i,\n            /(\\b|_)otp/i,\n            /price/i,\n            /amount/i,\n            /service/i,\n            /picard/i,\n            /search/i,\n            /contact/i,\n            /callme/i,\n            /chat/i,\n            /activate/i,\n            /msisdn/i,\n        ];\n    }\n\n    test(input) {\n        if (!input.matches(this.relevantInputTypes)) {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n\n        // Return NOT_RELEVANT if label matches an exception regex\n        if (\n            this.inputLabelExceptionRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n\n        // Return RELEVANT if label clearly states card number\n        if (\n            this.inputLabelRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            return RelevantInput.relevance.RELEVANT;\n        }\n\n        /*\n         * If label suggests card number, return RELEVANT if value\n         * also suggests card number or POTENTIALLY_RELEVANT if\n         * not. Otherwise, return NOT_RELEVANT as this element will\n         * not become relevant unless a label changes (unlikely).\n         */\n        if (\n            this.suspectInputLabelRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            if (this.inputValueRegexes.some(r => r.test(input.value))) {\n                return RelevantInput.relevance.RELEVANT;\n            } else {\n                return RelevantInput.relevance.POTENTIALLY_RELEVANT;\n            }\n        } else {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n    }\n}\n\n    const inputAnalyser = new InputAnalyser();\n\n    const resourceBlockList = new Set([\n                            \"6C590F97DDB1A11AE795B3050E4C3866\", \"D285BE79786E2ECE396941331C5BA65E\", \"59DF8523520DE7F08FB53D3D12BD94F5\", \"1FB784EBAFDA1618023756BF5B6D7E46\", \"0D6E1B5F03B81F5C37813B22E29C4FE2\", \"EFAB0ECE9383D50C555B8168C3E2A4DD\", \"AC8A4B13A411A4E43E61D0A034FA4A55\", \"25A781F6D1F94EEACCF1E00E844D9E14\", \"3920B04ED0D215EA738F524F60485F23\", \"05F4A2D3161A92EB82A44F15FC2A3AEC\", \"C5A1BE7B3855B0A25275B2877E005CB9\", \"00069CEC347076721EBE2257978A0A33\", \"9A513130D02F360BBDC16D8AC0C579D8\", \"7CDB2A3A29E50F90C1000252EF523AD6\", \"25CE912FAF2376026AAD0174AD5A0329\", \"739E1F67AA5F64A06FC469F6030802D4\", \"F49368C9A05C29EF49411999C5987F2C\", \"AB2DD89FB892155275D8780CF527BC93\", \"CFE758EB18FBFE70B1F690241B75D2B1\", \"E43C3463B40A790545F4149FF06B419E\", \"8B6D4C76A7CCF5EB720660B8B2D3F5EB\", \"45573B3E2F613AF5BF2145DF004BABD1\", \"E3DB4E968B371DE94CE607199A062F39\", \"A4EFF1240C5DE614B394FB738CBD28E2\", \"F42D10E84B9A69A8A24062319F425A83\", \"90646BE39982594A58EDF5D2630CCFFD\", \"669853BEA9B84EA51D7302A7FBFF5BC3\", \"0BA566B4777526F451108100107DC5ED\", \"AB19E12BFFBC47244BDCC2403EAA9037\", \"61DAF8537240639277FD1A5908A131A4\", \"6F1DC1309CFE734C038685D8F17A3B6B\", \"DCC3DB001334F29C1C0208D15E9EAB49\", \"2DBD7D54022C28273ADE3147B8EEB076\", \"AE3760E93412C65EC5BF63537B037019\", \"4CD96A830DAC0E0B5EBE578B0DD870AE\", \"4C70569BBE1821F5655382660807FA77\", \"05ECA17FF0EA78669ED43B2BD7B63571\", \"44E503B3621513B9D3666F284E4DF47A\", \"8F1C92E7665860AE70CBB228DED86111\", \"D69552885B55C4097E5725F805815639\", \"B4357A9D889E974CDF3F9740E48F9A1B\", \"5709DC60CFC365B7064B06C709A9D32B\", \"14E8729CBB16AC3625EAA0D7A679E56E\", \"793D4DABAAA377A12F2B6CE88C5FB389\", \"C5D1DCE2AE0D8CE79054FC16CB13D65A\", \"89F91750BD4A9397D1F8CE183757FF70\", \"50B8BC213FB188A687173C1A0A77EA57\"\n                        ]);\n    const evilJSPatterns = new Set([\n                            \n                        ]);\n\n    // Extract body object\n    function extractBody(data) {\n        if (!data) {\n            return Promise.resolve({});\n        }\n        if (FormData.prototype.isPrototypeOf(data)) {\n            const formData = {};\n            for (const [key, value] of data.entries()) {\n                formData[key] = value;\n            }\n            return Promise.resolve({ formData });\n        }\n        // Create a temporary Request object to parse the body\n        const tempRequest = new Request(\'\', { body: data, method: \'POST\' });\n        return extractBodyFromRequest(tempRequest);\n    }\n\n    function extractBodyFromRequest(request) {\n        // Try to extract as text\n        return request\n            .text()\n            .then(text => {\n                return { text };\n            })\n            .catch(e => {\n                return {};\n            });\n    }\n\n    /**\n     * This function checks if the request hostname/URL is in the blocklist.\n     * Used when identifying whether or not to forward request to background\n     * script.\n     * @param hostname Hostname to check against blocklist.\n     * @param url Request url to check against evil JS patterns\n     * @returns boolean true if hostname is in the blocklist, false if\n     * it is not in blocklist or if blocklist is not provided.\n     */\n    function requestInBlocklist(hostname, url) {\n        if (resourceBlockList === undefined || evilJSPatterns === undefined) {\n            return false;\n        }\n        if (testEvilJS(url.replace(/^(https?:)?\\/\\//i, \'\'))) {\n            return true;\n        }\n        while (!testEvilJS(hostname) && !testResource(hostname)) {\n            const newHostname = hostname.replace(/^.+?\\./g, \'\');\n\n            if (hostname === newHostname) return false;\n\n            hostname = newHostname;\n        }\n        return true;\n    }\n\n    function testEvilJS(url) {\n        return evilJSPatterns.has(base64hash(sha512, url));\n    }\n\n    function testResource(hostname) {\n        return resourceBlockList.has(\n            md5(cons.evilResourcesSalt + hostname).toUpperCase()\n        );\n    }\n\n    /*\n     * Implementations to overwrite window.XMLHttpRequest.prototype.open,\n     * window.XMLHttpRequest.prototype.send and window.fetch. These are (to my knowledge) the two\n     * APIs that can be used to make requests within Javascript. If there are more, then they will\n     * also need to be overridden and intercepted like below.\n     */\n\n    /**\n     * This function generates functions to be used for wrapping and intercepting network\n     * requests, namely from XMLHttpRequest and the Fetch API. It works by taking the real\n     * function we want to intercept the request for, and then uses document.currentScript to work\n     * out which file initiated the request. It sends those details to the content script which\n     * forwards it to the background script, and then we await a confirmation of receipt from the\n     * background page. Finally, the real request is sent by forwarding parameters to the original\n     * function passed in.\n     * @param realFn A reference to the real function that is being intercepted.\n     * @param getRequestUrlFn A function used to get the request URL from the object. This is\n     * necessary as the mechanisms for different objects differs depending on implementation.\n     * @param getRequestBodyPr A function used to get the request body from the object. Returns\n     * a promise. It should be null if it\'s not applicable (e.g. if it sets the src of an image or script).\n     * @param type The type of request, which will be a ResourceType:\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType\n     * @returns {function(...[*]=): PromiseLike<any | never>}\n     */\n    function generateRequestInterceptFn(\n        realFn,\n        getRequestUrlFn,\n        getRequestBodyPr,\n        type\n    ) {\n        const patched = function(...args) {\n            /*\n             * We create a link DOM element in order to correctly generate the\n             * absolute path of the request, automatically transforming from a\n             * relative path if necessary.\n             */\n            const requestUrl = normaliseUrl(\n                getRequestUrlFn.bind(this)(...args)\n            );\n\n            let requestHostname;\n            let pageHostname;\n\n            try {\n                requestHostname = new URL(requestUrl).hostname;\n                pageHostname = new URL(location.href).hostname;\n            } catch (e) {\n                return realFn.apply(this, args);\n            }\n\n            /*\n             * If hostname is in the blocklist, the request should always be\n             * sent to the background script.\n             */\n            // eslint-disable-next-line prefer-const\n            let forceStall = false;\n\n            if (!forceStall) {\n                /*\n                 * Delaying outgoing requests sometimes causes problems\n                 * with elements such as video players and dynamically\n                 * loaded images, so it should only be done when\n                 * necessary. Before inputAnalyser is defined and when the\n                 * request is to the same hostname as the page, this can\n                 * be skipped.\n                 */\n                if (\n                    requestHostname === pageHostname ||\n                    inputAnalyser === undefined\n                ) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * It can also be skipped if there is no sensitive\n                 * information entered into a form on the page yet.\n                 */\n                const relevantFilledInputs = inputAnalyser.relevantFilledInputs;\n                if (!relevantFilledInputs.length) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * For requests with no body, it\'s only necessary to delay\n                 * them when the sensitive information from the form\n                 * inputs is present in the request URL.\n                 */\n                const ia = new InputAnalyser(relevantFilledInputs);\n                const relevantInputValues = ia.relevantInputValues;\n                const urlMatcher = new URLParamsMatcher(requestUrl);\n\n                if (\n                    getRequestBodyPr === null &&\n                    !urlMatcher.findInputMatches(relevantInputValues).length\n                ) {\n                    return realFn.apply(this, args);\n                }\n            }\n\n            /*\n             * Create a promise that resolves immediately if the request body\n             * does not apply to this request.\n             */\n            if (getRequestBodyPr === null) {\n                getRequestBodyPr = function() {\n                    return Promise.resolve({});\n                };\n            }\n\n            let fileName;\n            if (document.currentScript && document.currentScript.src) {\n                fileName = document.currentScript.src;\n            } else if (callbackCurrentScript && callbackCurrentScript.src) {\n                fileName = callbackCurrentScript.src;\n            } else {\n                fileName = location.href;\n            }\n\n            return getRequestBodyPr\n                .bind(this)(...args)\n                .then(requestBody => {\n                    /*\n                     * Generate the information object to send through to the\n                     * content script including the request URL, the initiator\n                     * filename and a timestamp relative to the page load.\n                     */\n                    const detail = {\n                        fileName,\n                        requestBody,\n                        requestUrl,\n                        timestamp: performance.now(),\n                        type,\n                    };\n\n                    document.dispatchEvent(\n                        new CustomEvent(cons.capturedIntercept, {\n                            detail,\n                        })\n                    );\n\n                    return new Promise(resolve => {\n                        /*\n                         * Send the initiator\'s file name and other info to the\n                         * background script, then wait for confirmation that the\n                         * details made it to the background page before calling\n                         * the real function. This is to avoid any race conditions\n                         * whereby the details haven\'t been added to the tab data\n                         * before onBeforeRequest/onBeforeSendHeaders gets called.\n                         */\n                        document.addEventListener(\n                            cons.confirmedIntercept,\n                            function compareData(customEvent) {\n                                const receivedData = JSON.parse(\n                                    customEvent.detail\n                                );\n                                if (detailsMatch(detail, receivedData)) {\n                                    /*\n                                     * If it\'s the correct confirmation, then we\n                                     * can resolve the promise and call the real\n                                     * function in the finally block.\n                                     */\n                                    document.removeEventListener(\n                                        cons.confirmedIntercept,\n                                        compareData,\n                                        false\n                                    );\n                                    resolve();\n                                }\n                            }\n                        );\n                    }).then(() => realFn.apply(this, args));\n                });\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    function detailsMatch(first, second) {\n        const comparisonKeys = [\'fileName\', \'requestUrl\', \'timestamp\', \'type\'];\n        for (const key of comparisonKeys) {\n            if (first[key] !== second[key]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    window.fetch = generateRequestInterceptFn(\n        realFetch,\n        request => (typeof request === \'string\' ? request : request.url),\n        (request, options) => {\n            // Check if argument is a Request object.\n            if (request instanceof Request) {\n                return extractBodyFromRequest(request);\n            }\n            return options ? extractBody(options.body) : Promise.resolve({});\n        },\n        \'xmlhttprequest\'\n    );\n\n    window.XMLHttpRequest.prototype.send = generateRequestInterceptFn(\n        realSend,\n        function getRequestUrl() {\n            return this._requestUrl;\n        },\n        extractBody,\n        \'xmlhttprequest\'\n    );\n\n    /*\n     * This function is intercepted so that we can capture the value of the\n     * request URL, since it isn\'t present in the arguments for\n     * XMLHttpRequest.send (above).\n     */\n    window.XMLHttpRequest.prototype.open = function open(...args) {\n        [, this._requestUrl] = args;\n        realOpen.apply(this, args);\n    };\n    window.XMLHttpRequest.prototype.open.toString = () => realOpen.toString();\n\n    /*\n     * Monkeypatches for setting the src of images and scripts, an\n     * indirect way of sending requests that is often employed by\n     * skimmers. The getters are also overridden to make the behaviour\n     * seem identical to the original implementation. Otherwise, if a\n     * script were to set an src and immediately read it, it would\n     * come back as the old value.\n     */\n\n    /*\n     * WeakMap maps an object to a value. This is used to store the\n     * new src of an object when its true src property may not yet\n     * have been set.\n     */\n    const srcMap = new WeakMap();\n    const requestElements = [\n        {\n            srcElement: HTMLImageElement.prototype,\n            type: \'image\',\n        },\n        {\n            srcElement: HTMLScriptElement.prototype,\n            type: \'script\',\n        },\n    ];\n\n    for (const { srcElement, type } of requestElements) {\n        const realGetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .get;\n        const realSetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .set;\n        const realGetAttribute = srcElement.getAttribute;\n        const realSetAttribute = srcElement.setAttribute;\n        const realRemoveAttribute = srcElement.removeAttribute;\n\n        const interceptFn = generateRequestInterceptFn(\n            realSetSrc,\n            src => `${src}`,\n            null,\n            type\n        );\n\n        function setSrc(url) {\n            /*\n             * It\'s valid to provide a TrustedScriptURL wrapping the URL but we need to\n             * return a string in the getter, so we convert the url to a string here.\n             *\n             * The url can also be undefined/null so we can\'t just call `.toString()`.\n             *\n             * See #56.\n             */\n            srcMap.set(this, `${url}`);\n            interceptFn.call(this, url);\n            return url;\n        }\n\n        function getSrc() {\n            if (srcMap.get(this) === undefined) {\n                return realGetSrc.call(this);\n            } else {\n                return normaliseUrl(srcMap.get(this));\n            }\n        }\n\n        getSrc.toString = () => realGetSrc.toString();\n        setSrc.toString = () => realSetSrc.toString();\n\n        Object.defineProperty(srcElement, \'src\', {\n            get: getSrc,\n            set: setSrc,\n        });\n\n        srcElement.setAttribute = function(attributeName, value) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                setSrc.call(this, value);\n            } else {\n                realSetAttribute.call(this, attributeName, value);\n            }\n        };\n        srcElement.setAttribute.toString = () => realSetAttribute.toString();\n\n        srcElement.getAttribute = function(attributeName) {\n            if (\n                attributeName.toUpperCase() === \'SRC\' &&\n                srcMap.get(this) !== undefined\n            ) {\n                return srcMap.get(this);\n            } else {\n                return realGetAttribute.call(this, attributeName);\n            }\n        };\n        srcElement.getAttribute.toString = () => realGetAttribute.toString();\n\n        srcElement.removeAttribute = function(attributeName) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                srcMap.delete(this);\n            }\n\n            return realRemoveAttribute.call(this, attributeName);\n        };\n        srcElement.removeAttribute.toString = () =>\n            realRemoveAttribute.toString();\n    }\n})(\n    window.XMLHttpRequest.prototype.open,\n    window.XMLHttpRequest.prototype.send,\n    window.fetch\n);\n</script><script>/*\n * This entire block is wrapped in an IIFE to prevent polluting the scope of the web page with\n * functions created by this extension.\n */\n(function(realOpen, realSend, realFetch) {\n    /*\n     * Sometimes pages overwrite standard objects with global\n     * variables. Where possible, we extract fresh versions from an\n     * iframe.\n     */\n    const iframe = document.createElement(\'iframe\');\n    document.documentElement.appendChild(iframe);\n\n    // Initialise with global values\n    let URL = window.URL;\n    let WeakMap = window.WeakMap;\n    let WeakSet = window.WeakSet;\n    let TextDecoder = window.TextDecoder;\n    let Uint8Array = window.Uint8Array;\n    let decodeURIComponent = window.decodeURIComponent;\n    let URLSearchParams = window.URLSearchParams;\n    let Array = window.Array;\n    let Object = window.Object;\n    let atob = window.atob;\n    let CustomEvent = window.CustomEvent;\n    let performance = window.performance;\n    let JSON = window.JSON;\n\n    /*\n     * Under some circumstances the iframe isn\'t added\n     * successfully. If it has a contentWindow then we can extract the\n     * values from it, but otherwise we fall back on the global\n     * values.\n     */\n    if (iframe.contentWindow) {\n        // Try block because sometimes the CORS prevents access to iframe\n        try {\n            URL = iframe.contentWindow.URL;\n            WeakMap = iframe.contentWindow.WeakMap;\n            WeakSet = iframe.contentWindow.WeakSet;\n            TextDecoder = iframe.contentWindow.TextDecoder;\n            Uint8Array = iframe.contentWindow.Uint8Array;\n            decodeURIComponent = iframe.contentWindow.decodeURIComponent;\n            URLSearchParams = iframe.contentWindow.URLSearchParams;\n            Array = iframe.contentWindow.Array;\n            Object = iframe.contentWindow.Object;\n            atob = iframe.contentWindow.atob;\n            CustomEvent = iframe.contentWindow.CustomEvent;\n            performance = iframe.contentWindow.performance;\n            JSON = iframe.contentWindow.JSON;\n        } catch (error) {\n            // Empty catch block to prevent errors from showing in page logs\n        }\n    }\n\n    // If iframe was added successfully, remove it\n    if (iframe.parentNode) {\n        iframe.parentNode.removeChild(iframe);\n    }\n\n    /*\n     * If any of the values don\'t have the correct type, return and\n     * don\'t attempt any monkey patching. This avoids breaking web\n     * pages.\n     */\n    for (f of [\n        URL,\n        WeakMap,\n        WeakSet,\n        TextDecoder,\n        Uint8Array,\n        decodeURIComponent,\n        URLSearchParams,\n        Array,\n        Object,\n        atob,\n        CustomEvent,\n    ]) {\n        if (typeof f !== \'function\') {\n            return;\n        }\n    }\n\n    for (o of [performance, JSON]) {\n        if (typeof o !== \'object\') {\n            return;\n        }\n    }\n\n    /*\n     * Override common funcions that accept callbacks, such as\n     * setInterval, to keep track of what script each callback comes\n     * from. Skimmers commonly have the actual skimming code in a\n     * callback, so most of the time when we intercept a request,\n     * document.currentScript is null. To solve this, the script is\n     * stored in a closure when the callback is passed, and assigned\n     * to callbackCurrentscript when the callback is run.\n     */\n\n    let callbackCurrentScript = null;\n\n    // Maps callbacks originally passed to patched versions\n    const patchedCallbackMap = new WeakMap();\n\n    // Maps patched callbacks to original versions\n    const originalCallbackMap = new WeakMap();\n\n    /**\n     *  Generates the function with which to override the\n     *  callback-accepting functions.\n     *  @param realFn The real function, e.g. setInterval.\n     *  @param callbackIndex The index of the callback in the\n     *  arguments of realFn. For example, the callback is the first\n     *  argument in setInterval, so callbackIndex should be 0.\n     */\n    function generateAddCallbackFunction(realFn, callbackIndex) {\n        const patched = function(...args) {\n            const script = document.currentScript || callbackCurrentScript;\n\n            const callback = args[callbackIndex];\n\n            // If the callback isn\'t a function, there\'s nothing to patch\n            if (typeof callback !== \'function\') {\n                return realFn.apply(this, args);\n            }\n\n            // If a patch exists, use it instead of creating new function\n            let patchedCallback = patchedCallbackMap.get(callback);\n            if (!patchedCallback) {\n                patchedCallback = function() {\n                    callbackCurrentScript = script;\n                    const retVal = callback.apply(this, [...arguments]);\n                    callbackCurrentScript = null;\n                    return retVal;\n                };\n                patchedCallbackMap.set(callback, patchedCallback);\n                originalCallbackMap.set(patchedCallback, callback);\n            }\n\n            args[callbackIndex] = patchedCallback;\n            return realFn.apply(this, args);\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    const realSetInterval = window.setInterval;\n\n    window.setInterval = generateAddCallbackFunction(realSetInterval, 0);\n\n    const realSetTimeout = window.setTimeout;\n\n    window.setTimeout = generateAddCallbackFunction(realSetTimeout, 0);\n\n    const realPromiseThen = Promise.prototype.then;\n    Promise.prototype.then = generateAddCallbackFunction(realPromiseThen, 0);\n\n    const realPromiseCatch = Promise.prototype.catch;\n    Promise.prototype.catch = generateAddCallbackFunction(realPromiseCatch, 0);\n\n    const realPromiseFinally = Promise.prototype.finally;\n    if (realPromiseFinally) {\n        Promise.prototype.finally = generateAddCallbackFunction(\n            realPromiseFinally,\n            0\n        );\n    }\n\n    const realAddEventListener = EventTarget.prototype.addEventListener;\n\n    EventTarget.prototype.addEventListener = generateAddCallbackFunction(\n        realAddEventListener,\n        1\n    );\n\n    /*\n     * removeEventListener needs to be patched to map the passed\n     * listener to the patched listener.\n     */\n    const realRemoveEventListener = EventTarget.prototype.removeEventListener;\n    EventTarget.prototype.removeEventListener = function(...args) {\n        args[1] = patchedCallbackMap.get(args[1]) || args[1];\n\n        return realRemoveEventListener.apply(this, args);\n    };\n    EventTarget.prototype.removeEventListener.toString = () =>\n        realRemoveEventListener.toString();\n\n    /*\n     * Event listeners are sometimes set by directly assigning to a\n     * property from the GlobalEventHandlers mixin,\n     * e.g. \'submitBtn.onclick\'. For each of the GlobalEventHandlers\n     * implementations that are likely to be relevant, the event\n     * properties likely to be used by skimmers are overridden with\n     * our version that tracks currentScript when assigned.\n     */\n    for (const eventTarget of [\n        HTMLElement.prototype,\n        Document.prototype,\n        window,\n    ]) {\n        for (const eventName of [\n            \'onclick\',\n            \'onblur\',\n            \'onfocus\',\n            \'onchange\',\n            \'oninput\',\n            \'onkeydown\',\n            \'onkeypress\',\n            \'onkeyup\',\n            \'onload\',\n            \'onloadend\',\n            \'onmousedown\',\n            \'onmouseenter\',\n            \'onmouseleave\',\n            \'onmousemove\',\n            \'onmouseout\',\n            \'onmouseover\',\n            \'onmouseup\',\n            \'onpointerdown\',\n            \'onpointermove\',\n            \'onpointerup\',\n            \'onpointerover\',\n            \'onpointerout\',\n            \'onpointerenter\',\n            \'onpointerleave\',\n            \'onsubmit\',\n        ]) {\n            const descriptor = Object.getOwnPropertyDescriptor(\n                eventTarget,\n                eventName\n            );\n\n            if (!descriptor) {\n                continue;\n            }\n\n            const realGetter = descriptor.get;\n            const realSetter = descriptor.set;\n\n            const patchedSetter = generateAddCallbackFunction(realSetter, 0);\n\n            /*\n             * In case a script checks the value of the property after\n             * setting it, the getter has to be patched to return the\n             * original callback.\n             */\n            const patchedGetter = function() {\n                const realValue = realGetter.call(this);\n\n                const originalCallback = originalCallbackMap.get(realValue);\n                if (originalCallback) {\n                    return originalCallback;\n                } else {\n                    return realValue;\n                }\n            };\n\n            patchedGetter.toString = () => realGetter.toString();\n\n            Object.defineProperty(eventTarget, eventName, {\n                get: patchedGetter,\n                set: patchedSetter,\n            });\n        }\n    }\n\n    /*\n     * This script has some dependencies that need to be included\n     * without polluting the global namespace. This is achieved by\n     * inserting them into the source at build time in the\n     * makefile. This is preferable to loading the dependencies via\n     * AJAX, because the latter doesn\'t work if the CSP disallows\n     * unsafe-eval. The following tag tells the makefile where to\n     * insert the dependencies.\n     */\n    // <DEPENDENCIES>\nclass InputAnalyser {\n    constructor(_inputs) {\n        this._inputs = _inputs;\n        this._irrelevantInputs = new WeakSet();\n\n        /*\n         * Not currently using Password because it was causing too\n         * many false positives. Some sites use external auth domains,\n         * and many have misconfigured analytics that collect\n         * passwords.\n         */\n        this.relevantInputs = [new CardNumber()];\n    }\n\n    get relevantInputValues() {\n        const inputs = {};\n        this._inputs.forEach(\n            input => (inputs[input.id || input.name] = input.value)\n        );\n\n        return inputs;\n    }\n\n    get relevantFilledInputs() {\n        return this.allInputs\n            .filter(input => this.isRelevantElement(input))\n            .filter(input => input.value.length >= cons.minInputSize)\n            .map(input => ({\n                id: input.id,\n                name: input.name,\n                value: input.value,\n            }));\n    }\n\n    get allInputs() {\n        this._inputs = [\n            ...document.querySelectorAll(\n                this.relevantInputs.map(i => i.relevantInputTypes).join(\', \')\n            ),\n        ];\n\n        return this._inputs;\n    }\n\n    static getLabelText(input) {\n        if (!input.labels.length) {\n            return \'\';\n        }\n        return stripSpecialChars(input.labels[0].textContent).trim();\n    }\n\n    /**\n     * Takes an HTMLElement and determines whether or not it could be relevant for the purposes of\n     * skimmer detection.\n     * @param {HTMLElement} input The element which is to be checked for relevancy.\n     * @returns {boolean} Returns true if the element is potentially relevant (e.g. a credit card\n     * field or other such sensitive input).\n     */\n    isRelevantElement(input) {\n        // If the element has been identified as irrelevant, return false\n        if (this._irrelevantInputs.has(input)) {\n            return false;\n        }\n\n        // Check the input against all the relevant input types\n        const relevances = this.relevantInputs.map(i => i.test(input));\n\n        /*\n         *  If they all agree that this input is unlikely to become\n         *  relevant, exclude it from further checks and return false\n         */\n        if (relevances.every(i => i === RelevantInput.relevance.NOT_RELEVANT)) {\n            this._irrelevantInputs.add(input);\n            return false;\n        }\n\n        // Return true if any consider the input to be relevant, otherwise false\n        return relevances.some(i => i === RelevantInput.relevance.RELEVANT);\n    }\n}\n/*\n * Input Matcher represents a class that can have matches with relevant inputs.\n * Each class that inherits must implement the findInputMatches method.\n */\nclass InputMatcher {\n    /*\n     * Finds any matches with the list of inputs.\n     * @param inputs. An object whose values represent relevant inputs.\n     * @return {[String]} The keys of the inputs that were found.\n     */\n    findInputMatches(inputs) {\n        throw new Error(\'findInputMatches has not been implemented.\');\n    }\n\n    /**\n     * Determines whether one or more values in needles are present in any of\n     * the keys of the haystacks.\n     * Checks both raw input and base64 encoded versions of the needles.\n     * @param needles An object. The haystacks are searched for the values of\n     * this object.\n     * @param haystacks The array of strings in which the needle(s) may reside.\n     * A string may optionally be supplied if only one value is present.\n     * @returns {[String]} The keys of the needles that were found in one or\n     * more haystacks in either raw or base64 encoded form.\n     */\n    static atLeastOneNeedleInHaystack(needles, haystacks) {\n        if (\n            needles === undefined ||\n            needles.length === 0 ||\n            haystacks === undefined\n        ) {\n            return [];\n        }\n\n        const searchArray = Array.isArray(haystacks) ? haystacks : [haystacks];\n\n        return Object.entries(needles)\n            .filter(([_, needle]) =>\n                searchArray.some(haystack => {\n                    if (isBase64Encoded(haystack)) {\n                        if (atob(haystack).indexOf(needle) !== -1) {\n                            return true;\n                        }\n                    }\n                    return haystack.indexOf(needle) !== -1;\n                })\n            )\n            .map(([input, _]) => input);\n    }\n}\n/*\n * Matcher for an object that cannot contain any inputs.\n */\nclass EmptyMatcher extends InputMatcher {\n    findInputMatches(inputs) {\n        return [];\n    }\n}\n\nclass URLParamsMatcher extends InputMatcher {\n    constructor(url) {\n        super();\n        this._url = url;\n    }\n\n    findInputMatches(inputs) {\n        try {\n            const parsedUrl = new URL(this._url);\n            return InputMatcher.atLeastOneNeedleInHaystack(inputs, [\n                ...parsedUrl.searchParams.values(),\n            ]);\n        } catch (e) {\n            return [];\n        }\n    }\n}\n\nclass StringBodyMatcher extends InputMatcher {\n    constructor(str) {\n        super();\n        this._str = str;\n    }\n\n    findInputMatches(inputs) {\n        let decoded;\n        try {\n            decoded = decodeURIComponent(this._str);\n        } catch (e) {\n            decoded = this._str;\n        }\n\n        const searchParams = new URLSearchParams(decoded);\n        const parsedQuery = {};\n        const parsedValues = [];\n\n        for (const [key, value] of searchParams.entries()) {\n            parsedQuery[key] = value;\n            parsedValues.push(value);\n        }\n\n        if (parsedQuery !== {} && parsedValues.indexOf(null) === -1) {\n            return InputMatcher.atLeastOneNeedleInHaystack(\n                inputs,\n                parsedValues\n            );\n        }\n        return InputMatcher.atLeastOneNeedleInHaystack(inputs, decoded);\n    }\n}\n\nclass FormBodyMatcher extends InputMatcher {\n    constructor(formData) {\n        super();\n        this._formData = formData;\n    }\n\n    findInputMatches(inputs) {\n        return InputMatcher.atLeastOneNeedleInHaystack(\n            inputs,\n            Object.values(this._formData).flat()\n        );\n    }\n}\n/**\r\n * Takes a string and removes a variety of \'special\' characters like punctuation.\r\n * @param str The string to be modified.\r\n * @returns {string} The resulting string without any special characters.\r\n */\r\nfunction stripSpecialChars(str) {\r\n    return str.replace(/[`~!@#$%^&*()_|+\\-=?;:\'\",.<>}{\\][\\\\/]/gi, \'\');\r\n}\r\n\r\n/**\r\n * Attempts to identify where a string is base-64 encoded or not.\r\n * @param string The raw string to be examined.\r\n * @returns {boolean} Returns true if the string matches a base-64 string. False otherwise.\r\n */\r\nfunction isBase64Encoded(string) {\r\n    return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(\r\n        string\r\n    );\r\n}\r\n\r\n// Computes the base64 sha512/sha224 hash of some text, removing trailing ==\r\nfunction base64hash(hashFunction, text) {\r\n    return btoa(\r\n        hashFunction\r\n            .array(text)\r\n            .map(char => String.fromCharCode(char))\r\n            .join(\'\')\r\n    ).slice(0, -2);\r\n}\r\n\r\nfunction normaliseUrl(str) {\r\n    const link = document.createElement(\'a\');\r\n    link.href = str;\r\n    return link.href;\r\n}\r\n\r\nfunction stripCredentials(urlstr) {\r\n    try {\r\n        const url = new URL(urlstr);\r\n        const params = url.searchParams;\r\n        params.forEach((_, key) => params.set(key, \'\'));\r\n        return url.toString();\r\n    } catch (e) {\r\n        return urlstr;\r\n    }\r\n}\r\n\r\n/**\r\n * Check if two URLs match regardless of any partial encoding.\r\n *\r\n * Uses decodeURI which can throw a URIError when url is malformed.\r\n */\r\nfunction urlsMatch(first, second) {\r\n    try {\r\n        return decodeURI(first) === decodeURI(second);\r\n    } catch (e) {\r\n        return false;\r\n    }\r\n}\r\n\r\nfunction sendMessage(type, data, tabId, options) {\r\n    if (tabId) {\r\n        chrome.tabs.sendMessage(tabId, { data, type }, options);\r\n    } else {\r\n        chrome.runtime.sendMessage({ data, type });\r\n    }\r\n}\r\n\r\n// Encodes the given input using base64url as defined by RFC4648 section 5.\r\nfunction base64Url(input) {\r\n    return btoa(input)\r\n        .replace(\'+\', \'-\')\r\n        .replace(\'/\', \'_\');\r\n}\r\n/*\n * Netcraft Extension\n * constants JavaScript\n */\n\n/* eslint sort-keys: \'off\' */\n/* sort-keys is disabled as the constants are separated by type */\n\n// Aligned values here make for easier reading, so we disable prettier\n\n// prettier-ignore\nconst cons = (function() {\n    const cryptojackersUrl = \'https://www.netcraft.com/apps/help/glossary/#crypto-miners\';\n    const skimmersUrl = \'https://www.netcraft.com/apps/help/glossary/#credit-card-skimmers\';\n    const evilResourceCrime = \'attempted to load\';\n\n    return {\n        fields: [\n            \'topsites\',\n            \'country\',\n            \'netblock\',\n            \'hoster\',\n            \'firstseen\',\n            \'rank\',\n            \'risk\',\n            \'uses_sslv3\',\n            \'pfs\',\n            \'heartbleed\',\n            \'heartbleed_message\',\n        ],\n\n        // options that can be toggle in options.js and installed flag\n\n        toggleOptions: [\n            \'installed\',\n            \'block\',\n            \'xss\',\n            \'suspicious\',\n            \'other-malicious-scripts\',\n            \'credential-leaks\',\n            \'analytics\',\n            \'cryptojackers\',\n            \'skimmers\',\n        ],\n\n        // domains\n        mirror:  \'https://mirror2.extension.netcraft.com/\',\n        toolbar: \'https://mirror.toolbar.netcraft.com/\',\n\n        // links and faqs\n        blogArchive:      \'https://news.netcraft.com/archives/\',\n        browserApp:       \'https://www.netcraft.com/apps/browser/\',\n        cryptojackersUrl,\n        extensionStore:   \'https://addons.mozilla.org/en-us/firefox/addon/netcraft-toolbar?src=external-blocked_visit-rating\',\n        heartbleedUrl:    \'https://www.netcraft.com/apps/help/glossary/#heartbleed\',\n        logBlockedVisit:  \'https://toolbar.netcraft.com/blocked_visit\',\n        netblock:         \'https://sitereport.netcraft.com/netblock?q=\',\n        pfsUrl:           \'https://www.netcraft.com/apps/help/glossary/#pfs\',\n        report:           \'https://sitereport.netcraft.com/?url=\',\n        reportAPI:        \'https://report.netcraft.com/api/v2/report/urls\',\n        reportMistake:    \'https://report.netcraft.com/report/mistake?url=\',\n        skimmersUrl,\n        sslv3Url:         \'https://www.netcraft.com/apps/help/glossary/#sslv3\',\n        submissionURL:    \'https://report.netcraft.com/submission/\',\n        topsites:         \'https://trends.netcraft.com/topsites?s=\',\n\n        // paths\n        block:              \'blocked.html\',\n        check:              \'check_url/v4/\',\n        danger:             \'Images/danger.png\',\n        embed:              \'/embedded\',\n        evilJSPatternsFeed: \'eviljspatterns\',\n        skimmerDomainWhitelistFeed: \'eviljsskimmerwhitelist\',\n        evilResourcesFeed:  \'blockdb/netcraft_extension_evil_resource_encrypted-yppL7bVh/\',\n        flags:              \'Images/flags/\',\n        force:              \'/dodns\',\n        heartbleed:         \'Images/heartbleed.png\',\n        info:               \'/info\',\n        poodlePost:         \'2014/10/15/googles-poodle-affects-oodles.html\',\n        warning:            \'Images/warning.png\',\n\n        // feed constants\n        evilResourceTypes: [\'cryptojacker\', \'skimmer\'],\n        evilResourcesSalt: \'C4jhYHxW\',\n        maxRevisions:      5,\n        randomSaltLength:  8,\n\n        // mappings of url_type => information\n        urlTypeInformationMappings: {\n            \'malicious_javascript/cryptojacker\': [\'web miners\',             cryptojackersUrl],\n            \'malicious_javascript/skimmer\':      [\'shopping site skimmers\', skimmersUrl],\n        },\n\n        // mappings of evilResourceTypes => user options\n        evilResourceOptionMappings: {\n            \'cryptojacker\': \'cryptojackers\',\n            \'skimmer\':      \'skimmers\'\n        },\n\n        // mappings of user options => url_type\n        optionUrlTypeMappings: {\n            \'block\':                   /^(?!malicious_javascript:)/,\n            \'cryptojackers\':           /^malicious_javascript\\/cryptojacker$/,\n            \'other-malicious-scripts\': /^malicious_javascript\\/(?!(skimmer|cryptojacker)$)/,\n            \'skimmers\':                /^malicious_javascript\\/skimmer$/,\n        },\n\n        // report constants\n        malwareSourceEmail:  \'extension-reported-malware@netcraft.com\',\n        malwareSourceUUID:   \'mwcCcApi89cobY9loJ9rWJ9HuOhlpwMj\',\n        extensionSourceUUID: \'qBEQ3NhEnwK4sypmuSxBJzRqpxZLRXxe\',\n\n        // report error default message\n        reportErrorMessage: \'An error occured with your submission. Please try again.\',\n\n        // mapping from report API errors to displayed messages\n        reportErrorMappings: {\n            \'Duplicate of a recent submission\': \'This URL has already been reported.\',\n            \'Supplied email is not valid\': \'Invalid email address. Please correct it and try again.\',\n        },\n\n        whitelistErrorMessages: {\n            invalidHostname: \'Invalid hostname, please correct it and try again\',\n            duplicate: \'Protection is already disabled for this site\',\n        },\n\n        // browserAction icon paths\n        iconNormal: {\n            16: \'Images/action-normal-16.png\',\n            24: \'Images/action-normal-24.png\',\n            32: \'Images/action-normal-32.png\'\n        },\n        iconDanger: {\n            16: \'Images/action-danger-16.png\',\n            24: \'Images/action-danger-24.png\',\n            32: \'Images/action-danger-32.png\'\n        },\n        iconWarning: {\n            16: \'Images/action-warning-16.png\',\n            24: \'Images/action-warning-24.png\',\n            32: \'Images/action-warning-32.png\'\n        },\n        iconDisabled: {\n            16: \'Images/action-disabled-16.png\',\n            24: \'Images/action-disabled-24.png\',\n            32: \'Images/action-disabled-32.png\'\n        },\n\n        // Extension internal message types\n        capturedIntercept:  \'interceptDetailsCaptured\',\n        confirmedIntercept: \'interceptDetailsConfirmed\',\n        contentReady:       \'contentScriptReady\',\n        leaksEnabled:       \'credentialLeaksEnabled\',\n        reportURL:          \'report\',\n        reqBlock:           \'block\',\n        reqDetails:         \'details\',\n        reqForce:           \'force\',\n        reqPopUp:           \'popup\',\n        reqPrintTimingData: \'time\', //debug\n        reqInputs:          \'requestInputValues\',\n        sendInputs:         \'sendInputs\',\n        sendIntercept:      \'sendInterceptDetails\',\n        updateOption:       \'updateOption\',\n        updateWhitelist:    \'whitelist\', // disable button whitelist\n\n        // Status codes\n        errEvilResource: \'EVILRESOURCE\',\n        errNoDetails:    \'NODETAILS\',\n        errNoInfo:       \'NOINFO\',\n        errBlocked:      \'BLOCKED\',\n        errPending:      \'PENDING\',\n        errServer:       \'ERROR\',\n        errSuspicious:   \'SUSPICIOUS\',\n        errTimeout:      \'TIMEOUT\',\n        errXSS:          \'XSS\',\n        noError:         \'OK\',\n\n        // Valid protocols (it is a regex match so ^ matches start of string)\n        protocols:    [\'^http\', \'^https\', \'^ftp\'],\n\n        // Human readable messages\n        msgNa:           \'NA\',\n        msgNewSite:      \'New Site\',\n        msgNoInfo:       \'Site information not available\',\n        msgNoServer:     \'Unable to contact Netcraft servers\',\n        msgSuspicious:   \'Suspicious URL Detected\',\n        msgXSS:          \'Suspected XSS Attack\',\n        msgBlocked:      {\n            \'malicious_javascript/skimmer\': {\n                title: \'Shopping Site Skimmer Detected\',\n                description: \'shopping site skimmer\'\n            },\n            \'malicious_javascript/dropsite\': {\n                title: \'Potential Credential Leak Detected\',\n                description: \'potential credential leak\'\n            },\n            \'malicious_javascript/cryptojacker\': {\n                title: \'Web Miner Detected\',\n                description: \'web miner\'\n            },\n            \'malware_c2/infrastructure\': {\n                title: \'Malicious URL\',\n                description: \'malicious page\'\n            },\n            \'default\': {\n                title: \'Suspected Phishing\',\n                description: \'suspected phishing site\'\n            },\n        },\n\n        // Human readable reasons\n        urlTypeCrimeMappings: {\n            \'malicious_javascript/skimmer\':      evilResourceCrime,\n            \'malicious_javascript/cryptojacker\': evilResourceCrime,\n            \'malicious_javascript/dropsite\':     \' is blocked because we detected that sensitive information was leaked from\',\n        },\n\n        // Timeout function value to clear cache etc.\n        timeout:      300000,\n\n        // Skimmer whitelist update interval (1 day)\n        skimmerWhitelistInterval: 1000 * 60 * 60 * 24,\n\n        // Google Analytics account\n        account:      \'UA-2150242-8\',\n        gaCategory:   \'Popup load times\',\n\n        // InputAnalyser constants\n        minInputSize: 5,\n\n        // First addresses of the private IPv4 netblocks\n        privateIPv4Blocks: [\n            \'10.0.0.0\',\n            \'172.16.0.0\',\n            \'192.168.0.0\'\n        ],\n\n        // Sync storage key for disable for this site button whitelist\n        disableButtonWhitelistKey: \'disableButtonWhitelist\',\n\n        // Local storage key for local credential leak detection whitelist\n        localCredentialLeakWhitelistKey: \'localCredentialLeakWhitelist\',\n\n        // Whitelisted fileName regexes for credential leak detection\n        credentialLeakFileNameWhitelist: [\n            /^https?:\\/\\/googleads.g.doubleclick.net\\//,\n        ],\n    };\n})();\n\n/*\n * recursively prevents any property or sub-property in cons from:\n *   - being added;\n *   - being removed; or\n *   - being modified.\n */\nfunction deepFreeze(object) {\n    Object.freeze(object);\n\n    for (const name of Object.getOwnPropertyNames(object)) {\n        if (typeof object[name] === \'object\') {\n            deepFreeze(object[name]);\n        }\n    }\n}\n\ndeepFreeze(cons);\n/*\r\n * RelevantInput objects check whether given form inputs should be\r\n * treated as sensitive data.\r\n *\r\n * This should be treated as an abstract class. Only the subclasses\r\n * are intended to be instantiated.\r\n */\r\nclass RelevantInput {\r\n    /*\r\n     * Enum of the possible return values of test():\r\n     * RELEVANT:\r\n     *     Input is relevant now, outgoing requests should be\r\n     *     checked for the contained value (subject to minimum length).\r\n     * POTENTIALLY RELEVANT:\r\n     *     Input should not be considered relevant now, but may be in\r\n     *     future so should continue to be checked.\r\n     * NOT_RELEVANT:\r\n     *     Input is not relevant and will not become relevant, so can\r\n     *     be ommitted from further checks to optimise performance.\r\n     */\r\n    static get relevance() {\r\n        return {\r\n            NOT_RELEVANT: 1,\r\n            POTENTIALLY_RELEVANT: 2,\r\n            RELEVANT: 3,\r\n        };\r\n    }\r\n\r\n    /*\r\n     * relevantInputTypes (string) - a comma separated list of\r\n     * selectors that describe the types of input that should be\r\n     * considered relevant.\r\n     */\r\n    constructor(relevantInputTypes) {\r\n        this.relevantInputTypes = relevantInputTypes;\r\n    }\r\n\r\n    /*\r\n     * Checks whether any of the given input\'s \'labels\' (the ID, name,\r\n     * or actual label) match the given regex.\r\n     */\r\n    static testInputLabel(regex, input) {\r\n        const labels = [\r\n            input.id,\r\n            input.name,\r\n            InputAnalyser.getLabelText(input),\r\n        ];\r\n        return labels.some(label => regex.test(label));\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for passwords.\r\n */\r\nclass Password extends RelevantInput {\r\n    constructor() {\r\n        super(\'input[type=password]\');\r\n    }\r\n\r\n    /*\r\n     * Passwords are verified purely by their input type.\r\n     */\r\n    test(input) {\r\n        if (input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\r\n/*\r\n * RelevantInput for card numbers.\r\n */\r\nclass CardNumber extends RelevantInput {\r\n    constructor() {\r\n        // Inputs with no type attribute act like text inputs\r\n        super(\r\n            \'input[type=text], input[type=tel], input[type=number], input:not([type])\'\r\n        );\r\n\r\n        // Definitely card number labels\r\n        this.inputLabelRegexes = [\r\n            /(cc|card).?(num|no)/i,\r\n            /カード番号/,\r\n            /Номер.*карты/i,\r\n            /信用卡卡號/i,\r\n            /信用卡号码/i,\r\n        ];\r\n\r\n        /*\r\n         * Matches incomplete card numbers, because ideally the\r\n         * skimmer should be detected before the victim gives away\r\n         * most of their card number.\r\n         */\r\n        this.inputValueRegexes = [/^(\\d{4}[-\\s]?){0,4}\\d{1,4}$/];\r\n\r\n        // Probably card number labels\r\n        this.suspectInputLabelRegexes = [\r\n            /card/i,\r\n            /tarjeta/i,\r\n            /carte/i,\r\n            /карты/i,\r\n            /卡/i,\r\n            /カード/i,\r\n            /카드/i,\r\n            /credit[^s]/i,\r\n        ];\r\n\r\n        // If it matches one of these, probably not a card number\r\n        this.inputLabelExceptionRegexes = [\r\n            /post/i,\r\n            /code/i,\r\n            /zip/i,\r\n            /acc(oun)?t?[-_]?n(um|o)/i,\r\n            /credit[-_]?(sum|limit)/i,\r\n            /loyalty/i,\r\n            /gift/i,\r\n            /idcard/i,\r\n            /cardid/i,\r\n            /club/i,\r\n            /sku/i,\r\n            /(\\b|_)otp/i,\r\n            /price/i,\r\n            /amount/i,\r\n            /service/i,\r\n            /picard/i,\r\n            /search/i,\r\n            /contact/i,\r\n            /callme/i,\r\n            /chat/i,\r\n            /activate/i,\r\n            /msisdn/i,\r\n        ];\r\n    }\r\n\r\n    test(input) {\r\n        if (!input.matches(this.relevantInputTypes)) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return NOT_RELEVANT if label matches an exception regex\r\n        if (\r\n            this.inputLabelExceptionRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n\r\n        // Return RELEVANT if label clearly states card number\r\n        if (\r\n            this.inputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            return RelevantInput.relevance.RELEVANT;\r\n        }\r\n\r\n        /*\r\n         * If label suggests card number, return RELEVANT if value\r\n         * also suggests card number or POTENTIALLY_RELEVANT if\r\n         * not. Otherwise, return NOT_RELEVANT as this element will\r\n         * not become relevant unless a label changes (unlikely).\r\n         */\r\n        if (\r\n            this.suspectInputLabelRegexes.some(r =>\r\n                RelevantInput.testInputLabel(r, input)\r\n            )\r\n        ) {\r\n            if (this.inputValueRegexes.some(r => r.test(input.value))) {\r\n                return RelevantInput.relevance.RELEVANT;\r\n            } else {\r\n                return RelevantInput.relevance.POTENTIALLY_RELEVANT;\r\n            }\r\n        } else {\r\n            return RelevantInput.relevance.NOT_RELEVANT;\r\n        }\r\n    }\r\n}\r\n\n    const inputAnalyser = new InputAnalyser();\n\n    const resourceBlockList = new Set([\n                            \"6C590F97DDB1A11AE795B3050E4C3866\", \"D285BE79786E2ECE396941331C5BA65E\", \"59DF8523520DE7F08FB53D3D12BD94F5\", \"1FB784EBAFDA1618023756BF5B6D7E46\", \"0D6E1B5F03B81F5C37813B22E29C4FE2\", \"EFAB0ECE9383D50C555B8168C3E2A4DD\", \"AC8A4B13A411A4E43E61D0A034FA4A55\", \"25A781F6D1F94EEACCF1E00E844D9E14\", \"3920B04ED0D215EA738F524F60485F23\", \"05F4A2D3161A92EB82A44F15FC2A3AEC\", \"C5A1BE7B3855B0A25275B2877E005CB9\", \"00069CEC347076721EBE2257978A0A33\", \"9A513130D02F360BBDC16D8AC0C579D8\", \"7CDB2A3A29E50F90C1000252EF523AD6\", \"25CE912FAF2376026AAD0174AD5A0329\", \"739E1F67AA5F64A06FC469F6030802D4\", \"F49368C9A05C29EF49411999C5987F2C\", \"AB2DD89FB892155275D8780CF527BC93\", \"CFE758EB18FBFE70B1F690241B75D2B1\", \"E43C3463B40A790545F4149FF06B419E\", \"8B6D4C76A7CCF5EB720660B8B2D3F5EB\", \"45573B3E2F613AF5BF2145DF004BABD1\", \"E3DB4E968B371DE94CE607199A062F39\", \"A4EFF1240C5DE614B394FB738CBD28E2\", \"F42D10E84B9A69A8A24062319F425A83\", \"90646BE39982594A58EDF5D2630CCFFD\", \"669853BEA9B84EA51D7302A7FBFF5BC3\", \"0BA566B4777526F451108100107DC5ED\", \"AB19E12BFFBC47244BDCC2403EAA9037\", \"61DAF8537240639277FD1A5908A131A4\", \"6F1DC1309CFE734C038685D8F17A3B6B\", \"DCC3DB001334F29C1C0208D15E9EAB49\", \"2DBD7D54022C28273ADE3147B8EEB076\", \"AE3760E93412C65EC5BF63537B037019\", \"4CD96A830DAC0E0B5EBE578B0DD870AE\", \"4C70569BBE1821F5655382660807FA77\", \"05ECA17FF0EA78669ED43B2BD7B63571\", \"44E503B3621513B9D3666F284E4DF47A\", \"8F1C92E7665860AE70CBB228DED86111\", \"D69552885B55C4097E5725F805815639\", \"B4357A9D889E974CDF3F9740E48F9A1B\", \"5709DC60CFC365B7064B06C709A9D32B\", \"14E8729CBB16AC3625EAA0D7A679E56E\", \"793D4DABAAA377A12F2B6CE88C5FB389\", \"C5D1DCE2AE0D8CE79054FC16CB13D65A\", \"89F91750BD4A9397D1F8CE183757FF70\", \"50B8BC213FB188A687173C1A0A77EA57\"\n                        ]);\n    const evilJSPatterns = new Set([\n                            \n                        ]);\n\n    // Extract body object\n    function extractBody(data) {\n        if (!data) {\n            return Promise.resolve({});\n        }\n        if (FormData.prototype.isPrototypeOf(data)) {\n            const formData = {};\n            for (const [key, value] of data.entries()) {\n                formData[key] = value;\n            }\n            return Promise.resolve({ formData });\n        }\n        // Create a temporary Request object to parse the body\n        const tempRequest = new Request(\'\', { body: data, method: \'POST\' });\n        return extractBodyFromRequest(tempRequest);\n    }\n\n    function extractBodyFromRequest(request) {\n        // Try to extract as text\n        return request\n            .text()\n            .then(text => {\n                return { text };\n            })\n            .catch(e => {\n                return {};\n            });\n    }\n\n    /**\n     * This function checks if the request hostname/URL is in the blocklist.\n     * Used when identifying whether or not to forward request to background\n     * script.\n     * @param hostname Hostname to check against blocklist.\n     * @param url Request url to check against evil JS patterns\n     * @returns boolean true if hostname is in the blocklist, false if\n     * it is not in blocklist or if blocklist is not provided.\n     */\n    function requestInBlocklist(hostname, url) {\n        if (resourceBlockList === undefined || evilJSPatterns === undefined) {\n            return false;\n        }\n        if (testEvilJS(url.replace(/^(https?:)?\\/\\//i, \'\'))) {\n            return true;\n        }\n        while (!testEvilJS(hostname) && !testResource(hostname)) {\n            const newHostname = hostname.replace(/^.+?\\./g, \'\');\n\n            if (hostname === newHostname) return false;\n\n            hostname = newHostname;\n        }\n        return true;\n    }\n\n    function testEvilJS(url) {\n        return evilJSPatterns.has(base64hash(sha512, url));\n    }\n\n    function testResource(hostname) {\n        return resourceBlockList.has(\n            md5(cons.evilResourcesSalt + hostname).toUpperCase()\n        );\n    }\n\n    /*\n     * Implementations to overwrite window.XMLHttpRequest.prototype.open,\n     * window.XMLHttpRequest.prototype.send and window.fetch. These are (to my knowledge) the two\n     * APIs that can be used to make requests within Javascript. If there are more, then they will\n     * also need to be overridden and intercepted like below.\n     */\n\n    /**\n     * This function generates functions to be used for wrapping and intercepting network\n     * requests, namely from XMLHttpRequest and the Fetch API. It works by taking the real\n     * function we want to intercept the request for, and then uses document.currentScript to work\n     * out which file initiated the request. It sends those details to the content script which\n     * forwards it to the background script, and then we await a confirmation of receipt from the\n     * background page. Finally, the real request is sent by forwarding parameters to the original\n     * function passed in.\n     * @param realFn A reference to the real function that is being intercepted.\n     * @param getRequestUrlFn A function used to get the request URL from the object. This is\n     * necessary as the mechanisms for different objects differs depending on implementation.\n     * @param getRequestBodyPr A function used to get the request body from the object. Returns\n     * a promise. It should be null if it\'s not applicable (e.g. if it sets the src of an image or script).\n     * @param type The type of request, which will be a ResourceType:\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType\n     * @returns {function(...[*]=): PromiseLike<any | never>}\n     */\n    function generateRequestInterceptFn(\n        realFn,\n        getRequestUrlFn,\n        getRequestBodyPr,\n        type\n    ) {\n        const patched = function(...args) {\n            /*\n             * We create a link DOM element in order to correctly generate the\n             * absolute path of the request, automatically transforming from a\n             * relative path if necessary.\n             */\n            const requestUrl = normaliseUrl(\n                getRequestUrlFn.bind(this)(...args)\n            );\n\n            let requestHostname;\n            let pageHostname;\n\n            try {\n                requestHostname = new URL(requestUrl).hostname;\n                pageHostname = new URL(location.href).hostname;\n            } catch (e) {\n                return realFn.apply(this, args);\n            }\n\n            /*\n             * If hostname is in the blocklist, the request should always be\n             * sent to the background script.\n             */\n            // eslint-disable-next-line prefer-const\n            let forceStall = false;\n\n            if (!forceStall) {\n                /*\n                 * Delaying outgoing requests sometimes causes problems\n                 * with elements such as video players and dynamically\n                 * loaded images, so it should only be done when\n                 * necessary. Before inputAnalyser is defined and when the\n                 * request is to the same hostname as the page, this can\n                 * be skipped.\n                 */\n                if (\n                    requestHostname === pageHostname ||\n                    inputAnalyser === undefined\n                ) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * It can also be skipped if there is no sensitive\n                 * information entered into a form on the page yet.\n                 */\n                const relevantFilledInputs = inputAnalyser.relevantFilledInputs;\n                if (!relevantFilledInputs.length) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * For requests with no body, it\'s only necessary to delay\n                 * them when the sensitive information from the form\n                 * inputs is present in the request URL.\n                 */\n                const ia = new InputAnalyser(relevantFilledInputs);\n                const relevantInputValues = ia.relevantInputValues;\n                const urlMatcher = new URLParamsMatcher(requestUrl);\n\n                if (\n                    getRequestBodyPr === null &&\n                    !urlMatcher.findInputMatches(relevantInputValues).length\n                ) {\n                    return realFn.apply(this, args);\n                }\n            }\n\n            /*\n             * Create a promise that resolves immediately if the request body\n             * does not apply to this request.\n             */\n            if (getRequestBodyPr === null) {\n                getRequestBodyPr = function() {\n                    return Promise.resolve({});\n                };\n            }\n\n            let fileName;\n            if (document.currentScript && document.currentScript.src) {\n                fileName = document.currentScript.src;\n            } else if (callbackCurrentScript && callbackCurrentScript.src) {\n                fileName = callbackCurrentScript.src;\n            } else {\n                fileName = location.href;\n            }\n\n            return getRequestBodyPr\n                .bind(this)(...args)\n                .then(requestBody => {\n                    /*\n                     * Generate the information object to send through to the\n                     * content script including the request URL, the initiator\n                     * filename and a timestamp relative to the page load.\n                     */\n                    const detail = {\n                        fileName,\n                        requestBody,\n                        requestUrl,\n                        timestamp: performance.now(),\n                        type,\n                    };\n\n                    document.dispatchEvent(\n                        new CustomEvent(cons.capturedIntercept, {\n                            detail,\n                        })\n                    );\n\n                    return new Promise(resolve => {\n                        /*\n                         * Send the initiator\'s file name and other info to the\n                         * background script, then wait for confirmation that the\n                         * details made it to the background page before calling\n                         * the real function. This is to avoid any race conditions\n                         * whereby the details haven\'t been added to the tab data\n                         * before onBeforeRequest/onBeforeSendHeaders gets called.\n                         */\n                        document.addEventListener(\n                            cons.confirmedIntercept,\n                            function compareData(customEvent) {\n                                const receivedData = JSON.parse(\n                                    customEvent.detail\n                                );\n                                if (detailsMatch(detail, receivedData)) {\n                                    /*\n                                     * If it\'s the correct confirmation, then we\n                                     * can resolve the promise and call the real\n                                     * function in the finally block.\n                                     */\n                                    document.removeEventListener(\n                                        cons.confirmedIntercept,\n                                        compareData,\n                                        false\n                                    );\n                                    resolve();\n                                }\n                            }\n                        );\n                    }).then(() => realFn.apply(this, args));\n                });\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    function detailsMatch(first, second) {\n        const comparisonKeys = [\'fileName\', \'requestUrl\', \'timestamp\', \'type\'];\n        for (const key of comparisonKeys) {\n            if (first[key] !== second[key]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    window.fetch = generateRequestInterceptFn(\n        realFetch,\n        request => (typeof request === \'string\' ? request : request.url),\n        (request, options) => {\n            // Check if argument is a Request object.\n            if (request instanceof Request) {\n                return extractBodyFromRequest(request);\n            }\n            return options ? extractBody(options.body) : Promise.resolve({});\n        },\n        \'xmlhttprequest\'\n    );\n\n    window.XMLHttpRequest.prototype.send = generateRequestInterceptFn(\n        realSend,\n        function getRequestUrl() {\n            return this._requestUrl;\n        },\n        extractBody,\n        \'xmlhttprequest\'\n    );\n\n    /*\n     * This function is intercepted so that we can capture the value of the\n     * request URL, since it isn\'t present in the arguments for\n     * XMLHttpRequest.send (above).\n     */\n    window.XMLHttpRequest.prototype.open = function open(...args) {\n        [, this._requestUrl] = args;\n        realOpen.apply(this, args);\n    };\n    window.XMLHttpRequest.prototype.open.toString = () => realOpen.toString();\n\n    /*\n     * Monkeypatches for setting the src of images and scripts, an\n     * indirect way of sending requests that is often employed by\n     * skimmers. The getters are also overridden to make the behaviour\n     * seem identical to the original implementation. Otherwise, if a\n     * script were to set an src and immediately read it, it would\n     * come back as the old value.\n     */\n\n    /*\n     * WeakMap maps an object to a value. This is used to store the\n     * new src of an object when its true src property may not yet\n     * have been set.\n     */\n    const srcMap = new WeakMap();\n    const requestElements = [\n        {\n            srcElement: HTMLImageElement.prototype,\n            type: \'image\',\n        },\n        {\n            srcElement: HTMLScriptElement.prototype,\n            type: \'script\',\n        },\n    ];\n\n    for (const { srcElement, type } of requestElements) {\n        const realGetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .get;\n        const realSetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .set;\n        const realGetAttribute = srcElement.getAttribute;\n        const realSetAttribute = srcElement.setAttribute;\n        const realRemoveAttribute = srcElement.removeAttribute;\n\n        const interceptFn = generateRequestInterceptFn(\n            realSetSrc,\n            src => `${src}`,\n            null,\n            type\n        );\n\n        function setSrc(url) {\n            /*\n             * It\'s valid to provide a TrustedScriptURL wrapping the URL but we need to\n             * return a string in the getter, so we convert the url to a string here.\n             *\n             * The url can also be undefined/null so we can\'t just call `.toString()`.\n             *\n             * See #56.\n             */\n            srcMap.set(this, `${url}`);\n            interceptFn.call(this, url);\n            return url;\n        }\n\n        function getSrc() {\n            if (srcMap.get(this) === undefined) {\n                return realGetSrc.call(this);\n            } else {\n                return normaliseUrl(srcMap.get(this));\n            }\n        }\n\n        getSrc.toString = () => realGetSrc.toString();\n        setSrc.toString = () => realSetSrc.toString();\n\n        Object.defineProperty(srcElement, \'src\', {\n            get: getSrc,\n            set: setSrc,\n        });\n\n        srcElement.setAttribute = function(attributeName, value) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                setSrc.call(this, value);\n            } else {\n                realSetAttribute.call(this, attributeName, value);\n            }\n        };\n        srcElement.setAttribute.toString = () => realSetAttribute.toString();\n\n        srcElement.getAttribute = function(attributeName) {\n            if (\n                attributeName.toUpperCase() === \'SRC\' &&\n                srcMap.get(this) !== undefined\n            ) {\n                return srcMap.get(this);\n            } else {\n                return realGetAttribute.call(this, attributeName);\n            }\n        };\n        srcElement.getAttribute.toString = () => realGetAttribute.toString();\n\n        srcElement.removeAttribute = function(attributeName) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                srcMap.delete(this);\n            }\n\n            return realRemoveAttribute.call(this, attributeName);\n        };\n        srcElement.removeAttribute.toString = () =>\n            realRemoveAttribute.toString();\n    }\n})(\n    window.XMLHttpRequest.prototype.open,\n    window.XMLHttpRequest.prototype.send,\n    window.fetch\n);\n</script><script>/*\n * This entire block is wrapped in an IIFE to prevent polluting the scope of the web page with\n * functions created by this extension.\n */\n(function(realOpen, realSend, realFetch) {\n    /*\n     * Sometimes pages overwrite standard objects with global\n     * variables. Where possible, we extract fresh versions from an\n     * iframe.\n     */\n    const iframe = document.createElement(\'iframe\');\n    document.documentElement.appendChild(iframe);\n\n    // Initialise with global values\n    let URL = window.URL;\n    let WeakMap = window.WeakMap;\n    let WeakSet = window.WeakSet;\n    let TextDecoder = window.TextDecoder;\n    let Uint8Array = window.Uint8Array;\n    let decodeURIComponent = window.decodeURIComponent;\n    let URLSearchParams = window.URLSearchParams;\n    let Array = window.Array;\n    let Object = window.Object;\n    let atob = window.atob;\n    let CustomEvent = window.CustomEvent;\n    let performance = window.performance;\n    let JSON = window.JSON;\n\n    /*\n     * Under some circumstances the iframe isn\'t added\n     * successfully. If it has a contentWindow then we can extract the\n     * values from it, but otherwise we fall back on the global\n     * values.\n     */\n    if (iframe.contentWindow) {\n        // Try block because sometimes the CORS prevents access to iframe\n        try {\n            URL = iframe.contentWindow.URL;\n            WeakMap = iframe.contentWindow.WeakMap;\n            WeakSet = iframe.contentWindow.WeakSet;\n            TextDecoder = iframe.contentWindow.TextDecoder;\n            Uint8Array = iframe.contentWindow.Uint8Array;\n            decodeURIComponent = iframe.contentWindow.decodeURIComponent;\n            URLSearchParams = iframe.contentWindow.URLSearchParams;\n            Array = iframe.contentWindow.Array;\n            Object = iframe.contentWindow.Object;\n            atob = iframe.contentWindow.atob;\n            CustomEvent = iframe.contentWindow.CustomEvent;\n            performance = iframe.contentWindow.performance;\n            JSON = iframe.contentWindow.JSON;\n        } catch (error) {\n            // Empty catch block to prevent errors from showing in page logs\n        }\n    }\n\n    // If iframe was added successfully, remove it\n    if (iframe.parentNode) {\n        iframe.parentNode.removeChild(iframe);\n    }\n\n    /*\n     * If any of the values don\'t have the correct type, return and\n     * don\'t attempt any monkey patching. This avoids breaking web\n     * pages.\n     */\n    for (f of [\n        URL,\n        WeakMap,\n        WeakSet,\n        TextDecoder,\n        Uint8Array,\n        decodeURIComponent,\n        URLSearchParams,\n        Array,\n        Object,\n        atob,\n        CustomEvent,\n    ]) {\n        if (typeof f !== \'function\') {\n            return;\n        }\n    }\n\n    for (o of [performance, JSON]) {\n        if (typeof o !== \'object\') {\n            return;\n        }\n    }\n\n    /*\n     * Override common funcions that accept callbacks, such as\n     * setInterval, to keep track of what script each callback comes\n     * from. Skimmers commonly have the actual skimming code in a\n     * callback, so most of the time when we intercept a request,\n     * document.currentScript is null. To solve this, the script is\n     * stored in a closure when the callback is passed, and assigned\n     * to callbackCurrentscript when the callback is run.\n     */\n\n    let callbackCurrentScript = null;\n\n    // Maps callbacks originally passed to patched versions\n    const patchedCallbackMap = new WeakMap();\n\n    // Maps patched callbacks to original versions\n    const originalCallbackMap = new WeakMap();\n\n    /**\n     *  Generates the function with which to override the\n     *  callback-accepting functions.\n     *  @param realFn The real function, e.g. setInterval.\n     *  @param callbackIndex The index of the callback in the\n     *  arguments of realFn. For example, the callback is the first\n     *  argument in setInterval, so callbackIndex should be 0.\n     */\n    function generateAddCallbackFunction(realFn, callbackIndex) {\n        const patched = function(...args) {\n            const script = document.currentScript || callbackCurrentScript;\n\n            const callback = args[callbackIndex];\n\n            // If the callback isn\'t a function, there\'s nothing to patch\n            if (typeof callback !== \'function\') {\n                return realFn.apply(this, args);\n            }\n\n            // If a patch exists, use it instead of creating new function\n            let patchedCallback = patchedCallbackMap.get(callback);\n            if (!patchedCallback) {\n                patchedCallback = function() {\n                    callbackCurrentScript = script;\n                    const retVal = callback.apply(this, [...arguments]);\n                    callbackCurrentScript = null;\n                    return retVal;\n                };\n                patchedCallbackMap.set(callback, patchedCallback);\n                originalCallbackMap.set(patchedCallback, callback);\n            }\n\n            args[callbackIndex] = patchedCallback;\n            return realFn.apply(this, args);\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    const realSetInterval = window.setInterval;\n\n    window.setInterval = generateAddCallbackFunction(realSetInterval, 0);\n\n    const realSetTimeout = window.setTimeout;\n\n    window.setTimeout = generateAddCallbackFunction(realSetTimeout, 0);\n\n    const realPromiseThen = Promise.prototype.then;\n    Promise.prototype.then = generateAddCallbackFunction(realPromiseThen, 0);\n\n    const realPromiseCatch = Promise.prototype.catch;\n    Promise.prototype.catch = generateAddCallbackFunction(realPromiseCatch, 0);\n\n    const realPromiseFinally = Promise.prototype.finally;\n    if (realPromiseFinally) {\n        Promise.prototype.finally = generateAddCallbackFunction(\n            realPromiseFinally,\n            0\n        );\n    }\n\n    const realAddEventListener = EventTarget.prototype.addEventListener;\n\n    EventTarget.prototype.addEventListener = generateAddCallbackFunction(\n        realAddEventListener,\n        1\n    );\n\n    /*\n     * removeEventListener needs to be patched to map the passed\n     * listener to the patched listener.\n     */\n    const realRemoveEventListener = EventTarget.prototype.removeEventListener;\n    EventTarget.prototype.removeEventListener = function(...args) {\n        args[1] = patchedCallbackMap.get(args[1]) || args[1];\n\n        return realRemoveEventListener.apply(this, args);\n    };\n    EventTarget.prototype.removeEventListener.toString = () =>\n        realRemoveEventListener.toString();\n\n    /*\n     * Event listeners are sometimes set by directly assigning to a\n     * property from the GlobalEventHandlers mixin,\n     * e.g. \'submitBtn.onclick\'. For each of the GlobalEventHandlers\n     * implementations that are likely to be relevant, the event\n     * properties likely to be used by skimmers are overridden with\n     * our version that tracks currentScript when assigned.\n     */\n    for (const eventTarget of [\n        HTMLElement.prototype,\n        Document.prototype,\n        window,\n    ]) {\n        for (const eventName of [\n            \'onclick\',\n            \'onblur\',\n            \'onfocus\',\n            \'onchange\',\n            \'oninput\',\n            \'onkeydown\',\n            \'onkeypress\',\n            \'onkeyup\',\n            \'onload\',\n            \'onloadend\',\n            \'onmousedown\',\n            \'onmouseenter\',\n            \'onmouseleave\',\n            \'onmousemove\',\n            \'onmouseout\',\n            \'onmouseover\',\n            \'onmouseup\',\n            \'onpointerdown\',\n            \'onpointermove\',\n            \'onpointerup\',\n            \'onpointerover\',\n            \'onpointerout\',\n            \'onpointerenter\',\n            \'onpointerleave\',\n            \'onsubmit\',\n        ]) {\n            const descriptor = Object.getOwnPropertyDescriptor(\n                eventTarget,\n                eventName\n            );\n\n            if (!descriptor) {\n                continue;\n            }\n\n            const realGetter = descriptor.get;\n            const realSetter = descriptor.set;\n\n            const patchedSetter = generateAddCallbackFunction(realSetter, 0);\n\n            /*\n             * In case a script checks the value of the property after\n             * setting it, the getter has to be patched to return the\n             * original callback.\n             */\n            const patchedGetter = function() {\n                const realValue = realGetter.call(this);\n\n                const originalCallback = originalCallbackMap.get(realValue);\n                if (originalCallback) {\n                    return originalCallback;\n                } else {\n                    return realValue;\n                }\n            };\n\n            patchedGetter.toString = () => realGetter.toString();\n\n            Object.defineProperty(eventTarget, eventName, {\n                get: patchedGetter,\n                set: patchedSetter,\n            });\n        }\n    }\n\n    /*\n     * This script has some dependencies that need to be included\n     * without polluting the global namespace. This is achieved by\n     * inserting them into the source at build time in the\n     * makefile. This is preferable to loading the dependencies via\n     * AJAX, because the latter doesn\'t work if the CSP disallows\n     * unsafe-eval. The following tag tells the makefile where to\n     * insert the dependencies.\n     */\n    // <DEPENDENCIES>\nclass InputAnalyser {\n    constructor(_inputs) {\n        this._inputs = _inputs;\n        this._irrelevantInputs = new WeakSet();\n\n        /*\n         * Not currently using Password because it was causing too\n         * many false positives. Some sites use external auth domains,\n         * and many have misconfigured analytics that collect\n         * passwords.\n         */\n        this.relevantInputs = [new CardNumber()];\n    }\n\n    get relevantInputValues() {\n        const inputs = {};\n        this._inputs.forEach(\n            input => (inputs[input.id || input.name] = input.value)\n        );\n\n        return inputs;\n    }\n\n    get relevantFilledInputs() {\n        return this.allInputs\n            .filter(input => this.isRelevantElement(input))\n            .filter(input => input.value.length >= cons.minInputSize)\n            .map(input => ({\n                id: input.id,\n                name: input.name,\n                value: input.value,\n            }));\n    }\n\n    get allInputs() {\n        this._inputs = [\n            ...document.querySelectorAll(\n                this.relevantInputs.map(i => i.relevantInputTypes).join(\', \')\n            ),\n        ];\n\n        return this._inputs;\n    }\n\n    static getLabelText(input) {\n        if (!input.labels.length) {\n            return \'\';\n        }\n        return stripSpecialChars(input.labels[0].textContent).trim();\n    }\n\n    /**\n     * Takes an HTMLElement and determines whether or not it could be relevant for the purposes of\n     * skimmer detection.\n     * @param {HTMLElement} input The element which is to be checked for relevancy.\n     * @returns {boolean} Returns true if the element is potentially relevant (e.g. a credit card\n     * field or other such sensitive input).\n     */\n    isRelevantElement(input) {\n        // If the element has been identified as irrelevant, return false\n        if (this._irrelevantInputs.has(input)) {\n            return false;\n        }\n\n        // Check the input against all the relevant input types\n        const relevances = this.relevantInputs.map(i => i.test(input));\n\n        /*\n         *  If they all agree that this input is unlikely to become\n         *  relevant, exclude it from further checks and return false\n         */\n        if (relevances.every(i => i === RelevantInput.relevance.NOT_RELEVANT)) {\n            this._irrelevantInputs.add(input);\n            return false;\n        }\n\n        // Return true if any consider the input to be relevant, otherwise false\n        return relevances.some(i => i === RelevantInput.relevance.RELEVANT);\n    }\n}\n/*\n * Input Matcher represents a class that can have matches with relevant inputs.\n * Each class that inherits must implement the findInputMatches method.\n */\nclass InputMatcher {\n    /*\n     * Finds any matches with the list of inputs.\n     * @param inputs. An object whose values represent relevant inputs.\n     * @return {[String]} The keys of the inputs that were found.\n     */\n    findInputMatches(inputs) {\n        throw new Error(\'findInputMatches has not been implemented.\');\n    }\n\n    /**\n     * Determines whether one or more values in needles are present in any of\n     * the keys of the haystacks.\n     * Checks both raw input and base64 encoded versions of the needles.\n     * @param needles An object. The haystacks are searched for the values of\n     * this object.\n     * @param haystacks The array of strings in which the needle(s) may reside.\n     * A string may optionally be supplied if only one value is present.\n     * @returns {[String]} The keys of the needles that were found in one or\n     * more haystacks in either raw or base64 encoded form.\n     */\n    static atLeastOneNeedleInHaystack(needles, haystacks) {\n        if (\n            needles === undefined ||\n            needles.length === 0 ||\n            haystacks === undefined\n        ) {\n            return [];\n        }\n\n        const searchArray = Array.isArray(haystacks) ? haystacks : [haystacks];\n\n        return Object.entries(needles)\n            .filter(([_, needle]) =>\n                searchArray.some(haystack => {\n                    if (isBase64Encoded(haystack)) {\n                        if (atob(haystack).indexOf(needle) !== -1) {\n                            return true;\n                        }\n                    }\n                    return haystack.indexOf(needle) !== -1;\n                })\n            )\n            .map(([input, _]) => input);\n    }\n}\n/*\n * Matcher for an object that cannot contain any inputs.\n */\nclass EmptyMatcher extends InputMatcher {\n    findInputMatches(inputs) {\n        return [];\n    }\n}\n\nclass URLParamsMatcher extends InputMatcher {\n    constructor(url) {\n        super();\n        this._url = url;\n    }\n\n    findInputMatches(inputs) {\n        try {\n            const parsedUrl = new URL(this._url);\n            return InputMatcher.atLeastOneNeedleInHaystack(inputs, [\n                ...parsedUrl.searchParams.values(),\n            ]);\n        } catch (e) {\n            return [];\n        }\n    }\n}\n\nclass StringBodyMatcher extends InputMatcher {\n    constructor(str) {\n        super();\n        this._str = str;\n    }\n\n    findInputMatches(inputs) {\n        let decoded;\n        try {\n            decoded = decodeURIComponent(this._str);\n        } catch (e) {\n            decoded = this._str;\n        }\n\n        const searchParams = new URLSearchParams(decoded);\n        const parsedQuery = {};\n        const parsedValues = [];\n\n        for (const [key, value] of searchParams.entries()) {\n            parsedQuery[key] = value;\n            parsedValues.push(value);\n        }\n\n        if (parsedQuery !== {} && parsedValues.indexOf(null) === -1) {\n            return InputMatcher.atLeastOneNeedleInHaystack(\n                inputs,\n                parsedValues\n            );\n        }\n        return InputMatcher.atLeastOneNeedleInHaystack(inputs, decoded);\n    }\n}\n\nclass FormBodyMatcher extends InputMatcher {\n    constructor(formData) {\n        super();\n        this._formData = formData;\n    }\n\n    findInputMatches(inputs) {\n        return InputMatcher.atLeastOneNeedleInHaystack(\n            inputs,\n            Object.values(this._formData).flat()\n        );\n    }\n}\n/**\n * Takes a string and removes a variety of \'special\' characters like punctuation.\n * @param str The string to be modified.\n * @returns {string} The resulting string without any special characters.\n */\nfunction stripSpecialChars(str) {\n    return str.replace(/[`~!@#$%^&*()_|+\\-=?;:\'\",.<>}{\\][\\\\/]/gi, \'\');\n}\n\n/**\n * Attempts to identify where a string is base-64 encoded or not.\n * @param string The raw string to be examined.\n * @returns {boolean} Returns true if the string matches a base-64 string. False otherwise.\n */\nfunction isBase64Encoded(string) {\n    return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(\n        string\n    );\n}\n\n// Computes the base64 sha512/sha224 hash of some text, removing trailing ==\nfunction base64hash(hashFunction, text) {\n    return btoa(\n        hashFunction\n            .array(text)\n            .map(char => String.fromCharCode(char))\n            .join(\'\')\n    ).slice(0, -2);\n}\n\nfunction normaliseUrl(str) {\n    const link = document.createElement(\'a\');\n    link.href = str;\n    return link.href;\n}\n\nfunction stripCredentials(urlstr) {\n    try {\n        const url = new URL(urlstr);\n        const params = url.searchParams;\n        params.forEach((_, key) => params.set(key, \'\'));\n        return url.toString();\n    } catch (e) {\n        return urlstr;\n    }\n}\n\n/**\n * Check if two URLs match regardless of any partial encoding.\n *\n * Uses decodeURI which can throw a URIError when url is malformed.\n */\nfunction urlsMatch(first, second) {\n    try {\n        return decodeURI(first) === decodeURI(second);\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction sendMessage(type, data, tabId, options) {\n    if (tabId) {\n        chrome.tabs.sendMessage(tabId, { data, type }, options);\n    } else {\n        chrome.runtime.sendMessage({ data, type });\n    }\n}\n\n// Encodes the given input using base64url as defined by RFC4648 section 5.\nfunction base64Url(input) {\n    return btoa(input)\n        .replace(\'+\', \'-\')\n        .replace(\'/\', \'_\');\n}\n/*\n * Netcraft Extension\n * constants JavaScript\n */\n\n/* eslint sort-keys: \'off\' */\n/* sort-keys is disabled as the constants are separated by type */\n\n// Aligned values here make for easier reading, so we disable prettier\n\n// prettier-ignore\nconst cons = (function() {\n    const cryptojackersUrl = \'https://www.netcraft.com/apps/help/glossary/#crypto-miners\';\n    const skimmersUrl = \'https://www.netcraft.com/apps/help/glossary/#credit-card-skimmers\';\n    const evilResourceCrime = \'attempted to load\';\n\n    return {\n        fields: [\n            \'topsites\',\n            \'country\',\n            \'netblock\',\n            \'hoster\',\n            \'firstseen\',\n            \'rank\',\n            \'risk\',\n            \'uses_sslv3\',\n            \'pfs\',\n            \'heartbleed\',\n            \'heartbleed_message\',\n        ],\n\n        // options that can be toggle in options.js and installed flag\n\n        toggleOptions: [\n            \'installed\',\n            \'block\',\n            \'xss\',\n            \'suspicious\',\n            \'other-malicious-scripts\',\n            \'credential-leaks\',\n            \'analytics\',\n            \'cryptojackers\',\n            \'skimmers\',\n        ],\n\n        // domains\n        mirror:  \'https://mirror2.extension.netcraft.com/\',\n        toolbar: \'https://mirror.toolbar.netcraft.com/\',\n\n        // links and faqs\n        blogArchive:      \'https://news.netcraft.com/archives/\',\n        browserApp:       \'https://www.netcraft.com/apps/browser/\',\n        cryptojackersUrl,\n        extensionStore:   \'https://addons.mozilla.org/en-us/firefox/addon/netcraft-toolbar?src=external-blocked_visit-rating\',\n        heartbleedUrl:    \'https://www.netcraft.com/apps/help/glossary/#heartbleed\',\n        logBlockedVisit:  \'https://toolbar.netcraft.com/blocked_visit\',\n        netblock:         \'https://sitereport.netcraft.com/netblock?q=\',\n        pfsUrl:           \'https://www.netcraft.com/apps/help/glossary/#pfs\',\n        report:           \'https://sitereport.netcraft.com/?url=\',\n        reportAPI:        \'https://report.netcraft.com/api/v2/report/urls\',\n        reportMistake:    \'https://report.netcraft.com/report/mistake?url=\',\n        skimmersUrl,\n        sslv3Url:         \'https://www.netcraft.com/apps/help/glossary/#sslv3\',\n        submissionURL:    \'https://report.netcraft.com/submission/\',\n        topsites:         \'https://trends.netcraft.com/topsites?s=\',\n\n        // paths\n        block:              \'blocked.html\',\n        check:              \'check_url/v4/\',\n        danger:             \'Images/danger.png\',\n        embed:              \'/embedded\',\n        evilJSPatternsFeed: \'eviljspatterns\',\n        skimmerDomainWhitelistFeed: \'eviljsskimmerwhitelist\',\n        evilResourcesFeed:  \'blockdb/netcraft_extension_evil_resource_encrypted-yppL7bVh/\',\n        flags:              \'Images/flags/\',\n        force:              \'/dodns\',\n        heartbleed:         \'Images/heartbleed.png\',\n        info:               \'/info\',\n        poodlePost:         \'2014/10/15/googles-poodle-affects-oodles.html\',\n        warning:            \'Images/warning.png\',\n\n        // feed constants\n        evilResourceTypes: [\'cryptojacker\', \'skimmer\'],\n        evilResourcesSalt: \'C4jhYHxW\',\n        maxRevisions:      5,\n        randomSaltLength:  8,\n\n        // mappings of url_type => information\n        urlTypeInformationMappings: {\n            \'malicious_javascript/cryptojacker\': [\'web miners\',             cryptojackersUrl],\n            \'malicious_javascript/skimmer\':      [\'shopping site skimmers\', skimmersUrl],\n        },\n\n        // mappings of evilResourceTypes => user options\n        evilResourceOptionMappings: {\n            \'cryptojacker\': \'cryptojackers\',\n            \'skimmer\':      \'skimmers\'\n        },\n\n        // mappings of user options => url_type\n        optionUrlTypeMappings: {\n            \'block\':                   /^(?!malicious_javascript:)/,\n            \'cryptojackers\':           /^malicious_javascript\\/cryptojacker$/,\n            \'other-malicious-scripts\': /^malicious_javascript\\/(?!(skimmer|cryptojacker)$)/,\n            \'skimmers\':                /^malicious_javascript\\/skimmer$/,\n        },\n\n        // report constants\n        malwareSourceEmail:  \'extension-reported-malware@netcraft.com\',\n        malwareSourceUUID:   \'mwcCcApi89cobY9loJ9rWJ9HuOhlpwMj\',\n        extensionSourceUUID: \'qBEQ3NhEnwK4sypmuSxBJzRqpxZLRXxe\',\n\n        // report error default message\n        reportErrorMessage: \'An error occured with your submission. Please try again.\',\n\n        // mapping from report API errors to displayed messages\n        reportErrorMappings: {\n            \'Duplicate of a recent submission\': \'This URL has already been reported.\',\n            \'Supplied email is not valid\': \'Invalid email address. Please correct it and try again.\',\n        },\n\n        whitelistErrorMessages: {\n            invalidHostname: \'Invalid hostname, please correct it and try again\',\n            duplicate: \'Protection is already disabled for this site\',\n        },\n\n        // browserAction icon paths\n        iconNormal: {\n            16: \'Images/action-normal-16.png\',\n            24: \'Images/action-normal-24.png\',\n            32: \'Images/action-normal-32.png\'\n        },\n        iconDanger: {\n            16: \'Images/action-danger-16.png\',\n            24: \'Images/action-danger-24.png\',\n            32: \'Images/action-danger-32.png\'\n        },\n        iconWarning: {\n            16: \'Images/action-warning-16.png\',\n            24: \'Images/action-warning-24.png\',\n            32: \'Images/action-warning-32.png\'\n        },\n        iconDisabled: {\n            16: \'Images/action-disabled-16.png\',\n            24: \'Images/action-disabled-24.png\',\n            32: \'Images/action-disabled-32.png\'\n        },\n\n        // Extension internal message types\n        capturedIntercept:  \'interceptDetailsCaptured\',\n        confirmedIntercept: \'interceptDetailsConfirmed\',\n        contentReady:       \'contentScriptReady\',\n        leaksEnabled:       \'credentialLeaksEnabled\',\n        reportURL:          \'report\',\n        reqBlock:           \'block\',\n        reqDetails:         \'details\',\n        reqForce:           \'force\',\n        reqPopUp:           \'popup\',\n        reqPrintTimingData: \'time\', //debug\n        reqInputs:          \'requestInputValues\',\n        sendInputs:         \'sendInputs\',\n        sendIntercept:      \'sendInterceptDetails\',\n        updateOption:       \'updateOption\',\n        updateWhitelist:    \'whitelist\', // disable button whitelist\n\n        // Status codes\n        errEvilResource: \'EVILRESOURCE\',\n        errNoDetails:    \'NODETAILS\',\n        errNoInfo:       \'NOINFO\',\n        errBlocked:      \'BLOCKED\',\n        errPending:      \'PENDING\',\n        errServer:       \'ERROR\',\n        errSuspicious:   \'SUSPICIOUS\',\n        errTimeout:      \'TIMEOUT\',\n        errXSS:          \'XSS\',\n        noError:         \'OK\',\n\n        // Valid protocols (it is a regex match so ^ matches start of string)\n        protocols:    [\'^http\', \'^https\', \'^ftp\'],\n\n        // Human readable messages\n        msgNa:           \'NA\',\n        msgNewSite:      \'New Site\',\n        msgNoInfo:       \'Site information not available\',\n        msgNoServer:     \'Unable to contact Netcraft servers\',\n        msgSuspicious:   \'Suspicious URL Detected\',\n        msgXSS:          \'Suspected XSS Attack\',\n        msgBlocked:      {\n            \'malicious_javascript/skimmer\': {\n                title: \'Shopping Site Skimmer Detected\',\n                description: \'shopping site skimmer\'\n            },\n            \'malicious_javascript/dropsite\': {\n                title: \'Potential Credential Leak Detected\',\n                description: \'potential credential leak\'\n            },\n            \'malicious_javascript/cryptojacker\': {\n                title: \'Web Miner Detected\',\n                description: \'web miner\'\n            },\n            \'malware_c2/infrastructure\': {\n                title: \'Malicious URL\',\n                description: \'malicious page\'\n            },\n            \'default\': {\n                title: \'Suspected Phishing\',\n                description: \'suspected phishing site\'\n            },\n        },\n\n        // Human readable reasons\n        urlTypeCrimeMappings: {\n            \'malicious_javascript/skimmer\':      evilResourceCrime,\n            \'malicious_javascript/cryptojacker\': evilResourceCrime,\n            \'malicious_javascript/dropsite\':     \' is blocked because we detected that sensitive information was leaked from\',\n        },\n\n        // Timeout function value to clear cache etc.\n        timeout:      300000,\n\n        // Skimmer whitelist update interval (1 day)\n        skimmerWhitelistInterval: 1000 * 60 * 60 * 24,\n\n        // Google Analytics account\n        account:      \'UA-2150242-8\',\n        gaCategory:   \'Popup load times\',\n\n        // InputAnalyser constants\n        minInputSize: 5,\n\n        // First addresses of the private IPv4 netblocks\n        privateIPv4Blocks: [\n            \'10.0.0.0\',\n            \'172.16.0.0\',\n            \'192.168.0.0\'\n        ],\n\n        // Sync storage key for disable for this site button whitelist\n        disableButtonWhitelistKey: \'disableButtonWhitelist\',\n\n        // Local storage key for local credential leak detection whitelist\n        localCredentialLeakWhitelistKey: \'localCredentialLeakWhitelist\',\n\n        // Whitelisted fileName regexes for credential leak detection\n        credentialLeakFileNameWhitelist: [\n            /^https?:\\/\\/googleads.g.doubleclick.net\\//,\n        ],\n    };\n})();\n\n/*\n * recursively prevents any property or sub-property in cons from:\n *   - being added;\n *   - being removed; or\n *   - being modified.\n */\nfunction deepFreeze(object) {\n    Object.freeze(object);\n\n    for (const name of Object.getOwnPropertyNames(object)) {\n        if (typeof object[name] === \'object\') {\n            deepFreeze(object[name]);\n        }\n    }\n}\n\ndeepFreeze(cons);\n/*\n * RelevantInput objects check whether given form inputs should be\n * treated as sensitive data.\n *\n * This should be treated as an abstract class. Only the subclasses\n * are intended to be instantiated.\n */\nclass RelevantInput {\n    /*\n     * Enum of the possible return values of test():\n     * RELEVANT:\n     *     Input is relevant now, outgoing requests should be\n     *     checked for the contained value (subject to minimum length).\n     * POTENTIALLY RELEVANT:\n     *     Input should not be considered relevant now, but may be in\n     *     future so should continue to be checked.\n     * NOT_RELEVANT:\n     *     Input is not relevant and will not become relevant, so can\n     *     be ommitted from further checks to optimise performance.\n     */\n    static get relevance() {\n        return {\n            NOT_RELEVANT: 1,\n            POTENTIALLY_RELEVANT: 2,\n            RELEVANT: 3,\n        };\n    }\n\n    /*\n     * relevantInputTypes (string) - a comma separated list of\n     * selectors that describe the types of input that should be\n     * considered relevant.\n     */\n    constructor(relevantInputTypes) {\n        this.relevantInputTypes = relevantInputTypes;\n    }\n\n    /*\n     * Checks whether any of the given input\'s \'labels\' (the ID, name,\n     * or actual label) match the given regex.\n     */\n    static testInputLabel(regex, input) {\n        const labels = [\n            input.id,\n            input.name,\n            InputAnalyser.getLabelText(input),\n        ];\n        return labels.some(label => regex.test(label));\n    }\n}\n\n/*\n * RelevantInput for passwords.\n */\nclass Password extends RelevantInput {\n    constructor() {\n        super(\'input[type=password]\');\n    }\n\n    /*\n     * Passwords are verified purely by their input type.\n     */\n    test(input) {\n        if (input.matches(this.relevantInputTypes)) {\n            return RelevantInput.relevance.RELEVANT;\n        } else {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n    }\n}\n\n/*\n * RelevantInput for card numbers.\n */\nclass CardNumber extends RelevantInput {\n    constructor() {\n        // Inputs with no type attribute act like text inputs\n        super(\n            \'input[type=text], input[type=tel], input[type=number], input:not([type])\'\n        );\n\n        // Definitely card number labels\n        this.inputLabelRegexes = [\n            /(cc|card).?(num|no)/i,\n            /カード番号/,\n            /Номер.*карты/i,\n            /信用卡卡號/i,\n            /信用卡号码/i,\n        ];\n\n        /*\n         * Matches incomplete card numbers, because ideally the\n         * skimmer should be detected before the victim gives away\n         * most of their card number.\n         */\n        this.inputValueRegexes = [/^(\\d{4}[-\\s]?){0,4}\\d{1,4}$/];\n\n        // Probably card number labels\n        this.suspectInputLabelRegexes = [\n            /card/i,\n            /tarjeta/i,\n            /carte/i,\n            /карты/i,\n            /卡/i,\n            /カード/i,\n            /카드/i,\n            /credit[^s]/i,\n        ];\n\n        // If it matches one of these, probably not a card number\n        this.inputLabelExceptionRegexes = [\n            /post/i,\n            /code/i,\n            /zip/i,\n            /acc(oun)?t?[-_]?n(um|o)/i,\n            /credit[-_]?(sum|limit)/i,\n            /loyalty/i,\n            /gift/i,\n            /idcard/i,\n            /cardid/i,\n            /club/i,\n            /sku/i,\n            /(\\b|_)otp/i,\n            /price/i,\n            /amount/i,\n            /service/i,\n            /picard/i,\n            /search/i,\n            /contact/i,\n            /callme/i,\n            /chat/i,\n            /activate/i,\n            /msisdn/i,\n        ];\n    }\n\n    test(input) {\n        if (!input.matches(this.relevantInputTypes)) {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n\n        // Return NOT_RELEVANT if label matches an exception regex\n        if (\n            this.inputLabelExceptionRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n\n        // Return RELEVANT if label clearly states card number\n        if (\n            this.inputLabelRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            return RelevantInput.relevance.RELEVANT;\n        }\n\n        /*\n         * If label suggests card number, return RELEVANT if value\n         * also suggests card number or POTENTIALLY_RELEVANT if\n         * not. Otherwise, return NOT_RELEVANT as this element will\n         * not become relevant unless a label changes (unlikely).\n         */\n        if (\n            this.suspectInputLabelRegexes.some(r =>\n                RelevantInput.testInputLabel(r, input)\n            )\n        ) {\n            if (this.inputValueRegexes.some(r => r.test(input.value))) {\n                return RelevantInput.relevance.RELEVANT;\n            } else {\n                return RelevantInput.relevance.POTENTIALLY_RELEVANT;\n            }\n        } else {\n            return RelevantInput.relevance.NOT_RELEVANT;\n        }\n    }\n}\n\n    const inputAnalyser = new InputAnalyser();\n\n    const resourceBlockList = new Set([\n                            \"6C590F97DDB1A11AE795B3050E4C3866\", \"D285BE79786E2ECE396941331C5BA65E\", \"59DF8523520DE7F08FB53D3D12BD94F5\", \"1FB784EBAFDA1618023756BF5B6D7E46\", \"0D6E1B5F03B81F5C37813B22E29C4FE2\", \"EFAB0ECE9383D50C555B8168C3E2A4DD\", \"AC8A4B13A411A4E43E61D0A034FA4A55\", \"25A781F6D1F94EEACCF1E00E844D9E14\", \"3920B04ED0D215EA738F524F60485F23\", \"05F4A2D3161A92EB82A44F15FC2A3AEC\", \"C5A1BE7B3855B0A25275B2877E005CB9\", \"00069CEC347076721EBE2257978A0A33\", \"9A513130D02F360BBDC16D8AC0C579D8\", \"7CDB2A3A29E50F90C1000252EF523AD6\", \"25CE912FAF2376026AAD0174AD5A0329\", \"739E1F67AA5F64A06FC469F6030802D4\", \"F49368C9A05C29EF49411999C5987F2C\", \"AB2DD89FB892155275D8780CF527BC93\", \"CFE758EB18FBFE70B1F690241B75D2B1\", \"E43C3463B40A790545F4149FF06B419E\", \"8B6D4C76A7CCF5EB720660B8B2D3F5EB\", \"45573B3E2F613AF5BF2145DF004BABD1\", \"E3DB4E968B371DE94CE607199A062F39\", \"A4EFF1240C5DE614B394FB738CBD28E2\", \"F42D10E84B9A69A8A24062319F425A83\", \"90646BE39982594A58EDF5D2630CCFFD\", \"669853BEA9B84EA51D7302A7FBFF5BC3\", \"0BA566B4777526F451108100107DC5ED\", \"AB19E12BFFBC47244BDCC2403EAA9037\", \"61DAF8537240639277FD1A5908A131A4\", \"6F1DC1309CFE734C038685D8F17A3B6B\", \"DCC3DB001334F29C1C0208D15E9EAB49\", \"2DBD7D54022C28273ADE3147B8EEB076\", \"AE3760E93412C65EC5BF63537B037019\", \"4CD96A830DAC0E0B5EBE578B0DD870AE\", \"4C70569BBE1821F5655382660807FA77\", \"05ECA17FF0EA78669ED43B2BD7B63571\", \"44E503B3621513B9D3666F284E4DF47A\", \"8F1C92E7665860AE70CBB228DED86111\", \"D69552885B55C4097E5725F805815639\", \"B4357A9D889E974CDF3F9740E48F9A1B\", \"5709DC60CFC365B7064B06C709A9D32B\", \"14E8729CBB16AC3625EAA0D7A679E56E\", \"793D4DABAAA377A12F2B6CE88C5FB389\", \"C5D1DCE2AE0D8CE79054FC16CB13D65A\", \"89F91750BD4A9397D1F8CE183757FF70\", \"50B8BC213FB188A687173C1A0A77EA57\"\n                        ]);\n    const evilJSPatterns = new Set([\n                            \n                        ]);\n\n    // Extract body object\n    function extractBody(data) {\n        if (!data) {\n            return Promise.resolve({});\n        }\n        if (FormData.prototype.isPrototypeOf(data)) {\n            const formData = {};\n            for (const [key, value] of data.entries()) {\n                formData[key] = value;\n            }\n            return Promise.resolve({ formData });\n        }\n        // Create a temporary Request object to parse the body\n        const tempRequest = new Request(\'\', { body: data, method: \'POST\' });\n        return extractBodyFromRequest(tempRequest);\n    }\n\n    function extractBodyFromRequest(request) {\n        // Try to extract as text\n        return request\n            .text()\n            .then(text => {\n                return { text };\n            })\n            .catch(e => {\n                return {};\n            });\n    }\n\n    /**\n     * This function checks if the request hostname/URL is in the blocklist.\n     * Used when identifying whether or not to forward request to background\n     * script.\n     * @param hostname Hostname to check against blocklist.\n     * @param url Request url to check against evil JS patterns\n     * @returns boolean true if hostname is in the blocklist, false if\n     * it is not in blocklist or if blocklist is not provided.\n     */\n    function requestInBlocklist(hostname, url) {\n        if (resourceBlockList === undefined || evilJSPatterns === undefined) {\n            return false;\n        }\n        if (testEvilJS(url.replace(/^(https?:)?\\/\\//i, \'\'))) {\n            return true;\n        }\n        while (!testEvilJS(hostname) && !testResource(hostname)) {\n            const newHostname = hostname.replace(/^.+?\\./g, \'\');\n\n            if (hostname === newHostname) return false;\n\n            hostname = newHostname;\n        }\n        return true;\n    }\n\n    function testEvilJS(url) {\n        return evilJSPatterns.has(base64hash(sha512, url));\n    }\n\n    function testResource(hostname) {\n        return resourceBlockList.has(\n            md5(cons.evilResourcesSalt + hostname).toUpperCase()\n        );\n    }\n\n    /*\n     * Implementations to overwrite window.XMLHttpRequest.prototype.open,\n     * window.XMLHttpRequest.prototype.send and window.fetch. These are (to my knowledge) the two\n     * APIs that can be used to make requests within Javascript. If there are more, then they will\n     * also need to be overridden and intercepted like below.\n     */\n\n    /**\n     * This function generates functions to be used for wrapping and intercepting network\n     * requests, namely from XMLHttpRequest and the Fetch API. It works by taking the real\n     * function we want to intercept the request for, and then uses document.currentScript to work\n     * out which file initiated the request. It sends those details to the content script which\n     * forwards it to the background script, and then we await a confirmation of receipt from the\n     * background page. Finally, the real request is sent by forwarding parameters to the original\n     * function passed in.\n     * @param realFn A reference to the real function that is being intercepted.\n     * @param getRequestUrlFn A function used to get the request URL from the object. This is\n     * necessary as the mechanisms for different objects differs depending on implementation.\n     * @param getRequestBodyPr A function used to get the request body from the object. Returns\n     * a promise. It should be null if it\'s not applicable (e.g. if it sets the src of an image or script).\n     * @param type The type of request, which will be a ResourceType:\n     * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType\n     * @returns {function(...[*]=): PromiseLike<any | never>}\n     */\n    function generateRequestInterceptFn(\n        realFn,\n        getRequestUrlFn,\n        getRequestBodyPr,\n        type\n    ) {\n        const patched = function(...args) {\n            /*\n             * We create a link DOM element in order to correctly generate the\n             * absolute path of the request, automatically transforming from a\n             * relative path if necessary.\n             */\n            const requestUrl = normaliseUrl(\n                getRequestUrlFn.bind(this)(...args)\n            );\n\n            let requestHostname;\n            let pageHostname;\n\n            try {\n                requestHostname = new URL(requestUrl).hostname;\n                pageHostname = new URL(location.href).hostname;\n            } catch (e) {\n                return realFn.apply(this, args);\n            }\n\n            /*\n             * If hostname is in the blocklist, the request should always be\n             * sent to the background script.\n             */\n            // eslint-disable-next-line prefer-const\n            let forceStall = false;\n\n            if (!forceStall) {\n                /*\n                 * Delaying outgoing requests sometimes causes problems\n                 * with elements such as video players and dynamically\n                 * loaded images, so it should only be done when\n                 * necessary. Before inputAnalyser is defined and when the\n                 * request is to the same hostname as the page, this can\n                 * be skipped.\n                 */\n                if (\n                    requestHostname === pageHostname ||\n                    inputAnalyser === undefined\n                ) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * It can also be skipped if there is no sensitive\n                 * information entered into a form on the page yet.\n                 */\n                const relevantFilledInputs = inputAnalyser.relevantFilledInputs;\n                if (!relevantFilledInputs.length) {\n                    return realFn.apply(this, args);\n                }\n\n                /*\n                 * For requests with no body, it\'s only necessary to delay\n                 * them when the sensitive information from the form\n                 * inputs is present in the request URL.\n                 */\n                const ia = new InputAnalyser(relevantFilledInputs);\n                const relevantInputValues = ia.relevantInputValues;\n                const urlMatcher = new URLParamsMatcher(requestUrl);\n\n                if (\n                    getRequestBodyPr === null &&\n                    !urlMatcher.findInputMatches(relevantInputValues).length\n                ) {\n                    return realFn.apply(this, args);\n                }\n            }\n\n            /*\n             * Create a promise that resolves immediately if the request body\n             * does not apply to this request.\n             */\n            if (getRequestBodyPr === null) {\n                getRequestBodyPr = function() {\n                    return Promise.resolve({});\n                };\n            }\n\n            let fileName;\n            if (document.currentScript && document.currentScript.src) {\n                fileName = document.currentScript.src;\n            } else if (callbackCurrentScript && callbackCurrentScript.src) {\n                fileName = callbackCurrentScript.src;\n            } else {\n                fileName = location.href;\n            }\n\n            return getRequestBodyPr\n                .bind(this)(...args)\n                .then(requestBody => {\n                    /*\n                     * Generate the information object to send through to the\n                     * content script including the request URL, the initiator\n                     * filename and a timestamp relative to the page load.\n                     */\n                    const detail = {\n                        fileName,\n                        requestBody,\n                        requestUrl,\n                        timestamp: performance.now(),\n                        type,\n                    };\n\n                    document.dispatchEvent(\n                        new CustomEvent(cons.capturedIntercept, {\n                            detail,\n                        })\n                    );\n\n                    return new Promise(resolve => {\n                        /*\n                         * Send the initiator\'s file name and other info to the\n                         * background script, then wait for confirmation that the\n                         * details made it to the background page before calling\n                         * the real function. This is to avoid any race conditions\n                         * whereby the details haven\'t been added to the tab data\n                         * before onBeforeRequest/onBeforeSendHeaders gets called.\n                         */\n                        document.addEventListener(\n                            cons.confirmedIntercept,\n                            function compareData(customEvent) {\n                                const receivedData = JSON.parse(\n                                    customEvent.detail\n                                );\n                                if (detailsMatch(detail, receivedData)) {\n                                    /*\n                                     * If it\'s the correct confirmation, then we\n                                     * can resolve the promise and call the real\n                                     * function in the finally block.\n                                     */\n                                    document.removeEventListener(\n                                        cons.confirmedIntercept,\n                                        compareData,\n                                        false\n                                    );\n                                    resolve();\n                                }\n                            }\n                        );\n                    }).then(() => realFn.apply(this, args));\n                });\n        };\n        // make toString() look native, #34\n        patched.toString = () => realFn.toString();\n        return patched;\n    }\n\n    function detailsMatch(first, second) {\n        const comparisonKeys = [\'fileName\', \'requestUrl\', \'timestamp\', \'type\'];\n        for (const key of comparisonKeys) {\n            if (first[key] !== second[key]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    window.fetch = generateRequestInterceptFn(\n        realFetch,\n        request => (typeof request === \'string\' ? request : request.url),\n        (request, options) => {\n            // Check if argument is a Request object.\n            if (request instanceof Request) {\n                return extractBodyFromRequest(request);\n            }\n            return options ? extractBody(options.body) : Promise.resolve({});\n        },\n        \'xmlhttprequest\'\n    );\n\n    window.XMLHttpRequest.prototype.send = generateRequestInterceptFn(\n        realSend,\n        function getRequestUrl() {\n            return this._requestUrl;\n        },\n        extractBody,\n        \'xmlhttprequest\'\n    );\n\n    /*\n     * This function is intercepted so that we can capture the value of the\n     * request URL, since it isn\'t present in the arguments for\n     * XMLHttpRequest.send (above).\n     */\n    window.XMLHttpRequest.prototype.open = function open(...args) {\n        [, this._requestUrl] = args;\n        realOpen.apply(this, args);\n    };\n    window.XMLHttpRequest.prototype.open.toString = () => realOpen.toString();\n\n    /*\n     * Monkeypatches for setting the src of images and scripts, an\n     * indirect way of sending requests that is often employed by\n     * skimmers. The getters are also overridden to make the behaviour\n     * seem identical to the original implementation. Otherwise, if a\n     * script were to set an src and immediately read it, it would\n     * come back as the old value.\n     */\n\n    /*\n     * WeakMap maps an object to a value. This is used to store the\n     * new src of an object when its true src property may not yet\n     * have been set.\n     */\n    const srcMap = new WeakMap();\n    const requestElements = [\n        {\n            srcElement: HTMLImageElement.prototype,\n            type: \'image\',\n        },\n        {\n            srcElement: HTMLScriptElement.prototype,\n            type: \'script\',\n        },\n    ];\n\n    for (const { srcElement, type } of requestElements) {\n        const realGetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .get;\n        const realSetSrc = Object.getOwnPropertyDescriptor(srcElement, \'src\')\n            .set;\n        const realGetAttribute = srcElement.getAttribute;\n        const realSetAttribute = srcElement.setAttribute;\n        const realRemoveAttribute = srcElement.removeAttribute;\n\n        const interceptFn = generateRequestInterceptFn(\n            realSetSrc,\n            src => `${src}`,\n            null,\n            type\n        );\n\n        function setSrc(url) {\n            /*\n             * It\'s valid to provide a TrustedScriptURL wrapping the URL but we need to\n             * return a string in the getter, so we convert the url to a string here.\n             *\n             * The url can also be undefined/null so we can\'t just call `.toString()`.\n             *\n             * See #56.\n             */\n            srcMap.set(this, `${url}`);\n            interceptFn.call(this, url);\n            return url;\n        }\n\n        function getSrc() {\n            if (srcMap.get(this) === undefined) {\n                return realGetSrc.call(this);\n            } else {\n                return normaliseUrl(srcMap.get(this));\n            }\n        }\n\n        getSrc.toString = () => realGetSrc.toString();\n        setSrc.toString = () => realSetSrc.toString();\n\n        Object.defineProperty(srcElement, \'src\', {\n            get: getSrc,\n            set: setSrc,\n        });\n\n        srcElement.setAttribute = function(attributeName, value) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                setSrc.call(this, value);\n            } else {\n                realSetAttribute.call(this, attributeName, value);\n            }\n        };\n        srcElement.setAttribute.toString = () => realSetAttribute.toString();\n\n        srcElement.getAttribute = function(attributeName) {\n            if (\n                attributeName.toUpperCase() === \'SRC\' &&\n                srcMap.get(this) !== undefined\n            ) {\n                return srcMap.get(this);\n            } else {\n                return realGetAttribute.call(this, attributeName);\n            }\n        };\n        srcElement.getAttribute.toString = () => realGetAttribute.toString();\n\n        srcElement.removeAttribute = function(attributeName) {\n            if (attributeName.toUpperCase() === \'SRC\') {\n                srcMap.delete(this);\n            }\n\n            return realRemoveAttribute.call(this, attributeName);\n        };\n        srcElement.removeAttribute.toString = () =>\n            realRemoveAttribute.toString();\n    }\n})(\n    window.XMLHttpRequest.prototype.open,\n    window.XMLHttpRequest.prototype.send,\n    window.fetch\n);\n</script>\n                    <style font-name=\"MS PMincho\">\n                        @font-face {\n                            font-family: \"MS PMincho\";\n                            src: url(https://sys.emarketing.pe/builder/font/ms-pmincho.ttf);\n                        }\n                    </style>\n                \n                    <style font-name=\"MS Mincho\">\n                        @font-face {\n                            font-family: \"MS Mincho\";\n                            src: url(https://sys.emarketing.pe/builder/font/ms-mincho.ttf);\n                        }\n                    </style>\n                <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"><link href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Outlined\" rel=\"stylesheet\">\n                \n            \n                \n            \n                \n            </head>\n  <body class=\"builderjs-layout\" spellcheck=\"false\">\n\n\n\n    <meta charset=\"UTF-8\">\n    <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\">\n    <meta name=\"x-apple-disable-message-reformatting\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta content=\"telephone=no\" name=\"format-detection\">\n    <title></title>\n    <!--[if (mso 16)]>\n    <style type=\"text/css\">\n    a {text-decoration: none;}\n    </style>\n    <![endif]-->\n    <!--[if gte mso 9]><style>sup { font-size: 100% !important; }</style><![endif]-->\n    <!--[if gte mso 9]>\n<xml>\n    <o:OfficeDocumentSettings>\n    <o:AllowPNG></o:AllowPNG>\n    <o:PixelsPerInch>96</o:PixelsPerInch>\n    </o:OfficeDocumentSettings>\n</xml>\n<![endif]-->\n    <!--[if !mso]><!-- -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Orbitron&amp;display=swap\" rel=\"stylesheet\">\n    <!--<![endif]-->\n\n\n\n    <div class=\"es-wrapper-color\">\n        <!--[if gte mso 9]>\n			<v:background xmlns:v=\"urn:schemas-microsoft-com:vml\" fill=\"t\">\n				<v:fill type=\"tile\" color=\"#f3f3f3\"></v:fill>\n			</v:background>\n		<![endif]-->\n        <table class=\"es-wrapper\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n            <tbody>\n                <tr>\n                    <td class=\"esd-email-paddings\" valign=\"top\">\n                        <table class=\"es-content esd-header-popover\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\">\n                            <tbody>\n                                <tr>\n                                    <td class=\"esd-stripe\" align=\"center\">\n                                        <table class=\"es-content-body\" style=\"background-color: #ffffff;\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" bgcolor=\"#ffffff\" align=\"center\">\n                                            <tbody>\n                                                <tr>\n                                                    <td class=\"esd-structure\" align=\"left\">\n                                                        <table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n                                                            <tbody>\n                                                                <tr>\n                                                                    <td class=\"es-m-p0r es-m-p20b esd-container-frame\" width=\"600\" valign=\"top\" align=\"center\">\n                                                                        <table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n                                                                            <tbody>\n                                                                                <tr>\n                                                                                    <td align=\"center\" class=\"esd-block-banner\" style=\"position: relative;\" esdev-config=\"h1\"><a target=\"_blank\" href=\"https://viewstripo.email\"><img class=\"adapt-img esdev-stretch-width esdev-banner-rendered\" src=\"https://hobezg.stripocdn.email/content/guids/bannerImgGuid/images/image16938504541591855.png\" alt=\"World tourism day\" title=\"World tourism day\" width=\"600\" style=\"display: block;\"></a></td>\n                                                                                </tr>\n                                                                            </tbody>\n                                                                        </table>\n                                                                    </td>\n                                                                </tr>\n                                                            </tbody>\n                                                        </table>\n                                                    </td>\n                                                </tr>\n                                            </tbody>\n                                        </table>\n                                    </td>\n                                </tr>\n                            </tbody>\n                        </table>\n                        <table cellpadding=\"0\" cellspacing=\"0\" class=\"es-content esd-footer-popover\" align=\"center\">\n                            <tbody>\n                                <tr>\n                                    <td class=\"esd-stripe\" align=\"center\">\n                                        <table bgcolor=\"#ffffff\" class=\"es-content-body\" align=\"center\" cellpadding=\"0\" cellspacing=\"0\" width=\"600\">\n                                            <tbody>\n                                                <tr>\n                                                    <td class=\"esd-structure es-p20t es-p35b es-p20r es-p20l\" align=\"left\">\n                                                        <table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n                                                            <tbody>\n                                                                <tr>\n                                                                    <td width=\"560\" class=\"esd-container-frame\" align=\"center\" valign=\"top\">\n                                                                        <table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n                                                                            <tbody>\n                                                                                <tr>\n                                                                                    <td align=\"center\" class=\"esd-block-text es-m-txt-c es-p20t es-p40r es-p40l es-m-p0r es-m-p0l\">\n                                                                                        <p><strong>Estás seguro si tu Sitio Web esta en un sistema actualizado?<br>Si no lo está, eres vulnerable de ataques de Hackers o Phishing</strong><br type=\"_moz\"></p>\n                                                                                    </td>\n                                                                                </tr>\n                                                                                <tr>\n                                                                                    <td align=\"center\" class=\"esd-block-text es-m-txt-c es-p15t es-p20b es-p40r es-p40l es-m-p0r es-m-p0l\">\n                                                                                        <p>Como cliente de Hosting Peru podemos ofrecerte un diagnostico completo GRATIS de su estado.<br><br>Si está actualizado ¡Te felicitaremos!<br><br>Si es vulnerable, HORROR!! Además del informe te podemos enviar una propuesta de renovación segura y total a precios super competitivos.<br>Solicita sin ningún compromiso un DIAGNÓSTICO GRATIS!<br>Llenando este sencillo formulario</p>\n                                                                                    </td>\n                                                                                </tr>\n                                                                                <tr>\n                                                                                    <td align=\"center\" class=\"esd-block-button es-p20t\">\n                                                                                        <!--[if mso]><a href=\"https://www.hostingperu.com.pe/diagnostico-web\" target=\"_blank\" hidden>\n	<v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" esdevVmlButton href=\"https://www.hostingperu.com.pe/diagnostico-web\" \n                style=\"height:41px; v-text-anchor:middle; width:272px\" arcsize=\"24%\" stroke=\"f\"  fillcolor=\"#ff5d00\">\n		<w:anchorlock></w:anchorlock>\n		<center style=\'color:#ffffff; font-family:arial, \"helvetica neue\", helvetica, sans-serif; font-size:15px; font-weight:400; line-height:15px;  mso-text-raise:1px\'>Solicitar diagnóstico Gratis</center>\n	</v:roundrect></a>\n<![endif]-->\n                                                                                        <!--[if !mso]><!-- --><span class=\"msohide es-button-border\" style=\"background: #ff5d00;\"><a href=\"https://www.hostingperu.com.pe/diagnostico-web\" class=\"es-button\" target=\"_blank\" style=\"background: #ff5d00; mso-border-alt: 10px solid #FF5D00\">Solicitar diagnóstico Gratis</a></span>\n                                                                                        <!--<![endif]-->\n                                                                                    </td>\n                                                                                </tr>\n                                                                            </tbody>\n                                                                        </table>\n                                                                    </td>\n                                                                </tr>\n                                                            </tbody>\n                                                        </table>\n                                                    </td>\n                                                </tr>\n                                            </tbody>\n                                        </table>\n                                    </td>\n                                </tr>\n                            </tbody>\n                        </table>\n                    </td>\n                </tr>\n            </tbody>\n        </table>\n    </div>\n\n\n</body></html>', '2023-09-11 18:21:22', '2023-09-11 18:23:23', 1, 0, NULL, 'email');

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_templates_categories`
--

CREATE TABLE `axme_templates_categories` (
  `id` int(10) UNSIGNED NOT NULL,
  `template_id` int(10) UNSIGNED NOT NULL,
  `category_id` int(10) UNSIGNED NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Volcado de datos para la tabla `axme_templates_categories`
--

INSERT INTO `axme_templates_categories` (`id`, `template_id`, `category_id`, `created_at`, `updated_at`) VALUES
(1, 1, 5, NULL, NULL),
(2, 2, 5, NULL, NULL),
(3, 3, 5, NULL, NULL),
(4, 4, 5, NULL, NULL),
(5, 5, 5, NULL, NULL),
(6, 6, 5, NULL, NULL),
(7, 7, 5, NULL, NULL),
(8, 8, 5, NULL, NULL),
(9, 9, 5, NULL, NULL),
(10, 10, 5, NULL, NULL),
(11, 11, 5, NULL, NULL),
(12, 12, 5, NULL, NULL),
(13, 13, 5, NULL, NULL),
(14, 14, 5, NULL, NULL),
(15, 15, 8, NULL, NULL),
(16, 16, 8, NULL, NULL),
(17, 17, 8, NULL, NULL),
(18, 18, 6, NULL, NULL),
(19, 27, 5, NULL, NULL),
(21, 29, 5, NULL, NULL),
(23, 31, 5, NULL, NULL);

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_template_categories`
--

CREATE TABLE `axme_template_categories` (
  `id` int(10) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Volcado de datos para la tabla `axme_template_categories`
--

INSERT INTO `axme_template_categories` (`id`, `uid`, `name`, `created_at`, `updated_at`) VALUES
(5, '64ff41987731f', 'Basic', '2023-09-11 16:34:32', '2023-09-11 16:34:32'),
(6, '64ff419877c77', 'Featured', '2023-09-11 16:34:32', '2023-09-11 16:34:32'),
(7, '64ff41987849c', 'Themes', '2023-09-11 16:34:32', '2023-09-11 16:34:32'),
(8, '64ff419878c8f', 'WooCommerce', '2023-09-11 16:34:32', '2023-09-11 16:34:32');

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_timelines`
--

CREATE TABLE `axme_timelines` (
  `id` int(10) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `automation2_id` int(10) UNSIGNED NOT NULL,
  `subscriber_id` int(10) UNSIGNED NOT NULL,
  `auto_trigger_id` int(10) UNSIGNED NOT NULL,
  `activity` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `activity_type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_tracking_domains`
--

CREATE TABLE `axme_tracking_domains` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `customer_id` int(10) UNSIGNED NOT NULL,
  `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `status` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `scheme` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `verification_method` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_tracking_logs`
--

CREATE TABLE `axme_tracking_logs` (
  `id` int(10) UNSIGNED NOT NULL,
  `runtime_message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `customer_id` int(10) UNSIGNED NOT NULL,
  `sending_server_id` int(10) UNSIGNED NOT NULL,
  `campaign_id` int(10) UNSIGNED DEFAULT NULL,
  `subscriber_id` int(10) UNSIGNED NOT NULL,
  `status` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `error` text COLLATE utf8mb4_unicode_ci,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `auto_trigger_id` int(10) UNSIGNED DEFAULT NULL,
  `sub_account_id` int(10) UNSIGNED DEFAULT NULL,
  `email_id` int(10) UNSIGNED DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Volcado de datos para la tabla `axme_tracking_logs`
--

INSERT INTO `axme_tracking_logs` (`id`, `runtime_message_id`, `message_id`, `customer_id`, `sending_server_id`, `campaign_id`, `subscriber_id`, `status`, `error`, `created_at`, `updated_at`, `auto_trigger_id`, `sub_account_id`, `email_id`) VALUES
(2, '1694455885855127.64ff584dd013f@hostingperu.com.pe', '1694455885855127.64ff584dd013f@hostingperu.com.pe', 2, 1, 4, 6, 'sent', NULL, '2023-09-11 18:11:25', '2023-09-11 18:11:25', NULL, NULL, NULL),
(4, NULL, NULL, 2, 1, 6, 6, 'failed', 'Call to a member function setNext() on null', '2023-09-11 18:24:44', '2023-09-11 18:24:44', NULL, NULL, NULL);

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_transactions`
--

CREATE TABLE `axme_transactions` (
  `id` int(10) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `invoice_id` int(10) UNSIGNED NOT NULL,
  `error` longtext COLLATE utf8mb4_unicode_ci,
  `status` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `method` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `metadata` longtext COLLATE utf8mb4_unicode_ci,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `allow_manual_review` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Volcado de datos para la tabla `axme_transactions`
--

INSERT INTO `axme_transactions` (`id`, `uid`, `invoice_id`, `error`, `status`, `method`, `metadata`, `created_at`, `updated_at`, `allow_manual_review`) VALUES
(1, '64ff56a10720d', 1, NULL, 'success', 'offline', NULL, '2023-09-11 18:04:17', '2023-09-11 18:04:17', 1);

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_unsubscribe_logs`
--

CREATE TABLE `axme_unsubscribe_logs` (
  `id` int(10) UNSIGNED NOT NULL,
  `message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `ip_address` text COLLATE utf8mb4_unicode_ci,
  `user_agent` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `subscriber_id` int(10) UNSIGNED DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_user_activations`
--

CREATE TABLE `axme_user_activations` (
  `id` int(10) UNSIGNED NOT NULL,
  `user_id` int(10) UNSIGNED NOT NULL,
  `token` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `axme_websites`
--

CREATE TABLE `axme_websites` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `title` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `url` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `status` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `customer_id` int(10) UNSIGNED DEFAULT NULL,
  `metadata` longtext COLLATE utf8mb4_unicode_ci,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `_tmp_subscriptions`
--

CREATE TABLE `_tmp_subscriptions` (
  `id` int(10) UNSIGNED NOT NULL,
  `uid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  `user_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `plan_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `status` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `current_period_ends_at` datetime DEFAULT NULL,
  `trial_ends_at` timestamp NULL DEFAULT NULL,
  `ends_at` datetime DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `timezone` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `started_at` datetime DEFAULT NULL,
  `last_period_ends_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Índices para tablas volcadas
--

--
-- Indices de la tabla `axme_admins`
--
ALTER TABLE `axme_admins`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_admins_user_id_foreign` (`user_id`),
  ADD KEY `axme_admins_creator_id_foreign` (`creator_id`),
  ADD KEY `axme_admins_contact_id_foreign` (`contact_id`),
  ADD KEY `axme_admins_admin_group_id_foreign` (`admin_group_id`),
  ADD KEY `axme_admins_language_id_foreign` (`language_id`);

--
-- Indices de la tabla `axme_admin_groups`
--
ALTER TABLE `axme_admin_groups`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_admin_groups_creator_id_foreign` (`creator_id`);

--
-- Indices de la tabla `axme_attachments`
--
ALTER TABLE `axme_attachments`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_automation2s`
--
ALTER TABLE `axme_automation2s`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_automation2s_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_automation2s_mail_list_id_foreign` (`mail_list_id`);

--
-- Indices de la tabla `axme_auto_triggers`
--
ALTER TABLE `axme_auto_triggers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_auto_triggers_subscriber_id_foreign` (`subscriber_id`),
  ADD KEY `axme_auto_triggers_automation2_id_foreign` (`automation2_id`);

--
-- Indices de la tabla `axme_billing_addresses`
--
ALTER TABLE `axme_billing_addresses`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_billing_addresses_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_billing_addresses_country_id_foreign` (`country_id`);

--
-- Indices de la tabla `axme_blacklists`
--
ALTER TABLE `axme_blacklists`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_blacklists_admin_id_foreign` (`admin_id`),
  ADD KEY `axme_blacklists_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_bounce_handlers`
--
ALTER TABLE `axme_bounce_handlers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_bounce_handlers_admin_id_foreign` (`admin_id`);

--
-- Indices de la tabla `axme_bounce_logs`
--
ALTER TABLE `axme_bounce_logs`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_campaigns`
--
ALTER TABLE `axme_campaigns`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_campaigns_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_campaigns_default_mail_list_id_foreign` (`default_mail_list_id`),
  ADD KEY `axme_campaigns_tracking_domain_id_foreign` (`tracking_domain_id`),
  ADD KEY `axme_campaigns_template_id_foreign` (`template_id`);

--
-- Indices de la tabla `axme_campaigns_lists_segments`
--
ALTER TABLE `axme_campaigns_lists_segments`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_cls_campaign_id_fk` (`campaign_id`),
  ADD KEY `axme_cls_mail_list_id_fk` (`mail_list_id`),
  ADD KEY `axme_cls_segment_id_fk` (`segment_id`);

--
-- Indices de la tabla `axme_campaign_links`
--
ALTER TABLE `axme_campaign_links`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_campaign_links_campaign_id_foreign` (`campaign_id`);

--
-- Indices de la tabla `axme_campaign_webhooks`
--
ALTER TABLE `axme_campaign_webhooks`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_campaign_webhooks_campaign_id_foreign` (`campaign_id`),
  ADD KEY `axme_campaign_webhooks_campaign_link_id_foreign` (`campaign_link_id`);

--
-- Indices de la tabla `axme_click_logs`
--
ALTER TABLE `axme_click_logs`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_click_logs_message_id_foreign` (`message_id`);

--
-- Indices de la tabla `axme_contacts`
--
ALTER TABLE `axme_contacts`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_contacts_country_id_foreign` (`country_id`);

--
-- Indices de la tabla `axme_countries`
--
ALTER TABLE `axme_countries`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_currencies`
--
ALTER TABLE `axme_currencies`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_currencies_admin_id_foreign` (`admin_id`);

--
-- Indices de la tabla `axme_customers`
--
ALTER TABLE `axme_customers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_customers_admin_id_foreign` (`admin_id`),
  ADD KEY `axme_customers_contact_id_foreign` (`contact_id`),
  ADD KEY `axme_customers_language_id_foreign` (`language_id`);

--
-- Indices de la tabla `axme_customer_groups`
--
ALTER TABLE `axme_customer_groups`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_customer_groups_admin_id_foreign` (`admin_id`);

--
-- Indices de la tabla `axme_customer_group_sending_servers`
--
ALTER TABLE `axme_customer_group_sending_servers`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_emails`
--
ALTER TABLE `axme_emails`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_emails_automation2_id_foreign` (`automation2_id`),
  ADD KEY `axme_emails_tracking_domain_id_foreign` (`tracking_domain_id`),
  ADD KEY `axme_emails_template_id_foreign` (`template_id`),
  ADD KEY `axme_emails_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_email_links`
--
ALTER TABLE `axme_email_links`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_email_links_email_id_foreign` (`email_id`);

--
-- Indices de la tabla `axme_email_verifications`
--
ALTER TABLE `axme_email_verifications`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_email_verifications_subscriber_id_foreign` (`subscriber_id`),
  ADD KEY `axme_ev_email_verification_server_id_fk` (`email_verification_server_id`),
  ADD KEY `axme_email_verifications_result_index` (`result`);

--
-- Indices de la tabla `axme_email_verification_servers`
--
ALTER TABLE `axme_email_verification_servers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_evs_admin_id_fk` (`admin_id`),
  ADD KEY `axme_evs_customer_id_fk` (`customer_id`);

--
-- Indices de la tabla `axme_email_webhooks`
--
ALTER TABLE `axme_email_webhooks`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_email_webhooks_email_id_foreign` (`email_id`),
  ADD KEY `axme_email_webhooks_email_link_id_foreign` (`email_link_id`);

--
-- Indices de la tabla `axme_failed_jobs`
--
ALTER TABLE `axme_failed_jobs`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `axme_failed_jobs_uuid_unique` (`uuid`);

--
-- Indices de la tabla `axme_feedback_logs`
--
ALTER TABLE `axme_feedback_logs`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_feedback_loop_handlers`
--
ALTER TABLE `axme_feedback_loop_handlers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_feedback_loop_handlers_admin_id_foreign` (`admin_id`);

--
-- Indices de la tabla `axme_fields`
--
ALTER TABLE `axme_fields`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_fields_mail_list_id_foreign` (`mail_list_id`);

--
-- Indices de la tabla `axme_field_options`
--
ALTER TABLE `axme_field_options`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_field_options_field_id_foreign` (`field_id`);

--
-- Indices de la tabla `axme_forms`
--
ALTER TABLE `axme_forms`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_forms_template_id_foreign` (`template_id`),
  ADD KEY `axme_forms_mail_list_id_foreign` (`mail_list_id`),
  ADD KEY `axme_forms_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_invoices`
--
ALTER TABLE `axme_invoices`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_invoices_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_invoices_currency_id_foreign` (`currency_id`);

--
-- Indices de la tabla `axme_invoice_items`
--
ALTER TABLE `axme_invoice_items`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_invoice_items_invoice_id_foreign` (`invoice_id`);

--
-- Indices de la tabla `axme_ip_locations`
--
ALTER TABLE `axme_ip_locations`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_ip_locations_ip_address_index` (`ip_address`);

--
-- Indices de la tabla `axme_jobs`
--
ALTER TABLE `axme_jobs`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_job_batches`
--
ALTER TABLE `axme_job_batches`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_job_monitors`
--
ALTER TABLE `axme_job_monitors`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_languages`
--
ALTER TABLE `axme_languages`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_layouts`
--
ALTER TABLE `axme_layouts`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_logs`
--
ALTER TABLE `axme_logs`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_logs_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_mail_lists`
--
ALTER TABLE `axme_mail_lists`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_mail_lists_contact_id_foreign` (`contact_id`),
  ADD KEY `axme_mail_lists_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_mail_lists_sending_servers`
--
ALTER TABLE `axme_mail_lists_sending_servers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_mlss_sending_server_id_fk` (`sending_server_id`),
  ADD KEY `axme_mlss_mail_list_id_fk` (`mail_list_id`);

--
-- Indices de la tabla `axme_migrations`
--
ALTER TABLE `axme_migrations`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_notifications`
--
ALTER TABLE `axme_notifications`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_notifications_admin_id_foreign` (`admin_id`),
  ADD KEY `axme_notifications_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_open_logs`
--
ALTER TABLE `axme_open_logs`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_open_logs_message_id_foreign` (`message_id`),
  ADD KEY `axme_open_logs_ip_address_index` (`ip_address`);

--
-- Indices de la tabla `axme_pages`
--
ALTER TABLE `axme_pages`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_pages_mail_list_id_foreign` (`mail_list_id`),
  ADD KEY `axme_pages_layout_id_foreign` (`layout_id`);

--
-- Indices de la tabla `axme_password_resets`
--
ALTER TABLE `axme_password_resets`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_password_resets_email_index` (`email`),
  ADD KEY `axme_password_resets_token_index` (`token`);

--
-- Indices de la tabla `axme_plans`
--
ALTER TABLE `axme_plans`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_plans_admin_id_foreign` (`admin_id`),
  ADD KEY `axme_plans_currency_id_foreign` (`currency_id`);

--
-- Indices de la tabla `axme_plans_email_verification_servers`
--
ALTER TABLE `axme_plans_email_verification_servers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_pevs_server_id_fk` (`server_id`),
  ADD KEY `axme_pevs_plan_id_fk` (`plan_id`);

--
-- Indices de la tabla `axme_plans_sending_servers`
--
ALTER TABLE `axme_plans_sending_servers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_pss_sending_server_id_fk` (`sending_server_id`),
  ADD KEY `axme_plans_sending_servers_plan_id_foreign` (`plan_id`);

--
-- Indices de la tabla `axme_plugins`
--
ALTER TABLE `axme_plugins`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_products`
--
ALTER TABLE `axme_products`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_products_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_products_source_id_foreign` (`source_id`);

--
-- Indices de la tabla `axme_segments`
--
ALTER TABLE `axme_segments`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_segments_mail_list_id_foreign` (`mail_list_id`);

--
-- Indices de la tabla `axme_segment_conditions`
--
ALTER TABLE `axme_segment_conditions`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_segment_conditions_segment_id_foreign` (`segment_id`),
  ADD KEY `axme_segment_conditions_field_id_foreign` (`field_id`);

--
-- Indices de la tabla `axme_senders`
--
ALTER TABLE `axme_senders`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_senders_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_senders_sending_server_id_foreign` (`sending_server_id`);

--
-- Indices de la tabla `axme_sending_domains`
--
ALTER TABLE `axme_sending_domains`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_sending_domains_admin_id_foreign` (`admin_id`),
  ADD KEY `axme_sending_domains_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_sending_domains_sending_server_id_foreign` (`sending_server_id`);

--
-- Indices de la tabla `axme_settings`
--
ALTER TABLE `axme_settings`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_sources`
--
ALTER TABLE `axme_sources`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_sources_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_sources_mail_list_id_foreign` (`mail_list_id`);

--
-- Indices de la tabla `axme_subscribers`
--
ALTER TABLE `axme_subscribers`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_subscribers_mail_list_id_email_index` (`mail_list_id`,`email`),
  ADD KEY `axme_subscribers_email_index` (`email`);

--
-- Indices de la tabla `axme_subscriber_fields`
--
ALTER TABLE `axme_subscriber_fields`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_subscriber_fields_subscriber_id_foreign` (`subscriber_id`),
  ADD KEY `axme_subscriber_fields_field_id_foreign` (`field_id`);

--
-- Indices de la tabla `axme_subscriptions`
--
ALTER TABLE `axme_subscriptions`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_subscriptions_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_subscriptions_plan_id_foreign` (`plan_id`);

--
-- Indices de la tabla `axme_subscription_logs`
--
ALTER TABLE `axme_subscription_logs`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_sl_subscription_id_fk` (`subscription_id`);

--
-- Indices de la tabla `axme_sub_accounts`
--
ALTER TABLE `axme_sub_accounts`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_sub_accounts_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_sub_accounts_sending_server_id_foreign` (`sending_server_id`);

--
-- Indices de la tabla `axme_templates`
--
ALTER TABLE `axme_templates`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_templates_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_templates_categories`
--
ALTER TABLE `axme_templates_categories`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_templates_categories_template_id_foreign` (`template_id`),
  ADD KEY `axme_templates_categories_category_id_foreign` (`category_id`);

--
-- Indices de la tabla `axme_template_categories`
--
ALTER TABLE `axme_template_categories`
  ADD PRIMARY KEY (`id`);

--
-- Indices de la tabla `axme_timelines`
--
ALTER TABLE `axme_timelines`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_timelines_automation2_id_foreign` (`automation2_id`),
  ADD KEY `axme_timelines_subscriber_id_foreign` (`subscriber_id`),
  ADD KEY `axme_timelines_auto_trigger_id_foreign` (`auto_trigger_id`);

--
-- Indices de la tabla `axme_tracking_domains`
--
ALTER TABLE `axme_tracking_domains`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_tracking_domains_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `axme_tracking_logs`
--
ALTER TABLE `axme_tracking_logs`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `axme_tracking_logs_runtime_message_id_unique` (`runtime_message_id`),
  ADD UNIQUE KEY `axme_tracking_logs_message_id_unique` (`message_id`),
  ADD KEY `axme_tracking_logs_customer_id_foreign` (`customer_id`),
  ADD KEY `axme_tracking_logs_sending_server_id_foreign` (`sending_server_id`),
  ADD KEY `axme_tracking_logs_subscriber_id_foreign` (`subscriber_id`),
  ADD KEY `axme_tracking_logs_auto_trigger_id_foreign` (`auto_trigger_id`),
  ADD KEY `axme_tracking_logs_sub_account_id_foreign` (`sub_account_id`),
  ADD KEY `axme_tracking_logs_email_id_foreign` (`email_id`),
  ADD KEY `axme_tracking_logs_campaign_id_foreign` (`campaign_id`);

--
-- Indices de la tabla `axme_transactions`
--
ALTER TABLE `axme_transactions`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_transactions_invoice_id_foreign` (`invoice_id`);

--
-- Indices de la tabla `axme_unsubscribe_logs`
--
ALTER TABLE `axme_unsubscribe_logs`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_unsubscribe_logs_message_id_foreign` (`message_id`),
  ADD KEY `axme_unsubscribe_logs_subscriber_id_foreign` (`subscriber_id`);

--
-- Indices de la tabla `axme_user_activations`
--
ALTER TABLE `axme_user_activations`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_user_activations_user_id_foreign` (`user_id`),
  ADD KEY `axme_user_activations_token_index` (`token`);

--
-- Indices de la tabla `axme_websites`
--
ALTER TABLE `axme_websites`
  ADD PRIMARY KEY (`id`),
  ADD KEY `axme_websites_customer_id_foreign` (`customer_id`);

--
-- Indices de la tabla `_tmp_subscriptions`
--
ALTER TABLE `_tmp_subscriptions`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `axme_admins`
--
ALTER TABLE `axme_admins`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_admin_groups`
--
ALTER TABLE `axme_admin_groups`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_attachments`
--
ALTER TABLE `axme_attachments`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_automation2s`
--
ALTER TABLE `axme_automation2s`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_auto_triggers`
--
ALTER TABLE `axme_auto_triggers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_billing_addresses`
--
ALTER TABLE `axme_billing_addresses`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_blacklists`
--
ALTER TABLE `axme_blacklists`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_bounce_handlers`
--
ALTER TABLE `axme_bounce_handlers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_bounce_logs`
--
ALTER TABLE `axme_bounce_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_campaigns`
--
ALTER TABLE `axme_campaigns`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;

--
-- AUTO_INCREMENT de la tabla `axme_campaigns_lists_segments`
--
ALTER TABLE `axme_campaigns_lists_segments`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;

--
-- AUTO_INCREMENT de la tabla `axme_campaign_links`
--
ALTER TABLE `axme_campaign_links`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;

--
-- AUTO_INCREMENT de la tabla `axme_campaign_webhooks`
--
ALTER TABLE `axme_campaign_webhooks`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_click_logs`
--
ALTER TABLE `axme_click_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_contacts`
--
ALTER TABLE `axme_contacts`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_countries`
--
ALTER TABLE `axme_countries`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=233;

--
-- AUTO_INCREMENT de la tabla `axme_currencies`
--
ALTER TABLE `axme_currencies`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_customers`
--
ALTER TABLE `axme_customers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_customer_groups`
--
ALTER TABLE `axme_customer_groups`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_customer_group_sending_servers`
--
ALTER TABLE `axme_customer_group_sending_servers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_emails`
--
ALTER TABLE `axme_emails`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_email_links`
--
ALTER TABLE `axme_email_links`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_email_verifications`
--
ALTER TABLE `axme_email_verifications`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_email_verification_servers`
--
ALTER TABLE `axme_email_verification_servers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_email_webhooks`
--
ALTER TABLE `axme_email_webhooks`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_failed_jobs`
--
ALTER TABLE `axme_failed_jobs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_feedback_logs`
--
ALTER TABLE `axme_feedback_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_feedback_loop_handlers`
--
ALTER TABLE `axme_feedback_loop_handlers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_fields`
--
ALTER TABLE `axme_fields`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

--
-- AUTO_INCREMENT de la tabla `axme_field_options`
--
ALTER TABLE `axme_field_options`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_forms`
--
ALTER TABLE `axme_forms`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_invoices`
--
ALTER TABLE `axme_invoices`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_invoice_items`
--
ALTER TABLE `axme_invoice_items`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_ip_locations`
--
ALTER TABLE `axme_ip_locations`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_jobs`
--
ALTER TABLE `axme_jobs`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_job_monitors`
--
ALTER TABLE `axme_job_monitors`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15;

--
-- AUTO_INCREMENT de la tabla `axme_languages`
--
ALTER TABLE `axme_languages`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;

--
-- AUTO_INCREMENT de la tabla `axme_layouts`
--
ALTER TABLE `axme_layouts`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=18;

--
-- AUTO_INCREMENT de la tabla `axme_logs`
--
ALTER TABLE `axme_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=20;

--
-- AUTO_INCREMENT de la tabla `axme_mail_lists`
--
ALTER TABLE `axme_mail_lists`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_mail_lists_sending_servers`
--
ALTER TABLE `axme_mail_lists_sending_servers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_migrations`
--
ALTER TABLE `axme_migrations`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=279;

--
-- AUTO_INCREMENT de la tabla `axme_notifications`
--
ALTER TABLE `axme_notifications`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=97;

--
-- AUTO_INCREMENT de la tabla `axme_open_logs`
--
ALTER TABLE `axme_open_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_pages`
--
ALTER TABLE `axme_pages`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_password_resets`
--
ALTER TABLE `axme_password_resets`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_plans`
--
ALTER TABLE `axme_plans`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;

--
-- AUTO_INCREMENT de la tabla `axme_plans_email_verification_servers`
--
ALTER TABLE `axme_plans_email_verification_servers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_plans_sending_servers`
--
ALTER TABLE `axme_plans_sending_servers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_plugins`
--
ALTER TABLE `axme_plugins`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_products`
--
ALTER TABLE `axme_products`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_segments`
--
ALTER TABLE `axme_segments`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_segment_conditions`
--
ALTER TABLE `axme_segment_conditions`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_senders`
--
ALTER TABLE `axme_senders`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_sending_domains`
--
ALTER TABLE `axme_sending_domains`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_settings`
--
ALTER TABLE `axme_settings`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=43;

--
-- AUTO_INCREMENT de la tabla `axme_sources`
--
ALTER TABLE `axme_sources`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_subscribers`
--
ALTER TABLE `axme_subscribers`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;

--
-- AUTO_INCREMENT de la tabla `axme_subscriber_fields`
--
ALTER TABLE `axme_subscriber_fields`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

--
-- AUTO_INCREMENT de la tabla `axme_subscriptions`
--
ALTER TABLE `axme_subscriptions`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_subscription_logs`
--
ALTER TABLE `axme_subscription_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT de la tabla `axme_sub_accounts`
--
ALTER TABLE `axme_sub_accounts`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_templates`
--
ALTER TABLE `axme_templates`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=32;

--
-- AUTO_INCREMENT de la tabla `axme_templates_categories`
--
ALTER TABLE `axme_templates_categories`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=24;

--
-- AUTO_INCREMENT de la tabla `axme_template_categories`
--
ALTER TABLE `axme_template_categories`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;

--
-- AUTO_INCREMENT de la tabla `axme_timelines`
--
ALTER TABLE `axme_timelines`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_tracking_domains`
--
ALTER TABLE `axme_tracking_domains`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_tracking_logs`
--
ALTER TABLE `axme_tracking_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;

--
-- AUTO_INCREMENT de la tabla `axme_transactions`
--
ALTER TABLE `axme_transactions`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT de la tabla `axme_unsubscribe_logs`
--
ALTER TABLE `axme_unsubscribe_logs`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_user_activations`
--
ALTER TABLE `axme_user_activations`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `axme_websites`
--
ALTER TABLE `axme_websites`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT de la tabla `_tmp_subscriptions`
--
ALTER TABLE `_tmp_subscriptions`
  MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- Restricciones para tablas volcadas
--

--
-- Filtros para la tabla `axme_admins`
--
ALTER TABLE `axme_admins`
  ADD CONSTRAINT `axme_admins_admin_group_id_foreign` FOREIGN KEY (`admin_group_id`) REFERENCES `axme_admin_groups` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_admins_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `axme_contacts` (`id`),
  ADD CONSTRAINT `axme_admins_creator_id_foreign` FOREIGN KEY (`creator_id`) REFERENCES `axme_users` (`id`),
  ADD CONSTRAINT `axme_admins_language_id_foreign` FOREIGN KEY (`language_id`) REFERENCES `axme_languages` (`id`),
  ADD CONSTRAINT `axme_admins_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `axme_users` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_admin_groups`
--
ALTER TABLE `axme_admin_groups`
  ADD CONSTRAINT `axme_admin_groups_creator_id_foreign` FOREIGN KEY (`creator_id`) REFERENCES `axme_users` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_automation2s`
--
ALTER TABLE `axme_automation2s`
  ADD CONSTRAINT `axme_automation2s_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_automation2s_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_auto_triggers`
--
ALTER TABLE `axme_auto_triggers`
  ADD CONSTRAINT `axme_auto_triggers_automation2_id_foreign` FOREIGN KEY (`automation2_id`) REFERENCES `axme_automation2s` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_auto_triggers_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_billing_addresses`
--
ALTER TABLE `axme_billing_addresses`
  ADD CONSTRAINT `axme_billing_addresses_country_id_foreign` FOREIGN KEY (`country_id`) REFERENCES `axme_countries` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_billing_addresses_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_blacklists`
--
ALTER TABLE `axme_blacklists`
  ADD CONSTRAINT `axme_blacklists_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_blacklists_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_bounce_handlers`
--
ALTER TABLE `axme_bounce_handlers`
  ADD CONSTRAINT `axme_bounce_handlers_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_campaigns`
--
ALTER TABLE `axme_campaigns`
  ADD CONSTRAINT `axme_campaigns_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_campaigns_default_mail_list_id_foreign` FOREIGN KEY (`default_mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_campaigns_template_id_foreign` FOREIGN KEY (`template_id`) REFERENCES `axme_templates` (`id`) ON DELETE SET NULL,
  ADD CONSTRAINT `axme_campaigns_tracking_domain_id_foreign` FOREIGN KEY (`tracking_domain_id`) REFERENCES `axme_tracking_domains` (`id`) ON DELETE SET NULL;

--
-- Filtros para la tabla `axme_campaigns_lists_segments`
--
ALTER TABLE `axme_campaigns_lists_segments`
  ADD CONSTRAINT `axme_cls_campaign_id_fk` FOREIGN KEY (`campaign_id`) REFERENCES `axme_campaigns` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_cls_mail_list_id_fk` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_cls_segment_id_fk` FOREIGN KEY (`segment_id`) REFERENCES `axme_segments` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_campaign_links`
--
ALTER TABLE `axme_campaign_links`
  ADD CONSTRAINT `axme_campaign_links_campaign_id_foreign` FOREIGN KEY (`campaign_id`) REFERENCES `axme_campaigns` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_campaign_webhooks`
--
ALTER TABLE `axme_campaign_webhooks`
  ADD CONSTRAINT `axme_campaign_webhooks_campaign_id_foreign` FOREIGN KEY (`campaign_id`) REFERENCES `axme_campaigns` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_campaign_webhooks_campaign_link_id_foreign` FOREIGN KEY (`campaign_link_id`) REFERENCES `axme_campaign_links` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_click_logs`
--
ALTER TABLE `axme_click_logs`
  ADD CONSTRAINT `axme_click_logs_message_id_foreign` FOREIGN KEY (`message_id`) REFERENCES `axme_tracking_logs` (`message_id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_contacts`
--
ALTER TABLE `axme_contacts`
  ADD CONSTRAINT `axme_contacts_country_id_foreign` FOREIGN KEY (`country_id`) REFERENCES `axme_countries` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_currencies`
--
ALTER TABLE `axme_currencies`
  ADD CONSTRAINT `axme_currencies_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_customers`
--
ALTER TABLE `axme_customers`
  ADD CONSTRAINT `axme_customers_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`),
  ADD CONSTRAINT `axme_customers_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `axme_contacts` (`id`),
  ADD CONSTRAINT `axme_customers_language_id_foreign` FOREIGN KEY (`language_id`) REFERENCES `axme_languages` (`id`);

--
-- Filtros para la tabla `axme_customer_groups`
--
ALTER TABLE `axme_customer_groups`
  ADD CONSTRAINT `axme_customer_groups_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_emails`
--
ALTER TABLE `axme_emails`
  ADD CONSTRAINT `axme_emails_automation2_id_foreign` FOREIGN KEY (`automation2_id`) REFERENCES `axme_automation2s` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_emails_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_emails_template_id_foreign` FOREIGN KEY (`template_id`) REFERENCES `axme_templates` (`id`) ON DELETE SET NULL,
  ADD CONSTRAINT `axme_emails_tracking_domain_id_foreign` FOREIGN KEY (`tracking_domain_id`) REFERENCES `axme_tracking_domains` (`id`) ON DELETE SET NULL;

--
-- Filtros para la tabla `axme_email_links`
--
ALTER TABLE `axme_email_links`
  ADD CONSTRAINT `axme_email_links_email_id_foreign` FOREIGN KEY (`email_id`) REFERENCES `axme_emails` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_email_verifications`
--
ALTER TABLE `axme_email_verifications`
  ADD CONSTRAINT `axme_email_verifications_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_ev_email_verification_server_id_fk` FOREIGN KEY (`email_verification_server_id`) REFERENCES `axme_email_verification_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_email_verification_servers`
--
ALTER TABLE `axme_email_verification_servers`
  ADD CONSTRAINT `axme_evs_admin_id_fk` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_evs_customer_id_fk` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_email_webhooks`
--
ALTER TABLE `axme_email_webhooks`
  ADD CONSTRAINT `axme_email_webhooks_email_id_foreign` FOREIGN KEY (`email_id`) REFERENCES `axme_emails` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_email_webhooks_email_link_id_foreign` FOREIGN KEY (`email_link_id`) REFERENCES `axme_email_links` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_feedback_loop_handlers`
--
ALTER TABLE `axme_feedback_loop_handlers`
  ADD CONSTRAINT `axme_feedback_loop_handlers_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_fields`
--
ALTER TABLE `axme_fields`
  ADD CONSTRAINT `axme_fields_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_field_options`
--
ALTER TABLE `axme_field_options`
  ADD CONSTRAINT `axme_field_options_field_id_foreign` FOREIGN KEY (`field_id`) REFERENCES `axme_fields` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_forms`
--
ALTER TABLE `axme_forms`
  ADD CONSTRAINT `axme_forms_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_forms_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_forms_template_id_foreign` FOREIGN KEY (`template_id`) REFERENCES `axme_templates` (`id`) ON DELETE SET NULL;

--
-- Filtros para la tabla `axme_invoices`
--
ALTER TABLE `axme_invoices`
  ADD CONSTRAINT `axme_invoices_currency_id_foreign` FOREIGN KEY (`currency_id`) REFERENCES `axme_currencies` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_invoices_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_invoice_items`
--
ALTER TABLE `axme_invoice_items`
  ADD CONSTRAINT `axme_invoice_items_invoice_id_foreign` FOREIGN KEY (`invoice_id`) REFERENCES `axme_invoices` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_logs`
--
ALTER TABLE `axme_logs`
  ADD CONSTRAINT `axme_logs_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_mail_lists`
--
ALTER TABLE `axme_mail_lists`
  ADD CONSTRAINT `axme_mail_lists_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `axme_contacts` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_mail_lists_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_mail_lists_sending_servers`
--
ALTER TABLE `axme_mail_lists_sending_servers`
  ADD CONSTRAINT `axme_mlss_mail_list_id_fk` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_mlss_sending_server_id_fk` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_notifications`
--
ALTER TABLE `axme_notifications`
  ADD CONSTRAINT `axme_notifications_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_notifications_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_open_logs`
--
ALTER TABLE `axme_open_logs`
  ADD CONSTRAINT `axme_open_logs_message_id_foreign` FOREIGN KEY (`message_id`) REFERENCES `axme_tracking_logs` (`message_id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_pages`
--
ALTER TABLE `axme_pages`
  ADD CONSTRAINT `axme_pages_layout_id_foreign` FOREIGN KEY (`layout_id`) REFERENCES `axme_layouts` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_pages_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_plans`
--
ALTER TABLE `axme_plans`
  ADD CONSTRAINT `axme_plans_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_plans_currency_id_foreign` FOREIGN KEY (`currency_id`) REFERENCES `axme_currencies` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_plans_email_verification_servers`
--
ALTER TABLE `axme_plans_email_verification_servers`
  ADD CONSTRAINT `axme_pevs_plan_id_fk` FOREIGN KEY (`plan_id`) REFERENCES `axme_plans` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_pevs_server_id_fk` FOREIGN KEY (`server_id`) REFERENCES `axme_email_verification_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_plans_sending_servers`
--
ALTER TABLE `axme_plans_sending_servers`
  ADD CONSTRAINT `axme_plans_sending_servers_plan_id_foreign` FOREIGN KEY (`plan_id`) REFERENCES `axme_plans` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_pss_sending_server_id_fk` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_products`
--
ALTER TABLE `axme_products`
  ADD CONSTRAINT `axme_products_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_products_source_id_foreign` FOREIGN KEY (`source_id`) REFERENCES `axme_sources` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_segments`
--
ALTER TABLE `axme_segments`
  ADD CONSTRAINT `axme_segments_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_segment_conditions`
--
ALTER TABLE `axme_segment_conditions`
  ADD CONSTRAINT `axme_segment_conditions_field_id_foreign` FOREIGN KEY (`field_id`) REFERENCES `axme_fields` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_segment_conditions_segment_id_foreign` FOREIGN KEY (`segment_id`) REFERENCES `axme_segments` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_senders`
--
ALTER TABLE `axme_senders`
  ADD CONSTRAINT `axme_senders_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_senders_sending_server_id_foreign` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_sending_domains`
--
ALTER TABLE `axme_sending_domains`
  ADD CONSTRAINT `axme_sending_domains_admin_id_foreign` FOREIGN KEY (`admin_id`) REFERENCES `axme_admins` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_sending_domains_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_sending_domains_sending_server_id_foreign` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_sources`
--
ALTER TABLE `axme_sources`
  ADD CONSTRAINT `axme_sources_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_sources_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_subscribers`
--
ALTER TABLE `axme_subscribers`
  ADD CONSTRAINT `axme_subscribers_mail_list_id_foreign` FOREIGN KEY (`mail_list_id`) REFERENCES `axme_mail_lists` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_subscriber_fields`
--
ALTER TABLE `axme_subscriber_fields`
  ADD CONSTRAINT `axme_subscriber_fields_field_id_foreign` FOREIGN KEY (`field_id`) REFERENCES `axme_fields` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_subscriber_fields_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_subscriptions`
--
ALTER TABLE `axme_subscriptions`
  ADD CONSTRAINT `axme_subscriptions_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_subscriptions_plan_id_foreign` FOREIGN KEY (`plan_id`) REFERENCES `axme_plans` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_subscription_logs`
--
ALTER TABLE `axme_subscription_logs`
  ADD CONSTRAINT `axme_sl_subscription_id_fk` FOREIGN KEY (`subscription_id`) REFERENCES `axme_subscriptions` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_sub_accounts`
--
ALTER TABLE `axme_sub_accounts`
  ADD CONSTRAINT `axme_sub_accounts_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_sub_accounts_sending_server_id_foreign` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_templates`
--
ALTER TABLE `axme_templates`
  ADD CONSTRAINT `axme_templates_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_templates_categories`
--
ALTER TABLE `axme_templates_categories`
  ADD CONSTRAINT `axme_templates_categories_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `axme_template_categories` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_templates_categories_template_id_foreign` FOREIGN KEY (`template_id`) REFERENCES `axme_templates` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_timelines`
--
ALTER TABLE `axme_timelines`
  ADD CONSTRAINT `axme_timelines_auto_trigger_id_foreign` FOREIGN KEY (`auto_trigger_id`) REFERENCES `axme_auto_triggers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_timelines_automation2_id_foreign` FOREIGN KEY (`automation2_id`) REFERENCES `axme_automation2s` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_timelines_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_tracking_domains`
--
ALTER TABLE `axme_tracking_domains`
  ADD CONSTRAINT `axme_tracking_domains_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_tracking_logs`
--
ALTER TABLE `axme_tracking_logs`
  ADD CONSTRAINT `axme_tracking_logs_auto_trigger_id_foreign` FOREIGN KEY (`auto_trigger_id`) REFERENCES `axme_auto_triggers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_campaign_id_foreign` FOREIGN KEY (`campaign_id`) REFERENCES `axme_campaigns` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_email_id_foreign` FOREIGN KEY (`email_id`) REFERENCES `axme_emails` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_sending_server_id_foreign` FOREIGN KEY (`sending_server_id`) REFERENCES `axme_sending_servers` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_sub_account_id_foreign` FOREIGN KEY (`sub_account_id`) REFERENCES `axme_sub_accounts` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `axme_tracking_logs_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_transactions`
--
ALTER TABLE `axme_transactions`
  ADD CONSTRAINT `axme_transactions_invoice_id_foreign` FOREIGN KEY (`invoice_id`) REFERENCES `axme_invoices` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_unsubscribe_logs`
--
ALTER TABLE `axme_unsubscribe_logs`
  ADD CONSTRAINT `axme_unsubscribe_logs_subscriber_id_foreign` FOREIGN KEY (`subscriber_id`) REFERENCES `axme_subscribers` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_user_activations`
--
ALTER TABLE `axme_user_activations`
  ADD CONSTRAINT `axme_user_activations_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `axme_users` (`id`) ON DELETE CASCADE;

--
-- Filtros para la tabla `axme_websites`
--
ALTER TABLE `axme_websites`
  ADD CONSTRAINT `axme_websites_customer_id_foreign` FOREIGN KEY (`customer_id`) REFERENCES `axme_customers` (`id`) ON DELETE CASCADE;
COMMIT;

