Skip to content

Commit

Permalink
Merge pull request #918 from jacob-js/view-source
Browse files Browse the repository at this point in the history
[NEW] add a way to show and download the source of an email
  • Loading branch information
kroky authored Feb 27, 2024
2 parents 63db6bc + 666f1a2 commit 2240fe2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 15 deletions.
31 changes: 17 additions & 14 deletions modules/core/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,11 @@ function setup_base_ajax_page($name, $source=false) {
* @subpackage core/functions
* @param string $name the page id
* @param string $source the module set name
* @param bool $use_layout true if this page uses the application layout
* @return void
*/
if (!hm_exists('setup_base_page')) {
function setup_base_page($name, $source=false) {
function setup_base_page($name, $source=false, $use_layout=true) {
add_handler($name, 'stay_logged_in', false, $source);
add_handler($name, 'login', false, $source);
add_handler($name, 'default_page_data', true, $source);
Expand All @@ -429,20 +430,22 @@ function setup_base_page($name, $source=false) {
add_output($name, 'js_data', false, $source);
add_output($name, 'js_search_data', true, $source);
add_output($name, 'header_end', false, $source);
add_output($name, 'content_start', false, $source);
add_output($name, 'login_start', false, $source);
add_output($name, 'login', false, $source);
add_output($name, 'login_end', false, $source);
add_output($name, 'loading_icon', true, $source);
add_output($name, 'date', true, $source);
add_output($name, 'msgs', false, $source);
add_output($name, 'folder_list_start', true, $source);
add_output($name, 'folder_list_end', true, $source);
add_output($name, 'content_section_start', true, $source);
add_output($name, 'content_section_end', true, $source);
add_output($name, 'save_reminder', true, $source);
if($use_layout) {
add_output($name, 'content_start', false, $source);
add_output($name, 'login_start', false, $source);
add_output($name, 'login', false, $source);
add_output($name, 'login_end', false, $source);
add_output($name, 'loading_icon', true, $source);
add_output($name, 'date', true, $source);
add_output($name, 'msgs', false, $source);
add_output($name, 'folder_list_start', true, $source);
add_output($name, 'folder_list_end', true, $source);
add_output($name, 'content_section_start', true, $source);
add_output($name, 'content_section_end', true, $source);
add_output($name, 'save_reminder', true, $source);
add_output($name, 'content_end', false, $source, 'page_js', 'after');
}
add_output($name, 'page_js', false, $source);
add_output($name, 'content_end', false, $source);
}}

/**
Expand Down
1 change: 1 addition & 0 deletions modules/core/setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
'deleted_server_id' => array(FILTER_SANITIZE_FULL_SPECIAL_CHARS, false),
'msg_headers' => array(FILTER_UNSAFE_RAW, false),
'msg_text' => array(FILTER_UNSAFE_RAW, false),
'msg_source' => array(FILTER_UNSAFE_RAW, false),
'msg_parts' => array(FILTER_UNSAFE_RAW, false),
'page_links' => array(FILTER_UNSAFE_RAW, false),
'folder_status' => array(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Expand Down
19 changes: 19 additions & 0 deletions modules/imap/handler_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,25 @@ public function process() {
}
}

/**
* Get message source from an IMAP server
*/
class Hm_Handler_imap_message_source extends Hm_Handler_Module {
public function process() {
$imap_server_id = $this->request->get['imap_server_id'];
$imap_msg_uid = $this->request->get['imap_msg_uid'];
$folder = $this->request->get['imap_folder'];
if ($imap_server_id && $imap_msg_uid && $folder) {
$cache = Hm_IMAP_List::get_cache($this->cache, $imap_server_id);
$imap = Hm_IMAP_List::connect($imap_server_id, $cache);
if ($imap->select_mailbox(hex2bin($folder))) {
$msg_source = $imap->get_message_content($imap_msg_uid, 0, false);
$this->out('msg_source', $msg_source);
}
}
}
}

/**
* Hide or unhide an IMAP server
* @subpackage imap/handler
Expand Down
25 changes: 25 additions & 0 deletions modules/imap/output_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ protected function output() {
$txt .= ' | <a class="hlink" id="move_message" href="#">'.$this->trans('Move').'</a>';
$txt .= ' | <a class="archive_link hlink" id="archive_message" href="#">'.$this->trans('Archive').'</a>';
$txt .= ' | ' . snooze_dropdown($this, isset($headers['X-Snoozed']));
$txt .= ' | <a class="hlink" id="show_message_source" href="#">' . $this->trans('Show Source') . '</a>';

if ($this->get('sieve_filters_enabled')) {
$server_id = $this->get('msg_server_id');
Expand Down Expand Up @@ -1284,3 +1285,27 @@ protected function output() {
$this->concat('msg_controls_extra', $res);
}
}

/**
* Output imap message source
* @subpackage imap/output
*/
class Hm_Output_imap_message_source extends Hm_Output_Module {
protected function output() {
$res = '<div class="w-auto mx-auto p-5">';
$res .= '
<div class="d-flex flex-column gap-2 mb-4">
<h1>Message source</h1>
<div class="d-flex justify-content-between mb-3">
<button class="btn btn-success" onclick="handleDownloadMsgSource()">Download</button>
<a href="#" class="hlink" onClick="handleCopyMsgSource(event)">Copy to clipboard</a>
</div>
</div>
';
if($this->get('msg_source')){
$res .= '<div><pre class="msg_source">'.$this->html_safe($this->get('msg_source')).'</pre></div>';
}
$res .= '</div>';
return $res;
}
}
10 changes: 9 additions & 1 deletion modules/imap/setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
add_handler('message', 'imap_remove_attachment', true, 'imap', 'message_list_type', 'after');
add_output('message', 'imap_server_ids', true, 'imap', 'page_js', 'before');

/* message source page */
setup_base_page('message_source', 'core', false);
add_output('message_source', 'imap_message_source', true);
add_handler('message_source', 'imap_message_source', true);

/* ajax mark as read */
setup_base_ajax_page('ajax_imap_mark_as_read', 'core');
add_handler('ajax_imap_mark_as_read', 'load_imap_servers_from_config', true, 'imap', 'load_user_data', 'after');
Expand Down Expand Up @@ -326,6 +331,7 @@
'ajax_imap_snooze',
'ajax_imap_unsnooze',
'ajax_imap_junk',
'message_source',
),

'allowed_output' => array(
Expand Down Expand Up @@ -353,7 +359,9 @@
'imap_download_message' => FILTER_VALIDATE_BOOLEAN,
'imap_remove_attachment' => FILTER_VALIDATE_BOOLEAN,
'imap_show_message' => FILTER_VALIDATE_BOOLEAN,
'imap_msg_part' => FILTER_DEFAULT
'imap_msg_part' => FILTER_DEFAULT,
'imap_msg_uid' => FILTER_DEFAULT,
'imap_folder' => FILTER_DEFAULT,
),

'allowed_post' => array(
Expand Down
25 changes: 25 additions & 0 deletions modules/imap/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ var imap_message_view_finished = function(msg_uid, detail, skip_links) {

return block_unblock_sender(msg_uid, detail, scope, action, sender, reject_message);
});
$('#show_message_source').on("click", function(e) {
e.preventDefault();
const detail = Hm_Utils.parse_folder_path(hm_list_path(), 'imap');
window.open(`?page=message_source&imap_msg_uid=${hm_msg_uid()}&imap_server_id=${detail.server_id}&imap_folder=${detail.folder}`);
});
$(document).on('click', '#unblock_sender', function(e) {
e.preventDefault();
var sender = '';
Expand Down Expand Up @@ -1375,3 +1380,23 @@ if (message) {
childList: true
});
}

const handleDownloadMsgSource = function() {
const messageSource = document.querySelector('pre.msg_source');
const blob = new Blob([messageSource.textContent], { type: "message/rfc822" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const subject = messageSource.textContent.match(/Subject: (.*)/)?.[1] || hm_msg_uid(); // Let's use the message UID if the subject is empty
a.download = subject + '.eml';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};

const handleCopyMsgSource = function(e) {
e.preventDefault();
const messageSource = document.querySelector('pre.msg_source');
navigator.clipboard.writeText(messageSource.textContent);
Hm_Notices.show(['Copied to clipboard']);
}

0 comments on commit 2240fe2

Please sign in to comment.