!import
1 //@line 41 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/mail/components/preferences/viewpasswords.js"
2
3 /*** =================== INITIALISATION CODE =================== ***/
4
5 var kObserverService;
6 var kSignonBundle;
7 var gSelectUserInUse = false;
8
9 // interface variables
10 var passwordmanager = null;
11
12 // password-manager lists
13 var signons = [];
14 var rejects = [];
15 var deletedSignons = [];
16 var deletedRejects = [];
17
18 var showingPasswords = false;
19
Startup
20 function Startup() {
21 // xpconnect to password manager interfaces
22 passwordmanager = Components.classes["@mozilla.org/passwordmanager;1"].getService(Components.interfaces.nsIPasswordManager);
23
24 kSignonBundle = document.getElementById("signonBundle");
25
26 // be prepared to reload the display if anything changes
27 kObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
28 kObserverService.addObserver(signonReloadDisplay, "signonChanged", false);
29
30 // be prepared to disable the buttons when selectuser dialog is in use
31 kObserverService.addObserver(signonReloadDisplay, "signonSelectUser", false);
32
33 signonsTree = document.getElementById("signonsTree");
34 rejectsTree = document.getElementById("rejectsTree");
35
36 // set initial password-manager tab
37 var tabBox = document.getElementById("tabbox");
38 tabBox.selectedTab = document.getElementById("signonsTab");
39
40 // label the show/hide password button and the close button
41 document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
42 document.documentElement.getButton("accept").label = kSignonBundle.getString("close");
43
44 // load password manager items
45 if (!LoadSignons()) {
46 return; /* user failed to unlock the database */
47 }
48 LoadRejects();
49 }
50
Shutdown
51 function Shutdown() {
52 kObserverService.removeObserver(signonReloadDisplay, "signonChanged");
53 kObserverService.removeObserver(signonReloadDisplay, "signonSelectUser");
54 }
55
56 var signonReloadDisplay = {
observe
57 observe: function(subject, topic, state) {
58 if (topic == "signonChanged") {
59 if (state == "signons") {
60 signons.length = 0;
61 if (lastSignonSortColumn == "host") {
62 lastSignonSortAscending = !lastSignonSortAscending; // prevents sort from being reversed
63 }
64 LoadSignons();
65 } else if (state == "rejects") {
66 rejects.length = 0;
67 if (lastRejectSortColumn == "host") {
68 lastRejectSortAscending = !lastRejectSortAscending; // prevents sort from being reversed
69 }
70 LoadRejects();
71 }
72 } else if (topic == "signonSelectUser") {
73 if (state == "suspend") {
74 gSelectUserInUse = true;
75 document.getElementById("removeSignon").disabled = true;
76 document.getElementById("removeAllSignons").disabled = true;
77 document.getElementById("togglePasswords").disabled = true;
78 } else if (state == "resume") {
79 gSelectUserInUse = false;
80 var selections = GetTreeSelections(signonsTree);
81 if (selections.length > 0) {
82 document.getElementById("removeSignon").disabled = false;
83 }
84 if (signons.length > 0) {
85 document.getElementById("removeAllSignons").disabled = false;
86 document.getElementById("togglePasswords").disabled = false;
87 }
88 } else if (state == "inUse") {
89 gSelectUserInUse = true;
90 }
91 }
92 }
93 }
94
95 /*** =================== SAVED SIGNONS CODE =================== ***/
96
97 var signonsTreeView = {
98 rowCount : 0,
setTree
99 setTree : function(tree) {},
getImageSrc
100 getImageSrc : function(row,column) {},
getProgressMode
101 getProgressMode : function(row,column) {},
getCellValue
102 getCellValue : function(row,column) {},
getCellText
103 getCellText : function(row,column) {
104 var rv="";
105 if (column.id=="siteCol") {
106 rv = signons[row].host;
107 } else if (column.id=="userCol") {
108 rv = signons[row].user;
109 } else if (column.id=="passwordCol") {
110 rv = signons[row].password;
111 }
112 return rv;
113 },
isSeparator
114 isSeparator : function(index) { return false; },
isSorted
115 isSorted : function() { return false; },
isContainer
116 isContainer : function(index) { return false; },
117 cycleHeader : function(column) {},
getRowProperties
118 getRowProperties : function(row,prop) {},
getColumnProperties
119 getColumnProperties : function(column,prop) {},
getCellProperties
120 getCellProperties : function(row,column,prop) {}
121 };
122 var signonsTree;
123
Signon
124 function Signon(number, host, user, rawuser, password) {
125 this.number = number;
126 this.host = host;
127 this.user = user;
128 this.rawuser = rawuser;
129 this.password = password;
130 }
131
LoadSignons
132 function LoadSignons() {
133 // loads signons into table
134 var enumerator = passwordmanager.enumerator;
135 var count = 0;
136
137 while (enumerator.hasMoreElements()) {
138 var nextPassword;
139 try {
140 nextPassword = enumerator.getNext();
141 } catch(e) {
142 /* user supplied invalid database key */
143 window.close();
144 return false;
145 }
146 nextPassword = nextPassword.QueryInterface(Components.interfaces.nsIPassword);
147 var host = nextPassword.host;
148 var user;
149 var password;
150 // try/catch in case decryption fails (invalid signon entry)
151 try {
152 user = nextPassword.user;
153 password = nextPassword.password;
154 } catch (e) {
155 // hide this entry
156 dump("could not decrypt user/password for host " + host + "\n");
157 continue;
158 }
159 var rawuser = user;
160
161 // if no username supplied, try to parse it out of the url
162 if (user == "") {
163 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
164 .getService(Components.interfaces.nsIIOService);
165 try {
166 user = ioService.newURI(host, null, null).username;
167 if (user == "") {
168 user = kSignonBundle.getString('noUserNameForPassword');
169 }
170 } catch(e) {
171 user = kSignonBundle.getString('noUserNameForPassword');
172 }
173 }
174
175 signons[count] = new Signon(count++, host, user, rawuser, password);
176 }
177 signonsTreeView.rowCount = signons.length;
178
179 // sort and display the table
180 signonsTree.treeBoxObject.view = signonsTreeView;
181 SignonColumnSort('host');
182
183 // disable "remove all signons" button if there are no signons
184 var element = document.getElementById("removeAllSignons");
185 var toggle = document.getElementById("togglePasswords");
186 if (signons.length == 0 || gSelectUserInUse) {
187 element.setAttribute("disabled","true");
188 toggle.setAttribute("disabled","true");
189 } else {
190 element.removeAttribute("disabled");
191 toggle.removeAttribute("disabled");
192 }
193
194 return true;
195 }
196
SignonSelected
197 function SignonSelected() {
198 var selections = GetTreeSelections(signonsTree);
199 if (selections.length && !gSelectUserInUse) {
200 document.getElementById("removeSignon").removeAttribute("disabled");
201 }
202 }
203
DeleteSignon
204 function DeleteSignon() {
205 DeleteSelectedItemFromTree(signonsTree, signonsTreeView,
206 signons, deletedSignons,
207 "removeSignon", "removeAllSignons");
208 FinalizeSignonDeletions();
209 }
210
DeleteAllSignons
211 function DeleteAllSignons() {
212 var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
213 .getService(Components.interfaces.nsIPromptService);
214
215 // Confirm the user wants to remove all passwords
216 var dummy = { value: false };
217 if (prompter.confirmEx(window,
218 kSignonBundle.getString("removeAllPasswordsTitle"),
219 kSignonBundle.getString("removeAllPasswordsPrompt"),
220 prompter.STD_YES_NO_BUTTONS + prompter.BUTTON_POS_1_DEFAULT,
221 null, null, null, null, dummy) == 1) // 1 == "No" button
222 return;
223
224 DeleteAllFromTree(signonsTree, signonsTreeView,
225 signons, deletedSignons,
226 "removeSignon", "removeAllSignons");
227 FinalizeSignonDeletions();
228 }
229
TogglePasswordVisible
230 function TogglePasswordVisible() {
231 if (!showingPasswords && !ConfirmShowPasswords())
232 return;
233
234 showingPasswords = !showingPasswords;
235 document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
236 document.getElementById("passwordCol").hidden = !showingPasswords;
237 }
238
AskUserShowPasswords
239 function AskUserShowPasswords() {
240 var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
241 var dummy = { value: false };
242
243 // Confirm the user wants to display passwords
244 return prompter.confirmEx(window,
245 null,
246 kSignonBundle.getString("noMasterPasswordPrompt"),
247 prompter.STD_YES_NO_BUTTONS,
248 null, null, null, null, dummy) == 0; // 0=="Yes" button
249 }
250
ConfirmShowPasswords
251 function ConfirmShowPasswords() {
252 // This doesn't harm if passwords are not encrypted
253 var tokendb = Components.classes["@mozilla.org/security/pk11tokendb;1"]
254 .createInstance(Components.interfaces.nsIPK11TokenDB);
255 var token = tokendb.getInternalKeyToken();
256
257 // If there is no master password, still give the user a chance to opt-out of displaying passwords
258 try
259 {
260 if (token.checkPassword(""))
261 return AskUserShowPasswords();
262 } catch (ex)
263 {
264 // for some reason the call to checkPassword throws an exception for users who have never set a master
265 // password before.
266 return AskUserShowPasswords();
267 }
268
269 // So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
270 try {
271 // Relogin and ask for the master password.
272 token.login(true); // 'true' means always prompt for token password. User will be prompted until
273 // clicking 'Cancel' or entering the correct password.
274 } catch (e) {
275 // An exception will be thrown if the user cancels the login prompt dialog.
276 // User is also logged out of Software Security Device.
277 }
278
279 return token.isLoggedIn();
280 }
281
FinalizeSignonDeletions
282 function FinalizeSignonDeletions() {
283 for (var s=0; s<deletedSignons.length; s++) {
284 passwordmanager.removeUser(deletedSignons[s].host, deletedSignons[s].rawuser);
285 }
286 deletedSignons.length = 0;
287 }
288
HandleSignonKeyPress
289 function HandleSignonKeyPress(e) {
290 if (e.keyCode == 46) {
291 DeleteSignonSelected();
292 }
293 }
294
295 var lastSignonSortColumn = "";
296 var lastSignonSortAscending = false;
297
SignonColumnSort
298 function SignonColumnSort(column) {
299 lastSignonSortAscending =
300 SortTree(signonsTree, signonsTreeView, signons,
301 column, lastSignonSortColumn, lastSignonSortAscending);
302 lastSignonSortColumn = column;
303 }
304
305 /*** =================== REJECTED SIGNONS CODE =================== ***/
306
307 var rejectsTreeView = {
308 rowCount : 0,
setTree
309 setTree : function(tree){},
getImageSrc
310 getImageSrc : function(row,column) {},
getProgressMode
311 getProgressMode : function(row,column) {},
getCellValue
312 getCellValue : function(row,column) {},
getCellText
313 getCellText : function(row,column){
314 var rv="";
315 if (column.id=="rejectCol") {
316 rv = rejects[row].host;
317 }
318 return rv;
319 },
isSeparator
320 isSeparator : function(index) {return false;},
isSorted
321 isSorted: function() { return false; },
isContainer
322 isContainer : function(index) {return false;},
323 cycleHeader : function(column) {},
getRowProperties
324 getRowProperties : function(row,prop){},
getColumnProperties
325 getColumnProperties : function(column,prop){},
getCellProperties
326 getCellProperties : function(row,column,prop){}
327 };
328 var rejectsTree;
329
Reject
330 function Reject(number, host) {
331 this.number = number;
332 this.host = host;
333 }
334
LoadRejects
335 function LoadRejects() {
336 var enumerator = passwordmanager.rejectEnumerator;
337 var count = 0;
338 while (enumerator.hasMoreElements()) {
339 var nextReject = enumerator.getNext();
340 nextReject = nextReject.QueryInterface(Components.interfaces.nsIPassword);
341 var host = nextReject.host;
342 rejects[count] = new Reject(count++, host);
343 }
344 rejectsTreeView.rowCount = rejects.length;
345
346 // sort and display the table
347 rejectsTree.treeBoxObject.view = rejectsTreeView;
348 RejectColumnSort('host');
349
350 var element = document.getElementById("removeAllRejects");
351 if (rejects.length == 0) {
352 element.setAttribute("disabled","true");
353 } else {
354 element.removeAttribute("disabled");
355 }
356 }
357
RejectSelected
358 function RejectSelected() {
359 var selections = GetTreeSelections(rejectsTree);
360 if (selections.length) {
361 document.getElementById("removeReject").removeAttribute("disabled");
362 }
363 }
364
DeleteReject
365 function DeleteReject() {
366 DeleteSelectedItemFromTree(rejectsTree, rejectsTreeView,
367 rejects, deletedRejects,
368 "removeReject", "removeAllRejects");
369 FinalizeRejectDeletions();
370 }
371
DeleteAllRejects
372 function DeleteAllRejects() {
373 DeleteAllFromTree(rejectsTree, rejectsTreeView,
374 rejects, deletedRejects,
375 "removeReject", "removeAllRejects");
376 FinalizeRejectDeletions();
377 }
378
FinalizeRejectDeletions
379 function FinalizeRejectDeletions() {
380 for (var r=0; r<deletedRejects.length; r++) {
381 passwordmanager.removeReject(deletedRejects[r].host);
382 }
383 deletedRejects.length = 0;
384 }
385
HandleRejectKeyPress
386 function HandleRejectKeyPress(e) {
387 if (e.keyCode == 46) {
388 DeleteRejectSelected();
389 }
390 }
391
392 var lastRejectSortColumn = "";
393 var lastRejectSortAscending = false;
394
RejectColumnSort
395 function RejectColumnSort(column) {
396 lastRejectSortAscending =
397 SortTree(rejectsTree, rejectsTreeView, rejects,
398 column, lastRejectSortColumn, lastRejectSortAscending);
399 lastRejectSortColumn = column;
400 }
401
402 /*** =================== GENERAL CODE =================== ***/
403
404 // Remove whitespace from both ends of a string
TrimString
405 function TrimString(string)
406 {
407 if (!string) {
408 return "";
409 }
410 return string.replace(/(^\s+)|(\s+$)/g, '')
411 }
412
DeleteAllFromTree
413 function DeleteAllFromTree
414 (tree, view, table, deletedTable, removeButton, removeAllButton) {
415
416 // remove all items from table and place in deleted table
417 for (var i=0; i<table.length; i++) {
418 deletedTable[deletedTable.length] = table[i];
419 }
420 table.length = 0;
421
422 // clear out selections
423 view.selection.select(-1);
424
425 // update the tree view and notify the tree
426 view.rowCount = 0;
427
428 var box = tree.treeBoxObject;
429 box.rowCountChanged(0, -deletedTable.length);
430 box.invalidate();
431
432
433 // disable buttons
434 document.getElementById(removeButton).setAttribute("disabled", "true")
435 document.getElementById(removeAllButton).setAttribute("disabled","true");
436 }
437
DeleteSelectedItemFromTree
438 function DeleteSelectedItemFromTree
439 (tree, view, table, deletedTable, removeButtonId, removeAllButtonId) {
440
441 var box = tree.treeBoxObject;
442
443 // Remove selected items from list (by setting them to null) and place in
444 // deleted list. At the same time, notify the tree of the row count changes.
445
446 var selection = box.view.selection;
447 var oldSelectStart = table.length;
448 box.beginUpdateBatch();
449
450 var selCount = selection.getRangeCount();
451 var min = new Object();
452 var max = new Object();
453
454 for (var s = 0; s < selCount; ++s) {
455 selection.getRangeAt(s, min, max);
456 var minVal = min.value;
457 var maxVal = max.value;
458
459 oldSelectStart = minVal < oldSelectStart ? minVal : oldSelectStart;
460
461 var rowCount = maxVal - minVal + 1;
462 view.rowCount -= rowCount;
463 box.rowCountChanged(minVal, -rowCount);
464
465 for (var i = minVal; i <= maxVal; ++i) {
466 deletedTable[deletedTable.length] = table[i];
467 table[i] = null;
468 }
469 }
470
471 // collapse list by removing all the null entries
472 for (var j = 0; j < table.length; ++j) {
473 if (!table[j]) {
474 var k = j;
475 while (k < table.length && !table[k])
476 k++;
477
478 table.splice(j, k-j);
479 }
480 }
481
482 box.endUpdateBatch();
483
484 // update selection and/or buttons
485 var removeButton = document.getElementById(removeButtonId);
486 var removeAllButton = document.getElementById(removeAllButtonId);
487
488 if (table.length) {
489 removeButton.removeAttribute("disabled");
490 removeAllButton.removeAttribute("disabled");
491
492 selection.select(oldSelectStart < table.length ? oldSelectStart : table.length - 1);
493 } else {
494 removeButton.setAttribute("disabled", "true");
495 removeAllButton.setAttribute("disabled", "true");
496 }
497 }
498
GetTreeSelections
499 function GetTreeSelections(tree) {
500 var selections = [];
501 var select = tree.view.selection;
502 if (select) {
503 var count = select.getRangeCount();
504 var min = new Object();
505 var max = new Object();
506 for (var i=0; i<count; i++) {
507 select.getRangeAt(i, min, max);
508 for (var k=min.value; k<=max.value; k++) {
509 if (k != -1) {
510 selections[selections.length] = k;
511 }
512 }
513 }
514 }
515 return selections;
516 }
517
SortTree
518 function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending, updateSelection) {
519
520 // remember which item was selected so we can restore it after the sort
521 var selections = GetTreeSelections(tree);
522 var selectedNumber = selections.length ? table[selections[0]].number : -1;
523
524 // determine if sort is to be ascending or descending
525 var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
526
527 // do the sort
528 var compareFunc;
529 if (ascending) {
compare
530 compareFunc = function compare(first, second) {
531 return CompareLowerCase(first[column], second[column]);
532 }
533 } else {
compare
534 compareFunc = function compare(first, second) {
535 return CompareLowerCase(second[column], first[column]);
536 }
537 }
538 table.sort(compareFunc);
539
540 // restore the selection
541 var selectedRow = -1;
542 if (selectedNumber>=0 && updateSelection) {
543 for (var s=0; s<table.length; s++) {
544 if (table[s].number == selectedNumber) {
545 // update selection
546 // note: we need to deselect before reselecting in order to trigger ...Selected()
547 tree.view.selection.select(-1);
548 tree.view.selection.select(s);
549 selectedRow = s;
550 break;
551 }
552 }
553 }
554
555 // display the results
556 tree.treeBoxObject.invalidate();
557 if (selectedRow >= 0) {
558 tree.treeBoxObject.ensureRowIsVisible(selectedRow)
559 }
560
561 return ascending;
562 }
563
564 /**
565 * Case insensitive string comparator.
566 */
CompareLowerCase
567 function CompareLowerCase(first, second) {
568
569 var firstLower = first.toLowerCase();
570 var secondLower = second.toLowerCase();
571
572 if (firstLower < secondLower) {
573 return -1;
574 }
575
576 if (firstLower > secondLower) {
577 return 1;
578 }
579
580 return 0;
581 }