Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external resources handling for inline message style #1002

Merged
merged 1 commit into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions modules/core/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -2469,3 +2469,112 @@ function getEmailProviderKey(email) {

return "";
}

/**
* Allow external resources for the provided element.
*
* @param {HTMLElement} element - The element containing the allow button.
* @param {string} messagePart - The message part associated with the resource.
* * @param {Boolean} inline - true if the message is displayed in inline mode, false otherwise.
* @returns {void}
*/
function handleAllowResource(element, messagePart, inline = false) {
element.querySelector('a').addEventListener('click', function (e) {
e.preventDefault();
$('.msg_text_inner').remove();
const externalSources = $(this).data('src').split(',');
externalSources?.forEach((source) => Hm_Utils.save_to_local_storage(source, 1));
if (inline) {
return inline_imap_msg(window.inline_msg_details, window.inline_msg_uid);
}
return get_message_content(messagePart, false, false, false, false, false);
});
}

/**
* Create and insert in the DOM an element containing a message and a button to allow the resource.
*
* @param {HTMLElement} element - The element having the blocked resource.
* @param {Boolean} inline - true if the message is displayed in inline mode, false otherwise.
* @returns {void}
*/
function handleInvisibleResource(element, inline = false) {
const dataSrc = element.dataset.src;

const allowResource = document.createElement('div');
allowResource.classList.add('alert', 'alert-warning', 'p-1');

const source = dataSrc.substring(0, 40) + (dataSrc.length > 40 ? '...' : '');
allowResource.innerHTML = `Source blocked: ${element.alt ? element.alt : source}
<a href="#" data-src="${dataSrc}" class="btn btn-light btn-sm">
Allow</a></div>
`;

document.querySelector('.external_notices').insertAdjacentElement('beforeend', allowResource);
handleAllowResource(allowResource, element.dataset.messagePart, inline);
}

const handleExternalResources = (inline) => {
const messageContainer = document.querySelector('.msg_text_inner');
messageContainer.insertAdjacentHTML('afterbegin', '<div class="external_notices"></div>');

const sender = document.querySelector('#contact_info').textContent.trim().replace(/\s/g, '_') + 'external_resources_allowed';
const elements = messageContainer.querySelectorAll('[data-src]');
const blockedResources = [];
elements.forEach(function (element) {

const dataSrc = element.dataset.src;
const senderAllowed = Hm_Utils.get_from_local_storage(sender);
const allowed = Hm_Utils.get_from_local_storage(dataSrc);

switch (Number(allowed) || Number(senderAllowed)) {
case 1:
element.src = dataSrc;
break;
default:
if ((allowed || senderAllowed) === null) {
Hm_Utils.save_to_local_storage(dataSrc, 0);
}
handleInvisibleResource(element, inline);
blockedResources.push(dataSrc);
break;
}
});

const noticesElement = document.createElement('div');
noticesElement.classList.add('notices');

if (blockedResources.length) {
const allowAll = document.createElement('div');
allowAll.classList.add('allow_image_link', 'all', 'fw-bold');
allowAll.textContent = 'For security reasons, external resources have been blocked.';
if (blockedResources.length > 1) {
const allowAllLink = document.createElement('a');
allowAllLink.classList.add('btn', 'btn-light', 'btn-sm');
allowAllLink.href = '#';
allowAllLink.dataset.src = blockedResources.join(',');
allowAllLink.textContent = 'Allow all';
allowAll.appendChild(allowAllLink);
handleAllowResource(allowAll, elements[0].dataset.messagePart, inline);
}
noticesElement.appendChild(allowAll);

const button = document.createElement('a');
button.classList.add('always_allow_image', 'btn', 'btn-light', 'btn-sm');
button.textContent = 'Always allow from this sender';
noticesElement.appendChild(button);

button.addEventListener('click', function (e) {
e.preventDefault();
Hm_Utils.save_to_local_storage(sender, 1);
$('.msg_text_inner').remove();
if (inline) {
inline_imap_msg(window.inline_msg_details, window.inline_msg_uid);
} else {
get_message_content(elements[0].dataset.messagePart, false, false, false, false, false)
}
});
}

document.querySelector('.external_notices').insertAdjacentElement('beforebegin', noticesElement);
};
122 changes: 11 additions & 111 deletions modules/imap/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -1265,119 +1265,19 @@ var imap_hide_add_contact_popup = function(event) {
popup.classList.toggle("show");
};

/**
* Allow external resources for the provided element.
*
* @param {HTMLElement} element - The element containing the allow button.
* @param {string} messagePart - The message part associated with the resource.
* @returns {void}
*/
function handleAllowResource(element, messagePart) {
element.querySelector('a').addEventListener('click', function (e) {
e.preventDefault();
$('.msg_text_inner').remove();
const externalSources = $(this).data('src').split(',');
externalSources?.forEach((source) => Hm_Utils.save_to_local_storage(source, 1));
return get_message_content(messagePart, false, false, false, false, false);
});
}

/**
* Create and insert in the DOM an element containing a message and a button to allow the resource.
*
* @param {HTMLElement} element - The element having the blocked resource.
* @returns {void}
*/
function handleInvisibleResource(element) {
const dataSrc = element.dataset.src;

const allowResource = document.createElement('div');
// allowResource.classList.add('allow_image_link');
allowResource.classList.add('alert', 'alert-warning', 'p-1');

const source = dataSrc.substring(0, 40) + (dataSrc.length > 40 ? '...' : '');
allowResource.innerHTML = `Source blocked: ${element.alt ? element.alt : source}
<a href="#" data-src="${dataSrc}" class="btn btn-light btn-sm">
Allow</a></div>
`;

document.querySelector('.external_notices').insertAdjacentElement('beforeend', allowResource);
handleAllowResource(allowResource, element.dataset.messagePart);
}

const mutation = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function (node) {
if (node.classList.contains('msg_text_inner')) {

// Extarnal resources notice boxes container
document.querySelector('.msg_text_inner').insertAdjacentHTML('afterbegin', '<div class="external_notices"></div>');

const sender = document.querySelector('#contact_info').textContent.trim().replace(/\s/g, '_') + 'external_resources_allowed';
const elements = node.querySelectorAll('[data-src]');
const blockedResources = [];
elements.forEach(function (element) {

const dataSrc = element.dataset.src;
const senderAllowed = Hm_Utils.get_from_local_storage(sender);
const allowed = Hm_Utils.get_from_local_storage(dataSrc);

switch (Number(allowed) || Number(senderAllowed)) {
case 1:
element.src = dataSrc;
break;
default:
if ((allowed || senderAllowed) === null) {
Hm_Utils.save_to_local_storage(dataSrc, 0);
}
handleInvisibleResource(element);
blockedResources.push(dataSrc);
break;
}
});

const noticesElement = document.createElement('div');
noticesElement.classList.add('notices');

if(blockedResources.length) {
const allowAll = document.createElement('div');
allowAll.classList.add('allow_image_link', 'all', 'fw-bold');
allowAll.textContent = 'For security reasons, external resources have been blocked.';
if (blockedResources.length > 1) {
const allowAllLink = document.createElement('a');
allowAllLink.classList.add('btn', 'btn-light', 'btn-sm');
allowAllLink.href = '#';
allowAllLink.dataset.src = blockedResources.join(',');
allowAllLink.textContent = 'Allow all';
allowAll.appendChild(allowAllLink);
handleAllowResource(allowAll, elements[0].dataset.messagePart);
}
noticesElement.appendChild(allowAll);

const button = document.createElement('a');
button.classList.add('always_allow_image', 'btn', 'btn-light', 'btn-sm');
button.textContent = 'Always allow from this sender';
noticesElement.appendChild(button);

button.addEventListener('click', function (e) {
e.preventDefault();
Hm_Utils.save_to_local_storage(sender, 1);
$('.msg_text_inner').remove();
get_message_content(elements[0].dataset.messagePart, false, false, false, false, false)
});
}

document.querySelector('.external_notices').insertAdjacentElement('beforebegin', noticesElement);
}
});
}
});
});

const message = document.querySelector('.msg_text');
if (message) {
mutation.observe(document.querySelector('.msg_text'), {
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function (node) {
if (node.classList.contains('msg_text_inner')) {
handleExternalResources();
}
});
}
});
}).observe(message, {
childList: true
});
}
Expand Down
33 changes: 33 additions & 0 deletions modules/inline_message/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ var capture_subject_click = function() {
return false;
}
else if (details['type'] == 'imap') {
// store the details and uid on the window object for later use when external resources are handled
window.inline_msg_details = details;
window.inline_msg_uid = uid;
inline_imap_msg(details, uid, list_path, inline_msg_loaded_callback);
return false;
}
Expand Down Expand Up @@ -173,3 +176,33 @@ $(function() {
}
}
});

const messagesListMutation = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function (node) {
if (node.classList.contains('msg_text') || node.classList.contains('inline_msg')) {
if(node.querySelector('.msg_text_inner')) {
handleExternalResources(true);
}
}
});
}
});
});

// for inline-right style, we observe the message_list element
const messagesList = document.querySelector('.message_list');
if (messagesList) {
messagesListMutation.observe(messagesList, {
childList: true
});
}

// for inline style, we observe the message_table_body element
const messageTableBody = document.querySelector('.message_table_body');
if (messageTableBody) {
messagesListMutation.observe(messageTableBody, {
childList: true
});
}
Loading