allow uploading structured messages

This commit is contained in:
Andris Reinman 2018-11-12 13:27:11 +02:00
parent e59f9e2776
commit 436a3aaa9b
6 changed files with 183 additions and 8 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-10-12T08:13:42.322Z", "url": "http://apidocjs.com", "version": "0.17.6" } });
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-12T11:25:20.064Z", "url": "http://apidocjs.com", "version": "0.17.6" } });

View file

@ -1 +1 @@
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-10-12T08:13:42.322Z", "url": "http://apidocjs.com", "version": "0.17.6" } }
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-12T11:25:20.064Z", "url": "http://apidocjs.com", "version": "0.17.6" } }

View file

@ -7,6 +7,8 @@ const log = require('npmlog');
const Joi = require('../joi');
const MongoPaging = require('mongo-cursor-pagination');
const addressparser = require('nodemailer/lib/addressparser');
const MailComposer = require('nodemailer/lib/mail-composer');
const htmlToText = require('html-to-text');
const ObjectID = require('mongodb').ObjectID;
const tools = require('../tools');
const consts = require('../consts');
@ -1865,7 +1867,30 @@ module.exports = (db, server, messageHandler) => {
* @apiParam {Boolean} [unseen=false] Is the message unseen or not
* @apiParam {Boolean} [draft=false] Is the message a draft or not
* @apiParam {Boolean} [flagged=false] Is the message flagged or not
* @apiParam {String} raw base64 encoded message source. Alternatively, you can provide this value as POST body by using message/rfc822 MIME type
* @apiParam {String} [raw] base64 encoded message source. Alternatively, you can provide this value as POST body by using message/rfc822 MIME type. If raw message is provided then it overrides any other mail configuration
* @apiParam {Object} [from] Address for the From: header
* @apiParam {String} from.name Name of the sender
* @apiParam {String} from.address Address of the sender
* @apiParam {Object[]} [to] Addresses for the To: header
* @apiParam {String} [to.name] Name of the recipient
* @apiParam {String} to.address Address of the recipient
* @apiParam {Object[]} [cc] Addresses for the Cc: header
* @apiParam {String} [cc.name] Name of the recipient
* @apiParam {String} cc.address Address of the recipient
* @apiParam {Object[]} [bcc] Addresses for the Bcc: header
* @apiParam {String} [bcc.name] Name of the recipient
* @apiParam {String} bcc.address Address of the recipient
* @apiParam {String} subject Message subject. If not then resolved from Reference message
* @apiParam {String} text Plaintext message
* @apiParam {String} html HTML formatted message
* @apiParam {Object[]} [headers] Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automaticall y
* @apiParam {String} headers.key Header key ('X-Mailer')
* @apiParam {String} headers.value Header value ('My Awesome Mailing Service')
* @apiParam {Object[]} [attachments] Attachments for the message
* @apiParam {String} attachments.content Base64 encoded attachment content
* @apiParam {String} [attachments.filename] Attachment filename
* @apiParam {String} [attachments.contentType] MIME type for the attachment file
* @apiParam {String} [attachments.cid] Content-ID value if you want to reference to this attachment from HTML formatted message
* @apiParam {String} [sess] Session identifier for the logs
* @apiParam {String} [ip] IP address for the logs
*
@ -1930,9 +1955,102 @@ module.exports = (db, server, messageHandler) => {
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
.default(false),
raw: Joi.binary()
.max(consts.MAX_ALLOWE_MESSAGE_SIZE)
.required(),
.empty(''),
time: Joi.date(),
from: Joi.object().keys({
name: Joi.string()
.empty('')
.max(255),
address: Joi.string()
.email()
.required()
}),
replyTo: Joi.object().keys({
name: Joi.string()
.empty('')
.max(255),
address: Joi.string()
.email()
.required()
}),
to: Joi.array().items(
Joi.object().keys({
name: Joi.string()
.empty('')
.max(255),
address: Joi.string()
.email()
.required()
})
),
cc: Joi.array().items(
Joi.object().keys({
name: Joi.string()
.empty('')
.max(255),
address: Joi.string()
.email()
.required()
})
),
bcc: Joi.array().items(
Joi.object().keys({
name: Joi.string()
.empty('')
.max(255),
address: Joi.string()
.email()
.required()
})
),
headers: Joi.array().items(
Joi.object().keys({
key: Joi.string()
.empty('')
.max(255),
value: Joi.string()
.empty('')
.max(100 * 1024)
})
),
subject: Joi.string()
.empty('')
.max(255),
text: Joi.string()
.empty('')
.max(1024 * 1024),
html: Joi.string()
.empty('')
.max(1024 * 1024),
attachments: Joi.array().items(
Joi.object().keys({
filename: Joi.string()
.empty('')
.max(255),
contentType: Joi.string()
.empty('')
.max(255),
encoding: Joi.string()
.empty('')
.default('base64'),
content: Joi.string().required(),
cid: Joi.string()
.empty('')
.max(255)
})
),
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
@ -1945,8 +2063,8 @@ module.exports = (db, server, messageHandler) => {
req.params[key] = req.query[key];
}
});
req.params.raw = req.params.raw || req.body;
req.params.raw = req.params.raw || req.body;
const result = Joi.validate(req.params, schema, {
abortEarly: false,
convert: true
@ -2022,6 +2140,42 @@ module.exports = (db, server, messageHandler) => {
return next();
}
if (!req.params.raw) {
let data = {
from: result.from,
date,
to: result.to || [],
cc: result.cc || [],
bcc: result.bcc || [],
subject: result.subject,
text: result.text || '',
html: result.html || '',
headers: result.headers || [],
attachments: result.attachments || [],
disableFileAccess: true,
disableUrlAccess: true
};
// ensure plaintext content if html is provided
if (data.html && !data.text) {
try {
// might explode on long or strange strings
data.text = htmlToText.fromString(data.html);
} catch (E) {
// ignore
}
}
raw = await getCompiledMessage(data);
}
if (!raw || !raw.length) {
res.json({
error: 'Empty message provided',
code: 'EmptyMessage'
});
return next();
}
if (userData.encryptMessages) {
try {
let encrypted = await encryptMessage(userData.pubKey, raw);
@ -2925,3 +3079,22 @@ function formatMessageListing(messageData) {
return response;
}
async function getCompiledMessage(data) {
return new Promise((resolve, reject) => {
let compiler = new MailComposer(data);
let compiled = compiler.compile();
let stream = compiled.createReadStream();
let chunks = [];
let chunklen;
stream.once('error', err => reject(err));
stream.once('readable', () => {
let chunk;
while ((chunk = stream.read()) !== null) {
chunks.push(chunk);
chunklen += chunk.length;
}
});
stream.once('end', () => resolve(Buffer.concat(chunks, chunklen)));
});
}

View file

@ -321,7 +321,9 @@ module.exports = (db, server, messageHandler, userHandler) => {
text: options.text || '',
html: options.html || '',
headers: extraHeaders.concat(options.headers || []),
attachments: options.attachments || []
attachments: options.attachments || [],
disableFileAccess: true,
disableUrlAccess: true
};
// ensure plaintext content if html is provided