mirror of
https://github.com/moparisthebest/mail
synced 2024-11-22 08:52:15 -05:00
Merge branch 'dev/attachments' into dev/attachments-ui
This commit is contained in:
commit
d1cfdbd321
@ -137,13 +137,12 @@ module.exports = function(grunt) {
|
||||
'imap-client/node_modules/mimelib/node_modules/addressparser/src/addressparser.js',
|
||||
'imap-client/node_modules/mimelib/node_modules/encoding/src/encoding.js',
|
||||
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/src/*.js',
|
||||
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/mime/src/*.js',
|
||||
'imap-client/node_modules/mailparser/src/*.js',
|
||||
'imap-client/node_modules/mailparser/node_modules/mime/src/mime.js',
|
||||
'imap-client/node_modules/mime/src/mime.js',
|
||||
'smtp-client/src/*.js',
|
||||
'smtp-client/node_modules/mailcomposer/src/*',
|
||||
'smtp-client/node_modules/nodemailer/src/*',
|
||||
'smtp-client/node_modules/nodemailer/node_modules/simplesmtp/src/*',
|
||||
'smtp-client/node_modules/nodemailer/node_modules/simplesmtp/src/*'
|
||||
],
|
||||
dest: 'src/lib/'
|
||||
},
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master",
|
||||
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/master",
|
||||
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/dev/attachments",
|
||||
"smtp-client": "https://github.com/whiteout-io/smtp-client/tarball/master",
|
||||
"requirejs": "2.1.10"
|
||||
},
|
||||
|
@ -277,7 +277,14 @@ define(function(require) {
|
||||
this.to = [{
|
||||
address: 'max.musterman@gmail.com'
|
||||
}]; // list of receivers
|
||||
this.attachments = (attachments) ? [true] : undefined;
|
||||
if (attachments) {
|
||||
// body structure with three attachments
|
||||
this.bodystructure = {"1": {"part": "1","type": "text/plain","parameters": {"charset": "us-ascii"},"encoding": "7bit","size": 9,"lines": 2},"2": {"part": "2","type": "application/octet-stream","parameters": {"name": "a.md"},"encoding": "7bit","size": 123,"disposition": [{"type": "attachment","filename": "a.md"}]},"3": {"part": "3","type": "application/octet-stream","parameters": {"name": "b.md"},"encoding": "7bit","size": 456,"disposition": [{"type": "attachment","filename": "b.md"}]},"4": {"part": "4","type": "application/octet-stream","parameters": {"name": "c.md"},"encoding": "7bit","size": 789,"disposition": [{"type": "attachment","filename": "c.md"}]},"type": "multipart/mixed"};
|
||||
this.attachments = [{"filename": "a.md","filesize": 123,"mimeType": "text/x-markdown","part": "2","content": null}, {"filename": "b.md","filesize": 456,"mimeType": "text/x-markdown","part": "3","content": null}, {"filename": "c.md","filesize": 789,"mimeType": "text/x-markdown","part": "4","content": null}];
|
||||
} else {
|
||||
this.bodystructure = {"part": "1","type": "text/plain","parameters": {"charset": "us-ascii"},"encoding": "7bit","size": 9,"lines": 2};
|
||||
this.attachments = [];
|
||||
}
|
||||
this.unread = unread;
|
||||
this.answered = answered;
|
||||
this.html = html;
|
||||
@ -286,7 +293,7 @@ define(function(require) {
|
||||
this.body = 'Here are a few pointers to help you get started with Whiteout Mail.\n\n# Write encrypted message\n- You can compose a message by clicking on the compose button on the upper right (keyboard shortcut is "n" for a new message or "r" to reply).\n- When typing the recipient\'s email address, secure recipients are marked with a blue label and insecure recipients are red.\n- When sending an email to insecure recipients, the default behavior for Whiteout Mail is to invite them to the service and only send the message content in an encrypted form, once they have joined.\n\n# Advanced features\n- To verify a recipient\'s PGP key, you can hover over the blue label containing their email address and their key fingerprint will be displayed.\n- To view your own key fingerprint, open the account view in the navigation bar on the left. You can compare these with your correspondants over a second channel such as a phonecall.\n\nWe hope this helped you to get started with Whiteout Mail.\n\nYour Whiteout Networks team'; // plaintext body
|
||||
};
|
||||
|
||||
var dummys = [new Email(true, true), new Email(true, false, true, true), new Email(false, true, true), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false)];
|
||||
var dummys = [new Email(true, true), new Email(true, false, true, true), new Email(false, true, true), new Email(false, true), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false)];
|
||||
|
||||
return dummys;
|
||||
}
|
||||
|
@ -2,14 +2,17 @@ define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var appController = require('js/app-controller'),
|
||||
download = require('js/util/download'),
|
||||
angular = require('angular'),
|
||||
crypto, keychain;
|
||||
emailDao, crypto, keychain;
|
||||
|
||||
//
|
||||
// Controller
|
||||
//
|
||||
|
||||
var ReadCtrl = function($scope) {
|
||||
|
||||
emailDao = appController._emailDao;
|
||||
crypto = appController._crypto;
|
||||
keychain = appController._keychain;
|
||||
|
||||
@ -74,6 +77,38 @@ define(function(require) {
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.download = function(attachment) {
|
||||
// download file to disk if content is available
|
||||
if (attachment.content) {
|
||||
saveToDisk(attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
var folder = $scope.state.nav.currentFolder;
|
||||
var email = $scope.state.mailList.selected;
|
||||
|
||||
emailDao.getAttachment({
|
||||
path: folder.path,
|
||||
uid: email.uid,
|
||||
attachment: attachment
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
saveToDisk(attachment);
|
||||
});
|
||||
|
||||
function saveToDisk(attachment) {
|
||||
download.createDownload({
|
||||
content: attachment.content,
|
||||
filename: attachment.filename,
|
||||
contentType: attachment.mimeType
|
||||
}, $scope.onError);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -805,7 +805,8 @@ define(function(require) {
|
||||
|
||||
if (!senderPubkey) {
|
||||
// this should only happen if a mail from another channel is in the inbox
|
||||
setBodyAndContinue('Public key for sender not found!');
|
||||
email.body = 'Public key for sender not found!';
|
||||
localCallback(null, email);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -817,7 +818,23 @@ define(function(require) {
|
||||
|
||||
// set encrypted flag
|
||||
email.encrypted = true;
|
||||
setBodyAndContinue(decrypted);
|
||||
|
||||
// does our message block even need to be parsed?
|
||||
// this is a very primitive detection if we have a mime node or plain text
|
||||
// taking this out breaks compatibility to clients < 0.5
|
||||
if (decrypted.indexOf('Content-Transfer-Encoding:') === -1 &&
|
||||
decrypted.indexOf('Content-Type:') === -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -828,11 +845,6 @@ define(function(require) {
|
||||
// parse email body for encrypted message block
|
||||
email.body = email.body.substring(start, end);
|
||||
}
|
||||
|
||||
function setBodyAndContinue(text) {
|
||||
email.body = text;
|
||||
localCallback(null, email);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -869,6 +881,18 @@ define(function(require) {
|
||||
}, callback);
|
||||
};
|
||||
|
||||
EmailDAO.prototype.getAttachment = function(options, callback) {
|
||||
if (!this._account.online) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.getAttachment(options, callback);
|
||||
};
|
||||
|
||||
EmailDAO.prototype.sendEncrypted = function(options, callback) {
|
||||
var self = this,
|
||||
email = options.email;
|
||||
@ -1068,6 +1092,10 @@ define(function(require) {
|
||||
}, callback);
|
||||
};
|
||||
|
||||
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
|
||||
this._imapClient.parseDecryptedMessageBlock(options, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an email messsage including the email body from imap
|
||||
* @param {String} options.messageId The
|
||||
|
@ -21,7 +21,19 @@
|
||||
</div>
|
||||
</div><!--/.headers-->
|
||||
|
||||
<div class="seperator-line"></div>
|
||||
<div ng-switch="state.mailList.selected.attachments !== undefined && state.mailList.selected.attachments.length > 0">
|
||||
<div ng-switch-when="true">
|
||||
<div class="attachments">
|
||||
<span ng-repeat="attachment in state.mailList.selected.attachments">
|
||||
<button ng-click="download(attachment)">{{attachment.filename}}</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-default>
|
||||
<div class="seperator-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="body">
|
||||
<!-- Render lines of a text-email in divs for easier styling -->
|
||||
|
@ -14,7 +14,7 @@ define(function(require) {
|
||||
var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub;
|
||||
|
||||
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
|
||||
dummyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
|
||||
dummyDecryptedMail, dummyLegacyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
|
||||
corruptedVerificationMail, corruptedVerificationUuid,
|
||||
nonWhitelistedMail;
|
||||
|
||||
@ -65,6 +65,20 @@ define(function(require) {
|
||||
answered: false
|
||||
};
|
||||
dummyDecryptedMail = {
|
||||
uid: 1234,
|
||||
from: [{
|
||||
address: 'asd@asd.de'
|
||||
}],
|
||||
to: [{
|
||||
address: 'qwe@qwe.de'
|
||||
}],
|
||||
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',
|
||||
unread: false,
|
||||
answered: false,
|
||||
receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----']
|
||||
};
|
||||
dummyLegacyDecryptedMail = {
|
||||
uid: 1234,
|
||||
from: [{
|
||||
address: 'asd@asd.de'
|
||||
@ -471,6 +485,19 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_imapParseMessageBlock', function() {
|
||||
it('should parse a message', function(done) {
|
||||
imapClientStub.parseDecryptedMessageBlock.yields(null, {});
|
||||
|
||||
dao._imapParseMessageBlock(function(err, msg) {
|
||||
expect(err).to.not.exist;
|
||||
expect(msg).to.exist;
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('_imapLogin', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
@ -823,7 +850,7 @@ define(function(require) {
|
||||
|
||||
describe('sync', function() {
|
||||
it('should work initially', function(done) {
|
||||
var folder, localListStub, invocations, imapSearchStub;
|
||||
var folder, localListStub, invocations, imapSearchStub, imapParseStub;
|
||||
|
||||
invocations = 0;
|
||||
folder = 'FOLDAAAA';
|
||||
@ -852,6 +879,13 @@ define(function(require) {
|
||||
answered: true
|
||||
}).yields(null, []);
|
||||
|
||||
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||
imapParseStub.withArgs({
|
||||
message: dummyEncryptedMail,
|
||||
block: dummyDecryptedMail.body
|
||||
}).yields(null, dummyDecryptedMail);
|
||||
|
||||
|
||||
dao.sync({
|
||||
folder: folder
|
||||
}, function(err) {
|
||||
@ -870,6 +904,7 @@ define(function(require) {
|
||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
||||
expect(imapSearchStub.calledThrice).to.be.true;
|
||||
expect(dao._account.folders[0].count).to.equal(1);
|
||||
expect(imapParseStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
@ -903,7 +938,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should initially sync downstream when storage is empty', function(done) {
|
||||
var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub;
|
||||
var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub, imapParseStub;
|
||||
|
||||
invocations = 0;
|
||||
folder = 'FOLDAAAA';
|
||||
@ -912,8 +947,8 @@ define(function(require) {
|
||||
path: folder
|
||||
}];
|
||||
|
||||
dummyEncryptedMail.unread = true;
|
||||
dummyEncryptedMail.answered = true;
|
||||
dummyDecryptedMail.unread = true;
|
||||
dummyDecryptedMail.answered = true;
|
||||
|
||||
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
|
||||
folder: folder
|
||||
@ -938,6 +973,9 @@ define(function(require) {
|
||||
answered: true
|
||||
}).yields(null, [dummyEncryptedMail.uid]);
|
||||
|
||||
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||
imapParseStub.yields(null, dummyDecryptedMail);
|
||||
|
||||
localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
|
||||
|
||||
dao.sync({
|
||||
@ -960,6 +998,7 @@ define(function(require) {
|
||||
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
|
||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
||||
expect(dao._account.folders[0].count).to.equal(1);
|
||||
expect(imapParseStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
@ -1162,7 +1201,7 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should error whilte removing messages from local', function(done) {
|
||||
it('should error while removing messages from local', function(done) {
|
||||
var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub;
|
||||
|
||||
invocations = 0;
|
||||
@ -1316,7 +1355,7 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch messages downstream from the remote', function(done) {
|
||||
it('should fetch legacy messages downstream from the remote', function(done) {
|
||||
var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub;
|
||||
|
||||
invocations = 0;
|
||||
@ -1348,10 +1387,75 @@ define(function(require) {
|
||||
uid: dummyEncryptedMail.uid
|
||||
}).yields(null, dummyEncryptedMail);
|
||||
|
||||
localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
|
||||
keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair);
|
||||
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyLegacyDecryptedMail.body);
|
||||
|
||||
|
||||
dao.sync({
|
||||
folder: folder
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
if (invocations === 0) {
|
||||
expect(dao._account.busy).to.be.true;
|
||||
invocations++;
|
||||
return;
|
||||
}
|
||||
|
||||
expect(dao._account.busy).to.be.false;
|
||||
expect(dao._account.folders[0].messages).to.not.be.empty;
|
||||
expect(localListStub.calledOnce).to.be.true;
|
||||
expect(imapSearchStub.calledThrice).to.be.true;
|
||||
expect(imapGetStub.calledOnce).to.be.true;
|
||||
expect(localStoreStub.calledOnce).to.be.true;
|
||||
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
|
||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch valid pgp messages downstream from the remote', function(done) {
|
||||
var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub, imapParseStub;
|
||||
|
||||
invocations = 0;
|
||||
folder = 'FOLDAAAA';
|
||||
dao._account.folders = [{
|
||||
type: 'Folder',
|
||||
path: folder,
|
||||
messages: []
|
||||
}];
|
||||
|
||||
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
|
||||
folder: folder
|
||||
}).yields(null, []);
|
||||
imapSearchStub = sinon.stub(dao, '_imapSearch');
|
||||
imapSearchStub.withArgs({
|
||||
folder: folder
|
||||
}).yields(null, [dummyEncryptedMail.uid]);
|
||||
imapSearchStub.withArgs({
|
||||
folder: folder,
|
||||
unread: true
|
||||
}).yields(null, []);
|
||||
imapSearchStub.withArgs({
|
||||
folder: folder,
|
||||
answered: true
|
||||
}).yields(null, []);
|
||||
|
||||
imapGetStub = sinon.stub(dao, '_imapGetMessage').withArgs({
|
||||
folder: folder,
|
||||
uid: dummyEncryptedMail.uid
|
||||
}).yields(null, dummyEncryptedMail);
|
||||
|
||||
localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
|
||||
keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair);
|
||||
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body);
|
||||
|
||||
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||
imapParseStub.withArgs({
|
||||
message: dummyEncryptedMail,
|
||||
block: dummyDecryptedMail.body
|
||||
}).yields(null, dummyDecryptedMail);
|
||||
|
||||
dao.sync({
|
||||
folder: folder
|
||||
@ -1372,6 +1476,7 @@ define(function(require) {
|
||||
expect(localStoreStub.calledOnce).to.be.true;
|
||||
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
|
||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
||||
expect(imapParseStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user