Fresh repository history for elektrine/elektrine-haraka hosted at https://git.elektrine.com/elektrine/elektrine-haraka.
181 lines
5.6 KiB
JavaScript
181 lines
5.6 KiB
JavaScript
/**
|
|
* Attachment Handler Module
|
|
*
|
|
* Extracts and processes email attachments from parsed emails.
|
|
* Converts attachment content to base64 for API transmission.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* Extract attachment information from a parsed email
|
|
*
|
|
* @param {Object} transaction - Haraka transaction object
|
|
* @param {Object} parsed - Parsed email from mailparser
|
|
* @param {Object} [options] - Options
|
|
* @param {boolean} [options.include_content=true] - Include base64 content
|
|
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
* @param {Function} [options.logger] - Logger function
|
|
* @returns {Object} Object with attachments array and count
|
|
*/
|
|
function extract(transaction, parsed, options = {}) {
|
|
const {
|
|
include_content = true,
|
|
debug = false,
|
|
logger = console.log
|
|
} = options;
|
|
|
|
if (debug) logger('Extracting attachment information');
|
|
|
|
let attachments = [];
|
|
let attachment_count = 0;
|
|
|
|
// Primary: Use mailparser attachments (best source for content)
|
|
if (parsed && parsed.attachments && parsed.attachments.length > 0) {
|
|
if (debug) logger(`Found ${parsed.attachments.length} parsed attachments`);
|
|
|
|
attachment_count = parsed.attachments.length;
|
|
attachments = parsed.attachments.map((att, index) => {
|
|
return format_attachment(att, index, include_content, debug, logger);
|
|
});
|
|
}
|
|
// Fallback: Check transaction notes for attachment plugin results
|
|
else if (transaction && transaction.notes && transaction.notes.attachment) {
|
|
if (debug) logger('Using attachment notes fallback');
|
|
|
|
const att_notes = transaction.notes.attachment;
|
|
|
|
if (att_notes.files && Array.isArray(att_notes.files)) {
|
|
attachment_count = att_notes.files.length;
|
|
attachments = att_notes.files.map((file, index) => ({
|
|
filename: file.filename || file.name || `attachment_${index}`,
|
|
content_type: file.ctype || file.content_type || 'application/octet-stream',
|
|
size: file.bytes || file.size || 0,
|
|
md5: file.md5,
|
|
content: null, // No content available from plugin notes
|
|
encoding: 'base64',
|
|
index: index
|
|
}));
|
|
}
|
|
}
|
|
|
|
if (debug) {
|
|
const with_content = attachments.filter(a => a.content).length;
|
|
logger(`Final attachment count: ${attachment_count}, with content: ${with_content}`);
|
|
}
|
|
|
|
return {
|
|
attachments,
|
|
count: attachment_count,
|
|
has_attachments: attachment_count > 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format a single attachment for API transmission
|
|
*
|
|
* @param {Object} att - Attachment object from mailparser
|
|
* @param {number} index - Attachment index
|
|
* @param {boolean} include_content - Include base64 content
|
|
* @param {boolean} debug - Debug mode
|
|
* @param {Function} logger - Logger function
|
|
* @returns {Object} Formatted attachment object
|
|
*/
|
|
function format_attachment(att, index, include_content, debug, logger) {
|
|
let content_base64 = null;
|
|
|
|
if (include_content && att.content) {
|
|
content_base64 = to_base64(att.content);
|
|
}
|
|
|
|
const attachment_info = {
|
|
filename: att.filename || att.name || `attachment_${index}`,
|
|
content_type: att.contentType || att.content_type || 'application/octet-stream',
|
|
size: att.size || (att.content ? att.content.length : 0),
|
|
content_id: att.cid || null,
|
|
content: content_base64,
|
|
encoding: 'base64',
|
|
index: index
|
|
};
|
|
|
|
if (debug) {
|
|
logger(`Attachment ${index}: ${attachment_info.filename}, ` +
|
|
`${attachment_info.size} bytes, type: ${attachment_info.content_type}`);
|
|
}
|
|
|
|
return attachment_info;
|
|
}
|
|
|
|
/**
|
|
* Convert content to base64 string
|
|
*
|
|
* @param {Buffer|string} content - Content to convert
|
|
* @returns {string|null} Base64 encoded string or null
|
|
*/
|
|
function to_base64(content) {
|
|
if (!content) return null;
|
|
|
|
if (Buffer.isBuffer(content)) {
|
|
return content.toString('base64');
|
|
}
|
|
|
|
if (typeof content === 'string') {
|
|
return Buffer.from(content).toString('base64');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Calculate total attachment size
|
|
*
|
|
* @param {Array} attachments - Array of attachment objects
|
|
* @returns {number} Total size in bytes
|
|
*/
|
|
function get_total_size(attachments) {
|
|
if (!attachments || !Array.isArray(attachments)) return 0;
|
|
|
|
return attachments.reduce((total, att) => {
|
|
return total + (att.size || 0);
|
|
}, 0);
|
|
}
|
|
|
|
/**
|
|
* Check if any attachment exceeds size limit
|
|
*
|
|
* @param {Array} attachments - Array of attachment objects
|
|
* @param {number} max_size - Maximum size in bytes
|
|
* @returns {boolean} True if any attachment exceeds limit
|
|
*/
|
|
function has_oversized(attachments, max_size) {
|
|
if (!attachments || !Array.isArray(attachments)) return false;
|
|
|
|
return attachments.some(att => (att.size || 0) > max_size);
|
|
}
|
|
|
|
/**
|
|
* Filter attachments by content type
|
|
*
|
|
* @param {Array} attachments - Array of attachment objects
|
|
* @param {string|string[]} content_types - Content type(s) to match
|
|
* @returns {Array} Filtered attachments
|
|
*/
|
|
function filter_by_type(attachments, content_types) {
|
|
if (!attachments || !Array.isArray(attachments)) return [];
|
|
|
|
const types = Array.isArray(content_types) ? content_types : [content_types];
|
|
|
|
return attachments.filter(att => {
|
|
const att_type = (att.content_type || '').toLowerCase();
|
|
return types.some(type => att_type.includes(type.toLowerCase()));
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
extract,
|
|
format_attachment,
|
|
to_base64,
|
|
get_total_size,
|
|
has_oversized,
|
|
filter_by_type
|
|
};
|