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.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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);
})();