Skip to content
Snippets Groups Projects
Commit 56147009 authored by David Byers's avatar David Byers
Browse files

First version of Firefox extension.

parent 6b30d08e
No related branches found
No related tags found
1 merge request!8Beta
Pipeline #33256 failed
// Microsoft 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.
// Background scripts
console.log('enter background');
browser.menus.create({
id: "liu-safelinks-copy",
title: browser.i18n.getMessage("copyLinkMenuTitle"),
contexts: ["link"],
visible: true,
targetUrlPatterns: ["*://*.safelinks.protection.outlook.com/*"],
});
browser.menus.onClicked.addListener((info, tab) => {
if (info.menuItemId == "liu-safelinks-copy") {
navigator.clipboard.writeText(untangleLink(info.linkUrl));
}
});
console.log('exit background');
File moved
// Microsoft 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.
// Display script
let currentPopupTarget = null;
let hidePopupTimeout = null;
let mutationObserver = null;
function enableMutationObserver() {
console.log('enter enableMutationObserver');
if (!mutationObserver) {
mutationObserver = new MutationObserver(withoutMutationObserver(fixAllTheLinks));
}
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
console.log('exit enableMutationObserver');
}
function disableMutationObserver() {
console.log('enter disableMutationObserver');
if (mutationObserver) {
mutationObserver.disconnect();
}
console.log('exit disableMutationObserver');
}
function withoutMutationObserver(func) {
return (...args) => {
try {
disableMutationObserver();
func(...args);
}
finally {
enableMutationObserver();
}
}
}
/**
* Return the popup div element, creating it if necessary.
* @returns {Element} The popup element.
*/
function getPopup() {
let popup = document.getElementById(safelinksPopupId);
if (!popup) {
popupElementLocked = false;
popup = document.createElement('div');
popup.id = safelinksPopupId;
popup.addEventListener('mouseenter', cancelHidePopup, {passive: true});
popup.addEventListener('mouseleave', scheduleHidePopup, {passive: true});
document.body.appendChild(popup);
}
return popup;
}
/**
* Cancel hiding the popup (if it has been scheduled) and set
* hidePopupTimeout to null.
*/
function cancelHidePopup() {
if (hidePopupTimeout) {
clearTimeout(hidePopupTimeout);
hidePopupTimeout = null;
}
}
/**
* Hide the current popup. If there is no popup, one will be created.
*/
function hidePopup() {
cancelHidePopup();
getPopup().classList.remove(safelinksPopupVisibleClass);
currentPopupTarget = undefined;
}
/**
* Schedule hiding the current popup.
*/
function scheduleHidePopup() {
if (!hidePopupTimeout) {
hidePopupTimeout = setTimeout(withoutMutationObserver(hidePopup), 100);
}
}
/**
* Get the absolute bounds of an element.
* @param {Element} elem - The element for which to return bounds.
* @returns {{top: number, left: number, right: number, bottom:
* number}} The top, left, right, and bottom coordinates of the
* element.
*/
function getAbsoluteBoundingRect(elem) {
let rect = elem.getBoundingClientRect();
let scrollLeft = window.scrollX;
let scrollTop = window.scrollY;
return {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
bottom: rect.bottom + window.scrollY,
right: rect.right + window.scrollX,
}
}
/**
* Attempt to ensure that at least part of an element is visible. If
* the element's right-hand coordinate is off-screen, move it
* on-screen without moving the left-hand side off-screen. If the
* bottom of the element is off-screen, move it on-screen.
* @param {Element} elem - The element to show.
*/
function clampElementToDocument(elem) {
let elemBounds = getAbsoluteBoundingRect(elem);
if (elemBounds.bottom > document.documentElement.scrollHeight) {
elem.style.removeProperty('top');
elem.style.bottom = 0;
}
if (elemBounds.right > document.documentElement.scrollWidth) {
elem.style.removeProperty('left');
elem.style.right = 0;
if (getAbsoluteBoundingRect(elem).left < 0) {
elem.style.left = 0;
}
}
}
/**
* Show the original URL of a link.
* @param {MouseEvent} event - The event triggering this handler.
*/
function showOriginalUrl(event) {
console.log('enter showOriginalUrl');
let popup = getPopup();
cancelHidePopup();
if (event.target != currentPopupTarget
|| !popup.classList.contains(safelinksPopupVisibleClass)) {
currentPopupTarget = event.target;
popup.textContent = untangleLink(event.target.href);
popup.style.removeProperty('bottom');
popup.style.removeProperty('right');
popup.style.left = event.clientX + 'px';
popup.style.top = event.clientY + 'px';
popup.classList.add(safelinksPopupVisibleClass);
clampElementToDocument(popup);
}
console.log('exit showOriginalUrl');
}
/**
* Add event handlers to a link so it will show the original url.
* @param {Element} link - The link to add the popup to.
*/
function addLinkPopup(link) {
link.addEventListener('mouseenter', withoutMutationObserver(showOriginalUrl), {passive: true});
link.addEventListener('mouseleave', scheduleHidePopup, {passive: true});
}
function fixAllTheLinks() {
console.log('enter fixAllTheLinks');
for (const link of document.links) {
// Untangle link text
for (const node of getTextNodes(link)) {
node.textContent = untangleLink(node.textContent);
}
// Create popup event handlers
if (isTangledLink(link.href)) {
addLinkPopup(link);
}
}
console.log('leave fixAllTheLinks');
}
fixAllTheLinks();
enableMutationObserver();
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 283.46 283.46" style="enable-background:new 0 0 283.46 283.46;" xml:space="preserve">
<style type="text/css">
.st0{fill:#231F20;}
.st1{fill:none;stroke:#231F20;stroke-width:36;stroke-linecap:round;stroke-miterlimit:10;}
.st2{fill:#ED1C24;}
.st3{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M209.46,74v135.46H74V74H209.46 M283.46,0H0v283.46h283.46V0L283.46,0z"/>
</g>
<path class="st1" d="M15.14,199.56"/>
<path class="st1" d="M15.14,216.86"/>
<g>
<polygon class="st2" points="97.71,88.66 150.06,36.3 99.24,-14.5 297.96,-14.5 297.96,184.2 246.66,132.89 194.3,185.25 "/>
<path class="st3" d="M283.46,0v149.2l-36.81-36.81l-52.36,52.36l-76.09-76.09l52.36-52.36L134.26,0H283.46 M312.46-29h-29H134.26
H64.23l49.52,49.51l15.8,15.79L97.71,68.15L77.2,88.66l20.51,20.51l76.09,76.09l20.51,20.51l20.51-20.51l31.85-31.85l16.3,16.3
l49.51,49.51V149.2V0V-29L312.46-29z"/>
</g>
</svg>
{
"manifest_version": 2,
"name": "Safe Links Cleaner",
"description": "__MSG_extensionDescription__",
"version": "1.0",
"author": "David Byers",
"homepage_url": "https://safelinks.gitlab-pages.liu.se/safelinks-cleaner-firefox/",
"default_locale": "en",
"icons": {
"48": "icon.svg",
"96": "icon.svg",
"144": "icon.svg",
"192": "icon.svg"
},
"background": {
"scripts": [
"common.js",
"background.js"
]
},
"content_scripts": [
{
"matches": ["*://outlook.office.com/*"],
"css": [
"/style.css"
],
"js": [
"common.js",
"content.js"
]
}
],
"permissions": [
"activeTab",
"clipboardWrite",
"menus"
]
}
File moved
{
"extensionDescription": {
"message": "Clean up display of links rewritten by Microsoft Defender for Office 365 Safe Links, so it is easy to see and copy the original link.",
"description": "Description of the extension."
},
"copyLinkMenuTitle": {
"message": "Copy original link",
"description": "Title of the copy original link menu item."
}
}
{
"extensionDescription": {
"message": "Ändra visning av länkar omskrivna av Microsoft Defender för Office 365 Safe Links, så det är enkelt att se och kopiera den ursrpungliga länken.",
"description": "Description of the extension."
},
"copyLinkMenuTitle": {
"message": "Kopiera ursprunglig länk",
"description": "Title of the copy original link menu item."
}
}
File moved
// Microsoft 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) {
console.log(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;
}
File moved
File moved
File moved
......@@ -4,7 +4,7 @@
"description": "__MSG_extensionDescription__",
"version": "1.3",
"author": "David Byers",
"homepage_url": "https://gitlab.liu.se/safelinks-cleaner-thunderbird/",
"homepage_url": "https://gitlab.liu.se/safelinks/safelinks-cleaner-thunderbird/",
"default_locale": "en",
"icons": {
"48": "icon.svg",
......
/*
Microsoft 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.
*/
#safelinks-cleaner-thunderbird-popup {
display: none;
background: #fffff8;
color: black;
padding: 3px 3px 4px 3px;
position: fixed;
z-index: 1000;
border: 1px solid black;
-webkit-box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5);
-moz-box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5);
box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5);
font: 14px sans-serif;
}
#safelinks-cleaner-thunderbird-popup.safelinks-cleaner-thunderbird-popup-visible {
display: block;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment