diff --git a/ImapManager.js b/ImapManager.js
index 7f1c5ad..dc86cfb 100644
--- a/ImapManager.js
+++ b/ImapManager.js
@@ -166,7 +166,7 @@ class ImapManager
end();
}
})
- .catch(e => reject(e));
+ .catch(reject);
});
f.once('end', () =>
diff --git a/Syncer.js b/Syncer.js
index 4ff4a4f..1bdcce9 100644
--- a/Syncer.js
+++ b/Syncer.js
@@ -1,9 +1,14 @@
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+
const Imap = require('imap');
const EventEmitter = require('events').EventEmitter;
const iconv = require('iconv-lite');
const MailParser = require('mailparser').MailParser;
const mimelib = require('mimelib');
+const fsp = require('./fsp.js');
const ImapManager = require('./ImapManager.js');
const sanitizeHtml = require('./sanitize.js');
const SQL = require('./select-builder-pgsql.js');
@@ -23,7 +28,16 @@ class Syncer
// public
async init(cfg)
{
- for (var i = 0; i < cfg.accounts.length; i++)
+ this.files_path = path.resolve(cfg.files_path);
+ try
+ {
+ fs.accessSync(this.files_path, fs.constants.R_OK || fs.constants.W_OK);
+ }
+ catch (e)
+ {
+ throw new Error(this.files_path+' is not writable');
+ }
+ for (let i = 0; i < cfg.accounts.length; i++)
{
await this.addAccount(cfg.accounts[i]);
}
@@ -402,15 +416,60 @@ class Syncer
}
}
- async parseMsg(msg)
+ async parseMsg(msg_text)
{
let parser = new MailParser({ streamAttachments: false, defaultCharset: 'windows-1251' });
- return await new Promise((r, j) =>
+ let msg = await new Promise((resolve, reject) =>
{
- parser.once('end', r);
- parser.write(msg);
+ parser.on('error', reject);
+ parser.once('end', resolve);
+ parser.write(msg_text);
parser.end();
});
+ let byid = {};
+ for (let a of msg.attachments||[])
+ {
+ byid[a.contentId||''] = a;
+ }
+ msg.html = msg.html.replace(/(]*src=["']?)cid:([^'"\s]{1,256})/g, (m, m1, m2) =>
+ {
+ if (!byid[m2])
+ {
+ return m1 + 'cid:' + m2;
+ }
+ return m1 + 'data:' + byid[m2].contentType + ';base64,' + byid[m2].toString('base64');
+ });
+ let attachments = [];
+ for (let a of msg.attachments||[])
+ {
+ let hash = crypto.createHash('sha1');
+ hash.update(a.content);
+ let sha1 = hash.digest('hex');
+ let subdir = sha1.substr(0, 2)+'/'+sha1.substr(2, 2);
+ let filename = subdir+'/'+sha1+'.bin';
+ if (!await fsp.exists(this.files_path+'/'+filename))
+ {
+ if (!await fsp.exists(this.files_path+'/'+sha1.substr(0, 2)))
+ {
+ await fsp.mkdir(this.files_path+'/'+sha1.substr(0, 2));
+ }
+ if (!await fsp.exists(this.files_path+'/'+subdir))
+ {
+ await fsp.mkdir(this.files_path+'/'+subdir);
+ }
+ await fsp.writeFile(this.files_path+'/'+filename, a.content);
+ }
+ attachments.push({
+ id: a.contentId,
+ name: a.fileName,
+ mimetype: a.contentType,
+ size: a.length,
+ sha1,
+ filename,
+ });
+ }
+ msg.attachments = attachments;
+ return msg;
}
extractAttachments(struct, attachments)
@@ -424,11 +483,11 @@ class Syncer
}
else if (struct[i].disposition && struct[i].disposition.type == 'attachment')
{
- attachments.push([
- mimelib.parseMimeWords(struct[i].disposition.params && struct[i].disposition.params.filename || struct[i].description || ''),
- struct[i].type+'/'+struct[i].subtype,
- struct[i].size,
- ]);
+ attachments.push({
+ name: mimelib.parseMimeWords(struct[i].disposition.params && struct[i].disposition.params.filename || struct[i].description || ''),
+ mimetype: struct[i].type+'/'+struct[i].subtype,
+ size: struct[i].size,
+ });
}
}
return attachments;
@@ -538,12 +597,10 @@ class Syncer
async fetchFullMessage(account_id, folder_id, folder_name, msg_uid)
{
- // FIXME: parse and save attachments
- // FIXME: replace inline images
let srv = await this.imap.getConnection(account_id, folder_name);
let upd = await this.imap.runFetch(
srv, msg_uid, { bodies: '' },
- async(messages, state) => await this._parseBody(messages, folder_id)
+ (messages, state) => this._parseBody(messages, folder_id)
);
this.imap.releaseConnection(account_id);
return upd;
@@ -556,11 +613,20 @@ class Syncer
let msg = messages[i];
let obj = await this.parseMsg(msg[0].headers);
obj.html = sanitizeHtml(obj.html);
- let upd = { body_text: obj.text||'', body_html: obj.html };
- upd.body_html_text = obj.html.replace(/