Merge pull request #59 from whiteout-io/dev/wo-286

Dev/wo 286
This commit is contained in:
Tankred Hase 2014-05-12 14:54:36 +02:00
commit c4d0029f34
13 changed files with 521 additions and 431 deletions

View File

@ -11,10 +11,10 @@
}, },
"dependencies": { "dependencies": {
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/v0.1.1", "crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/v0.1.1",
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/v0.2.6", "imap-client": "https://github.com/whiteout-io/imap-client/tarball/v0.3.0",
"mailreader": "https://github.com/whiteout-io/mailreader/tarball/v0.2.2", "mailreader": "https://github.com/whiteout-io/mailreader/tarball/v0.3.0",
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/v0.2.2", "pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/v0.3.0",
"pgpbuilder": "https://github.com/whiteout-io/pgpbuilder/tarball/v0.2.3", "pgpbuilder": "https://github.com/whiteout-io/pgpbuilder/tarball/v0.3.0",
"requirejs": "2.1.10" "requirejs": "2.1.10"
}, },
"devDependencies": { "devDependencies": {
@ -36,4 +36,4 @@
"grunt-contrib-compress": "~0.5.2", "grunt-contrib-compress": "~0.5.2",
"grunt-node-webkit-builder": "~0.1.17" "grunt-node-webkit-builder": "~0.1.17"
} }
} }

View File

@ -46,7 +46,7 @@ define(function(require) {
iconPath: '/img/icon.png', iconPath: '/img/icon.png',
verificationUrl: '/verify/', verificationUrl: '/verify/',
verificationUuidLength: 36, verificationUuidLength: 36,
dbVersion: 1, dbVersion: 2,
appVersion: appVersion appVersion: appVersion
}; };

View File

@ -72,7 +72,7 @@ define(function(require) {
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao); self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
self._crypto = pgp = new PGP(); self._crypto = pgp = new PGP();
self._pgpbuilder = pgpbuilder = new PgpBuilder(); self._pgpbuilder = pgpbuilder = new PgpBuilder();
self._emailSync = emailSync = new EmailSync(keychain, userStorage); self._emailSync = emailSync = new EmailSync(keychain, userStorage, mailreader);
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader, emailSync); self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader, emailSync);
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage); self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
self._updateHandler = new UpdateHandler(appConfigStore, userStorage); self._updateHandler = new UpdateHandler(appConfigStore, userStorage);
@ -129,7 +129,7 @@ define(function(require) {
}; };
pgpMailer = new PgpMailer(smtpOptions, self._pgpbuilder); pgpMailer = new PgpMailer(smtpOptions, self._pgpbuilder);
imapClient = new ImapClient(imapOptions, mailreader); imapClient = new ImapClient(imapOptions);
imapClient.onError = onImapError; imapClient.onError = onImapError;
// connect to clients // connect to clients

File diff suppressed because one or more lines are too long

View File

@ -70,8 +70,13 @@ define(function(require) {
$scope.node = undefined; $scope.node = undefined;
}); });
$scope.$watch('state.mailList.selected.body', function(body) { $scope.$watch('state.mailList.selected.body', function(body) {
if (!body || (body && $scope.state.mailList.selected.decrypted === false)) { $scope.node = undefined; // reset model
$scope.node = undefined; if (!body) {
return;
}
var selected = $scope.state.mailList.selected;
if (selected.encrypted && !selected.decrypted) {
return; return;
} }
@ -336,7 +341,10 @@ define(function(require) {
scope.html = undefined; scope.html = undefined;
if (value) { if (value) {
$timeout(function() { $timeout(function() {
scope.html = $sce.trustAsHtml(value); // wrap in html doc with scrollable html tag, since chrome apps does not scroll by default
var prefix = '<!DOCTYPE html><html style="overflow-y: auto"><head></head><body>';
var suffix = '</body></html>';
scope.html = $sce.trustAsHtml(prefix + value + suffix);
}); });
} }
}); });
@ -353,4 +361,4 @@ define(function(require) {
}); });
return ReadCtrl; return ReadCtrl;
}); });

View File

@ -274,123 +274,121 @@ define(function(require) {
message = options.message, message = options.message,
folder = options.folder; folder = options.folder;
if (message.loadingBody) { // the message either already has a body or is fetching it right now, so no need to become active here
return; if (message.loadingBody || typeof message.body !== 'undefined') {
}
// the message already has a body, so no need to become active here
if (message.body) {
return; return;
} }
message.loadingBody = true; message.loadingBody = true;
// the mail does not have its content in memory /*
readFromDevice(); * read this before inspecting the method!
*
* you will wonder about the round trip to the disk where we load the persisted object. there are two reasons for this behavior:
* 1) if you work with a message that was loaded from the disk, we strip the message.bodyParts array,
* because it is not really necessary to keep everything in memory
* 2) the message in memory is polluted by angular. angular tracks ordering of a list by adding a property
* to the model. this property is auto generated and must not be persisted.
*/
// if possible, read the message body from the device retrieveContent();
function readFromDevice() {
function retrieveContent() {
// load the local message from memory
self._emailSync._localListMessages({ self._emailSync._localListMessages({
folder: folder, folder: folder,
uid: message.uid uid: message.uid
}, function(err, localMessages) { }, function(err, localMessages) {
var localMessage; if (err || localMessages.length === 0) {
done(err);
if (err) {
message.loadingBody = false;
callback(err);
return; return;
} }
localMessage = localMessages[0]; var localMessage = localMessages[0];
if (!localMessage.body) { // do we need to fetch content from the imap server?
streamFromImap(); var needsFetch = false;
localMessage.bodyParts.forEach(function(part) {
needsFetch = (typeof part.content === 'undefined');
});
if (!needsFetch) {
// if we have all the content we need,
// we can extract the content
message.bodyParts = localMessage.bodyParts;
extractContent();
return; return;
} }
// attach the body to the mail object // get the raw content from the imap server
message.body = localMessage.body; self._emailSync._getBodyParts({
handleEncryptedContent();
});
}
// if reading the message body from the device was unsuccessful,
// stream the message from the imap server
function streamFromImap() {
self._emailSync._imapStreamText({
folder: folder,
message: message
}, function(error) {
if (error) {
message.loadingBody = false;
callback(error);
return;
}
message.loadingBody = false;
// do not write the object from the object used by angular to the disk, instead
// do a short round trip and write back the unpolluted object
self._emailSync._localListMessages({
folder: folder, folder: folder,
uid: message.uid uid: localMessage.uid,
}, function(error, storedMessages) { bodyParts: localMessage.bodyParts
if (error) { }, function(err, parsedBodyParts) {
callback(error); if (err) {
done(err);
return; return;
} }
storedMessages[0].body = message.body; message.bodyParts = parsedBodyParts;
localMessage.bodyParts = parsedBodyParts;
// persist it to disk
self._emailSync._localStoreMessages({ self._emailSync._localStoreMessages({
folder: folder, folder: folder,
emails: storedMessages emails: [localMessage]
}, function(error) { }, function(error) {
if (error) { if (error) {
callback(error); done(error);
return; return;
} }
handleEncryptedContent(); // extract the content
extractContent();
}); });
}); });
}); });
} }
function handleEncryptedContent() { function extractContent() {
// normally, the imap-client should already have set the message.encrypted flag. problem: if we have pgp/inline,
// we can't reliably determine if the message is encrypted before we have inspected the payload...
message.encrypted = containsArmoredCiphertext(message);
// cleans the message body from everything but the ciphertext
if (message.encrypted) { if (message.encrypted) {
message.decrypted = false; // show the encrypted message
extractCiphertext(); message.body = self._emailSync.filterBodyParts(message.bodyParts, 'encrypted')[0].content;
done();
return;
} }
// for unencrypted messages, this is the array where the body parts are located
var root = message.bodyParts;
if (message.signed) {
var signedPart = self._emailSync.filterBodyParts(message.bodyParts, 'signed')[0];
message.message = signedPart.message;
message.signature = signedPart.signature;
// TODO check integrity
// in case of a signed message, you only want to show the signed content and ignore the rest
root = signedPart.content;
}
message.attachments = self._emailSync.filterBodyParts(root, 'attachment');
message.body = _.pluck(self._emailSync.filterBodyParts(root, 'text'), 'content').join('\n');
message.html = _.pluck(self._emailSync.filterBodyParts(root, 'html'), 'content').join('\n');
done();
}
function done(err) {
message.loadingBody = false; message.loadingBody = false;
callback(null, message); callback(err, err ? undefined : message);
} }
function containsArmoredCiphertext() {
return message.body.indexOf(str.cryptPrefix) !== -1 && message.body.indexOf(str.cryptSuffix) !== -1;
}
function extractCiphertext() {
var start = message.body.indexOf(str.cryptPrefix),
end = message.body.indexOf(str.cryptSuffix) + str.cryptSuffix.length;
// parse message body for encrypted message block
message.body = message.body.substring(start, end);
}
}; };
EmailDAO.prototype.decryptBody = function(options, callback) { EmailDAO.prototype.decryptBody = function(options, callback) {
var self = this, var self = this,
message = options.message; message = options.message;
// the message has no body, is not encrypted or has already been decrypted // the message is decrypting has no body, is not encrypted or has already been decrypted
if (message.decryptingBody || !message.body || !message.encrypted || message.decrypted) { if (message.decryptingBody || !message.body || !message.encrypted || message.decrypted) {
return; return;
} }
@ -400,69 +398,65 @@ define(function(require) {
// get the sender's public key for signature checking // get the sender's public key for signature checking
self._keychain.getReceiverPublicKey(message.from[0].address, function(err, senderPublicKey) { self._keychain.getReceiverPublicKey(message.from[0].address, function(err, senderPublicKey) {
if (err) { if (err) {
message.decryptingBody = false; done(err);
callback(err);
return; return;
} }
if (!senderPublicKey) { if (!senderPublicKey) {
// this should only happen if a mail from another channel is in the inbox // this should only happen if a mail from another channel is in the inbox
message.body = 'Public key for sender not found!'; showError('Public key for sender not found!');
message.decryptingBody = false;
callback(null, message);
return; return;
} }
// get the receiver's public key to check the message signature // get the receiver's public key to check the message signature
self._crypto.decrypt(message.body, senderPublicKey.publicKey, function(err, decrypted) { var encryptedNode = self._emailSync.filterBodyParts(message.bodyParts, 'encrypted')[0];
// if an error occurs during decryption, display the error message as the message content self._crypto.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
decrypted = decrypted || err.errMsg || 'Error occurred during decryption'; if (err || !decrypted) {
showError(err.errMsg || err.message);
// this is a very primitive detection if we have PGP/MIME or PGP/INLINE
if (!self._mailreader.isRfc(decrypted)) {
message.body = decrypted;
message.decrypted = true;
message.decryptingBody = false;
callback(null, message);
return; return;
} }
// parse the decrypted MIME message // the mailparser works on the .raw property
self._imapParseMessageBlock({ encryptedNode.raw = decrypted;
message: message,
raw: decrypted // parse the decrpyted raw content in the mailparser
}, function(error) { self._mailreader.parse({
if (error) { bodyParts: [encryptedNode]
message.decryptingBody = false; }, function(err, parsedBodyParts) {
callback(error); if (err) {
showError(err.errMsg || err.message);
return; return;
} }
message.decrypted = true; // we have successfully interpreted the descrypted message,
// so let's update the views on the message parts
// remove the pgp-signature from the attachments message.body = _.pluck(self._emailSync.filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n');
message.attachments = _.reject(message.attachments, function(attmt) { message.html = _.pluck(self._emailSync.filterBodyParts(parsedBodyParts, 'html'), 'content').join('\n');
message.attachments = _.reject(self._emailSync.filterBodyParts(parsedBodyParts, 'attachment'), function(attmt) {
// remove the pgp-signature from the attachments
return attmt.mimeType === "application/pgp-signature"; return attmt.mimeType === "application/pgp-signature";
}); });
message.decrypted = true;
// we're done here! // we're done here!
message.decryptingBody = false; done();
callback(null, message);
}); });
}); });
}); });
};
EmailDAO.prototype.getAttachment = function(options, callback) { function showError(msg) {
if (!this._account.online) { message.body = msg;
callback({ message.decrypted = true; // display error msh in body
errMsg: 'Client is currently offline!', done();
code: 42
});
return;
} }
this._imapClient.getAttachment(options, callback); function done(err) {
message.decryptingBody = false;
callback(err, err ? undefined : message);
}
}; };
EmailDAO.prototype.sendEncrypted = function(options, callback) { EmailDAO.prototype.sendEncrypted = function(options, callback) {
@ -541,10 +535,6 @@ define(function(require) {
this._imapClient.logout(callback); this._imapClient.logout(callback);
}; };
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
this._mailreader.parseRfc(options, callback);
};
/** /**
* List the folders in the user's IMAP mailbox. * List the folders in the user's IMAP mailbox.
*/ */

View File

@ -5,9 +5,10 @@ define(function(require) {
config = require('js/app-config').config, config = require('js/app-config').config,
str = require('js/app-config').string; str = require('js/app-config').string;
var EmailSync = function(keychain, devicestorage) { var EmailSync = function(keychain, devicestorage, mailreader) {
this._keychain = keychain; this._keychain = keychain;
this._devicestorage = devicestorage; this._devicestorage = devicestorage;
this._mailreader = mailreader;
}; };
EmailSync.prototype.init = function(options, callback) { EmailSync.prototype.init = function(options, callback) {
@ -168,8 +169,8 @@ define(function(require) {
} }
storedMessages.forEach(function(storedMessage) { storedMessages.forEach(function(storedMessage) {
// remove the body to not load unnecessary data to memory // remove the body parts to not load unnecessary data to memory
delete storedMessage.body; delete storedMessage.bodyParts;
folder.messages.push(storedMessage); folder.messages.push(storedMessage);
}); });
@ -619,10 +620,11 @@ define(function(require) {
} }
function handleVerification(message, localCallback) { function handleVerification(message, localCallback) {
self._imapStreamText({ self._getBodyParts({
folder: options.folder, folder: options.folder,
message: message uid: message.uid,
}, function(error) { bodyParts: message.bodyParts
}, function(error, parsedBodyParts) {
// we could not stream the text to determine if the verification was valid or not // we could not stream the text to determine if the verification was valid or not
// so handle it as if it were valid // so handle it as if it were valid
if (error) { if (error) {
@ -630,8 +632,9 @@ define(function(require) {
return; return;
} }
var verificationUrlPrefix = config.cloudUrl + config.verificationUrl, var body = _.pluck(self.filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n'),
uuid = message.body.split(verificationUrlPrefix).pop().substr(0, config.verificationUuidLength), verificationUrlPrefix = config.cloudUrl + config.verificationUrl,
uuid = body.split(verificationUrlPrefix).pop().substr(0, config.verificationUuidLength),
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); 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);
// there's no valid uuid in the message, so forget about it // there's no valid uuid in the message, so forget about it
@ -707,12 +710,8 @@ define(function(require) {
return; return;
} }
this._imapClient.updateFlags({ options.path = options.folder;
path: options.folder, this._imapClient.updateFlags(options, callback);
uid: options.uid,
unread: options.unread,
answered: options.answered
}, callback);
}; };
/** /**
@ -731,18 +730,8 @@ define(function(require) {
return; return;
} }
var o = { options.path = options.folder;
path: options.folder this._imapClient.search(options, callback);
};
if (typeof options.answered !== 'undefined') {
o.answered = options.answered;
}
if (typeof options.unread !== 'undefined') {
o.unread = options.unread;
}
this._imapClient.search(o, callback);
}; };
EmailSync.prototype._imapDeleteMessage = function(options, callback) { EmailSync.prototype._imapDeleteMessage = function(options, callback) {
@ -754,10 +743,8 @@ define(function(require) {
return; return;
} }
this._imapClient.deleteMessage({ options.path = options.folder;
path: options.folder, this._imapClient.deleteMessage(options, callback);
uid: options.uid
}, callback);
}; };
/** /**
@ -778,20 +765,18 @@ define(function(require) {
return; return;
} }
self._imapClient.listMessagesByUid({ options.path = options.folder;
path: options.folder, self._imapClient.listMessages(options, callback);
firstUid: options.firstUid,
lastUid: options.lastUid
}, callback);
}; };
/** /**
* Stream an email messsage's body * Stream an email messsage's body
* @param {String} options.folder The folder * @param {String} options.folder The folder
* @param {Object} options.message The message, as retrieved by _imapListMessages * @param {String} options.uid the message's uid
* @param {Object} options.bodyParts The message, as retrieved by _imapListMessages
* @param {Function} callback (error, message) The callback when the imap client is done streaming message text content * @param {Function} callback (error, message) The callback when the imap client is done streaming message text content
*/ */
EmailSync.prototype._imapStreamText = function(options, callback) { EmailSync.prototype._getBodyParts = function(options, callback) {
var self = this; var self = this;
if (!this._account.online) { if (!this._account.online) {
@ -802,11 +787,37 @@ define(function(require) {
return; return;
} }
self._imapClient.getBody({ options.path = options.folder;
path: options.folder, self._imapClient.getBodyParts(options, function(err) {
message: options.message if (err) {
}, callback); callback(err);
return;
}
// interpret the raw content of the email
self._mailreader.parse(options, callback);
});
}; };
/**
* Helper function that recursively traverses the body parts tree. Looks for bodyParts that match the provided type and aggregates them
* @param {[type]} bodyParts The bodyParts array
* @param {[type]} type The type to look up
* @param {undefined} result Leave undefined, only used for recursion
*/
EmailSync.prototype.filterBodyParts = function(bodyParts, type, result) {
var self = this;
result = result || [];
bodyParts.forEach(function(part) {
if (part.type === type) {
result.push(part);
} else if (Array.isArray(part.content)) {
self.filterBodyParts(part.content, type, result);
}
});
return result;
};
return EmailSync; return EmailSync;
}); });

View File

@ -2,7 +2,8 @@ define(function(require) {
'use strict'; 'use strict';
var cfg = require('js/app-config').config, var cfg = require('js/app-config').config,
updateV1 = require('js/util/update/update-v1'); updateV1 = require('js/util/update/update-v1'),
updateV2 = require('js/util/update/update-v2');
/** /**
* Handles database migration * Handles database migration
@ -10,7 +11,7 @@ define(function(require) {
var UpdateHandler = function(appConfigStorage, userStorage) { var UpdateHandler = function(appConfigStorage, userStorage) {
this._appConfigStorage = appConfigStorage; this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage; this._userStorage = userStorage;
this._updateScripts = [updateV1]; this._updateScripts = [updateV1, updateV2];
}; };
/** /**

View File

@ -0,0 +1,28 @@
define(function() {
'use strict';
/**
* Update handler for transition database version 1 -> 2
*
* In database version 2, the stored email objects have to be purged, because the
* new data model stores information about the email structure in the property 'bodyParts'.
*/
function updateV2(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 2;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
return updateV2;
});

View File

@ -60,7 +60,7 @@
<!-- Render text body as colored conversation in recursive text nodes --> <!-- Render text body as colored conversation in recursive text nodes -->
<div class="body" <div class="body"
ng-if="!html && (state.mailList.selected === undefined || (state.mailList.selected.encrypted === false && state.mailList.selected.body !== undefined) || (state.mailList.selected.encrypted === true && state.mailList.selected.decrypted === true))"> ng-if="!html && (state.mailList.selected === undefined || (!state.mailList.selected.encrypted && state.mailList.selected.body !== undefined) || (state.mailList.selected.encrypted === true && state.mailList.selected.decrypted === true))">
<div ng-include="'tpl/text-node.html'" <div ng-include="'tpl/text-node.html'"
ng-if="node !== undefined" ng-if="node !== undefined"
ng-repeat="child in node.children track by $index" ng-repeat="child in node.children track by $index"

View File

@ -447,17 +447,6 @@ define(function(require) {
}); });
}); });
describe('_imapParseMessageBlock', function() {
it('should parse a message', function(done) {
var parseRfc = sinon.stub(mailreader, 'parseRfc').withArgs({}).yields();
dao._imapParseMessageBlock({}, function() {
expect(parseRfc.calledOnce).to.be.true;
done();
});
});
});
describe('_imapLogin', function() { describe('_imapLogin', function() {
it('should fail when disconnected', function(done) { it('should fail when disconnected', function(done) {
dao.onDisconnect(null, function(err) { dao.onDisconnect(null, function(err) {
@ -616,6 +605,10 @@ define(function(require) {
describe('getBody', function() { describe('getBody', function() {
var folder = 'asdasdasdasdasd',
uid = 1234,
localListStub, localStoreStub, imapGetStub;
it('should not do anything if the message already has content', function() { it('should not do anything if the message already has content', function() {
var message = { var message = {
body: 'bender is great!' body: 'bender is great!'
@ -629,11 +622,9 @@ define(function(require) {
}); });
it('should read an unencrypted body from the device', function(done) { it('should read an unencrypted body from the device', function(done) {
var message, uid, folder, body, localListStub; var message, body;
folder = 'asdasdasdasdasd';
body = 'bender is great! bender is great!'; body = 'bender is great! bender is great!';
uid = 1234;
message = { message = {
uid: uid uid: uid
}; };
@ -642,10 +633,12 @@ define(function(require) {
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [{ }).yieldsAsync(null, [{
body: body bodyParts: [{
type: 'text',
content: body
}]
}]); }]);
dao.getBody({ dao.getBody({
message: message, message: message,
folder: folder folder: folder
@ -653,8 +646,7 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(msg.body).to.not.be.empty; expect(msg.body).to.equal(body);
expect(msg.encrypted).to.be.false;
expect(msg.loadingBody).to.be.false; expect(msg.loadingBody).to.be.false;
expect(localListStub.calledOnce).to.be.true; expect(localListStub.calledOnce).to.be.true;
@ -665,20 +657,26 @@ define(function(require) {
}); });
it('should read an encrypted body from the device', function(done) { it('should read an encrypted body from the device', function(done) {
var message, uid, folder, body, localListStub; var message, ct, pt;
folder = 'asdasdasdasdasd'; pt = 'bender is great!';
body = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'; ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----';
uid = 1234;
message = { message = {
uid: uid uid: uid,
encrypted: true
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [{ }).yieldsAsync(null, [{
body: body bodyParts: [{
type: 'text',
content: pt
}, {
type: 'encrypted',
content: ct
}]
}]); }]);
dao.getBody({ dao.getBody({
@ -688,9 +686,8 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(msg.body).to.not.be.empty; expect(msg.body).to.equal(ct);
expect(msg.encrypted).to.be.true; expect(msg.encrypted).to.be.true;
expect(msg.decrypted).to.be.false;
expect(message.loadingBody).to.be.false; expect(message.loadingBody).to.be.false;
expect(localListStub.calledOnce).to.be.true; expect(localListStub.calledOnce).to.be.true;
@ -700,14 +697,17 @@ define(function(require) {
expect(message.loadingBody).to.be.true; expect(message.loadingBody).to.be.true;
}); });
it('should stream an unencrypted body from imap', function(done) { it('should stream from imap and set plain text body', function(done) {
var message, uid, folder, body, localListStub, localStoreStub, imapStreamStub; var message, body;
folder = 'asdasdasdasdasd'; folder = 'asdasdasdasdasd';
body = 'bender is great! bender is great!'; body = 'bender is great! bender is great!';
uid = 1234; uid = 1234;
message = { message = {
uid: uid uid: uid,
bodyParts: [{
type: 'text'
}]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({
@ -720,16 +720,14 @@ define(function(require) {
emails: [message] emails: [message]
}).yieldsAsync(); }).yieldsAsync();
imapStreamStub = sinon.stub(emailSync, '_imapStreamText', function(opts, cb) { imapGetStub = sinon.stub(emailSync, '_getBodyParts').withArgs({
expect(opts).to.deep.equal({ folder: folder,
folder: folder, uid: message.uid,
message: message bodyParts: message.bodyParts
}); }).yieldsAsync(null, [{
type: 'text',
message.body = body; content: body
cb(); }]);
});
dao.getBody({ dao.getBody({
message: message, message: message,
@ -738,12 +736,11 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(msg.body).to.not.be.empty; expect(msg.body).to.equal(body);
expect(msg.encrypted).to.be.false;
expect(msg.loadingBody).to.be.false; expect(msg.loadingBody).to.be.false;
expect(localListStub.calledTwice).to.be.true; expect(localListStub.calledOnce).to.be.true;
expect(imapStreamStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true;
expect(localStoreStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true;
done(); done();
@ -751,14 +748,19 @@ define(function(require) {
expect(message.loadingBody).to.be.true; expect(message.loadingBody).to.be.true;
}); });
it('should stream an encrypted body from imap', function(done) { it('should stream from imap and set encrypted body', function(done) {
var message, uid, folder, body, localListStub, localStoreStub, imapStreamStub; var message, ct, pt;
folder = 'asdasdasdasdasd'; pt = 'bender is great';
body = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'; ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----';
uid = 1234;
message = { message = {
uid: uid uid: uid,
encrypted: true,
bodyParts: [{
type: 'text'
}, {
type: 'encrypted'
}]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({
@ -771,15 +773,17 @@ define(function(require) {
emails: [message] emails: [message]
}).yieldsAsync(); }).yieldsAsync();
imapStreamStub = sinon.stub(emailSync, '_imapStreamText', function(opts, cb) { imapGetStub = sinon.stub(emailSync, '_getBodyParts').withArgs({
expect(opts).to.deep.equal({ folder: folder,
folder: folder, uid: message.uid,
message: message bodyParts: message.bodyParts
}); }).yieldsAsync(null, [{
type: 'text',
message.body = body; content: pt
cb(); }, {
}); type: 'encrypted',
content: ct
}]);
dao.getBody({ dao.getBody({
@ -789,13 +793,12 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(msg.body).to.not.be.empty; expect(msg.body).to.equal(ct);
expect(msg.encrypted).to.be.true; expect(msg.encrypted).to.be.true;
expect(msg.decrypted).to.be.false;
expect(msg.loadingBody).to.be.false; expect(msg.loadingBody).to.be.false;
expect(localListStub.calledTwice).to.be.true; expect(localListStub.calledOnce).to.be.true;
expect(imapStreamStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true;
expect(localStoreStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true;
done(); done();
@ -804,22 +807,19 @@ define(function(require) {
}); });
it('fail to stream from imap due to error when persisting', function(done) { it('fail to stream from imap due to error when persisting', function(done) {
var message, uid, folder, body, localListStub, localStoreStub, imapStreamStub; var message = {
uid: uid,
folder = 'asdasdasdasdasd'; bodyParts: [{
body = 'THIS IS THE BODY'; type: 'text'
uid = 1234; }]
message = {
uid: uid
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').yieldsAsync(null, [message]); localListStub = sinon.stub(emailSync, '_localListMessages').yieldsAsync(null, [message]);
localStoreStub = sinon.stub(emailSync, '_localStoreMessages').yieldsAsync({}); localStoreStub = sinon.stub(emailSync, '_localStoreMessages').yieldsAsync({});
imapGetStub = sinon.stub(emailSync, '_getBodyParts').yieldsAsync(null, [{
imapStreamStub = sinon.stub(emailSync, '_imapStreamText', function(opts, cb) { type: 'text',
message.body = body; content: 'bender is great! bender is great!'
cb(); }]);
});
dao.getBody({ dao.getBody({
message: message, message: message,
@ -827,8 +827,8 @@ define(function(require) {
}, function(err, msg) { }, function(err, msg) {
expect(err).to.exist; expect(err).to.exist;
expect(msg).to.not.exist; expect(msg).to.not.exist;
expect(localListStub.calledTwice).to.be.true; expect(localListStub.calledOnce).to.be.true;
expect(imapStreamStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true;
expect(localStoreStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true;
expect(message.loadingBody).to.be.false; expect(message.loadingBody).to.be.false;
@ -838,21 +838,15 @@ define(function(require) {
}); });
it('fail to stream from imap due to stream error', function(done) { it('fail to stream from imap due to stream error', function(done) {
var message, uid, folder, body, localListStub, localStoreStub, imapStreamStub; var message = {
uid: uid,
folder = 'asdasdasdasdasd'; bodyParts: [{
uid = 1234; type: 'text'
message = { }]
uid: uid
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').yields(null, [{}]); localListStub = sinon.stub(emailSync, '_localListMessages').yields(null, [message]);
imapGetStub = sinon.stub(emailSync, '_getBodyParts').yieldsAsync({});
imapStreamStub = sinon.stub(emailSync, '_imapStreamText', function(opts, cb) {
message.body = body;
cb({});
});
localStoreStub = sinon.stub(emailSync, '_localStoreMessages'); localStoreStub = sinon.stub(emailSync, '_localStoreMessages');
dao.getBody({ dao.getBody({
@ -862,7 +856,7 @@ define(function(require) {
expect(err).to.exist; expect(err).to.exist;
expect(msg).to.not.exist; expect(msg).to.not.exist;
expect(localListStub.calledOnce).to.be.true; expect(localListStub.calledOnce).to.be.true;
expect(imapStreamStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true;
expect(localStoreStub.called).to.be.false; expect(localStoreStub.called).to.be.false;
expect(message.loadingBody).to.be.false; expect(message.loadingBody).to.be.false;
@ -873,55 +867,89 @@ define(function(require) {
}); });
describe('decryptBody', function() { describe('decryptBody', function() {
it('should not do anything when the message is not encrypted', function() { it('should do nothing when the message is not encrypted', function() {
var message = { var message = {
encrypted: false encrypted: false,
decrypted: true,
body: 'asd'
}; };
dao.decryptBody({ dao.decryptBody({
message: message message: message
}); });
// should do nothing
}); });
it('should not do anything when the message is already decrypted', function() { it('should do nothing when the message is already decrypted', function() {
var message = { var message = {
encrypted: true, encrypted: true,
decrypted: true decrypted: true,
body: 'asd'
}; };
dao.decryptBody({ dao.decryptBody({
message: message message: message
}); });
});
// should do nothing it('should do nothing when the message has no body', function() {
var message = {
encrypted: true,
decrypted: false,
body: ''
};
dao.decryptBody({
message: message
});
});
it('should do nothing when the message is decrypting', function() {
var message = {
encrypted: true,
decrypted: false,
body: 'asd',
decryptingBody: true
};
dao.decryptBody({
message: message
});
}); });
it('decrypt a pgp/mime message', function(done) { it('decrypt a pgp/mime message', function(done) {
var message, parsedBody, mimeBody, parseStub; var message, ct, pt, parsed, parseStub;
pt = 'bender is great';
ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----';
parsed = 'bender! bender! bender!';
message = { message = {
from: [{ from: [{
address: 'asdasdasd' address: 'asdasdasd'
}], }],
body: ct,
encrypted: true, encrypted: true,
decrypted: false, bodyParts: [{
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----' type: 'encrypted',
content: ct
}]
}; };
mimeBody = 'Content-Type: asdasdasd';
parsedBody = 'body? yes.';
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey); keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, mimeBody); pgpStub.decrypt.withArgs(ct, mockKeyPair.publicKey.publicKey).yieldsAsync(null, pt);
parseStub = sinon.stub(dao, '_imapParseMessageBlock', function(o, cb) { parseStub = sinon.stub(mailreader, 'parse').withArgs({
expect(o.message).to.equal(message); bodyParts: [{
expect(o.raw).to.equal(mimeBody); type: 'encrypted',
content: ct,
o.message.body = parsedBody; raw: pt
cb(null, o.message); }]
}); }).yieldsAsync(null, [{
type: 'encrypted',
content: [{
type: 'text',
content: parsed
}]
}]);
dao.decryptBody({ dao.decryptBody({
message: message message: message
@ -929,110 +957,72 @@ define(function(require) {
expect(error).to.not.exist; expect(error).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(msg.decrypted).to.be.true; expect(message.decrypted).to.be.true;
expect(msg.body).to.equal(parsedBody); expect(message.body).to.equal(parsed);
expect(msg.decryptingBody).to.be.false; expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.calledOnce).to.be.true; expect(parseStub.calledOnce).to.be.true;
mailreader.parse.restore();
done(); done();
}); });
expect(message.decryptingBody).to.be.true; expect(message.decryptingBody).to.be.true;
}); });
it('decrypt a pgp/inline message', function(done) {
var message, plaintextBody, parseStub;
message = {
from: [{
address: 'asdasdasd'
}],
encrypted: true,
decrypted: false,
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'
};
plaintextBody = 'body? yes.';
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, plaintextBody);
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
dao.decryptBody({
message: message
}, function(error, msg) {
expect(error).to.not.exist;
expect(msg).to.equal(message);
expect(msg.decrypted).to.be.true;
expect(msg.body).to.equal(plaintextBody);
expect(msg.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.called).to.be.false;
done();
});
expect(message.decryptingBody).to.be.true;
});
it('should fail during decryption message', function(done) { it('should fail during decryption message', function(done) {
var message, plaintextBody, parseStub, errMsg; var message = {
message = {
from: [{ from: [{
address: 'asdasdasd' address: 'asdasdasd'
}], }],
body: 'asdjafuad',
encrypted: true, encrypted: true,
decrypted: false, bodyParts: [{
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----' type: 'encrypted',
content: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'
}]
}; };
plaintextBody = 'body? yes.'; var parseStub = sinon.spy(mailreader, 'parse');
errMsg = 'yaddayadda'; keychainStub.getReceiverPublicKey.yields(null, mockKeyPair.publicKey);
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yields(null, mockKeyPair.publicKey);
pgpStub.decrypt.yields({ pgpStub.decrypt.yields({
errMsg: errMsg errMsg: 'asd'
}); });
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
dao.decryptBody({ dao.decryptBody({
message: message message: message
}, function(error, msg) { }, function(error, msg) {
expect(error).to.not.exist; expect(error).to.not.exist;
expect(msg.body).to.equal('asd');
expect(msg).to.equal(message); expect(msg).to.exist;
expect(msg.decrypted).to.be.true; expect(message.decryptingBody).to.be.false;
expect(msg.body).to.equal(errMsg);
expect(msg.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.called).to.be.false; expect(parseStub.called).to.be.false;
mailreader.parse.restore();
done(); done();
}); });
}); });
it('should fail during key export', function(done) { it('should fail during key export', function(done) {
var message, parseStub; var message = {
message = {
from: [{ from: [{
address: 'asdasdasd' address: 'asdasdasd'
}], }],
encrypted: true, encrypted: true,
decrypted: false, body: 'asdjafuad',
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----' bodyParts: [{
type: 'encrypted',
content: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'
}]
}; };
var parseStub = sinon.spy(mailreader, 'parse');
keychainStub.getReceiverPublicKey.yields({}); keychainStub.getReceiverPublicKey.yields({});
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
dao.decryptBody({ dao.decryptBody({
message: message message: message
@ -1041,13 +1031,13 @@ define(function(require) {
expect(msg).to.not.exist; expect(msg).to.not.exist;
expect(message.decrypted).to.be.false;
expect(message.decryptingBody).to.be.false; expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.called).to.be.false; expect(pgpStub.decrypt.called).to.be.false;
expect(parseStub.called).to.be.false; expect(parseStub.called).to.be.false;
mailreader.parse.restore();
done(); done();
}); });
}); });

View File

@ -3,6 +3,7 @@ define(function(require) {
var EmailSync = require('js/dao/email-sync'), var EmailSync = require('js/dao/email-sync'),
KeychainDAO = require('js/dao/keychain-dao'), KeychainDAO = require('js/dao/keychain-dao'),
mailreader = require('mailreader'),
ImapClient = require('imap-client'), ImapClient = require('imap-client'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'), DeviceStorageDAO = require('js/dao/devicestorage-dao'),
expect = chai.expect; expect = chai.expect;
@ -13,8 +14,7 @@ define(function(require) {
var emailSync, keychainStub, imapClientStub, devicestorageStub; var emailSync, keychainStub, imapClientStub, devicestorageStub;
var emailAddress, mockkeyId, dummyEncryptedMail, var emailAddress, mockkeyId, dummyEncryptedMail,
dummyDecryptedMail, mockKeyPair, account, verificationMail, verificationUuid, dummyDecryptedMail, mockKeyPair, account, verificationMail, verificationUuid, corruptedVerificationUuid,
corruptedVerificationMail, corruptedVerificationUuid,
nonWhitelistedMail; nonWhitelistedMail;
beforeEach(function(done) { beforeEach(function(done) {
@ -29,7 +29,9 @@ define(function(require) {
address: 'qwe@qwe.de' address: 'qwe@qwe.de'
}], }],
subject: 'qweasd', subject: 'qweasd',
body: '-----BEGIN PGP MESSAGE-----\nasd\n-----END PGP MESSAGE-----', bodyParts: [{
type: 'encrypted'
}],
unread: false, unread: false,
answered: false answered: false
}; };
@ -43,24 +45,13 @@ define(function(require) {
address: 'safewithme.testuser@gmail.com' address: 'safewithme.testuser@gmail.com'
}], // list of receivers }], // list of receivers
subject: "[whiteout] New public key uploaded", // Subject line subject: "[whiteout] New public key uploaded", // Subject line
body: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + verificationUuid, // plaintext body bodyParts: [{
type: 'text'
}],
unread: false, unread: false,
answered: false answered: false
}; };
corruptedVerificationUuid = 'OMFG_FUCKING_BASTARD_UUID_FROM_HELL!'; corruptedVerificationUuid = 'OMFG_FUCKING_BASTARD_UUID_FROM_HELL!';
corruptedVerificationMail = {
from: [{
name: 'Whiteout Test',
address: 'whiteout.test@t-online.de'
}], // sender address
to: [{
address: 'safewithme.testuser@gmail.com'
}], // list of receivers
subject: "[whiteout] New public key uploaded", // Subject line
body: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + corruptedVerificationUuid, // plaintext body
unread: false,
answered: false
};
dummyDecryptedMail = { dummyDecryptedMail = {
uid: 1234, uid: 1234,
from: [{ from: [{
@ -70,7 +61,9 @@ define(function(require) {
address: 'qwe@qwe.de' address: 'qwe@qwe.de'
}], }],
subject: 'qweasd', subject: 'qweasd',
body: 'Content-Type: multipart/signed;\r\n boundary="Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2";\r\n protocol="application/pgp-signature";\r\n micalg=pgp-sha512\r\n\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Type: multipart/mixed;\r\n boundary="Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA"\r\n\r\n\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\n\r\nasdasd \r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Disposition: attachment;\r\n filename=dummy.txt\r\nContent-Type: text/plain;\r\n name="dummy.txt"\r\nContent-Transfer-Encoding: 7bit\r\n\r\noaudbcoaurbvosuabvlasdjbfalwubjvawvb\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA--\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=signature.asc\r\nContent-Type: application/pgp-signature;\r\n name=signature.asc\r\nContent-Description: Message signed with OpenPGP using GPGMail\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nComment: GPGTools - https://gpgtools.org\r\n\r\niQEcBAEBCgAGBQJS2kO1AAoJEDzmUwH7XO/cP+YH/2PSBxX1ZZd83Uf9qBGDY807\r\niHOdgPFXm64YjSnohO7XsPcnmihqP1ipS2aaCXFC3/Vgb9nc4isQFS+i1VdPwfuR\r\n1Pd2l3dC4/nD4xO9h/W6JW7Yd24NS5TJD5cA7LYwQ8LF+rOzByMatiTMmecAUCe8\r\nEEalEjuogojk4IacA8dg/bfLqQu9E+0GYUJBcI97dx/0jZ0qMOxbWOQLsJ3DnUnV\r\nOad7pAIbHEO6T0EBsH7TyTj4RRHkP6SKE0mm6ZYUC7KCk2Z3MtkASTxUrnqW5qZ5\r\noaXUO9GEc8KZcmbCdhZY2Y5h+dmucaO0jpbeSKkvtYyD4KZrSvt7NTb/0dSLh4Y=\r\n=G8km\r\n-----END PGP SIGNATURE-----\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2--\r\n', bodyParts: [{
type: 'text'
}],
unread: false, unread: false,
answered: false, answered: false,
}; };
@ -83,7 +76,9 @@ define(function(require) {
address: 'qwe@qwe.de' address: 'qwe@qwe.de'
}], }],
subject: 'qweasd', subject: 'qweasd',
body: 'asd' bodyParts: [{
type: 'text'
}],
}; };
mockKeyPair = { mockKeyPair = {
publicKey: { publicKey: {
@ -106,10 +101,11 @@ define(function(require) {
imapClientStub = sinon.createStubInstance(ImapClient); imapClientStub = sinon.createStubInstance(ImapClient);
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO); devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
emailSync = new EmailSync(keychainStub, devicestorageStub); emailSync = new EmailSync(keychainStub, devicestorageStub, mailreader);
expect(emailSync._keychain).to.equal(keychainStub); expect(emailSync._keychain).to.equal(keychainStub);
expect(emailSync._devicestorage).to.equal(devicestorageStub); expect(emailSync._devicestorage).to.equal(devicestorageStub);
expect(emailSync._mailreader).to.equal(mailreader);
// init // init
emailSync.init({ emailSync.init({
@ -154,6 +150,8 @@ define(function(require) {
describe('_imapSearch', function() { describe('_imapSearch', function() {
var path = 'FOLDAAAA';
it('should fail when disconnected', function(done) { it('should fail when disconnected', function(done) {
// this is set in the emailDao.onDisconnect // this is set in the emailDao.onDisconnect
emailSync._account.online = false; emailSync._account.online = false;
@ -164,10 +162,9 @@ define(function(require) {
}); });
}); });
it('should work', function(done) { it('should list all uids', function(done) {
var path = 'FOLDAAAA';
imapClientStub.search.withArgs({ imapClientStub.search.withArgs({
folder: path,
path: path path: path
}).yields(); }).yields();
@ -175,10 +172,10 @@ define(function(require) {
folder: path folder: path
}, done); }, done);
}); });
it('should work', function(done) {
var path = 'FOLDAAAA';
it('should list answered uids', function(done) {
imapClientStub.search.withArgs({ imapClientStub.search.withArgs({
folder: path,
path: path, path: path,
answered: true answered: true
}).yields(); }).yields();
@ -188,10 +185,10 @@ define(function(require) {
answered: true answered: true
}, done); }, done);
}); });
it('should work', function(done) {
var path = 'FOLDAAAA';
it('should list unread uids', function(done) {
imapClientStub.search.withArgs({ imapClientStub.search.withArgs({
folder: path,
path: path, path: path,
unread: true unread: true
}).yields(); }).yields();
@ -204,6 +201,9 @@ define(function(require) {
}); });
describe('_imapDeleteMessage', function() { describe('_imapDeleteMessage', function() {
var path = 'FOLDAAAA',
uid = 1337;
it('should fail when disconnected', function(done) { it('should fail when disconnected', function(done) {
// this is set in the emailDao.onDisconnect // this is set in the emailDao.onDisconnect
emailSync._account.online = false; emailSync._account.online = false;
@ -215,11 +215,9 @@ define(function(require) {
}); });
it('should work', function(done) { it('should work', function(done) {
var path = 'FOLDAAAA',
uid = 1337;
imapClientStub.deleteMessage.withArgs({ imapClientStub.deleteMessage.withArgs({
path: path, path: path,
folder: path,
uid: uid uid: uid
}).yields(); }).yields();
@ -231,13 +229,15 @@ define(function(require) {
}); });
describe('_imapListMessages', function() { describe('_imapListMessages', function() {
it('should work', function(done) { var path = 'FOLDAAAA',
var path = 'FOLDAAAA', firstUid = 1337,
firstUid = 1337, lastUid = 1339;
lastUid = 1339;
imapClientStub.listMessagesByUid.withArgs({
it('should work', function(done) {
imapClientStub.listMessages.withArgs({
path: path, path: path,
folder: path,
firstUid: firstUid, firstUid: firstUid,
lastUid: lastUid lastUid: lastUid
}).yields(null, []); }).yields(null, []);
@ -250,18 +250,14 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msgs).to.exist; expect(msgs).to.exist;
expect(imapClientStub.listMessagesByUid.calledOnce).to.be.true; expect(imapClientStub.listMessages.calledOnce).to.be.true;
done(); done();
}); });
}); });
it('should not work when listMessagesByUid fails', function(done) { it('should not work when listMessages fails', function(done) {
var path = 'FOLDAAAA', imapClientStub.listMessages.yields({});
firstUid = 1337,
lastUid = 1339;
imapClientStub.listMessagesByUid.yields({});
emailSync._imapListMessages({ emailSync._imapListMessages({
folder: path, folder: path,
@ -271,7 +267,7 @@ define(function(require) {
expect(err).to.exist; expect(err).to.exist;
expect(msgs).to.not.exist; expect(msgs).to.not.exist;
expect(imapClientStub.listMessagesByUid.calledOnce).to.be.true; expect(imapClientStub.listMessages.calledOnce).to.be.true;
done(); done();
}); });
@ -288,42 +284,50 @@ define(function(require) {
}); });
}); });
describe('_imapStreamText', function() { describe('_getBodyParts', function() {
var path = 'FOLDAAAA',
parseStub;
it('should work', function(done) { it('should work', function(done) {
var path = 'FOLDAAAA'; var o = {
imapClientStub.getBody.withArgs({
path: path,
message: {}
}).yields(null, {});
emailSync._imapStreamText({
folder: path, folder: path,
message: {} uid: 123,
}, function(err, msg) { bodyParts: []
};
imapClientStub.getBodyParts.withArgs(o).yields(null, {});
parseStub = sinon.stub(mailreader, 'parse').withArgs(o).yields(null, []);
emailSync._getBodyParts(o, function(err, parts) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(msg).to.exist; expect(parts).to.exist;
expect(imapClientStub.getBody.calledOnce).to.be.true; expect(imapClientStub.getBodyParts.calledOnce).to.be.true;
expect(parseStub.calledOnce).to.be.true;
mailreader.parse.restore();
done(); done();
}); });
}); });
it('should not work when getBody fails', function(done) { it('should not work when getBody fails', function(done) {
var path = 'FOLDAAAA'; var o = {
imapClientStub.getBody.yields({});
emailSync._imapStreamText({
folder: path, folder: path,
message: {} uid: 123,
}, function(err, msg) { bodyParts: []
};
imapClientStub.getBodyParts.yields({});
parseStub = sinon.spy(mailreader, 'parse');
emailSync._getBodyParts(o, function(err, msg) {
expect(err).to.exist; expect(err).to.exist;
expect(msg).to.not.exist; expect(msg).to.not.exist;
expect(imapClientStub.getBody.calledOnce).to.be.true; expect(imapClientStub.getBodyParts.calledOnce).to.be.true;
expect(parseStub.called).to.be.false;
mailreader.parse.restore();
done(); done();
}); });
}); });
@ -332,7 +336,7 @@ define(function(require) {
// this is set in the emailDao.onDisconnect // this is set in the emailDao.onDisconnect
emailSync._account.online = false; emailSync._account.online = false;
emailSync._imapStreamText({}, function(err) { emailSync._getBodyParts({}, function(err) {
expect(err.code).to.equal(42); expect(err.code).to.equal(42);
done(); done();
}); });
@ -950,13 +954,12 @@ define(function(require) {
}).yields(null, []); }).yields(null, []);
imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]); imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]);
imapGetStub = sinon.stub(emailSync, '_getBodyParts').yields(null, [{
imapGetStub = sinon.stub(emailSync, '_imapStreamText').yields(null); type: 'text',
content: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + verificationUuid,
}]);
keychainStub.verifyPublicKey.withArgs(verificationUuid).yields(); keychainStub.verifyPublicKey.withArgs(verificationUuid).yields();
localStoreStub = sinon.stub(emailSync, '_localStoreMessages'); localStoreStub = sinon.stub(emailSync, '_localStoreMessages');
imapDeleteStub = sinon.stub(emailSync, '_imapDeleteMessage').withArgs({ imapDeleteStub = sinon.stub(emailSync, '_imapDeleteMessage').withArgs({
folder: folder, folder: folder,
uid: verificationMail.uid uid: verificationMail.uid
@ -1012,7 +1015,10 @@ define(function(require) {
answered: true answered: true
}).yields(null, []); }).yields(null, []);
imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]); imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]);
imapGetStub = sinon.stub(emailSync, '_imapStreamText').yields(null); imapGetStub = sinon.stub(emailSync, '_getBodyParts').yields(null, [{
type: 'text',
content: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + verificationUuid,
}]);
keychainStub.verifyPublicKey.withArgs(verificationUuid).yields(); keychainStub.verifyPublicKey.withArgs(verificationUuid).yields();
imapDeleteStub = sinon.stub(emailSync, '_imapDeleteMessage').yields({}); imapDeleteStub = sinon.stub(emailSync, '_imapDeleteMessage').yields({});
@ -1073,7 +1079,10 @@ define(function(require) {
answered: true answered: true
}).yields(null, []); }).yields(null, []);
imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]); imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]);
imapGetStub = sinon.stub(emailSync, '_imapStreamText').yields(null); imapGetStub = sinon.stub(emailSync, '_getBodyParts').yields(null, [{
type: 'text',
content: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + verificationUuid,
}]);
keychainStub.verifyPublicKey.withArgs(verificationUuid).yields({ keychainStub.verifyPublicKey.withArgs(verificationUuid).yields({
errMsg: 'fubar' errMsg: 'fubar'
}); });
@ -1134,7 +1143,7 @@ define(function(require) {
imapSearchStub = sinon.stub(emailSync, '_imapSearch'); imapSearchStub = sinon.stub(emailSync, '_imapSearch');
imapSearchStub.withArgs({ imapSearchStub.withArgs({
folder: folder folder: folder
}).yields(null, [corruptedVerificationMail.uid]); }).yields(null, [verificationMail.uid]);
imapSearchStub.withArgs({ imapSearchStub.withArgs({
folder: folder, folder: folder,
unread: true unread: true
@ -1146,12 +1155,15 @@ define(function(require) {
localStoreStub = sinon.stub(emailSync, '_localStoreMessages').withArgs({ localStoreStub = sinon.stub(emailSync, '_localStoreMessages').withArgs({
folder: folder, folder: folder,
emails: [corruptedVerificationMail] emails: [verificationMail]
}).yields(); }).yields();
imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [corruptedVerificationMail]); imapListMessagesStub = sinon.stub(emailSync, '_imapListMessages').yields(null, [verificationMail]);
imapGetStub = sinon.stub(emailSync, '_imapStreamText').yields(null); imapGetStub = sinon.stub(emailSync, '_getBodyParts').yields(null, [{
type: 'text',
content: 'yadda yadda bla blabla foo bar https://keys.whiteout.io/verify/' + corruptedVerificationUuid,
}]);
keychainStub.verifyPublicKey.withArgs(corruptedVerificationUuid).yields({ keychainStub.verifyPublicKey.withArgs(corruptedVerificationUuid).yields({
errMsg: 'fubar' errMsg: 'fubar'
}); });
@ -1602,19 +1614,16 @@ define(function(require) {
describe('mark', function() { describe('mark', function() {
it('should work', function(done) { it('should work', function(done) {
imapClientStub.updateFlags.withArgs({ var o = {
path: 'asdf',
uid: 1,
unread: false,
answered: false
}).yields();
emailSync._imapMark({
folder: 'asdf', folder: 'asdf',
uid: 1, uid: 1,
unread: false, unread: false,
answered: false answered: false
}, function(err) { };
imapClientStub.updateFlags.withArgs(o).yields();
emailSync._imapMark(o, function(err) {
expect(imapClientStub.updateFlags.calledOnce).to.be.true; expect(imapClientStub.updateFlags.calledOnce).to.be.true;
expect(err).to.not.exist; expect(err).to.not.exist;
done(); done();

View File

@ -6,11 +6,11 @@ define(function(require) {
UpdateHandler = require('js/util/update/update-handler'), UpdateHandler = require('js/util/update/update-handler'),
expect = chai.expect; expect = chai.expect;
chai.Assertion.includeStack = true;
describe('UpdateHandler', function() { describe('UpdateHandler', function() {
var updateHandler, appConfigStorageStub, userStorageStub, origDbVersion; var updateHandler, appConfigStorageStub, userStorageStub, origDbVersion;
chai.Assertion.includeStack = true;
beforeEach(function() { beforeEach(function() {
origDbVersion = cfg.dbVersion; origDbVersion = cfg.dbVersion;
appConfigStorageStub = sinon.createStubInstance(DeviceStorageDAO); appConfigStorageStub = sinon.createStubInstance(DeviceStorageDAO);
@ -160,6 +160,59 @@ define(function(require) {
}); });
}); });
}); });
describe('v1 -> v2', function() {
var emailDbType = 'email_';
beforeEach(function() {
cfg.dbVersion = 2; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [1]); // database version is 0
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([2], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync({});
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync({});
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
}); });
}); });
}); });