1
0
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:
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/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/'
},

View File

@ -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"
},

View File

@ -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;
}

View File

@ -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);
}
};
};
//

View File

@ -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

View File

@ -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 -->

View File

@ -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();
});
});