Greasy Fork is available in English.

Gmail Link Domain Validator (Query Safe Engine)

Warns if links in a Gmail body do not match the sender's core domain, safely handling URLs with email parameters.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Advertisement:

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

Advertisement:

// ==UserScript==
// @name         Gmail Link Domain Validator (Query Safe Engine)
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Warns if links in a Gmail body do not match the sender's core domain, safely handling URLs with email parameters.
// @author       KevinP
// @match        https://mail.google.com/mail/*
// @grant        none
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Extracts the primary domain block (e.g., "google.com" from "sub.mail.google.com")
    function getRootDomain(urlOrEmail) {
        try {
            let clean = urlOrEmail.trim().toLowerCase();

            // If it's an email address, just look at what's after the '@'
            if (clean.includes('@') && !clean.startsWith('http://') && !clean.startsWith('https://')) {
                clean = clean.split('@')[1];
            }

            // Strip out query parameters and paths entirely before parsing to prevent query injection bugs
            if (clean.includes('?')) clean = clean.split('?')[0];
            if (clean.includes('/')) {
                // Keep the protocol if present, otherwise split by slash
                if (clean.startsWith('http://') || clean.startsWith('https://')) {
                    const urlParts = clean.split('/');
                    clean = urlParts[2]; // Index 2 is the hostname (e.g., accounts.google.com)
                } else {
                    clean = clean.split('/')[0];
                }
            }

            // Ensure it has a protocol string for the native URL parser to read it as a hostname
            if (!clean.startsWith('http://') && !clean.startsWith('https://')) {
                clean = 'https://' + clean;
            }

            const parsed = new URL(clean);
            const hostname = parsed.hostname;

            const parts = hostname.split('.');
            if (parts.length <= 2) return hostname;

            const secondToLast = parts[parts.length - 2];
            const commonCcTlds = ['co', 'com', 'org', 'net', 'gov', 'edu', 'ac'];

            if (commonCcTlds.includes(secondToLast) && parts.length > 2) {
                return parts.slice(-3).join('.');
            }

            return parts.slice(-2).join('.');
        } catch (e) {
            return null;
        }
    }

    function verifyGmailLinks() {
        // 1. Target Gmail's primary content view area
        const activeThread = document.querySelector('div[role="main"]');
        if (!activeThread) return;

        // 2. Select any element that looks like an email body card
        const emailBodies = activeThread.querySelectorAll('.a3s, [dir="ltr"]');

        emailBodies.forEach(body => {
            if (body.innerHTML.length < 50 || body.getAttribute('data-domain-checked') === 'true') return;

            // 3. Ascend up the tree to isolate this specific email card block
            let currentParent = body.parentElement;
            let senderElement = null;
            let loops = 0;

            while (currentParent && !senderElement && loops < 10) {
                senderElement = currentParent.querySelector('span[email]');
                if (!senderElement) {
                    currentParent = currentParent.parentElement;
                    loops++;
                }
            }

            if (!senderElement) return;

            const senderEmail = senderElement.getAttribute('email');
            const senderRoot = getRootDomain(senderEmail);
            if (!senderRoot) return;

            // Mark this body block processed
            body.setAttribute('data-domain-checked', 'true');

            // 4. Find all hyperlinks inside this message body
            const links = body.querySelectorAll('a[href]');

            links.forEach(link => {
                const linkHref = link.getAttribute('href');
                const linkRoot = getRootDomain(linkHref);

                // Ignore anchors, local layout actions, and email links
                if (!linkRoot || linkHref.startsWith('mailto:') || linkHref.startsWith('#')) {
                    return;
                }

                // 5. Check if domains match or satisfy special exception rules
                let isMatch = (linkRoot === senderRoot);

                // Exception: Allow google.com senders to safely link to c.gle targets
                if (senderRoot === 'google.com' && linkRoot === 'c.gle') {
                    isMatch = true;
                }

                // 6. Highlight mismatched targets
                if (!isMatch) {
                    link.style.backgroundColor = '#ffebee';
                    link.style.border = '2px dashed #d32f2f';
                    link.style.padding = '2px 4px';
                    link.style.borderRadius = '3px';
                    link.title = `⚠️ WARNING: Link targets ${linkRoot}, but sender is ${senderRoot}`;

                    if (!link.querySelector('.domain-warn-icon')) {
                        const icon = document.createElement('span');
                        icon.className = 'domain-warn-icon';
                        icon.innerText = '⚠️ ';
                        link.insertBefore(icon, link.firstChild);
                    }
                }
            });
        });
    }

    // Check the interface every 1 second
    setInterval(verifyGmailLinks, 1000);
})();