Skip to content
Snippets Groups Projects
links.js 3.97 KiB
Newer Older
// ATP Safe Links Cleaner
// Copyright 2021 David Byers <david.byers@liu.se>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// Shared code


/**
 * Regexp that matches safe links. The original URL must be collected
 * in match group 1.
 */
const safelinksRegexp = new RegExp(
    'https?://[^.]+[.]safelinks[.]protection[.]outlook[.]com/[?]url=([^&]+)&.*',
    'gi'
);


/**
 * The ID for the popup element that is added to the HTML document.
 */
const safelinksPopupId = 'safelinks-cleaner-thunderbird-popup';


/**
 * The class that is added to the popup when visible.
 */
const safelinksPopupVisibleClass = 'safelinks-cleaner-thunderbird-popup-visible';


/**
 * Return the original URL for a safe link.
 * @param {string} link - The safe link.
 * @returns {string} The original link or the safe link if there was an error.
 */
function untangleLink(link) {
    return link.replaceAll(
	safelinksRegexp, (match, url) => {
	    try {
		return decodeURIComponent(url);
	    }
	    catch (e) {
		return url;
	    }
	});
}


/**
 * Check if a link is a safe link.
 * @param {string} link - The URL to check.
 * @returns {boolean} Returns true if the link is a safe link.
 */
function isTangledLink(link) {
    return link.match(safelinksRegexp);
}

/**
 * Return the text nodes under a DOM element.
 * @param {Element} elem - The element to return text nodes for.
 * @returns {Element[]} The text elements under elem.
 */
function getTextNodes(elem) {
    var result = [];
    if (elem) {
	for (var nodes = elem.childNodes, i = nodes.length; i--;) {
	    let node = nodes[i];
	    let nodeType = node.nodeType;
	    
	    if (nodeType == Node.TEXT_NODE) {
		result.push(node);
	    }
	    else if (nodeType == Node.ELEMENT_NODE
		     || nodeType == Node.DOCUMENT_NODE
		     || nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
		result = result.concat(getTextNodes(node));
	    }
	}
    }
    return result;
}
David Byers's avatar
David Byers committed


/**
 * Fix all the links in the document.
 * @param {Element} root - DOM element in which to fix links.
David Byers's avatar
David Byers committed
 */
function fixAllTheLinks(root) {
    console.log('enter fixAllTheLinks'); // DEBUG
    for (const link of root.getElementsByTagName('a')) {
	console.log(link);	// DEBUG
	if (link.href) {
	    // Untangle link text
	    for (const node of getTextNodes(link)) {
		node.textContent = untangleLink(node.textContent);
	    }

	    // Create popup event handlers
	    if (isTangledLink(link.href)) {
		addLinkPopup(link);
	    }
David Byers's avatar
David Byers committed
	}
    console.log('exit fixAllTheLinks'); // DEBUG
David Byers's avatar
David Byers committed


/**
 * Remove all safe links in an element
 * @param {Element} root - DOM element in which to fix links.
 */
function removeAllTheLinks(root) {
    console.log('enter removeAllTheLinks'); // DEBUG
    for (const link of root.getElementsByTagName('a')) {
	console.log(link);	// DEBUG
David Byers's avatar
David Byers committed
	if (isTangledLink(link.href)) {
	    link.href = untangleLink(link.href);
David Byers's avatar
David Byers committed
	}
    }
    for (const textNode of getTextNodes(root)) {
	textNode.textContent = untangleLink(textNode.textContent);
    }
    console.log('exit removeAllTheLinks'); // DEBUG
David Byers's avatar
David Byers committed
}