diff --git a/docs/api/openapi.yml b/docs/api/openapi.yml index 832ba470..546553e5 100644 --- a/docs/api/openapi.yml +++ b/docs/api/openapi.yml @@ -1711,6 +1711,12 @@ paths: description: Ordering of the records by insert date schema: $ref: '#/components/schemas/Order' + - name: includeHeaders + in: query + description: 'Comma separated list of header keys to include in the response' + schema: + type: string + example: 'List-ID, MIME-version' - name: next in: query description: 'Cursor value for next page, retrieved from nextCursor response value' @@ -2037,6 +2043,12 @@ paths: description: Ordering of the records by insert date. If no order is supplied, results are sorted by heir mongoDB ObjectId. schema: $ref: '#/components/schemas/Order' + - name: includeHeaders + in: query + description: 'Comma separated list of header keys to include in the response' + schema: + type: string + example: 'List-ID, MIME-version' - name: page in: query description: 'Current page number. Informational only, page numbers start from 1' @@ -6918,6 +6930,9 @@ components: metaData: type: object description: Custom metadata value. Included if metaData query argument was true + headers: + type: object + description: Header object keys requested with the includeHeaders argument GetFilesResult: required: - id diff --git a/lib/api/messages.js b/lib/api/messages.js index d3eaf3cb..b197aad6 100644 --- a/lib/api/messages.js +++ b/lib/api/messages.js @@ -387,6 +387,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti let sortAscending = result.value.order === 'asc'; let filterUnseen = result.value.unseen; + let includeHeaders = result.value.includeHeaders ? result.value.includeHeaders.split(',') : false; + let mailboxData; try { mailboxData = await db.database.collection('mailboxes').findOne( @@ -443,13 +445,6 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti hdate: true, idate: true, subject: true, - 'mimeTree.parsedHeader.from': true, - 'mimeTree.parsedHeader.sender': true, - 'mimeTree.parsedHeader.to': true, - 'mimeTree.parsedHeader.cc': true, - 'mimeTree.parsedHeader.bcc': true, - 'mimeTree.parsedHeader.content-type': true, - 'mimeTree.parsedHeader.references': true, ha: true, size: true, intro: true, @@ -466,6 +461,24 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti sortAscending }; + if (includeHeaders) { + // get all headers + opts.fields.projection['mimeTree.parsedHeader'] = true; + } else { + // get only required headers + for (let requiredHeader of [ + 'mimeTree.parsedHeader.from', + 'mimeTree.parsedHeader.sender', + 'mimeTree.parsedHeader.to', + 'mimeTree.parsedHeader.cc', + 'mimeTree.parsedHeader.bcc', + 'mimeTree.parsedHeader.content-type', + 'mimeTree.parsedHeader.references' + ]) { + opts.fields.projection[requiredHeader] = true; + } + } + if (pageNext) { opts.next = pageNext; } else if ((!page || page > 1) && pagePrevious) { @@ -500,7 +513,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti previousCursor: listing.hasPrevious ? listing.previous : false, nextCursor: listing.hasNext ? listing.next : false, specialUse: mailboxData.specialUse, - results: (listing.results || []).map(formatMessageListing) + results: (listing.results || []).map(entry => formatMessageListing(entry, includeHeaders)) }; return res.json(response); @@ -532,6 +545,12 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti attachments: booleanSchema, flagged: booleanSchema, unseen: booleanSchema, + includeHeaders: Joi.string() + .max(1024) + .trim() + .empty('') + .example('List-ID, MIME-Version') + .description('Comma separated list of header keys to include in the response'), searchable: booleanSchema, sess: sessSchema, ip: sessIPSchema @@ -546,6 +565,12 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti threadCounters: booleanSchema.default(false), limit: Joi.number().default(20).min(1).max(250), order: Joi.any().empty('').allow('asc', 'desc').optional(), + includeHeaders: Joi.string() + .max(1024) + .trim() + .empty('') + .example('List-ID, MIME-Version') + .description('Comma separated list of header keys to include in the response'), next: nextPageCursorSchema, previous: previousPageCursorSchema, page: pageNrSchema @@ -581,6 +606,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti let pagePrevious = result.value.previous; let order = result.value.order; + let includeHeaders = result.value.includeHeaders ? result.value.includeHeaders.split(',') : false; + let filter; let query; @@ -588,29 +615,24 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti let hasESFeatureFlag = await db.redis.sismember(`feature:indexing`, user.toString()); if (hasESFeatureFlag) { // search from ElasticSearch + /* + // TODO: paging and cursors for ElasticSearch results let searchQuery = await getElasticSearchQuery(db, user, result.value.q); const esclient = getClient(); - console.log( - util.inspect( - { - index: config.elasticsearch.index, - body: { query: searchQuery, sort: { uid: 'desc' } } - }, - false, - 22, - true - ) - ); - let searchResult = await esclient.search({ + const searchOpts = { index: config.elasticsearch.index, body: { query: searchQuery, sort: { uid: 'desc' } } - }); + }; + + let searchResult = await esclient.search(searchOpts); + const searchHits = searchResult && searchResult.body && searchResult.body.hits; console.log('ES RESULTS'); console.log(util.inspect(searchResult, false, 22, true)); + */ } filter = await getMongoDBQuery(db, user, result.value.q); @@ -640,13 +662,6 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti hdate: true, idate: true, subject: true, - 'mimeTree.parsedHeader.from': true, - 'mimeTree.parsedHeader.sender': true, - 'mimeTree.parsedHeader.to': true, - 'mimeTree.parsedHeader.cc': true, - 'mimeTree.parsedHeader.bcc': true, - 'mimeTree.parsedHeader.content-type': true, - 'mimeTree.parsedHeader.references': true, ha: true, intro: true, size: true, @@ -663,6 +678,24 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti sortAscending: order === 'asc' ? true : undefined }; + if (includeHeaders) { + // get all headers + opts.fields.projection['mimeTree.parsedHeader'] = true; + } else { + // get only required headers + for (let requiredHeader of [ + 'mimeTree.parsedHeader.from', + 'mimeTree.parsedHeader.sender', + 'mimeTree.parsedHeader.to', + 'mimeTree.parsedHeader.cc', + 'mimeTree.parsedHeader.bcc', + 'mimeTree.parsedHeader.content-type', + 'mimeTree.parsedHeader.references' + ]) { + opts.fields.projection[requiredHeader] = true; + } + } + if (pageNext) { opts.next = pageNext; } else if ((!page || page > 1) && pagePrevious) { @@ -697,7 +730,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti page, previousCursor: listing.hasPrevious ? listing.previous : false, nextCursor: listing.hasNext ? listing.next : false, - results: (listing.results || []).map(formatMessageListing) + results: (listing.results || []).map(entry => formatMessageListing(entry, includeHeaders)) }; return res.json(response); @@ -2499,6 +2532,12 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti next: nextPageCursorSchema, previous: previousPageCursorSchema, order: Joi.any().empty('').allow('asc', 'desc').default('desc'), + includeHeaders: Joi.string() + .max(1024) + .trim() + .empty('') + .example('List-ID, MIME-Version') + .description('Comma separated list of header keys to include in the response'), page: pageNrSchema, sess: sessSchema, ip: sessIPSchema @@ -2533,6 +2572,8 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti let pagePrevious = result.value.previous; let sortAscending = result.value.order === 'asc'; + let includeHeaders = result.value.includeHeaders ? result.value.includeHeaders.split(',') : false; + let total = await db.database.collection('archived').countDocuments({ user }); let opts = { @@ -2551,13 +2592,6 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti hdate: true, idate: true, subject: true, - 'mimeTree.parsedHeader.from': true, - 'mimeTree.parsedHeader.sender': true, - 'mimeTree.parsedHeader.to': true, - 'mimeTree.parsedHeader.cc': true, - 'mimeTree.parsedHeader.bcc': true, - 'mimeTree.parsedHeader.content-type': true, - 'mimeTree.parsedHeader.references': true, ha: true, intro: true, size: true, @@ -2573,6 +2607,24 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti sortAscending }; + if (includeHeaders) { + // get all headers + opts.fields.projection['mimeTree.parsedHeader'] = true; + } else { + // get only required headers + for (let requiredHeader of [ + 'mimeTree.parsedHeader.from', + 'mimeTree.parsedHeader.sender', + 'mimeTree.parsedHeader.to', + 'mimeTree.parsedHeader.cc', + 'mimeTree.parsedHeader.bcc', + 'mimeTree.parsedHeader.content-type', + 'mimeTree.parsedHeader.references' + ]) { + opts.fields.projection[requiredHeader] = true; + } + } + if (pageNext) { opts.next = pageNext; } else if ((!page || page > 1) && pagePrevious) { @@ -2606,7 +2658,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti m.uid = m._id; return m; }) - .map(formatMessageListing) + .map(entry => formatMessageListing(entry, includeHeaders)) }; return res.json(response); @@ -3127,7 +3179,17 @@ function leftPad(val, chr, len) { return chr.repeat(len - val.toString().length) + val; } -function formatMessageListing(messageData) { +function formatMessageListing(messageData, includeHeaders) { + includeHeaders = [] + .concat(includeHeaders || []) + .map(entry => { + if (typeof entry !== 'string') { + return false; + } + return entry.toLowerCase().trim(); + }) + .filter(entry => entry); + let parsedHeader = (messageData.mimeTree && messageData.mimeTree.parsedHeader) || {}; let from = parsedHeader.from || @@ -3176,6 +3238,15 @@ function formatMessageListing(messageData) { bimi: messageData.bimi }; + if (includeHeaders.length) { + response.headers = {}; + for (let headerKey of includeHeaders) { + if (parsedHeader[headerKey]) { + response.headers[headerKey] = parsedHeader[headerKey]; + } + } + } + if (messageData.meta && 'custom' in messageData.meta) { response.metaData = tools.formatMetaData(messageData.meta.custom); }