1
/* ***** BEGIN LICENSE BLOCK *****
2
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
* The contents of this file are subject to the Mozilla Public
5
* License Version 1.1 (the "MPL"); you may not use this file
6
* except in compliance with the MPL. You may obtain a copy of
7
* the MPL at http://www.mozilla.org/MPL/
9
* Software distributed under the MPL is distributed on an "AS
10
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
* implied. See the MPL for the specific language governing
12
* rights and limitations under the MPL.
14
* The Original Code is Enigmail.
16
* The Initial Developer of the Original Code is Patrick Brunschwig.
17
* Portions created by Patrick Brunschwig <patrick@mozilla-enigmail.org> are
18
* Copyright (C) 2011 Patrick Brunschwig. All Rights Reserved.
22
* Alternatively, the contents of this file may be used under the terms of
23
* either the GNU General Public License Version 2 or later (the "GPL"), or
24
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25
* in which case the provisions of the GPL or the LGPL are applicable instead
26
* of those above. If you wish to allow use of your version of this file only
27
* under the terms of either the GPL or the LGPL, and not to allow others to
28
* use your version of this file under the terms of the MPL, indicate your
29
* decision by deleting the provisions above and replace them with the notice
30
* and other provisions required by the GPL or the LGPL. If you do not delete
31
* the provisions above, a recipient may use your version of this file under
32
* the terms of any one of the MPL, the GPL or the LGPL.
33
* ***** END LICENSE BLOCK ***** */
37
* Common Enigmail crypto-related GUI functionality
39
* Import into a JS component using
40
* 'Components.utils.import("resource://enigmail/commonFuncs.jsm");'
43
Components.utils.import("resource://enigmail/enigmailCommon.jsm");
45
var EXPORTED_SYMBOLS = [ "EnigmailFuncs" ];
47
const Cc = Components.classes;
48
const Ci = Components.interfaces;
50
const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
52
// field ID's of key list (as described in the doc/DETAILS file in the GnuPG distribution)
58
const OWNERTRUST_ID = 8;
60
const SIG_TYPE_ID = 10;
61
const KEY_USE_FOR_ID = 11;
63
var gTxtConverter = null;
68
* download key(s) from a keyserver
70
downloadKeys: function (win, inputObj, resultObj)
72
EnigmailCommon.DEBUG_LOG("commonFuncs.jsm: downloadKeys: searchList="+inputObj.searchList+"\n");
74
resultObj.importedKeys=0;
76
var ioService = Cc[IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
77
if (ioService && ioService.offline) {
78
EnigmailCommon.alert(win, EnigmailCommon.getString("needOnline"));
83
if (inputObj.searchList) {
84
valueObj = { keyId: "<"+inputObj.searchList.join("> <")+">" };
87
var keysrvObj = new Object();
89
win.openDialog("chrome://enigmail/content/enigmailKeyserverDlg.xul",
90
"", "dialog,modal,centerscreen", valueObj, keysrvObj);
91
if (! keysrvObj.value) {
95
inputObj.keyserver = keysrvObj.value;
96
if (! inputObj.searchList) {
97
inputObj.searchList = keysrvObj.email.split(/[,; ]+/);
100
win.openDialog("chrome://enigmail/content/enigmailSearchKey.xul",
101
"", "dialog,modal,centerscreen", inputObj, resultObj);
105
* Format a key fingerprint
107
formatFpr: function (fingerprint)
109
// format key fingerprint
111
var fpr = fingerprint.match(/(....)(....)(....)(....)(....)(....)(....)(....)(....)?(....)?/);
112
if (fpr && fpr.length > 2) {
121
* get a list of plain email addresses without name or surrounding <>
123
stripEmail: function (mailAddrs)
127
while ((qStart = mailAddrs.indexOf('"')) != -1) {
128
qEnd = mailAddrs.indexOf('"', qStart+1);
130
EnigmailCommon.ERROR_LOG("commonFuncs.jsm: stripEmail: Unmatched quote in mail address: "+mailAddrs+"\n");
131
throw Components.results.NS_ERROR_FAILURE;
134
mailAddrs = mailAddrs.substring(0,qStart) + mailAddrs.substring(qEnd+1);
137
// Eliminate all whitespace, just to be safe
138
mailAddrs = mailAddrs.replace(/\s+/g,"");
140
// Extract pure e-mail address list (stripping out angle brackets)
141
mailAddrs = mailAddrs.replace(/(^|,)[^,]*<([^>]+)>[^,]*/g,"$1$2");
146
collapseAdvanced: function (obj, attribute, dummy)
148
EnigmailCommon.DEBUG_LOG("commonFuncs.jsm: collapseAdvanced:\n");
150
var advancedUser = EnigmailCommon.getPref("advancedUser");
152
obj = obj.firstChild;
154
if (obj.getAttribute("advanced") == "true") {
156
obj.removeAttribute(attribute);
159
obj.setAttribute(attribute, "true");
162
else if (obj.getAttribute("advanced") == "reverse") {
164
obj.setAttribute(attribute, "true");
167
obj.removeAttribute(attribute);
171
obj = obj.nextSibling;
175
openSetupWizard: function (win)
177
win.open("chrome://enigmail/content/enigmailSetupWizard.xul",
178
"", "chrome,centerscreen");
181
openHelpWindow: function (source)
183
EnigmailCommon.openWin("enigmail:help",
184
"chrome://enigmail/content/enigmailHelp.xul?src="+source,
185
"centerscreen,resizable");
188
openAboutWindow: function ()
190
EnigmailCommon.openWin("about:enigmail",
191
"chrome://enigmail/content/enigmailAbout.xul",
192
"resizable,centerscreen");
195
openRulesEditor: function ()
197
EnigmailCommon.openWin("enigmail:rulesEditor",
198
"chrome://enigmail/content/enigmailRulesEditor.xul",
199
"dialog,centerscreen,resizable");
202
openKeyManager: function (win)
204
EnigmailCommon.getService(win);
206
EnigmailCommon.openWin("enigmail:KeyManager",
207
"chrome://enigmail/content/enigmailKeyManager.xul",
211
openKeyGen: function ()
213
EnigmailCommon.openWin("enigmail:generateKey",
214
"chrome://enigmail/content/enigmailKeygen.xul",
215
"chrome,modal,resizable=yes");
218
openCardDetails: function ()
220
EnigmailCommon.openWin("enigmail:cardDetails",
221
"chrome://enigmail/content/enigmailCardDetails.xul",
225
openConsoleWindow: function ()
227
EnigmailCommon.openWin("enigmail:console",
228
"chrome://enigmail/content/enigmailConsole.xul",
229
"resizable,centerscreen");
232
openDebugLog: function(win)
234
var logDirectory = EnigmailCommon.getPref("logDirectory");
237
EnigmailCommon.alert(win, EnigmailCommon.getString("noLogDir"));
241
var svc = EnigmailCommon.enigmailSvc;
243
EnigmailCommon.alert(win, EnigmailCommon.getString("noLogFile"));
247
if (! svc.logFileStream) {
248
EnigmailCommon.alert(win, EnigmailCommon.getString("restartForLog"));
252
svc.logFileStream.flush();
254
logDirectory = logDirectory.replace(/\\/g, "/");
256
var logFileURL = "file:///" + logDirectory + "/enigdbug.txt";
257
var opts="fileUrl=" + escape(logFileURL) + "&title=" +
258
escape(EnigmailCommon.getString("debugLog.title"));
260
EnigmailCommon.openWin("enigmail:logFile",
261
"chrome://enigmail/content/enigmailViewFile.xul?"+opts,
262
"resizable,centerscreen");
265
openPrefWindow: function (win, showBasic, selectTab)
267
EnigmailCommon.DEBUG_LOG("enigmailCommon.js: prefWindow\n");
269
EnigmailCommon.getService(win);
271
win.openDialog("chrome://enigmail/content/pref-enigmail.xul",
272
"_blank", "chrome,resizable=yes",
273
{'showBasic': showBasic,
274
'clientType': 'thunderbird',
275
'selectTab': selectTab});
278
createNewRule: function (win, emailAddress)
280
// make sure the rules database is loaded
281
var enigmailSvc = EnigmailCommon.getService(win);
282
if (!enigmailSvc) return false;
284
var rulesListObj= new Object;
287
enigmailSvc.getRulesData(rulesListObj);
288
var inputObj=new Object;
289
var resultObj=new Object;
290
inputObj.toAddress="{"+emailAddress+"}";
292
inputObj.command = "add";
293
win.openDialog("chrome://enigmail/content/enigmailSingleRcptSettings.xul","",
294
"dialog,modal,centerscreen,resizable", inputObj, resultObj);
298
editKeyTrust: function (win, userIdArr, keyIdArr)
304
var resultObj = { refresh: false };
305
win.openDialog("chrome://enigmail/content/enigmailEditKeyTrustDlg.xul","",
306
"dialog,modal,centerscreen,resizable", inputObj, resultObj);
307
return resultObj.refresh;
310
signKey: function (win, userId, keyId, signingKeyHint)
315
signingKeyHint: signingKeyHint
317
var resultObj = { refresh: false };
318
win.openDialog("chrome://enigmail/content/enigmailSignKeyDlg.xul","",
319
"dialog,modal,centerscreen,resizable", inputObj, resultObj);
320
return resultObj.refresh;
323
showPhoto: function (win, keyId, userId, photoNumber)
325
var enigmailSvc = EnigmailCommon.getService(win);
328
if (photoNumber==null) photoNumber=0;
330
var exitCodeObj = new Object();
331
var errorMsgObj = new Object();
332
var photoPath = enigmailSvc.showKeyPhoto("0x"+keyId, photoNumber, exitCodeObj, errorMsgObj);
334
if (photoPath && exitCodeObj.value==0) {
336
var photoFile = Cc[EnigmailCommon.LOCAL_FILE_CONTRACTID].
337
createInstance(Ci.nsIFile);
338
photoFile.initWithPath(photoPath);
339
if (! (photoFile.isFile() && photoFile.isReadable())) {
340
EnigmailCommon.alert(win, EnigmailCommon.getString("error.photoPathNotReadable", photoPath));
343
var ioServ = Cc[EnigmailCommon.IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
344
var photoUri = ioServ.newFileURI(photoFile).spec;
351
win.openDialog("chrome://enigmail/content/enigmailDispPhoto.xul",
353
"chrome,modal,resizable,dialog,centerscreen",
356
// delete the photo file
357
photoFile.remove(false);
363
EnigmailCommon.alert(win, EnigmailCommon.getString("noPhotoAvailable"));
368
openKeyDetails: function (win, keyId, refresh)
372
this.loadKeyList(win, refresh, keyListObj);
376
keyListArr: keyListObj.keyList,
377
secKey: keyListObj.keyList[ keyId ].secretAvailable
379
var resultObj = { refresh: false };
380
win.openDialog("chrome://enigmail/content/enigmailKeyDetailsDlg.xul", "",
381
"dialog,modal,centerscreen,resizable", inputObj, resultObj);
382
if (resultObj.refresh) {
383
enigmailRefreshKeys();
388
* Load the key list into memory
389
* sortDirection: 1 = ascending / -1 = descending
391
loadKeyList: function (win, refresh, keyListObj, sortColumn, sortDirection)
393
EnigmailCommon.DEBUG_LOG("enigmailFuncs.jsm: loadKeyList\n");
395
if (! sortColumn) sortColumn = "userid";
396
if (! sortDirection) sortDirection = 1;
398
const TRUSTLEVEL_SORTED="oidreD-qnmfu"; // trust level sorted by increasing level of trust
400
var sortByKeyId = function (a, b) {
401
return (a.keyId < b.keyId) ? -sortDirection : sortDirection;
404
var sortByKeyIdShort = function (a, b) {
405
return (a.keyId.substr(-8,8) < b.keyId.substr(-8 ,8)) ? -sortDirection : sortDirection;
408
var sortByUserId = function (a, b) {
409
return (a.userId < b.userId) ? -sortDirection : sortDirection;
412
var sortByFpr = function (a, b) {
413
return (keyListObj.keyList[a.keyId].fpr < keyListObj.keyList[b.keyId].fpr) ? -sortDirection : sortDirection;
416
var sortByKeyType = function (a, b) {
417
return (keyListObj.keyList[a.keyId].secretAvailable < keyListObj.keyList[b.keyId].secretAvailable) ? -sortDirection : sortDirection;
421
var sortByValidity = function (a, b) {
422
return (TRUSTLEVEL_SORTED.indexOf(EnigmailFuncs.getTrustCode(keyListObj.keyList[a.keyId])) < TRUSTLEVEL_SORTED.indexOf(EnigmailFuncs.getTrustCode(keyListObj.keyList[b.keyId]))) ? -sortDirection : sortDirection;
425
var sortByTrust = function (a, b) {
426
return (TRUSTLEVEL_SORTED.indexOf(keyListObj.keyList[a.keyId].ownerTrust) < TRUSTLEVEL_SORTED.indexOf(keyListObj.keyList[b.keyId].ownerTrust)) ? -sortDirection : sortDirection;
429
var sortByExpiry = function (a, b) {
430
return (keyListObj.keyList[a.keyId].expiryTime < keyListObj.keyList[b.keyId].expiryTime) ? -sortDirection : sortDirection;
433
var aGpgUserList = this.obtainKeyList(win, false, refresh);
434
if (!aGpgUserList) return;
436
var aGpgSecretsList = this.obtainKeyList(win, true, refresh);
437
if (!aGpgSecretsList && !refresh) {
438
if (EnigmailCommon.confirmDlg(EnigmailCommon.getString("noSecretKeys"),
439
EnigmailCommon.getString("keyMan.button.generateKey"),
440
EnigmailCommon.getString("keyMan.button.skip"))) {
442
this.loadKeyList(true, keyListObj);
446
keyListObj.keyList = new Array();
447
keyListObj.keySortList = new Array();
449
var keyObj = new Object();
451
var uatNum=0; // counter for photos (counts per key)
453
for (i=0; i<aGpgUserList.length; i++) {
454
var listRow=aGpgUserList[i].split(/:/);
455
if (listRow.length>=0) {
456
switch (listRow[0]) {
458
keyObj = new Object();
460
keyObj.expiry=EnigmailCommon.getDateTime(listRow[EXPIRY_ID], true, false);
461
keyObj.expiryTime = Number(listRow[EXPIRY_ID]);
462
keyObj.created=EnigmailCommon.getDateTime(listRow[CREATED_ID], true, false);
463
keyObj.keyId=listRow[KEY_ID];
464
keyObj.keyTrust=listRow[KEY_TRUST_ID];
465
keyObj.keyUseFor=listRow[KEY_USE_FOR_ID];
466
keyObj.ownerTrust=listRow[OWNERTRUST_ID];
467
keyObj.SubUserIds=new Array();
469
keyObj.photoAvailable=false;
470
keyObj.secretAvailable=false;
471
keyListObj.keyList[listRow[KEY_ID]] = keyObj;
474
keyObj.fpr=listRow[USERID_ID];
477
if (listRow[USERID_ID].length == 0) {
478
listRow[USERID_ID] = "-";
480
if (typeof(keyObj.userId) != "string") {
481
keyObj.userId=EnigmailCommon.convertGpgToUnicode(listRow[USERID_ID]);
482
keyListObj.keySortList.push({
483
userId: keyObj.userId.toLowerCase(),
486
if (TRUSTLEVEL_SORTED.indexOf(listRow[KEY_TRUST_ID]) < TRUSTLEVEL_SORTED.indexOf(keyObj.keyTrust)) {
487
// reduce key trust if primary UID is less trusted than public key
488
keyObj.keyTrust = listRow[KEY_TRUST_ID];
493
userId: EnigmailCommon.convertGpgToUnicode(listRow[USERID_ID]),
494
keyTrust: listRow[KEY_TRUST_ID],
497
keyObj.SubUserIds.push(subUserId);
501
if (listRow[USERID_ID].indexOf("1 ")==0) {
502
var userId=EnigmailCommon.getString("userAtt.photo");
503
keyObj.SubUserIds.push({userId: userId,
504
keyTrust:listRow[KEY_TRUST_ID],
507
keyObj.photoAvailable=true;
514
// search and mark keys that have secret keys
515
for (i=0; i<aGpgSecretsList.length; i++) {
516
listRow=aGpgSecretsList[i].split(/:/);
517
if (listRow.length>=0) {
518
if (listRow[0] == "sec") {
519
if (typeof(keyListObj.keyList[listRow[KEY_ID]]) == "object") {
520
keyListObj.keyList[listRow[KEY_ID]].secretAvailable=true;
526
switch (sortColumn.toLowerCase()) {
528
keyListObj.keySortList.sort(sortByKeyId);
531
keyListObj.keySortList.sort(sortByKeyIdShort);
534
keyListObj.keySortList.sort(sortByFpr);
537
keyListObj.keySortList.sort(sortByKeyType);
540
keyListObj.keySortList.sort(sortByValidity);
543
keyListObj.keySortList.sort(sortByTrust);
546
keyListObj.keySortList.sort(sortByExpiry);
549
keyListObj.keySortList.sort(sortByUserId);
553
getTrustCode: function (keyObj)
555
// return a merged value of trust level "key disabled"
556
if (keyObj.keyUseFor.indexOf("D")>=0)
559
return keyObj.keyTrust;
563
// Obtain kay list from GnuPG
564
obtainKeyList: function (win, secretOnly, refresh)
566
EnigmailCommon.DEBUG_LOG("enigmailFuncs.jsm: obtainKeyList\n");
570
var exitCodeObj = new Object();
571
var statusFlagsObj = new Object();
572
var errorMsgObj = new Object();
574
var enigmailSvc = EnigmailCommon.getService(win);
575
if (! enigmailSvc) return null;
577
userList = enigmailSvc.getUserIdList(secretOnly,
582
if (exitCodeObj.value != 0) {
583
EnigmailCommon.alert(win, errorMsgObj.value);
587
EnigmailCommon.ERROR_LOG("ERROR in enigmailFuncs: obtainKeyList"+ex.toString()+"\n");
590
if (typeof(userList) == "string") {
591
return userList.split(/\n/);
598
getSignMsg: function (identity)
600
EnigmailCommon.DEBUG_LOG("enigmailCommon.jsm: getSignMsg: identity.key="+identity.key+"\n");
603
EnigmailCommon.getPref("configuredVersion"); // dummy call to getPref to ensure initialization
605
var prefRoot = EnigmailCommon.prefRoot;
607
if (prefRoot.getPrefType("mail.identity."+identity.key+".pgpSignPlain")==0) {
608
if (prefRoot.getPrefType("mail.identity."+identity.key+".pgpSignMsg")==0) {
609
sign=identity.getBoolAttribute("pgpAlwaysSign");
610
identity.setBoolAttribute("pgpSignEncrypted", sign);
611
identity.setBoolAttribute("pgpSignPlain", sign);
614
sign = identity.getIntAttribute("pgpSignMsg");
615
identity.setBoolAttribute("pgpSignEncrypted", sign==1);
616
identity.setBoolAttribute("pgpSignPlain", sign>0);
618
prefRoot.deleteBranch("mail.identity."+identity.key+".pgpSignMsg");
619
prefRoot.deleteBranch("mail.identity."+identity.key+".pgpAlwaysSign");
623
// this function tries to mimic the Thunderbird plaintext viewer
624
formatPlaintextMsg: function (plainTxt)
627
gTxtConverter = Cc["@mozilla.org/txttohtmlconv;1"].createInstance(Ci.mozITXTToHTMLConv);
629
if (! EnigmailCommon.prefRoot)
630
EnigmailCommon.getPref("configuredVersion");
632
var prefRoot = EnigmailCommon.prefRoot;
635
// set the style stuff according to perferences
637
switch (prefRoot.getIntPref("mail.quoted_style")) {
639
fontStyle="font-weight: bold; "; break;
641
fontStyle="font-style: italic; "; break;
643
fontStyle="font-weight: bold; font-style: italic; "; break;
646
switch (prefRoot.getIntPref("mail.quoted_size")) {
648
fontStyle += "font-size: large; "; break;
650
fontStyle += "font-size: small; "; break;
653
fontStyle += "color: "+prefRoot.getCharPref("mail.citation_color")+";";
655
var convFlags = Ci.mozITXTToHTMLConv.kURLs;
656
if (prefRoot.getBoolPref("mail.display_glyph"))
657
convFlags |= Ci.mozITXTToHTMLConv.kGlyphSubstitution;
658
if (prefRoot.getBoolPref("mail.display_struct"))
659
convFlags |= Ci.mozITXTToHTMLConv.kStructPhrase;
661
// start processing the message
663
plainTxt = plainTxt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
664
var lines = plainTxt.split(/\n/);
665
var oldCiteLevel = 0;
668
var logLineStart = { value: 0 };
669
var isSignature = false;
671
for (var i=0; i < lines.length; i++) {
673
oldCiteLevel = citeLevel;
674
if (lines[i].search(/^[\> \t]*\>$/) == 0)
677
citeLevel = gTxtConverter.citeLevelTXT(lines[i], logLineStart);
679
if (citeLevel > oldCiteLevel) {
682
for (let j=0; j < citeLevel - oldCiteLevel; j++) {
683
preface += '<blockquote type="cite" style="'+fontStyle+'">';
685
preface += '<pre wrap="">\n';
687
else if (citeLevel < oldCiteLevel) {
689
for (let j = 0; j < oldCiteLevel - citeLevel; j++)
690
preface += "</blockquote>";
692
preface += '<pre wrap="">\n';
695
if (logLineStart.value > 0) {
696
preface += '<span class="moz-txt-citetags">' +
697
gTxtConverter.scanTXT(lines[i].substr(0, logLineStart.value), convFlags) +
700
else if (lines[i] == "-- ") {
701
preface+='<div class=\"moz-txt-sig\">';
704
lines[i] = preface + gTxtConverter.scanTXT(lines[i].substr(logLineStart.value), convFlags);
708
var r='<pre wrap="">' + lines.join("\n") + (isSignature ? '</div>': '') + '</pre>';
709
//EnigmailCommon.DEBUG_LOG("enigmailFuncs.jsm: r='"+r+"'\n");
715
* extract the data fields following a header.
716
* e.g. ContentType: xyz; Aa=b; cc=d
717
* returns aa=b and cc=d in an array of arrays
719
getHeaderData: function (data) {
720
EnigmailCommon.DEBUG_LOG("enigmailCommon.jsm: getHeaderData: "+data.substr(0, 100)+"\n");
721
var a = data.split(/\n/);
723
for (let i = 0; i < a.length; i++) {
724
if (a[i].length == 0) break;
725
let b = a[i].split(/;/);
727
// extract "abc = xyz" tuples
728
for (let j=0; j < b.length; j++) {
729
let m = b[j].match(/^(\s*)([^=\s;]+)(\s*)(=)(\s*)(.*)(\s*)$/);
731
// m[2]: identifier / m[6]: data
732
res[m[2].toLowerCase()] = m[6].replace(/\s*$/, "");
733
EnigmailCommon.DEBUG_LOG("enigmailCommon.jsm: getHeaderData: "+m[2].toLowerCase()+" = "+res[m[2].toLowerCase()] +"\n");
736
if (i == 0 && a[i].indexOf(";") < 0) break;
737
if (i > 0 && a[i].search(/^\s/) < 0) break;