2013-08-19 15:13:32 -04:00
|
|
|
define(function(require) {
|
2013-08-16 14:50:47 -04:00
|
|
|
'use strict';
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
var util = require('cryptoLib/util'),
|
|
|
|
_ = require('underscore'),
|
2013-11-08 03:29:04 -05:00
|
|
|
str = require('js/app-config').string,
|
2013-11-17 07:34:57 -05:00
|
|
|
config = require('js/app-config').config;
|
2013-08-27 13:17:06 -04:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
var EmailDAO = function(keychain, crypto, devicestorage) {
|
2013-08-16 14:50:47 -04:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
self._keychain = keychain;
|
2013-09-26 07:26:57 -04:00
|
|
|
self._crypto = crypto;
|
|
|
|
self._devicestorage = devicestorage;
|
2013-08-16 14:50:47 -04:00
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
//
|
|
|
|
// External API
|
|
|
|
//
|
|
|
|
|
|
|
|
EmailDAO.prototype.init = function(options, callback) {
|
|
|
|
var self = this,
|
|
|
|
keypair;
|
2013-08-16 14:50:47 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
self._account = options.account;
|
|
|
|
self._account.busy = false;
|
2013-12-09 13:21:52 -05:00
|
|
|
self._account.online = false;
|
2013-08-16 14:50:47 -04:00
|
|
|
|
|
|
|
// validate email address
|
2013-08-22 10:18:48 -04:00
|
|
|
var emailAddress = self._account.emailAddress;
|
2013-08-30 05:42:32 -04:00
|
|
|
if (!util.validateEmailAddress(emailAddress)) {
|
2013-08-16 14:50:47 -04:00
|
|
|
callback({
|
|
|
|
errMsg: 'The user email address must be specified!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-26 11:37:56 -04:00
|
|
|
// init keychain and then crypto module
|
|
|
|
initKeychain();
|
2013-08-16 14:50:47 -04:00
|
|
|
|
|
|
|
function initKeychain() {
|
|
|
|
// init user's local database
|
2013-09-26 07:26:57 -04:00
|
|
|
self._devicestorage.init(emailAddress, function() {
|
|
|
|
// call getUserKeyPair to read/sync keypair with devicestorage/cloud
|
|
|
|
self._keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
keypair = storedKeypair;
|
|
|
|
initFolders();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function initFolders() {
|
2013-12-09 13:21:52 -05:00
|
|
|
// try init folders from memory, since imap client not initiated yet
|
|
|
|
self._imapListFolders(function(err, folders) {
|
2013-12-10 17:05:17 -05:00
|
|
|
// dont handle offline case this time
|
|
|
|
if (err && err.code !== 42) {
|
2013-12-03 13:21:50 -05:00
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
self._account.folders = folders;
|
|
|
|
callback(null, keypair);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2013-12-03 13:21:50 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
EmailDAO.prototype.onConnect = function(options, callback) {
|
|
|
|
var self = this;
|
2013-12-06 11:47:38 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
self._imapClient = options.imapClient;
|
|
|
|
self._smtpClient = options.smtpClient;
|
|
|
|
|
|
|
|
// delegation-esque pattern to mitigate between node-style events and plain js
|
|
|
|
self._imapClient.onIncomingMessage = function(message) {
|
|
|
|
if (typeof self.onIncomingMessage === 'function') {
|
|
|
|
self.onIncomingMessage(message);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// connect to newly created imap client
|
|
|
|
self._imapLogin(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set status to online
|
|
|
|
self._account.online = true;
|
|
|
|
|
|
|
|
// check memory
|
|
|
|
if (self._account.folders) {
|
|
|
|
// no need to init folder again on connect... already in memory
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// init folders
|
|
|
|
self._imapListFolders(function(err, folders) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._account.folders = folders;
|
|
|
|
callback();
|
2013-08-16 14:50:47 -04:00
|
|
|
});
|
2013-12-09 13:21:52 -05:00
|
|
|
});
|
2013-10-21 07:10:42 -04:00
|
|
|
};
|
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
EmailDAO.prototype.onDisconnect = function(options, callback) {
|
|
|
|
// set status to online
|
|
|
|
this._account.online = false;
|
|
|
|
this._imapClient = undefined;
|
|
|
|
this._smtpClient = undefined;
|
|
|
|
|
|
|
|
callback();
|
|
|
|
};
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
EmailDAO.prototype.unlock = function(options, callback) {
|
2013-10-21 07:10:42 -04:00
|
|
|
var self = this;
|
2013-10-29 07:19:27 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
if (options.keypair) {
|
2013-10-21 07:10:42 -04:00
|
|
|
// import existing key pair into crypto module
|
|
|
|
self._crypto.importKeys({
|
2013-12-03 13:21:50 -05:00
|
|
|
passphrase: options.passphrase,
|
|
|
|
privateKeyArmored: options.keypair.privateKey.encryptedKey,
|
|
|
|
publicKeyArmored: options.keypair.publicKey.publicKey
|
2013-10-21 07:10:42 -04:00
|
|
|
}, callback);
|
|
|
|
return;
|
|
|
|
}
|
2013-08-16 14:50:47 -04:00
|
|
|
|
2013-10-21 07:10:42 -04:00
|
|
|
// no keypair for is stored for the user... generate a new one
|
|
|
|
self._crypto.generateKeys({
|
|
|
|
emailAddress: self._account.emailAddress,
|
|
|
|
keySize: self._account.asymKeySize,
|
2013-12-03 13:21:50 -05:00
|
|
|
passphrase: options.passphrase
|
2013-10-21 07:10:42 -04:00
|
|
|
}, function(err, generatedKeypair) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
2013-10-11 21:19:01 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
handleGenerated(generatedKeypair);
|
|
|
|
});
|
|
|
|
|
|
|
|
function handleGenerated(generatedKeypair) {
|
2013-10-21 07:10:42 -04:00
|
|
|
// import the new key pair into crypto module
|
|
|
|
self._crypto.importKeys({
|
2013-12-03 13:21:50 -05:00
|
|
|
passphrase: options.passphrase,
|
2013-10-21 07:10:42 -04:00
|
|
|
privateKeyArmored: generatedKeypair.privateKeyArmored,
|
|
|
|
publicKeyArmored: generatedKeypair.publicKeyArmored
|
|
|
|
}, function(err) {
|
2013-08-16 14:50:47 -04:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-21 07:10:42 -04:00
|
|
|
// persist newly generated keypair
|
|
|
|
var newKeypair = {
|
|
|
|
publicKey: {
|
|
|
|
_id: generatedKeypair.keyId,
|
|
|
|
userId: self._account.emailAddress,
|
|
|
|
publicKey: generatedKeypair.publicKeyArmored
|
|
|
|
},
|
|
|
|
privateKey: {
|
|
|
|
_id: generatedKeypair.keyId,
|
|
|
|
userId: self._account.emailAddress,
|
|
|
|
encryptedKey: generatedKeypair.privateKeyArmored
|
2013-10-11 21:19:01 -04:00
|
|
|
}
|
2013-10-21 07:10:42 -04:00
|
|
|
};
|
|
|
|
self._keychain.putUserKeyPair(newKeypair, callback);
|
2013-08-16 14:50:47 -04:00
|
|
|
});
|
2013-10-10 13:15:16 -04:00
|
|
|
}
|
2013-09-20 12:44:14 -04:00
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype.sync = function(options, callback) {
|
|
|
|
/*
|
|
|
|
* Here's how delta sync works:
|
2013-12-05 12:28:18 -05:00
|
|
|
*
|
|
|
|
* First, we sync the messages between memory and local storage, based on their uid
|
|
|
|
* delta1: storage > memory => we deleted messages, remove from remote and memory
|
2013-12-03 13:21:50 -05:00
|
|
|
* delta2: memory > storage => we added messages, push to remote <<< not supported yet
|
2013-12-05 12:28:18 -05:00
|
|
|
*
|
|
|
|
* Second, we check the delta for the flags
|
2013-12-06 10:37:55 -05:00
|
|
|
* deltaF2: memory > storage => we changed flags, sync them to the remote and memory
|
2013-12-05 12:28:18 -05:00
|
|
|
*
|
|
|
|
* Third, we go on to sync between imap and memory, again based on uid
|
|
|
|
* delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage
|
|
|
|
* delta4: imap > memory => we have new messages available, fetch to memory and storage
|
|
|
|
*
|
|
|
|
* Fourth, we pull changes in the flags downstream
|
2013-12-06 10:37:55 -05:00
|
|
|
* deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
|
2013-12-03 13:21:50 -05:00
|
|
|
*/
|
2013-08-20 07:30:35 -04:00
|
|
|
|
2013-09-26 07:26:57 -04:00
|
|
|
var self = this,
|
2013-12-03 13:21:50 -05:00
|
|
|
folder,
|
2013-12-05 12:28:18 -05:00
|
|
|
delta1 /*, delta2 */ , delta3, delta4, //message
|
2013-12-06 10:37:55 -05:00
|
|
|
deltaF2, deltaF4,
|
2013-12-03 13:21:50 -05:00
|
|
|
isFolderInitialized;
|
|
|
|
|
2013-09-26 07:26:57 -04:00
|
|
|
|
|
|
|
// validate options
|
2013-10-12 13:39:09 -04:00
|
|
|
if (!options.folder) {
|
2013-09-26 07:26:57 -04:00
|
|
|
callback({
|
|
|
|
errMsg: 'Invalid options!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
// check busy status
|
2013-12-03 13:21:50 -05:00
|
|
|
if (self._account.busy) {
|
|
|
|
callback({
|
2013-12-05 09:02:41 -05:00
|
|
|
errMsg: 'Sync aborted: Previous sync still in progress',
|
|
|
|
code: 409
|
2013-11-08 03:29:04 -05:00
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
return;
|
|
|
|
}
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
// not busy -> set busy
|
2013-12-03 13:21:50 -05:00
|
|
|
self._account.busy = true;
|
|
|
|
|
|
|
|
folder = _.findWhere(self._account.folders, {
|
|
|
|
path: options.folder
|
2013-11-08 03:29:04 -05:00
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
isFolderInitialized = !! folder.messages;
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// initial filling from local storage is an exception from the normal sync.
|
|
|
|
// after reading from local storage, do imap sync
|
|
|
|
if (!isFolderInitialized) {
|
|
|
|
initFolderMessages();
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
doLocalDelta();
|
2013-10-04 09:47:30 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function initFolderMessages() {
|
|
|
|
folder.messages = [];
|
|
|
|
self._localListMessages({
|
|
|
|
folder: folder.path
|
2013-12-06 10:37:55 -05:00
|
|
|
}, function(err, storedMessages) {
|
2013-12-03 13:21:50 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
if (_.isEmpty(storedMessages)) {
|
2013-12-03 13:21:50 -05:00
|
|
|
// if there's nothing here, we're good
|
|
|
|
callback();
|
|
|
|
doImapDelta();
|
|
|
|
return;
|
|
|
|
}
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
var after = _.after(storedMessages.length, function() {
|
2013-12-03 13:21:50 -05:00
|
|
|
callback();
|
|
|
|
doImapDelta();
|
|
|
|
});
|
2013-11-08 03:29:04 -05:00
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
storedMessages.forEach(function(storedMessage) {
|
|
|
|
handleMessage(storedMessage, function(err, cleartextMessage) {
|
2013-12-03 13:21:50 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
folder.messages.push(cleartextMessage);
|
|
|
|
after();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-11-08 03:29:04 -05:00
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function doLocalDelta() {
|
|
|
|
self._localListMessages({
|
|
|
|
folder: folder.path
|
2013-12-06 10:37:55 -05:00
|
|
|
}, function(err, storedMessages) {
|
2013-12-03 13:21:50 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
/*
|
|
|
|
* delta1: storage > memory => we deleted messages, remove from remote
|
|
|
|
* delta2: memory > storage => we added messages, push to remote
|
2013-12-06 10:37:55 -05:00
|
|
|
* deltaF2: memory > storage => we changed flags, sync them to the remote and memory
|
2013-12-03 13:21:50 -05:00
|
|
|
*/
|
2013-12-06 10:37:55 -05:00
|
|
|
delta1 = checkDelta(storedMessages, folder.messages);
|
|
|
|
// delta2 = checkDelta(folder.messages, storedMessages); // not supported yet
|
|
|
|
deltaF2 = checkFlags(folder.messages, storedMessages);
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
doDelta1();
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function doDelta1() {
|
2013-12-05 12:28:18 -05:00
|
|
|
if (_.isEmpty(delta1)) {
|
2013-12-06 10:37:55 -05:00
|
|
|
doDeltaF2();
|
2013-12-05 12:28:18 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
var after = _.after(delta1.length, function() {
|
2013-12-06 10:37:55 -05:00
|
|
|
doDeltaF2();
|
2013-12-03 13:21:50 -05:00
|
|
|
});
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
// deltaF2 contains references to the in-memory messages
|
|
|
|
delta1.forEach(function(inMemoryMessage) {
|
2013-12-03 13:21:50 -05:00
|
|
|
var deleteMe = {
|
|
|
|
folder: folder.path,
|
2013-12-06 10:37:55 -05:00
|
|
|
uid: inMemoryMessage.uid
|
2013-12-03 13:21:50 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
self._imapDeleteMessage(deleteMe, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._localDeleteMessage(deleteMe, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
after();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-12-05 12:28:18 -05:00
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
function doDeltaF2() {
|
|
|
|
if (_.isEmpty(deltaF2)) {
|
2013-12-05 12:28:18 -05:00
|
|
|
callback();
|
|
|
|
doImapDelta();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
var after = _.after(deltaF2.length, function() {
|
2013-12-05 12:28:18 -05:00
|
|
|
callback();
|
|
|
|
doImapDelta();
|
|
|
|
});
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
// deltaF2 contains references to the in-memory messages
|
|
|
|
deltaF2.forEach(function(inMemoryMessage) {
|
2013-12-05 13:10:30 -05:00
|
|
|
self._imapMark({
|
2013-12-05 12:28:18 -05:00
|
|
|
folder: folder.path,
|
2013-12-06 10:37:55 -05:00
|
|
|
uid: inMemoryMessage.uid,
|
|
|
|
unread: inMemoryMessage.unread,
|
|
|
|
answered: inMemoryMessage.answered
|
2013-12-05 12:28:18 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
var storedMessage = _.findWhere(storedMessages, {
|
|
|
|
uid: inMemoryMessage.uid
|
2013-12-06 09:03:42 -05:00
|
|
|
});
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
storedMessage.unread = inMemoryMessage.unread;
|
|
|
|
storedMessage.answered = inMemoryMessage.answered;
|
2013-12-06 09:03:42 -05:00
|
|
|
|
2013-12-05 12:28:18 -05:00
|
|
|
self._localStoreMessages({
|
|
|
|
folder: folder.path,
|
2013-12-06 10:37:55 -05:00
|
|
|
emails: [storedMessage]
|
2013-12-05 12:28:18 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
after();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-12-03 13:21:50 -05:00
|
|
|
});
|
2013-09-26 07:26:57 -04:00
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function doImapDelta() {
|
2013-12-12 07:14:49 -05:00
|
|
|
self._imapSearch({
|
2013-12-03 13:21:50 -05:00
|
|
|
folder: folder.path
|
2013-12-12 07:14:49 -05:00
|
|
|
}, function(err, uids) {
|
2013-11-08 03:29:04 -05:00
|
|
|
if (err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
self._account.busy = false;
|
2013-11-08 03:29:04 -05:00
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
// uidWrappers is just to wrap the bare uids in an object { uid: 123 } so
|
|
|
|
// the checkDelta function can treat it like something that resembles a stripped down email object...
|
|
|
|
var uidWrappers = _.map(uids, function(uid) {
|
|
|
|
return {
|
|
|
|
uid: uid
|
|
|
|
};
|
2013-12-03 14:24:12 -05:00
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
/*
|
2013-12-05 12:28:18 -05:00
|
|
|
* delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage
|
|
|
|
* delta4: imap > memory => we have new messages available, fetch to memory and storage
|
2013-12-03 13:21:50 -05:00
|
|
|
*/
|
2013-12-12 07:14:49 -05:00
|
|
|
delta3 = checkDelta(folder.messages, uidWrappers);
|
|
|
|
delta4 = checkDelta(uidWrappers, folder.messages);
|
2013-11-21 11:09:47 -05:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
doDelta3();
|
|
|
|
|
|
|
|
// we deleted messages directly from the remote, remove from memory and storage
|
|
|
|
function doDelta3() {
|
|
|
|
if (_.isEmpty(delta3)) {
|
|
|
|
doDelta4();
|
|
|
|
return;
|
2013-11-08 03:29:04 -05:00
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
var after = _.after(delta3.length, function() {
|
|
|
|
// we're done with delta 3, so let's continue
|
|
|
|
doDelta4();
|
|
|
|
});
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
// delta3 contains references to the in-memory messages that have been deleted from the remote
|
|
|
|
delta3.forEach(function(inMemoryMessage) {
|
2013-12-03 13:21:50 -05:00
|
|
|
// remove delta3 from local storage
|
|
|
|
self._localDeleteMessage({
|
|
|
|
folder: folder.path,
|
2013-12-06 10:37:55 -05:00
|
|
|
uid: inMemoryMessage.uid
|
2013-12-03 13:21:50 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
// remove delta3 from memory
|
|
|
|
var idx = folder.messages.indexOf(inMemoryMessage);
|
|
|
|
folder.messages.splice(idx, 1);
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
after();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have new messages available, fetch to memory and storage
|
|
|
|
// (downstream sync)
|
|
|
|
function doDelta4() {
|
2013-12-12 07:14:49 -05:00
|
|
|
// eliminate uids smaller than the biggest local uid, i.e. just fetch everything
|
|
|
|
// that came in AFTER the most recent email we have in memory. Keep in mind that
|
|
|
|
// uids are strictly ascending, so there can't be a NEW mail in the mailbox with a
|
|
|
|
// uid smaller than anything we've encountered before.
|
|
|
|
if (!_.isEmpty(folder.messages)) {
|
|
|
|
var localUids = _.pluck(folder.messages, 'uid'),
|
|
|
|
maxLocalUid = Math.max.apply(null, localUids);
|
|
|
|
|
|
|
|
// eliminate everything prior to maxLocalUid
|
|
|
|
delta4 = _.filter(delta4, function(uidWrapper) {
|
|
|
|
return uidWrapper.uid > maxLocalUid;
|
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
syncNextItem();
|
|
|
|
|
|
|
|
function syncNextItem() {
|
|
|
|
// no delta, we're done here
|
|
|
|
if (_.isEmpty(delta4)) {
|
|
|
|
doDeltaF4();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// delta4 contains the headers that are newly available on the remote
|
|
|
|
var nextUidWrapper = delta4.shift();
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
// get the whole message
|
|
|
|
self._imapGetMessage({
|
|
|
|
folder: folder.path,
|
2013-12-12 07:14:49 -05:00
|
|
|
uid: nextUidWrapper.uid
|
2013-12-03 13:21:50 -05:00
|
|
|
}, function(err, message) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-08 10:04:18 -05:00
|
|
|
// imap filtering is insufficient, since google ignores non-alphabetical characters
|
|
|
|
if (message.subject.indexOf(str.subjectPrefix.trim()) === -1) {
|
2013-12-12 07:14:49 -05:00
|
|
|
syncNextItem();
|
|
|
|
return;
|
|
|
|
}
|
2013-12-09 04:40:17 -05:00
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
if (isVerificationMail(message)) {
|
|
|
|
verify(message, function(err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
syncNextItem();
|
2013-12-03 13:21:50 -05:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the encrypted message to the local storage
|
|
|
|
self._localStoreMessages({
|
|
|
|
folder: folder.path,
|
2013-12-12 07:14:49 -05:00
|
|
|
emails: [message]
|
2013-12-03 13:21:50 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// decrypt and add to folder in memory
|
2013-12-12 07:14:49 -05:00
|
|
|
handleMessage(message, function(err, cleartextMessage) {
|
2013-12-03 13:21:50 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
folder.messages.push(cleartextMessage);
|
2013-12-12 07:14:49 -05:00
|
|
|
syncNextItem();
|
2013-12-03 13:21:50 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-12-12 07:14:49 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function doDeltaF4() {
|
|
|
|
var answeredUids, unreadUids;
|
|
|
|
|
|
|
|
getUnreadUids();
|
|
|
|
|
|
|
|
// find all the relevant unread mails
|
|
|
|
function getUnreadUids() {
|
|
|
|
self._imapSearch({
|
|
|
|
folder: folder.path,
|
|
|
|
unread: true
|
|
|
|
}, function(err, uids) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we're done here, let's get all the answered mails
|
|
|
|
unreadUids = uids;
|
|
|
|
getAnsweredUids();
|
2013-12-03 13:21:50 -05:00
|
|
|
});
|
|
|
|
}
|
2013-12-05 12:28:18 -05:00
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
// find all the relevant answered mails
|
|
|
|
function getAnsweredUids() {
|
|
|
|
// find all the relevant answered mails
|
|
|
|
self._imapSearch({
|
|
|
|
folder: folder.path,
|
|
|
|
answered: true
|
|
|
|
}, function(err, uids) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we're done here, let's update what we have in memory and persist that!
|
|
|
|
answeredUids = uids;
|
|
|
|
updateFlags();
|
|
|
|
});
|
2013-12-06 11:47:38 -05:00
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateFlags() {
|
|
|
|
// deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
|
|
|
|
deltaF4 = [];
|
|
|
|
|
|
|
|
folder.messages.forEach(function(msg) {
|
|
|
|
// if the message's uid is among the uids that should be unread,
|
|
|
|
// AND the message is not unread, we clearly have to change that
|
|
|
|
var shouldBeUnread = _.contains(unreadUids, msg.uid);
|
|
|
|
if (msg.unread === shouldBeUnread) {
|
|
|
|
// everything is in order, we're good here
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.unread = shouldBeUnread;
|
|
|
|
deltaF4.push(msg);
|
|
|
|
});
|
|
|
|
|
|
|
|
folder.messages.forEach(function(msg) {
|
|
|
|
// if the message's uid is among the uids that should be answered,
|
|
|
|
// AND the message is not answered, we clearly have to change that
|
|
|
|
var shouldBeAnswered = _.contains(answeredUids, msg.uid);
|
|
|
|
if (msg.answered === shouldBeAnswered) {
|
|
|
|
// everything is in order, we're good here
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.answered = shouldBeAnswered;
|
|
|
|
deltaF4.push(msg);
|
|
|
|
});
|
|
|
|
|
|
|
|
// maybe a mail had BOTH flags wrong, so let's create
|
|
|
|
// a duplicate-free version of deltaF4
|
|
|
|
deltaF4 = _.uniq(deltaF4);
|
2013-12-06 11:47:38 -05:00
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
// everything up to date? fine, we're done!
|
2013-12-06 11:47:38 -05:00
|
|
|
if (_.isEmpty(deltaF4)) {
|
|
|
|
finishSync();
|
2013-12-05 12:28:18 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
var after = _.after(deltaF4.length, function() {
|
2013-12-12 07:14:49 -05:00
|
|
|
// we're doing updating everything
|
2013-12-06 11:47:38 -05:00
|
|
|
finishSync();
|
2013-12-05 12:28:18 -05:00
|
|
|
});
|
|
|
|
|
2013-12-12 08:14:09 -05:00
|
|
|
// alright, so let's sync the corrected messages
|
2013-12-12 07:14:49 -05:00
|
|
|
deltaF4.forEach(function(inMemoryMessage) {
|
2013-12-06 09:03:42 -05:00
|
|
|
// do a short round trip to the database to avoid re-encrypting,
|
|
|
|
// instead use the encrypted object in the storage
|
|
|
|
self._localListMessages({
|
2013-12-05 12:28:18 -05:00
|
|
|
folder: folder.path,
|
2013-12-12 07:14:49 -05:00
|
|
|
uid: inMemoryMessage.uid
|
2013-12-06 10:37:55 -05:00
|
|
|
}, function(err, storedMessages) {
|
2013-12-05 12:28:18 -05:00
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-06 10:37:55 -05:00
|
|
|
var storedMessage = storedMessages[0];
|
2013-12-12 07:14:49 -05:00
|
|
|
storedMessage.unread = inMemoryMessage.unread;
|
|
|
|
storedMessage.answered = inMemoryMessage.answered;
|
2013-12-06 09:03:42 -05:00
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
// persist the modified object
|
2013-12-06 09:03:42 -05:00
|
|
|
self._localStoreMessages({
|
|
|
|
folder: folder.path,
|
2013-12-06 10:37:55 -05:00
|
|
|
emails: [storedMessage]
|
2013-12-06 09:03:42 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
self._account.busy = false;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
// and we're done.
|
2013-12-06 09:03:42 -05:00
|
|
|
after();
|
|
|
|
});
|
2013-12-05 12:28:18 -05:00
|
|
|
});
|
2013-12-12 07:14:49 -05:00
|
|
|
|
2013-12-05 12:28:18 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
function finishSync() {
|
|
|
|
// after all the tags are up to date, let's adjust the unread mail count
|
|
|
|
folder.count = _.filter(folder.messages, function(msg) {
|
|
|
|
return msg.unread === true;
|
|
|
|
}).length;
|
|
|
|
// allow the next sync to take place
|
|
|
|
self._account.busy = false;
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}
|
2013-12-03 13:21:50 -05:00
|
|
|
}
|
2013-11-21 11:44:03 -05:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
/*
|
|
|
|
* Checks which messages are included in a, but not in b
|
|
|
|
*/
|
|
|
|
function checkDelta(a, b) {
|
|
|
|
var i, msg, exists,
|
|
|
|
delta = [];
|
|
|
|
|
|
|
|
// find the delta
|
|
|
|
for (i = a.length - 1; i >= 0; i--) {
|
|
|
|
msg = a[i];
|
|
|
|
exists = _.findWhere(b, {
|
|
|
|
uid: msg.uid
|
|
|
|
});
|
|
|
|
if (!exists) {
|
|
|
|
delta.push(msg);
|
|
|
|
}
|
2013-11-21 11:44:03 -05:00
|
|
|
}
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
return delta;
|
|
|
|
}
|
|
|
|
|
2013-12-05 12:28:18 -05:00
|
|
|
/*
|
|
|
|
* checks if there are some flags that have changed in a and b
|
|
|
|
*/
|
|
|
|
function checkFlags(a, b) {
|
|
|
|
var i, aI, bI,
|
|
|
|
delta = [];
|
|
|
|
|
|
|
|
// find the delta
|
|
|
|
for (i = a.length - 1; i >= 0; i--) {
|
|
|
|
aI = a[i];
|
|
|
|
bI = _.findWhere(b, {
|
|
|
|
uid: aI.uid
|
|
|
|
});
|
|
|
|
if (bI && (aI.unread !== bI.unread || aI.answered !== bI.answered)) {
|
|
|
|
delta.push(aI);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return delta;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function isVerificationMail(email) {
|
|
|
|
return email.subject === str.subjectPrefix + str.verificationSubject;
|
2013-11-08 03:29:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function verify(email, localCallback) {
|
2014-01-08 10:04:18 -05:00
|
|
|
var uuid, isValidUuid, index, verifyUrlPrefix = config.cloudUrl + config.verificationUrl;
|
2013-11-08 03:29:04 -05:00
|
|
|
|
|
|
|
if (!email.unread) {
|
|
|
|
// don't bother if the email was already marked as read
|
|
|
|
localCallback();
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
index = email.body.indexOf(verifyUrlPrefix);
|
2013-11-08 03:29:04 -05:00
|
|
|
if (index === -1) {
|
2013-12-03 13:21:50 -05:00
|
|
|
// there's no url in the email, so forget about that.
|
2013-11-08 03:29:04 -05:00
|
|
|
localCallback();
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
uuid = email.body.substr(index + verifyUrlPrefix.length, config.verificationUuidLength);
|
2014-01-08 10:04:18 -05:00
|
|
|
isValidUuid = new RegExp('[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}').test(uuid);
|
|
|
|
if (!isValidUuid) {
|
|
|
|
// there's no valid uuid in the email, so forget about that, too.
|
|
|
|
localCallback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-08 03:29:04 -05:00
|
|
|
self._keychain.verifyPublicKey(uuid, function(err) {
|
|
|
|
if (err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
localCallback({
|
2013-11-14 15:39:20 -05:00
|
|
|
errMsg: 'Verifying your public key failed: ' + err.errMsg
|
|
|
|
});
|
2013-11-08 03:29:04 -05:00
|
|
|
return;
|
|
|
|
}
|
2013-10-11 21:19:01 -04:00
|
|
|
|
2013-11-08 03:29:04 -05:00
|
|
|
// public key has been verified, mark the message as read, delete it, and ignore it in the future
|
2013-12-05 13:10:30 -05:00
|
|
|
self._imapMark({
|
2013-11-08 03:29:04 -05:00
|
|
|
folder: options.folder,
|
2013-12-05 12:28:18 -05:00
|
|
|
uid: email.uid,
|
|
|
|
unread: false
|
2013-11-08 03:29:04 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
localCallback(err);
|
2013-11-08 03:29:04 -05:00
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
self._imapDeleteMessage({
|
2013-11-08 03:29:04 -05:00
|
|
|
folder: options.folder,
|
|
|
|
uid: email.uid
|
2013-12-03 13:21:50 -05:00
|
|
|
}, localCallback);
|
2013-09-26 07:26:57 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function handleMessage(message, localCallback) {
|
2013-12-04 11:35:44 -05:00
|
|
|
message.subject = message.subject.split(str.subjectPrefix)[1];
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
if (containsArmoredCiphertext(message)) {
|
|
|
|
decrypt(message, localCallback);
|
2013-10-04 09:47:30 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// cleartext mail
|
|
|
|
localCallback(null, message);
|
|
|
|
}
|
2013-10-04 09:47:30 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function containsArmoredCiphertext(email) {
|
|
|
|
return typeof email.body === 'string' && email.body.indexOf(str.cryptPrefix) !== -1 && email.body.indexOf(str.cryptSuffix) !== -1;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function decrypt(email, localCallback) {
|
|
|
|
var sender;
|
2013-09-28 13:04:15 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
extractArmoredContent(email);
|
|
|
|
|
|
|
|
// fetch public key required to verify signatures
|
|
|
|
sender = email.from[0].address;
|
|
|
|
self._keychain.getReceiverPublicKey(sender, function(err, senderPubkey) {
|
2013-09-26 07:26:57 -04:00
|
|
|
if (err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
localCallback(err);
|
2013-09-26 07:26:57 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
if (!senderPubkey) {
|
|
|
|
// this should only happen if a mail from another channel is in the inbox
|
2014-01-18 05:42:28 -05:00
|
|
|
email.body = 'Public key for sender not found!';
|
|
|
|
localCallback(null, email);
|
2013-12-03 13:21:50 -05:00
|
|
|
return;
|
|
|
|
}
|
2013-09-26 07:26:57 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// decrypt and verfiy signatures
|
|
|
|
self._crypto.decrypt(email.body, senderPubkey.publicKey, function(err, decrypted) {
|
2013-09-26 07:26:57 -04:00
|
|
|
if (err) {
|
2013-12-03 13:21:50 -05:00
|
|
|
decrypted = err.errMsg;
|
2013-09-26 07:26:57 -04:00
|
|
|
}
|
|
|
|
|
2013-12-04 12:58:53 -05:00
|
|
|
// set encrypted flag
|
|
|
|
email.encrypted = true;
|
2014-01-18 05:42:28 -05:00
|
|
|
|
|
|
|
// does our message block even need to be parsed?
|
|
|
|
if (decrypted.indexOf('Content-Type: multipart/signed') === -1) {
|
|
|
|
// decrypted message is plain text and not a well-formed email
|
|
|
|
email.body = decrypted;
|
|
|
|
localCallback(null, email);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse decrypted message
|
|
|
|
self._imapParseMessageBlock({
|
|
|
|
message: email,
|
|
|
|
block: decrypted
|
|
|
|
}, localCallback);
|
2013-09-26 07:26:57 -04:00
|
|
|
});
|
|
|
|
});
|
2013-08-21 07:43:19 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
function extractArmoredContent(email) {
|
|
|
|
var start = email.body.indexOf(str.cryptPrefix),
|
|
|
|
end = email.body.indexOf(str.cryptSuffix) + str.cryptSuffix.length;
|
2013-10-16 12:56:18 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// parse email body for encrypted message block
|
|
|
|
email.body = email.body.substring(start, end);
|
2013-08-29 10:01:40 -04:00
|
|
|
}
|
2013-10-16 12:56:18 -04:00
|
|
|
}
|
2013-08-20 07:30:35 -04:00
|
|
|
};
|
|
|
|
|
2013-12-05 13:10:30 -05:00
|
|
|
EmailDAO.prototype._imapMark = function(options, callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
this._imapClient.updateFlags({
|
2013-11-13 11:05:21 -05:00
|
|
|
path: options.folder,
|
|
|
|
uid: options.uid,
|
2013-12-05 12:28:18 -05:00
|
|
|
unread: options.unread,
|
|
|
|
answered: options.answered
|
2013-11-13 11:05:21 -05:00
|
|
|
}, callback);
|
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype.move = function(options, callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
this._imapClient.moveMessage({
|
|
|
|
path: options.folder,
|
|
|
|
uid: options.uid,
|
|
|
|
destination: options.destination
|
|
|
|
}, callback);
|
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2014-01-16 09:37:08 -05:00
|
|
|
EmailDAO.prototype.getAttachment = function(options, callback) {
|
|
|
|
if (!this._account.online) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._imapClient.getAttachment(options, callback);
|
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype.sendEncrypted = function(options, callback) {
|
2013-10-04 10:29:32 -04:00
|
|
|
var self = this,
|
2013-12-03 13:21:50 -05:00
|
|
|
email = options.email;
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-04 10:29:32 -04:00
|
|
|
// validate the email input
|
2014-01-14 07:26:23 -05:00
|
|
|
if (!email.to || !email.from || !email.to[0].address || !email.from[0].address || !Array.isArray(email.receiverKeys)) {
|
2013-10-04 10:29:32 -04:00
|
|
|
callback({
|
|
|
|
errMsg: 'Invalid email object!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-13 12:38:45 -05:00
|
|
|
// public key found... encrypt and send
|
|
|
|
self._encrypt({
|
|
|
|
email: email,
|
|
|
|
keys: email.receiverKeys // this Array is set in writer controller
|
|
|
|
}, function(err, email) {
|
2013-10-04 10:29:32 -04:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-13 12:38:45 -05:00
|
|
|
self._smtpClient.send(email, callback);
|
2013-10-04 10:29:32 -04:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype.sendPlaintext = function(options, callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
this._smtpClient.send(options.email, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Internal API
|
|
|
|
//
|
|
|
|
|
|
|
|
// Encryption API
|
|
|
|
|
|
|
|
EmailDAO.prototype._encrypt = function(options, callback) {
|
2013-10-04 10:29:32 -04:00
|
|
|
var self = this,
|
2013-12-03 13:21:50 -05:00
|
|
|
pt = options.email.body;
|
|
|
|
|
2013-12-04 09:35:43 -05:00
|
|
|
options.keys = options.keys || [];
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-10-11 21:19:01 -04:00
|
|
|
// get own public key so send message can be read
|
|
|
|
self._crypto.exportKeys(function(err, ownKeys) {
|
2013-10-04 10:29:32 -04:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-11 21:19:01 -04:00
|
|
|
// add own public key to receiver list
|
2013-12-03 13:21:50 -05:00
|
|
|
options.keys.push(ownKeys.publicKeyArmored);
|
2013-10-11 21:19:01 -04:00
|
|
|
// encrypt the email
|
2013-12-03 13:21:50 -05:00
|
|
|
self._crypto.encrypt(pt, options.keys, function(err, ct) {
|
2013-10-11 21:19:01 -04:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-10-11 21:19:01 -04:00
|
|
|
// bundle encrypted email together for sending
|
2013-11-21 11:37:07 -05:00
|
|
|
frameEncryptedMessage(options.email, ct);
|
|
|
|
callback(null, options.email);
|
2013-10-11 21:19:01 -04:00
|
|
|
});
|
2013-10-04 10:29:32 -04:00
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
|
|
|
|
function frameEncryptedMessage(email, ct) {
|
|
|
|
var greeting,
|
|
|
|
message = str.message + '\n\n\n',
|
|
|
|
signature = '\n\n' + str.signature + '\n\n';
|
|
|
|
|
|
|
|
// get first name of recipient
|
|
|
|
greeting = 'Hi ' + (email.to[0].name || email.to[0].address).split('@')[0].split('.')[0].split(' ')[0] + ',\n\n';
|
|
|
|
|
|
|
|
// build encrypted text body
|
|
|
|
email.body = greeting + message + ct + signature;
|
2013-12-04 11:35:44 -05:00
|
|
|
email.subject = str.subjectPrefix + email.subject;
|
2013-12-03 13:21:50 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Local Storage API
|
|
|
|
|
|
|
|
EmailDAO.prototype._localListMessages = function(options, callback) {
|
|
|
|
var dbType = 'email_' + options.folder;
|
2013-12-06 09:03:42 -05:00
|
|
|
if (typeof options.uid !== 'undefined') {
|
|
|
|
dbType = dbType + '_' + options.uid;
|
|
|
|
}
|
2013-12-03 13:21:50 -05:00
|
|
|
this._devicestorage.listItems(dbType, 0, null, callback);
|
2013-10-04 10:29:32 -04:00
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype._localStoreMessages = function(options, callback) {
|
|
|
|
var dbType = 'email_' + options.folder;
|
|
|
|
this._devicestorage.storeList(options.emails, dbType, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
EmailDAO.prototype._localDeleteMessage = function(options, callback) {
|
|
|
|
if (!options.folder || !options.uid) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Invalid options!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var dbType = 'email_' + options.folder + '_' + options.uid;
|
|
|
|
this._devicestorage.removeList(dbType, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// IMAP API
|
|
|
|
|
2013-10-04 10:29:32 -04:00
|
|
|
/**
|
2013-12-03 13:21:50 -05:00
|
|
|
* Login the imap client
|
2013-10-04 10:29:32 -04:00
|
|
|
*/
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype._imapLogin = function(callback) {
|
2013-12-09 13:21:52 -05:00
|
|
|
if (!this._imapClient) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// login IMAP client if existent
|
|
|
|
this._imapClient.login(callback);
|
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
/**
|
|
|
|
* Cleanup by logging the user off.
|
|
|
|
*/
|
|
|
|
EmailDAO.prototype._imapLogout = function(callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
this._imapClient.logout(callback);
|
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
/**
|
2013-12-12 07:14:49 -05:00
|
|
|
* Returns the relevant messages corresponding to the search terms in the options
|
|
|
|
* @param {String} options.folder The folder's path
|
|
|
|
* @param {Boolean} options.answered (optional) Mails with or without the \Answered flag set.
|
|
|
|
* @param {Boolean} options.unread (optional) Mails with or without the \Seen flag set.
|
|
|
|
* @param {Function} callback(error, uids) invoked with the uids of messages matching the search terms, or an error object if an error occurred
|
2013-12-03 13:21:50 -05:00
|
|
|
*/
|
2013-12-12 07:14:49 -05:00
|
|
|
EmailDAO.prototype._imapSearch = function(options, callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:14:49 -05:00
|
|
|
var o = {
|
2013-12-03 13:21:50 -05:00
|
|
|
path: options.folder,
|
2013-12-12 07:14:49 -05:00
|
|
|
subject: str.subjectPrefix
|
|
|
|
};
|
|
|
|
|
|
|
|
if (typeof options.answered !== 'undefined') {
|
|
|
|
o.answered = options.answered;
|
|
|
|
}
|
|
|
|
if (typeof options.unread !== 'undefined') {
|
|
|
|
o.unread = options.unread;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._imapClient.search(o, callback);
|
2013-12-03 13:21:50 -05:00
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype._imapDeleteMessage = function(options, callback) {
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
this._imapClient.deleteMessage({
|
|
|
|
path: options.folder,
|
|
|
|
uid: options.uid
|
|
|
|
}, callback);
|
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2014-01-18 05:42:28 -05:00
|
|
|
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
|
|
|
|
this._imapClient.parseDecryptedMessageBlock(options, callback);
|
|
|
|
};
|
|
|
|
|
2013-10-04 10:29:32 -04:00
|
|
|
/**
|
2013-12-03 13:21:50 -05:00
|
|
|
* Get an email messsage including the email body from imap
|
|
|
|
* @param {String} options.messageId The
|
2013-10-04 10:29:32 -04:00
|
|
|
*/
|
2013-12-03 13:21:50 -05:00
|
|
|
EmailDAO.prototype._imapGetMessage = function(options, callback) {
|
2013-12-12 07:14:49 -05:00
|
|
|
var self = this;
|
|
|
|
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!this._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-14 10:32:36 -05:00
|
|
|
self._imapClient.getMessage({
|
2013-12-03 13:21:50 -05:00
|
|
|
path: options.folder,
|
2014-01-14 10:32:36 -05:00
|
|
|
uid: options.uid
|
|
|
|
}, function(err, message) {
|
2013-12-12 07:14:49 -05:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-14 10:32:36 -05:00
|
|
|
callback(null, message);
|
2013-12-12 07:14:49 -05:00
|
|
|
});
|
2013-12-03 13:21:50 -05:00
|
|
|
};
|
2013-10-04 10:29:32 -04:00
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
/**
|
|
|
|
* List the folders in the user's IMAP mailbox.
|
|
|
|
*/
|
|
|
|
EmailDAO.prototype._imapListFolders = function(callback) {
|
|
|
|
var self = this,
|
|
|
|
dbType = 'folders';
|
|
|
|
|
|
|
|
// check local cache
|
|
|
|
self._devicestorage.listItems(dbType, 0, null, function(err, stored) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!stored || stored.length < 1) {
|
|
|
|
// no folders cached... fetch from server
|
|
|
|
fetchFromServer();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, stored[0]);
|
|
|
|
});
|
|
|
|
|
|
|
|
function fetchFromServer() {
|
|
|
|
var folders;
|
|
|
|
|
2013-12-12 08:47:04 -05:00
|
|
|
if (!self._account.online) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback({
|
|
|
|
errMsg: 'Client is currently offline!',
|
|
|
|
code: 42
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// fetch list from imap server
|
|
|
|
self._imapClient.listWellKnownFolders(function(err, wellKnownFolders) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
folders = [
|
|
|
|
wellKnownFolders.inbox,
|
|
|
|
wellKnownFolders.sent, {
|
|
|
|
type: 'Outbox',
|
|
|
|
path: 'OUTBOX'
|
|
|
|
},
|
|
|
|
wellKnownFolders.drafts,
|
|
|
|
wellKnownFolders.trash
|
|
|
|
];
|
|
|
|
|
|
|
|
// cache locally
|
|
|
|
// persist encrypted list in device storage
|
|
|
|
self._devicestorage.storeList([folders], dbType, function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, folders);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-10-04 10:29:32 -04:00
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// to be removed and solved with IMAP!
|
2013-11-21 11:37:07 -05:00
|
|
|
EmailDAO.prototype.store = function(email, callback) {
|
|
|
|
var self = this,
|
|
|
|
dbType = 'email_OUTBOX';
|
|
|
|
|
|
|
|
email.id = util.UUID();
|
|
|
|
|
|
|
|
// encrypt
|
2013-12-03 13:21:50 -05:00
|
|
|
self._encrypt({
|
2013-11-21 11:37:07 -05:00
|
|
|
email: email
|
|
|
|
}, function(err, email) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// store to local storage
|
|
|
|
self._devicestorage.storeList([email], dbType, callback);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-12-03 13:21:50 -05:00
|
|
|
// to be removed and solved with IMAP!
|
2013-11-21 11:37:07 -05:00
|
|
|
EmailDAO.prototype.list = function(callback) {
|
|
|
|
var self = this,
|
|
|
|
dbType = 'email_OUTBOX';
|
|
|
|
|
|
|
|
self._devicestorage.listItems(dbType, 0, null, function(err, mails) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mails.length === 0) {
|
|
|
|
callback(null, []);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._crypto.exportKeys(function(err, ownKeys) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var after = _.after(mails.length, function() {
|
|
|
|
callback(null, mails);
|
|
|
|
});
|
|
|
|
|
|
|
|
mails.forEach(function(mail) {
|
|
|
|
mail.body = str.cryptPrefix + mail.body.split(str.cryptPrefix)[1].split(str.cryptSuffix)[0] + str.cryptSuffix;
|
2013-12-04 11:35:44 -05:00
|
|
|
mail.subject = mail.subject.split(str.subjectPrefix)[1];
|
2013-11-21 11:37:07 -05:00
|
|
|
self._crypto.decrypt(mail.body, ownKeys.publicKeyArmored, function(err, decrypted) {
|
|
|
|
mail.body = err ? err.errMsg : decrypted;
|
|
|
|
after();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-08-16 14:50:47 -04:00
|
|
|
return EmailDAO;
|
2013-08-28 04:31:53 -04:00
|
|
|
});
|