// 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; } /** * Fix all the links in the document. * @param {Element} root - DOM element in which to fix links. */ function fixAllTheLinks(root) { for (const link of root.getElementsByTagName('a')) { 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); } } } } /** * Remove all safe links in an element * @param {Element} root - DOM element in which to fix links. */ function removeAllTheLinks(root) { for (const link of root.getElementsByTagName('a')) { if (isTangledLink(link.href)) { link.href = untangleLink(link.href); } } for (const textNode of getTextNodes(root)) { textNode.textContent = untangleLink(textNode.textContent); } }