1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-22 17:02:17 -05:00

Merge branch 'dev/attachments' into dev/attachments-ui

This commit is contained in:
Tankred Hase 2014-02-03 19:27:38 +01:00
commit d1cfdbd321
7 changed files with 208 additions and 22 deletions

View File

@ -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/addressparser/src/addressparser.js',
'imap-client/node_modules/mimelib/node_modules/encoding/src/encoding.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/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/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/src/*.js',
'smtp-client/node_modules/mailcomposer/src/*', 'smtp-client/node_modules/mailcomposer/src/*',
'smtp-client/node_modules/nodemailer/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/' dest: 'src/lib/'
}, },

View File

@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master", "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", "smtp-client": "https://github.com/whiteout-io/smtp-client/tarball/master",
"requirejs": "2.1.10" "requirejs": "2.1.10"
}, },

View File

@ -277,7 +277,14 @@ define(function(require) {
this.to = [{ this.to = [{
address: 'max.musterman@gmail.com' address: 'max.musterman@gmail.com'
}]; // list of receivers }]; // 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.unread = unread;
this.answered = answered; this.answered = answered;
this.html = html; 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 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; return dummys;
} }

View File

@ -2,14 +2,17 @@ define(function(require) {
'use strict'; 'use strict';
var appController = require('js/app-controller'), var appController = require('js/app-controller'),
download = require('js/util/download'),
angular = require('angular'), angular = require('angular'),
crypto, keychain; emailDao, crypto, keychain;
// //
// Controller // Controller
// //
var ReadCtrl = function($scope) { var ReadCtrl = function($scope) {
emailDao = appController._emailDao;
crypto = appController._crypto; crypto = appController._crypto;
keychain = appController._keychain; keychain = appController._keychain;
@ -74,6 +77,38 @@ define(function(require) {
$scope.$apply(); $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);
}
};
}; };
// //

View File

@ -805,7 +805,8 @@ define(function(require) {
if (!senderPubkey) { if (!senderPubkey) {
// 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
setBodyAndContinue('Public key for sender not found!'); email.body = 'Public key for sender not found!';
localCallback(null, email);
return; return;
} }
@ -817,7 +818,23 @@ define(function(require) {
// set encrypted flag // set encrypted flag
email.encrypted = true; 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 // parse email body for encrypted message block
email.body = email.body.substring(start, end); email.body = email.body.substring(start, end);
} }
function setBodyAndContinue(text) {
email.body = text;
localCallback(null, email);
}
} }
}; };
@ -869,6 +881,18 @@ define(function(require) {
}, callback); }, 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) { EmailDAO.prototype.sendEncrypted = function(options, callback) {
var self = this, var self = this,
email = options.email; email = options.email;
@ -1068,6 +1092,10 @@ define(function(require) {
}, callback); }, callback);
}; };
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
this._imapClient.parseDecryptedMessageBlock(options, callback);
};
/** /**
* Get an email messsage including the email body from imap * Get an email messsage including the email body from imap
* @param {String} options.messageId The * @param {String} options.messageId The

View File

@ -21,7 +21,19 @@
</div> </div>
</div><!--/.headers--> </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"> <div class="body">
<!-- Render lines of a text-email in divs for easier styling --> <!-- Render lines of a text-email in divs for easier styling -->

View File

@ -14,7 +14,7 @@ define(function(require) {
var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub; var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub;
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail, var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
dummyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid, dummyDecryptedMail, dummyLegacyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
corruptedVerificationMail, corruptedVerificationUuid, corruptedVerificationMail, corruptedVerificationUuid,
nonWhitelistedMail; nonWhitelistedMail;
@ -65,6 +65,20 @@ define(function(require) {
answered: false answered: false
}; };
dummyDecryptedMail = { 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, uid: 1234,
from: [{ from: [{
address: 'asd@asd.de' 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() { 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) {
@ -823,7 +850,7 @@ define(function(require) {
describe('sync', function() { describe('sync', function() {
it('should work initially', function(done) { it('should work initially', function(done) {
var folder, localListStub, invocations, imapSearchStub; var folder, localListStub, invocations, imapSearchStub, imapParseStub;
invocations = 0; invocations = 0;
folder = 'FOLDAAAA'; folder = 'FOLDAAAA';
@ -852,6 +879,13 @@ define(function(require) {
answered: true answered: true
}).yields(null, []); }).yields(null, []);
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
imapParseStub.withArgs({
message: dummyEncryptedMail,
block: dummyDecryptedMail.body
}).yields(null, dummyDecryptedMail);
dao.sync({ dao.sync({
folder: folder folder: folder
}, function(err) { }, function(err) {
@ -870,6 +904,7 @@ define(function(require) {
expect(pgpStub.decrypt.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(imapSearchStub.calledThrice).to.be.true; expect(imapSearchStub.calledThrice).to.be.true;
expect(dao._account.folders[0].count).to.equal(1); expect(dao._account.folders[0].count).to.equal(1);
expect(imapParseStub.calledOnce).to.be.true;
done(); done();
}); });
@ -903,7 +938,7 @@ define(function(require) {
}); });
it('should initially sync downstream when storage is empty', function(done) { 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; invocations = 0;
folder = 'FOLDAAAA'; folder = 'FOLDAAAA';
@ -912,8 +947,8 @@ define(function(require) {
path: folder path: folder
}]; }];
dummyEncryptedMail.unread = true; dummyDecryptedMail.unread = true;
dummyEncryptedMail.answered = true; dummyDecryptedMail.answered = true;
localListStub = sinon.stub(dao, '_localListMessages').withArgs({ localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder folder: folder
@ -938,6 +973,9 @@ define(function(require) {
answered: true answered: true
}).yields(null, [dummyEncryptedMail.uid]); }).yields(null, [dummyEncryptedMail.uid]);
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
imapParseStub.yields(null, dummyDecryptedMail);
localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
dao.sync({ dao.sync({
@ -960,6 +998,7 @@ define(function(require) {
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(dao._account.folders[0].count).to.equal(1); expect(dao._account.folders[0].count).to.equal(1);
expect(imapParseStub.calledOnce).to.be.true;
done(); 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; var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub;
invocations = 0; 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; var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub;
invocations = 0; invocations = 0;
@ -1348,10 +1387,75 @@ define(function(require) {
uid: dummyEncryptedMail.uid uid: dummyEncryptedMail.uid
}).yields(null, dummyEncryptedMail); }).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(); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair);
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); 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({ dao.sync({
folder: folder folder: folder
@ -1372,6 +1476,7 @@ define(function(require) {
expect(localStoreStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true;
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(imapParseStub.calledOnce).to.be.true;
done(); done();
}); });
}); });