diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index 887240ba9..7dd46a727 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -109,6 +109,7 @@ 'Settings': 'settings', 'Menu': 'menu', 'PopupComposeOpenPGP': 'compose-open-pgp', + 'PopupMessageOpenPGP': 'message-open-pgp', 'PopupKeyboardShortcutsHelp': 'popup-keyboard-shortcuts-help', 'PopupAsk': 'popup-ask' }; diff --git a/dev/Common/Links.js b/dev/Common/Links.js index 450c9924d..7b569bddc 100644 --- a/dev/Common/Links.js +++ b/dev/Common/Links.js @@ -390,7 +390,7 @@ */ Links.prototype.openPgpJs = function () { - return this.sStaticPrefix + 'js/min/openpgp.js'; + return this.sStaticPrefix + 'js/min/openpgp.js'; }; /** diff --git a/dev/External/ko.js b/dev/External/ko.js index 9fe17256c..187d0bfae 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -7,6 +7,7 @@ window = require('window'), _ = require('_'), $ = require('$'), + JSON = require('JSON'), Opentip = require('Opentip'), fDisposalTooltipHelper = function (oElement) { @@ -63,6 +64,15 @@ } }; + ko.bindingHandlers.json = { + 'init': function (oElement, fValueAccessor) { + $(oElement).text(JSON.stringify(ko.unwrap(fValueAccessor()))); + }, + 'update': function (oElement, fValueAccessor) { + $(oElement).text(JSON.stringify(ko.unwrap(fValueAccessor()))); + } + }; + ko.bindingHandlers.tooltip = { 'init': function (oElement, fValueAccessor) { diff --git a/dev/Model/Message.js b/dev/Model/Message.js index f05d1c3e5..87fc7036c 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -14,8 +14,6 @@ Globals = require('Common/Globals'), Links = require('Common/Links'), - PgpStore = require('Stores/User/Pgp'), - AttachmentModel = require('Model/Attachment'), MessageHelper = require('Helper/Message'), @@ -82,7 +80,6 @@ }, this); this.body = null; - this.plainRaw = ''; this.isHtml = ko.observable(false); this.hasImages = ko.observable(false); @@ -984,25 +981,6 @@ { this.body.data('rl-is-html', !!this.isHtml()); this.body.data('rl-has-images', !!this.hasImages()); - - this.body.data('rl-plain-raw', this.plainRaw); - - if (require('Stores/User/Pgp').capaOpenPGP()) - { - this.body.data('rl-plain-pgp-signed', !!this.isPgpSigned()); - this.body.data('rl-plain-pgp-encrypted', !!this.isPgpEncrypted()); - this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus()); - this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser()); - } - } - }; - - MessageModel.prototype.storePgpVerifyDataInDom = function () - { - if (this.body && require('Stores/User/Pgp').capaOpenPGP()) - { - this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus()); - this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser()); } }; @@ -1012,157 +990,6 @@ { this.isHtml(!!this.body.data('rl-is-html')); this.hasImages(!!this.body.data('rl-has-images')); - - this.plainRaw = Utils.pString(this.body.data('rl-plain-raw')); - - if (require('Stores/User/Pgp').capaOpenPGP()) - { - this.isPgpSigned(!!this.body.data('rl-plain-pgp-signed')); - this.isPgpEncrypted(!!this.body.data('rl-plain-pgp-encrypted')); - this.pgpSignedVerifyStatus(this.body.data('rl-pgp-verify-status')); - this.pgpSignedVerifyUser(this.body.data('rl-pgp-verify-user')); - } - else - { - this.isPgpSigned(false); - this.isPgpEncrypted(false); - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None); - this.pgpSignedVerifyUser(''); - } - } - }; - - MessageModel.prototype.verifyPgpSignedClearMessage = function () - { - if (this.isPgpSigned()) - { - var - aRes = [], - mPgpMessage = null, - sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '', - aPublicKeys = PgpStore.findPublicKeysByEmail(sFrom), - oValidKey = null, - oValidSysKey = null, - sPlain = '' - ; - - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error); - this.pgpSignedVerifyUser(''); - - try - { - mPgpMessage = PgpStore.openpgp.cleartext.readArmored(this.plainRaw); - if (mPgpMessage && mPgpMessage.getText) - { - this.pgpSignedVerifyStatus( - aPublicKeys.length ? Enums.SignedVerifyStatus.Unverified : Enums.SignedVerifyStatus.UnknownPublicKeys); - - aRes = mPgpMessage.verify(aPublicKeys); - if (aRes && 0 < aRes.length) - { - oValidKey = _.find(aRes, function (oItem) { - return oItem && oItem.keyid && oItem.valid; - }); - - if (oValidKey) - { - oValidSysKey = PgpStore.findPublicKeyByHex(oValidKey.keyid.toHex()); - if (oValidSysKey) - { - sPlain = mPgpMessage.getText(); - - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success); - this.pgpSignedVerifyUser(oValidSysKey.user); - - sPlain = - Globals.$div.empty().append( - $('
').text(sPlain)
-									).html()
-									;
-
-								Globals.$div.empty();
-
-								this.replacePlaneTextBody(sPlain);
-							}
-						}
-					}
-				}
-			}
-			catch (oExc) {}
-
-			this.storePgpVerifyDataInDom();
-		}
-	};
-
-	MessageModel.prototype.decryptPgpEncryptedMessage = function (sPassword)
-	{
-		if (this.isPgpEncrypted())
-		{
-			var
-				aRes = [],
-				mPgpMessage = null,
-				mPgpMessageDecrypted = null,
-				sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '',
-				aPublicKey = PgpStore.findPublicKeysByEmail(sFrom),
-				oPrivateKey = PgpStore.findSelfPrivateKey(sPassword),
-				oValidKey = null,
-				oValidSysKey = null,
-				sPlain = ''
-			;
-
-			this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error);
-			this.pgpSignedVerifyUser('');
-
-			if (!oPrivateKey)
-			{
-				this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.UnknownPrivateKey);
-			}
-
-			try
-			{
-				mPgpMessage = PgpStore.openpgp.message.readArmored(this.plainRaw);
-				if (mPgpMessage && oPrivateKey && mPgpMessage.decrypt)
-				{
-					this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Unverified);
-
-					mPgpMessageDecrypted = mPgpMessage.decrypt(oPrivateKey);
-					if (mPgpMessageDecrypted)
-					{
-						aRes = mPgpMessageDecrypted.verify(aPublicKey);
-						if (aRes && 0 < aRes.length)
-						{
-							oValidKey = _.find(aRes, function (oItem) {
-								return oItem && oItem.keyid && oItem.valid;
-							});
-
-							if (oValidKey)
-							{
-								oValidSysKey = PgpStore.findPublicKeyByHex(oValidKey.keyid.toHex());
-								if (oValidSysKey)
-								{
-									this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success);
-									this.pgpSignedVerifyUser(oValidSysKey.user);
-								}
-							}
-						}
-
-						sPlain = mPgpMessageDecrypted.getText();
-
-						sPlain =
-							Globals.$div.empty().append(
-							$('
').text(sPlain)
-							).html()
-							;
-
-						Globals.$div.empty();
-
-						this.replacePlaneTextBody(sPlain);
-					}
-				}
-			}
-			catch (oExc) {}
-
-			this.storePgpVerifyDataInDom();
 		}
 	};
 
diff --git a/dev/Model/OpenPgpKey.js b/dev/Model/OpenPgpKey.js
index 22c46bbbe..896bd056b 100644
--- a/dev/Model/OpenPgpKey.js
+++ b/dev/Model/OpenPgpKey.js
@@ -7,6 +7,8 @@
 		_ = require('_'),
 		ko = require('ko'),
 
+		PgpStore = require('Stores/User/Pgp'),
+
 		AbstractModel = require('Knoin/AbstractModel')
 	;
 
@@ -45,6 +47,22 @@
 	OpenPgpKeyModel.prototype.armor = '';
 	OpenPgpKeyModel.prototype.isPrivate = false;
 
+	OpenPgpKeyModel.prototype.getNativeKeys = function ()
+	{
+		var oKey = null;
+		try
+		{
+			oKey = PgpStore.openpgp.key.readArmored(this.armor);
+			if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
+			{
+				return oKey.keys;
+			}
+		}
+		catch (e) {}
+
+		return null;
+	};
+
 	module.exports = OpenPgpKeyModel;
 
 }());
\ No newline at end of file
diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js
index 9e7a3eed3..df2be5ecf 100644
--- a/dev/Stores/User/Message.js
+++ b/dev/Stores/User/Message.js
@@ -22,6 +22,7 @@
 
 		AppStore = require('Stores/User/App'),
 		FolderStore = require('Stores/User/Folder'),
+		PgpStore = require('Stores/User/Pgp'),
 		SettingsStore = require('Stores/User/Settings'),
 
 		Remote = require('Remote/User/Ajax'),
@@ -502,6 +503,19 @@
 		}
 	};
 
+	/**
+	 * @param {Object} oMessageTextBody
+	 */
+	MessageUserStore.prototype.initOpenPgpControls = function (oMessageTextBody)
+	{
+		if (oMessageTextBody && oMessageTextBody.find)
+		{
+			oMessageTextBody.find('.b-plain-openpgp:not(.inited)').each(function () {
+				PgpStore.initMessageBodyControls($(this));
+			});
+		}
+	};
+
 	MessageUserStore.prototype.setMessage = function (oData, bCached)
 	{
 		var
@@ -512,6 +526,7 @@
 			oBody = null,
 			oTextBody = null,
 			sId = '',
+			sPlain = '',
 			sResultHtml = '',
 			bPgpSigned = false,
 			bPgpEncrypted = false,
@@ -579,13 +594,13 @@
 
 							if ((oMessage.isPgpSigned() || oMessage.isPgpEncrypted()) && require('Stores/User/Pgp').capaOpenPGP())
 							{
-								oMessage.plainRaw = Utils.pString(oData.Result.Plain);
+								sPlain = Utils.pString(oData.Result.Plain);
 
-								bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw);
+								bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(sPlain);
 								if (!bPgpEncrypted)
 								{
-									bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) &&
-										/-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw);
+									bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(sPlain) &&
+										/-----BEGIN PGP SIGNATURE-----/.test(sPlain);
 								}
 
 								Globals.$div.empty();
@@ -593,7 +608,7 @@
 								{
 									sResultHtml =
 										Globals.$div.append(
-											$('
').text(oMessage.plainRaw)
+											$('
').text(sPlain)
 										).html()
 									;
 								}
@@ -601,11 +616,13 @@
 								{
 									sResultHtml =
 										Globals.$div.append(
-											$('
').text(oMessage.plainRaw)
+											$('
').text(sPlain)
 										).html()
 									;
 								}
 
+								sPlain = '';
+
 								Globals.$div.empty();
 
 								oMessage.isPgpSigned(bPgpSigned);
@@ -624,8 +641,6 @@
 
 						oMessage.isHtml(!!bIsHtml);
 						oMessage.hasImages(!!bHasExternals);
-						oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
-						oMessage.pgpSignedVerifyUser('');
 
 						oMessage.body = oBody;
 						if (oMessage.body)
@@ -663,6 +678,8 @@
 
 					if (oBody)
 					{
+						this.initOpenPgpControls(oBody);
+
 						this.initBlockquoteSwitcher(oBody);
 					}
 
diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js
index a6e62e6e0..44815d225 100644
--- a/dev/Stores/User/Pgp.js
+++ b/dev/Stores/User/Pgp.js
@@ -5,6 +5,10 @@
 	var
 		_ = require('_'),
 		ko = require('ko'),
+		$ = require('$'),
+		kn = require('Knoin/Knoin'),
+
+		Translator = require('Common/Translator'),
 
 		Utils = require('Common/Utils')
 	;
@@ -41,32 +45,41 @@
 	PgpUserStore.prototype.findPublicKeyByHex = function (sHash)
 	{
 		return _.find(this.openpgpkeysPublic(), function (oItem) {
-			return oItem && sHash === oItem.id;
+			return sHash && oItem && sHash === oItem.id;
+		});
+	};
+
+	PgpUserStore.prototype.findPrivateKeyByHex = function (sHash)
+	{
+		return _.find(this.openpgpkeysPrivate(), function (oItem) {
+			return sHash && oItem && sHash === oItem.id;
 		});
 	};
 
 	PgpUserStore.prototype.findPublicKeysByEmail = function (sEmail)
+	{
+		return _.compact(_.flatten(_.map(this.openpgpkeysPublic(), function (oItem) {
+			var oKey = oItem && sEmail === oItem.email ? oItem : null;
+			return oKey ? oKey.getNativeKeys() : [null];
+		}), true));
+	};
+
+	PgpUserStore.prototype.findPublicKeysBySigningKeyIds = function (aSigningKeyIds)
 	{
 		var self = this;
-		return _.compact(_.map(this.openpgpkeysPublic(), function (oItem) {
+		return _.compact(_.flatten(_.map(aSigningKeyIds, function (oId) {
+			var oKey = oId && oId.toHex ? self.findPublicKeyByHex(oId.toHex()) : null;
+			return oKey ? oKey.getNativeKeys() : [null];
+		}), true));
+	};
 
-			var oKey = null;
-			if (oItem && sEmail === oItem.email)
-			{
-				try
-				{
-					oKey = self.openpgp.key.readArmored(oItem.armor);
-					if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
-					{
-						return oKey.keys[0];
-					}
-				}
-				catch (e) {}
-			}
-
-			return null;
-
-		}));
+	PgpUserStore.prototype.findPrivateKeysByEncryptionKeyIds = function (aEncryptionKeyIds, bReturnWrapKeys)
+	{
+		var self = this;
+		return _.compact(_.flatten(_.map(aEncryptionKeyIds, function (oId) {
+			var oKey = oId && oId.toHex ? self.findPrivateKeyByHex(oId.toHex()) : null;
+			return oKey ? (bReturnWrapKeys ? [oKey] : oKey.getNativeKeys()) : [null];
+		}), true));
 	};
 
 	/**
@@ -77,6 +90,7 @@
 	PgpUserStore.prototype.findPrivateKeyByEmail = function (sEmail, sPassword)
 	{
 		var
+			oPrivateKeys = [],
 			oPrivateKey = null,
 			oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
 				return oItem && sEmail === oItem.email;
@@ -85,18 +99,15 @@
 
 		if (oKey)
 		{
+			oPrivateKeys = oKey.getNativeKeys();
+			oPrivateKey = oPrivateKeys[0] || null;
+
 			try
 			{
-				oPrivateKey = this.openpgp.key.readArmored(oKey.armor);
-				if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
+				if (oPrivateKey)
 				{
-					oPrivateKey = oPrivateKey.keys[0];
 					oPrivateKey.decrypt(Utils.pString(sPassword));
 				}
-				else
-				{
-					oPrivateKey = null;
-				}
 			}
 			catch (e)
 			{
@@ -116,6 +127,259 @@
 		return this.findPrivateKeyByEmail(require('Stores/User/Account').email(), sPassword);
 	};
 
+	PgpUserStore.prototype.decryptMessage = function (oMessage, fCallback)
+	{
+		var self = this, aPrivateKeys = [], aEncryptionKeyIds = [];
+		if (oMessage && oMessage.getSigningKeyIds)
+		{
+			aEncryptionKeyIds = oMessage.getEncryptionKeyIds();
+			if (aEncryptionKeyIds)
+			{
+				aPrivateKeys = this.findPrivateKeysByEncryptionKeyIds(aEncryptionKeyIds, true);
+				if (aPrivateKeys && 0 < aPrivateKeys.length)
+				{
+					kn.showScreenPopup(require('View/Popup/MessageOpenPgp'), [function (oDecriptedKey) {
+
+						if (oDecriptedKey)
+						{
+							var oPrivateKey = null, oDecryptedMessage = null;
+							try
+							{
+								oDecryptedMessage = oMessage.decrypt(oDecriptedKey);
+							}
+							catch (e)
+							{
+								oDecryptedMessage = null;
+							}
+
+							if (oDecryptedMessage)
+							{
+								oPrivateKey = self.findPrivateKeyByHex(oDecriptedKey.primaryKey.keyid.toHex());
+								if (oPrivateKey)
+								{
+									self.verifyMessage(oDecryptedMessage, function (oValidKey, aSigningKeyIds) {
+										fCallback(oPrivateKey, oDecryptedMessage, oValidKey || null, aSigningKeyIds || null);
+									});
+								}
+								else
+								{
+									fCallback(oPrivateKey, oDecryptedMessage);
+								}
+							}
+							else
+							{
+								fCallback(oPrivateKey, oDecryptedMessage);
+							}
+						}
+						else
+						{
+							fCallback(null, null);
+						}
+
+					}, aPrivateKeys]);
+
+					return false;
+				}
+			}
+		}
+
+		fCallback(null, null);
+
+		return false;
+	};
+
+	PgpUserStore.prototype.verifyMessage = function (oMessage, fCallback)
+	{
+		var oValid = null, aResult = [], aPublicKeys = [], aSigningKeyIds = [];
+		if (oMessage && oMessage.getSigningKeyIds)
+		{
+			aSigningKeyIds = oMessage.getSigningKeyIds();
+			if (aSigningKeyIds && 0 < aSigningKeyIds.length)
+			{
+				aPublicKeys = this.findPublicKeysBySigningKeyIds(aSigningKeyIds);
+				if (aPublicKeys && 0 < aPublicKeys.length)
+				{
+					try
+					{
+						aResult = oMessage.verify(aPublicKeys);
+						oValid = _.find(_.isArray(aResult) ? aResult : [], function (oItem) {
+							return oItem && oItem.valid && oItem.keyid;
+						});
+
+						if (oValid && oValid.keyid && oValid.keyid && oValid.keyid.toHex)
+						{
+							fCallback(this.findPublicKeyByHex(oValid.keyid.toHex()));
+							return true;
+						}
+					}
+					catch (e) {}
+				}
+
+				fCallback(null, aSigningKeyIds);
+				return false;
+			}
+		}
+
+		fCallback(null);
+		return false;
+	};
+
+	/**
+	 * @param {*} mDom
+	 */
+	PgpUserStore.prototype.controlsHelper = function (mDom, oVerControl, bSuccess, sTitle, sText)
+	{
+		if (bSuccess)
+		{
+			mDom.removeClass('error').addClass('success').attr('title', sTitle);
+			oVerControl.removeClass('error').addClass('success').attr('title', sTitle);
+		}
+		else
+		{
+			mDom.removeClass('success').addClass('error').attr('title', sTitle);
+			oVerControl.removeClass('success').addClass('error').attr('title', sTitle);
+		}
+
+		if (undefined !== sText)
+		{
+			mDom.text(Utils.trim(sText.replace(/(\u200C|\u0002)/g, '')));
+		}
+	};
+
+	/**
+	 * @param {*} mDom
+	 */
+	PgpUserStore.prototype.initMessageBodyControls = function (mDom)
+	{
+		if (mDom && !mDom.hasClass('inited'))
+		{
+			mDom.addClass('inited');
+
+			var
+				self = this,
+				bEncrypted = mDom.hasClass('encrypted'),
+				bSigned = mDom.hasClass('signed'),
+				oVerControl = null,
+				sData = ''
+			;
+
+			if (bEncrypted || bSigned)
+			{
+				sData = mDom.text();
+				mDom.data('openpgp-original', sData);
+
+				if (bEncrypted)
+				{
+					oVerControl = $('
') + .attr('title', Translator.i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC')); + + oVerControl.on('click', function () { + if ($(this).hasClass('success')) + { + return false; + } + + var oMessage = self.openpgp.message.readArmored(sData); + if (oMessage && oMessage.getText && oMessage.verify && oMessage.decrypt) + { + self.decryptMessage(oMessage, function (oValidPrivateKey, oDecriptedMessage, oValidPublicKey, aSigningKeyIds) { + + if (oDecriptedMessage) + { + if (oValidPublicKey) + { + self.controlsHelper(mDom, oVerControl, true, Translator.i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { + 'USER': oValidPublicKey.user + ' (' + oValidPublicKey.id + ')' + }), oDecriptedMessage.getText()); + } + else if (oValidPrivateKey) + { + var + aKeyIds = Utils.isNonEmptyArray(aSigningKeyIds) ? aSigningKeyIds : null, + sAdditional = aKeyIds ? _.compact(_.map(aKeyIds, function (oItem) { + return oItem && oItem.toHex ? oItem.toHex() : null; + })).join(', ') : '' + ; + + self.controlsHelper(mDom, oVerControl, false, + Translator.i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + + (sAdditional ? ' (' + sAdditional + ')' : ''), + oDecriptedMessage.getText()); + } + else + { + self.controlsHelper(mDom, oVerControl, false, + Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); + } + } + else + { + self.controlsHelper(mDom, oVerControl, false, + Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); + } + }); + + return false; + } + + self.controlsHelper(mDom, oVerControl, false, Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); + return false; + + }); + } + else if (bSigned) + { + oVerControl = $('
') + .attr('title', Translator.i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC')); + + oVerControl.on('click', function () { + + if ($(this).hasClass('success') || $(this).hasClass('error')) + { + return false; + } + + var oMessage = self.openpgp.cleartext.readArmored(sData); + if (oMessage && oMessage.getText && oMessage.verify) + { + self.verifyMessage(oMessage, function (oValidKey, aSigningKeyIds) { + if (oValidKey) + { + self.controlsHelper(mDom, oVerControl, true, Translator.i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { + 'USER': oValidKey.user + ' (' + oValidKey.id + ')' + }), oMessage.getText()); + } + else + { + var + aKeyIds = Utils.isNonEmptyArray(aSigningKeyIds) ? aSigningKeyIds : null, + sAdditional = aKeyIds ? _.compact(_.map(aKeyIds, function (oItem) { + return oItem && oItem.toHex ? oItem.toHex() : null; + })).join(', ') : '' + ; + + self.controlsHelper(mDom, oVerControl, false, + Translator.i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + + (sAdditional ? ' (' + sAdditional + ')' : '')); + } + }); + + return false; + } + + self.controlsHelper(mDom, oVerControl, false, Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); + return false; + }); + } + + if (oVerControl) + { + mDom.before(oVerControl).before('
'); + } + } + } + }; + module.exports = new PgpUserStore(); }()); diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less index 25f6dc3cc..a1440fee4 100644 --- a/dev/Styles/MessageView.less +++ b/dev/Styles/MessageView.less @@ -407,6 +407,16 @@ html.rl-no-preview-pane { display: inline-block; padding: 6px 10px; border: 1px dashed #666; + background: #fff; + + &.success { + border-color: green; + background-color: rgba(0, 255, 0, 0.03); + } + &.error { + border-color: red; + background-color: rgba(255, 0, 0, 0.03); + } } blockquote { @@ -424,6 +434,27 @@ html.rl-no-preview-pane { color: red; } } + + .b-openpgp-control { + + display: inline-block; + cursor: pointer; + color: #777; + /*float: left;*/ + + &:hover { + color: #111; + } + + &.success { + color: green; + cursor: help; + } + + &.error { + color: red; + } + } } } } diff --git a/dev/Styles/OpenPgpKey.less b/dev/Styles/OpenPgpKey.less index 611c5e5c3..8b2d753da 100644 --- a/dev/Styles/OpenPgpKey.less +++ b/dev/Styles/OpenPgpKey.less @@ -1,5 +1,5 @@ .popups { - .b-open-pgp-key-view-content, .b-open-pgp-key-generate-content, .b-open-pgp-key-add-content, .b-compose-open-pgp-content { + .b-open-pgp-key-view-content, .b-open-pgp-key-generate-content, .b-open-pgp-key-add-content, .b-compose-open-pgp-content, .b-message-open-pgp-content { .modal-header { background-color: #fff; @@ -18,4 +18,35 @@ overflow: auto; } } + + .b-message-open-pgp-content { + &.modal { + width: 700px; + } + + .key-list { + + margin-top: 5px; + overflow: hidden; + + &__item { + + color: #555; + cursor: pointer; + text-overflow: ellipsis; + white-space: nowrap; + + &__radio { + padding-right: 5px; + } + + &__name { + + &:hover { + border-bottom: 1px dashed #555; + } + } + } + } + } } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index f9bf54df5..4e588b2ec 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -632,7 +632,7 @@ oEditor.setPlain(sResult); }); }, - this.oEditor.getData(), + this.oEditor.getData(false, true), this.currentIdentity(), this.to(), this.cc(), diff --git a/dev/View/Popup/ComposeOpenPgp.js b/dev/View/Popup/ComposeOpenPgp.js index e77a82f18..55a3f638e 100644 --- a/dev/View/Popup/ComposeOpenPgp.js +++ b/dev/View/Popup/ComposeOpenPgp.js @@ -109,25 +109,20 @@ if (self.resultCallback && bResult) { - try { - + var oPromise = null; + try + { if (oPrivateKey && 0 === aPublicKeys.length) { - self.resultCallback( - PgpStore.openpgp.signClearMessage([oPrivateKey], self.text()) - ); + oPromise = PgpStore.openpgp.signClearMessage([oPrivateKey], self.text()); } else if (oPrivateKey && 0 < aPublicKeys.length) { - self.resultCallback( - PgpStore.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text()) - ); + oPromise = PgpStore.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text()); } else if (!oPrivateKey && 0 < aPublicKeys.length) { - self.resultCallback( - PgpStore.openpgp.encryptMessage(aPublicKeys, self.text()) - ); + oPromise = PgpStore.openpgp.encryptMessage(aPublicKeys, self.text()); } } catch (e) @@ -135,14 +130,30 @@ self.notification(Translator.i18n('PGP_NOTIFICATIONS/PGP_ERROR', { 'ERROR': '' + e })); - - bResult = false; } - } - if (bResult) - { - self.cancelCommand(); + if (oPromise) + { + try + { + oPromise.then(function (mData) { + + self.resultCallback(mData); + self.cancelCommand(); + + })['catch'](function (e) { + self.notification(Translator.i18n('PGP_NOTIFICATIONS/PGP_ERROR', { + 'ERROR': '' + e + })); + }); + } + catch (e) + { + self.notification(Translator.i18n('PGP_NOTIFICATIONS/PGP_ERROR', { + 'ERROR': '' + e + })); + } + } } self.submitRequest(false); diff --git a/dev/View/Popup/MessageOpenPgp.js b/dev/View/Popup/MessageOpenPgp.js new file mode 100644 index 000000000..7a861c58c --- /dev/null +++ b/dev/View/Popup/MessageOpenPgp.js @@ -0,0 +1,179 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + key = require('key'), + + Utils = require('Common/Utils'), + Enums = require('Common/Enums'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function MessageOpenPgpPopupView() + { + AbstractView.call(this, 'Popups', 'PopupsMessageOpenPgp'); + + this.notification = ko.observable(''); + + this.selectedKey = ko.observable(null); + this.privateKeys = ko.observableArray([]); + + this.password = ko.observable(''); + this.password.focus = ko.observable(false); + this.buttonFocus = ko.observable(false); + + this.resultCallback = null; + + this.submitRequest = ko.observable(false); + + // commands + this.doCommand = Utils.createCommand(this, function () { + + this.submitRequest(true); + +_.delay(_.bind(function() { + + var + oPrivateKeys = [], + oPrivateKey = null + ; + + try + { + if (this.resultCallback && this.selectedKey()) + { + oPrivateKeys = this.selectedKey().getNativeKeys(); + oPrivateKey = oPrivateKeys && oPrivateKeys[0] ? oPrivateKeys[0] : null; + + if (oPrivateKey) + { + try + { + if (!oPrivateKey.decrypt(Utils.pString(this.password()))) + { + oPrivateKey = null; + } + } + catch (e) + { + oPrivateKey = null; + } + } + } + } + catch (oExc) + { + oPrivateKey = null; + } + + this.submitRequest(false); + + this.cancelCommand(); + this.resultCallback(oPrivateKey); + +}, this), 100); + + }, function () { + return !this.submitRequest(); + }); + + this.sDefaultKeyScope = Enums.KeyState.PopupMessageOpenPGP; + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/Popup/MessageOpenPgp'], MessageOpenPgpPopupView); + _.extend(MessageOpenPgpPopupView.prototype, AbstractView.prototype); + + MessageOpenPgpPopupView.prototype.clearPopup = function () + { + this.notification(''); + + this.password(''); + this.password.focus(false); + this.buttonFocus(false); + + this.selectedKey(false); + this.submitRequest(false); + + this.resultCallback = null; + this.privateKeys([]); + }; + + MessageOpenPgpPopupView.prototype.onBuild = function (oDom) + { + key('tab,shift+tab', Enums.KeyState.PopupMessageOpenPGP, _.bind(function () { + + switch (true) + { + case this.password.focus(): + this.buttonFocus(true); + break; + case this.buttonFocus(): + this.password.focus(true); + break; + } + + return false; + + }, this)); + + var self = this; + + oDom + .on('click', '.key-list__item', function () { + + oDom.find('.key-list__item .key-list__item__radio') + .addClass('icon-radio-unchecked') + .removeClass('icon-radio-checked') + ; + + $(this).find('.key-list__item__radio') + .removeClass('icon-radio-unchecked') + .addClass('icon-radio-checked') + ; + + self.selectedKey(ko.dataFor(this)); + + self.password.focus(true); + }) + ; + }; + + MessageOpenPgpPopupView.prototype.onHideWithDelay = function () + { + this.clearPopup(); + }; + + MessageOpenPgpPopupView.prototype.onShowWithDelay = function () + { + this.password.focus(true); +// this.buttonFocus(true); + }; + + MessageOpenPgpPopupView.prototype.onShow = function (fCallback, aPrivateKeys) + { + this.clearPopup(); + + this.resultCallback = fCallback; + this.privateKeys(aPrivateKeys); + + if (this.viewModelDom) + { + this.viewModelDom.find('.key-list__item').first().click(); + } + }; + + module.exports = MessageOpenPgpPopupView; + +}()); \ No newline at end of file diff --git a/dev/View/Popup/NewOpenPgpKey.js b/dev/View/Popup/NewOpenPgpKey.js index e929a438a..99e764c34 100644 --- a/dev/View/Popup/NewOpenPgpKey.js +++ b/dev/View/Popup/NewOpenPgpKey.js @@ -62,29 +62,36 @@ _.delay(function () { - mKeyPair = false; + var mPromise = false; + try { - mKeyPair = PgpStore.openpgp.generateKeyPair({ + + mPromise = PgpStore.openpgp.generateKeyPair({ 'userId': sUserID, 'numBits': Utils.pInt(self.keyBitLength()), 'passphrase': Utils.trim(self.password()) }); - } catch (e) { -// window.console.log(e); - } - if (mKeyPair && mKeyPair.privateKeyArmored) - { - oOpenpgpKeyring.privateKeys.importKey(mKeyPair.privateKeyArmored); - oOpenpgpKeyring.publicKeys.importKey(mKeyPair.publicKeyArmored); - oOpenpgpKeyring.store(); + mPromise.then(function () { - require('App/User').reloadOpenPgpKeys(); - Utils.delegateRun(self, 'cancelCommand'); - } + self.submitRequest(false); + + if (mKeyPair && mKeyPair.privateKeyArmored) + { + oOpenpgpKeyring.privateKeys.importKey(mKeyPair.privateKeyArmored); + oOpenpgpKeyring.publicKeys.importKey(mKeyPair.publicKeyArmored); + oOpenpgpKeyring.store(); + + require('App/User').reloadOpenPgpKeys(); + Utils.delegateRun(self, 'cancelCommand'); + } + + })['catch'](function() { + self.submitRequest(false); + }); + + } catch (e) {} - self.submitRequest(false); - }, 100); return true; diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 11a4017bc..4109bdc0b 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -303,16 +303,6 @@ this.viewIsImportant = ko.observable(false); this.viewIsFlagged = ko.observable(false); -// PGP - this.viewPgpPassword = ko.observable(''); - this.viewPgpSignedVerifyStatus = ko.computed(function () { - return this.message() ? this.message().pgpSignedVerifyStatus() : Enums.SignedVerifyStatus.None; - }, this); - - this.viewPgpSignedVerifyUser = ko.computed(function () { - return this.message() ? this.message().pgpSignedVerifyUser() : ''; - }, this); - this.viewFromDkimStatusIconClass = ko.computed(function () { var sResult = 'icon-none iconcolor-display-none'; @@ -361,8 +351,6 @@ this.messageActiveDom(null); - this.viewPgpPassword(''); - if (oMessage) { this.showAttachmnetControls(false); @@ -523,48 +511,6 @@ return sResult; }; - MessageViewMailBoxUserView.prototype.isPgpActionVisible = function () - { - return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus(); - }; - - MessageViewMailBoxUserView.prototype.isPgpStatusVerifyVisible = function () - { - return Enums.SignedVerifyStatus.None !== this.viewPgpSignedVerifyStatus(); - }; - - MessageViewMailBoxUserView.prototype.isPgpStatusVerifySuccess = function () - { - return Enums.SignedVerifyStatus.Success === this.viewPgpSignedVerifyStatus(); - }; - - MessageViewMailBoxUserView.prototype.pgpStatusVerifyMessage = function () - { - var sResult = ''; - switch (this.viewPgpSignedVerifyStatus()) - { - case Enums.SignedVerifyStatus.UnknownPublicKeys: - sResult = Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND'); - break; - case Enums.SignedVerifyStatus.UnknownPrivateKey: - sResult = Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND'); - break; - case Enums.SignedVerifyStatus.Unverified: - sResult = Translator.i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE'); - break; - case Enums.SignedVerifyStatus.Error: - sResult = Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'); - break; - case Enums.SignedVerifyStatus.Success: - sResult = Translator.i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { - 'USER': this.viewPgpSignedVerifyUser() - }); - break; - } - - return sResult; - }; - MessageViewMailBoxUserView.prototype.fullScreen = function () { this.fullScreenMode(true); @@ -1256,31 +1202,6 @@ return 0 < iCnt ? (100 > iCnt ? iCnt : '99+') : ''; }; - - /** - * @param {MessageModel} oMessage - */ - MessageViewMailBoxUserView.prototype.verifyPgpSignedClearMessage = function (oMessage) - { - if (oMessage) - { - oMessage.verifyPgpSignedClearMessage(); - } - - this.checkHeaderHeight(); - }; - - /** - * @param {MessageModel} oMessage - */ - MessageViewMailBoxUserView.prototype.decryptPgpEncryptedMessage = function (oMessage) - { - if (oMessage) - { - oMessage.decryptPgpEncryptedMessage(this.viewPgpPassword()); - } - }; - /** * @param {MessageModel} oMessage */ diff --git a/gulpfile.js b/gulpfile.js index 51d9eb205..889e35164 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,6 +11,7 @@ var devVersion: '0.0.0', releasesPath: 'build/dist/releases', community: true, + watch: false, rainloopBuilded: false, destPath: '', @@ -30,17 +31,22 @@ var } }, + _ = require('lodash'), fs = require('node-fs'), path = require('path'), + notifier = require('node-notifier'), + gulp = require('gulp'), concat = require('gulp-concat-util'), header = require('gulp-header'), - eol = require('gulp-eol'), stripbom = require('gulp-stripbom'), rename = require('gulp-rename'), replace = require('gulp-replace'), uglify = require('gulp-uglify'), + notify = require("gulp-notify"), plumber = require('gulp-plumber'), + gulpif = require('gulp-if'), + eol = require('gulp-eol'), gutil = require('gulp-util') ; @@ -136,7 +142,7 @@ cfg.paths.js = { openpgp: { name: 'openpgp.js', src: [ - 'vendors/openpgp/openpgp-0.7.2.min.js' + 'vendors/openpgp/openpgp-0.10.1.min.js' ] }, encrypt: { @@ -201,14 +207,17 @@ cfg.paths.js = { // CSS gulp.task('less:main', function() { var less = require('gulp-less'); + return gulp.src(cfg.paths.less.main.src) + .pipe(gulpif(cfg.watch, plumber({errorHandler: notify.onError("Error: <%= error.message %>")}))) .pipe(less({ 'paths': cfg.paths.less.main.options.paths })) .pipe(rename(cfg.paths.less.main.name)) .pipe(eol('\n', true)) .pipe(gulp.dest(cfg.paths.staticCSS)) - .on('error', gutil.log); + .on('error', gutil.log) + ; }); gulp.task('css:main-begin', ['less:main'], function() { @@ -220,7 +229,6 @@ gulp.task('css:main-begin', ['less:main'], function() { ; return gulp.src(cfg.paths.css.main.src) - .pipe(plumber()) .pipe(concat(cfg.paths.css.main.name)) .pipe(autoprefixer('last 3 versions', '> 1%', 'ie 9', 'Firefox ESR', 'Opera 12.1')) // .pipe(csscomb()) @@ -252,7 +260,6 @@ gulp.task('css:main', ['css:clear-less']); gulp.task('css:main:min', ['css:main'], function() { var minifyCss = require('gulp-minify-css'); return gulp.src(cfg.paths.staticCSS + cfg.paths.css.main.name) - .pipe(plumber()) .pipe(minifyCss({ 'keepSpecialComments': 0 })) @@ -330,10 +337,51 @@ gulp.task('js:webpack', ['js:webpack:clear'], function(callback) { } webpack(webpackCfg, function(err, stats) { - if (err) { - throw new gutil.PluginError('webpack', err); + + var + fN = function (err) { + if (err) + { + gutil.log('[webpack]', '---'); + gutil.log('[webpack]', err.error ? err.error.toString() : ''); + gutil.log('[webpack]', err.message || ''); + gutil.log('[webpack]', '---'); + + notifier.notify({ + 'sound': true, + 'title': 'webpack', + 'message': err.error ? err.error.toString() : err.message + }); + } + } + ; + + if (err) + { + if (cfg.watch) + { + fN(err); + } + else + { + throw new gutil.PluginError('webpack', err); + } } - gutil.log('[webpack]', stats.toString({})); + else if (stats && stats.compilation && stats.compilation.errors && + stats.compilation.errors[0]) + { + if (cfg.watch) + { + _.each(stats.compilation.errors, function (err) { + fN(err); + }); + } + else + { + throw new gutil.PluginError('webpack', stats.compilation.errors[0]); + } + } + callback(); }); }); @@ -616,11 +664,13 @@ gulp.task('owncloud+', ['package:community-off', 'owncloud-']); //WATCH gulp.task('watch', ['fast'], function() { + cfg.watch = true; gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']); gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']); }); gulp.task('watch+', ['fast+'], function() { + cfg.watch = true; gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']); gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']); }); diff --git a/package.json b/package.json index 78156e8cd..71feea861 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,78 @@ { - "name": "RainLoop", - "title": "RainLoop Webmail", - "version": "1.9.1", - "release": "335", - "description": "Simple, modern & fast web-based email client", - "homepage": "http://rainloop.net", - "main": "gulpfile.js", - "author": { - "name": "RainLoop Team", - "email": "support@rainloop.net", - "web": "http://rainloop.net" - }, - "repository": { - "type": "git", - "url": "git://github.com/RainLoop/rainloop-webmail.git" - }, - "licenses": [ - { - "type": "AGPL 3.0", - "ulr": "http://www.gnu.org/licenses/agpl-3.0.html" - }, - { - "type": "RainLoop Software License", - "ulr": "http://www.rainloop.net/licensing/" - } - ], - "bugs": { - "url": "https://github.com/RainLoop/rainloop-webmail/issues" - }, - "keywords": [ - "webmail", - "php", - "simple", - "modern", - "mail", - "web-based", - "email", - "client", - "plugins" - ], - "readmeFilename": "README.md", - "ownCloudPackageVersion": "4.3", - "engines": { - "node": ">= 0.10.0" - }, - "devDependencies": { - - "node-fs": "*", - "rimraf": "*", - "jshint-summary": "*", - - "webpack": "*", - - "gulp": "*", - "gulp-util": "*", - "gulp-uglify": "*", - "gulp-rimraf": "*", - "gulp-jshint": "*", - "gulp-less": "1.3.6", - "gulp-zip": "*", - "gulp-rename": "*", - "gulp-replace": "*", - "gulp-header": "*", - "gulp-eol": "*", - "gulp-stripbom": "*", - "gulp-minify-css": "*", - "gulp-autoprefixer": "*", - "gulp-csscomb": "*", - "gulp-closure-compiler": "*", - "gulp-csslint": "*", - "gulp-beautify": "*", - "gulp-plumber": "*", - "gulp-concat-util": "*" - } + "name": "RainLoop", + "title": "RainLoop Webmail", + "version": "1.9.1", + "release": "335", + "description": "Simple, modern & fast web-based email client", + "homepage": "http://rainloop.net", + "main": "gulpfile.js", + "author": { + "name": "RainLoop Team", + "email": "support@rainloop.net", + "web": "http://rainloop.net" + }, + "repository": { + "type": "git", + "url": "git://github.com/RainLoop/rainloop-webmail.git" + }, + "licenses": [ + { + "type": "AGPL 3.0", + "ulr": "http://www.gnu.org/licenses/agpl-3.0.html" + }, + { + "type": "RainLoop Software License", + "ulr": "http://www.rainloop.net/licensing/" + } + ], + "bugs": { + "url": "https://github.com/RainLoop/rainloop-webmail/issues" + }, + "keywords": [ + "webmail", + "php", + "simple", + "modern", + "mail", + "web-based", + "email", + "client", + "plugins" + ], + "readmeFilename": "README.md", + "ownCloudPackageVersion": "4.3", + "engines": { + "node": ">= 0.10.0" + }, + "devDependencies": { + "node-fs": "*", + "rimraf": "*", + "jshint-summary": "*", + "webpack": "*", + "gulp": "*", + "gulp-util": "*", + "gulp-uglify": "*", + "gulp-rimraf": "*", + "gulp-jshint": "*", + "gulp-less": "1.3.6", + "gulp-zip": "*", + "gulp-rename": "*", + "gulp-replace": "*", + "gulp-header": "*", + "gulp-eol": "*", + "gulp-stripbom": "*", + "gulp-minify-css": "*", + "gulp-autoprefixer": "*", + "gulp-csscomb": "*", + "gulp-closure-compiler": "*", + "gulp-csslint": "*", + "gulp-beautify": "*", + "gulp-plumber": "*", + "gulp-concat-util": "*", + "gulp-notify": "~2.2.0", + "gulp-through": "~0.3.0", + "lodash": "~3.9.3", + "gulp-if": "~1.2.5", + "node-notifier": "~4.2.3" + } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index e46eac87e..87a601fca 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -460,6 +460,7 @@ class PdoAddressBook $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + $aResponse = null; try { $aResponse = $oClient->propFind($sPath, array( diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html index ab0051716..40a343a09 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html @@ -38,7 +38,7 @@
- Powered by RainLoop + Powered by RainLoop
diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsLicensing.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsLicensing.html index c1f914a9e..ef0f14692 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsLicensing.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminSettingsLicensing.html @@ -68,7 +68,7 @@    - +    diff --git a/rainloop/v/0.0.0/app/templates/Views/User/Login.html b/rainloop/v/0.0.0/app/templates/Views/User/Login.html index 2b4bf549b..2f5a6a555 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/Login.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/Login.html @@ -116,7 +116,7 @@
- Powered by RainLoop + Powered by RainLoop
-
- -    - -
-
- -    - -
-
- -    - -    - -