From 1f27778ae88947027ec613bb3e4f1dd3aff6351f Mon Sep 17 00:00:00 2001 From: NickOvt Date: Mon, 8 Apr 2024 09:47:27 +0300 Subject: [PATCH] fix(api-messages-attachment-download): is sendAsString param is set, and is set to true then decode the original file and send back as UTF-8 ZMS-134 (#655) * add attachment charset in the indexer * test adding a field * test another way * walk through mimetree to find the attachment charset and decode the message using this charset. Decoding is piped to the output stream * test * revert last change * use regex for check, fix piping * fixes --- lib/api/messages.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/api/messages.js b/lib/api/messages.js index 449e8da7..ad0e2105 100644 --- a/lib/api/messages.js +++ b/lib/api/messages.js @@ -23,6 +23,7 @@ const TaskHandler = require('../task-handler'); const { prepareSearchFilter, uidRangeStringToQuery } = require('../prepare-search-filter'); const { getMongoDBQuery /*, getElasticSearchQuery*/ } = require('../search-query'); //const { getClient } = require('../elasticsearch'); +let iconv = require('iconv-lite'); const BimiHandler = require('../bimi-handler'); const { @@ -1528,7 +1529,11 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti summary: 'Download Attachment', description: 'This method returns attachment file contents in binary form', validationObjs: { - queryParams: {}, + queryParams: { + sendAsString: booleanSchema + .default(false) + .description('If true then sends the original attachment back in string format with correct encoding.') + }, pathParams: { user: userId, mailbox: mailboxId, @@ -1600,7 +1605,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti _id: true, user: true, attachments: true, - 'mimeTree.attachmentMap': true + mimeTree: true } } ); @@ -1637,6 +1642,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti }); } + let [attachmentCharset] = getAttachmentCharset(messageData.mimeTree, attachment); + res.writeHead(200, { 'Content-Type': attachmentData.contentType || 'application/octet-stream' }); @@ -1678,6 +1685,10 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti } else if (attachmentData.transferEncoding === 'quoted-printable') { attachmentStream.pipe(new libqp.Decoder()).pipe(res); } else { + if (!/ascii|utf[-_]?8/i.test(attachmentCharset) && result.value.sendAsString) { + attachmentStream.pipe(iconv.decodeStream(attachmentCharset)).pipe(res); + return; + } attachmentStream.pipe(res); } }) @@ -3901,3 +3912,25 @@ function parseAddresses(data) { walk([].concat(data || [])); return Array.from(addresses); } + +function getAttachmentCharset(mimeTree, attachmentId) { + if (mimeTree.attachmentId && mimeTree.attachmentId === attachmentId) { + // current mimeTree (sub)object has the attachmentId field, and it is the one we search + // get the parsedHeader -> content-type -> params -> charset + + return [mimeTree.parsedHeader['content-type']?.params?.charset || 'UTF-8', true]; + } else if (mimeTree.childNodes) { + // current mimetree (sub)object does not have the attachmentId field and it is not equal to the one we search + // loop childNodes + let charset; + for (const childNode of Object.values(mimeTree.childNodes)) { + charset = getAttachmentCharset(childNode, attachmentId); + if (charset[1] === true) { + // actually found the charset, early return + return charset; + } + } + } + + return ['UTF-8', false]; +}