home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2008 October / PCgo 2008-10 (DVD).iso / interface / contents / vollversionen_6617 / 21733 / files / xulrunner / components / nsLoginManagerPrompter.js < prev    next >
Encoding:
Text File  |  2008-08-20  |  39.4 KB  |  1,110 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *  Ehsan Akhgari <ehsan.akhgari@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41. const Cr = Components.results;
  42.  
  43. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  44.  
  45. /*
  46.  * LoginManagerPromptFactory
  47.  *
  48.  * Implements nsIPromptFactory
  49.  *
  50.  * Invoked by NS_NewAuthPrompter2()
  51.  * [embedding/components/windowwatcher/src/nsPrompt.cpp]
  52.  */
  53. function LoginManagerPromptFactory() {}
  54.  
  55. LoginManagerPromptFactory.prototype = {
  56.  
  57.     classDescription : "LoginManagerPromptFactory",
  58.     contractID : "@mozilla.org/passwordmanager/authpromptfactory;1",
  59.     classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
  60.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
  61.  
  62.     getPrompt : function (aWindow, aIID) {
  63.         var prompt = new LoginManagerPrompter().QueryInterface(aIID);
  64.         prompt.init(aWindow);
  65.         return prompt;
  66.     }
  67. }; // end of LoginManagerPromptFactory implementation
  68.  
  69.  
  70.  
  71.  
  72. /* ==================== LoginManagerPrompter ==================== */
  73.  
  74.  
  75.  
  76.  
  77. /*
  78.  * LoginManagerPrompter
  79.  *
  80.  * Implements interfaces for prompting the user to enter/save/change auth info.
  81.  *
  82.  * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
  83.  *
  84.  * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
  85.  * (eg HTTP Authenticate, FTP login).
  86.  *
  87.  * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
  88.  * found in HTML forms.
  89.  */
  90. function LoginManagerPrompter() {}
  91.  
  92. LoginManagerPrompter.prototype = {
  93.  
  94.     classDescription : "LoginManagerPrompter",
  95.     contractID : "@mozilla.org/login-manager/prompter;1",
  96.     classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
  97.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
  98.                                             Ci.nsIAuthPrompt2,
  99.                                             Ci.nsILoginManagerPrompter]),
  100.  
  101.     _window        : null,
  102.     _debug         : false, // mirrors signon.debug
  103.  
  104.     __pwmgr : null, // Password Manager service
  105.     get _pwmgr() {
  106.         if (!this.__pwmgr)
  107.             this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
  108.                            getService(Ci.nsILoginManager);
  109.         return this.__pwmgr;
  110.     },
  111.  
  112.     __logService : null, // Console logging service, used for debugging.
  113.     get _logService() {
  114.         if (!this.__logService)
  115.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  116.                                 getService(Ci.nsIConsoleService);
  117.         return this.__logService;
  118.     },
  119.  
  120.     __promptService : null, // Prompt service for user interaction
  121.     get _promptService() {
  122.         if (!this.__promptService)
  123.             this.__promptService =
  124.                 Cc["@mozilla.org/embedcomp/prompt-service;1"].
  125.                 getService(Ci.nsIPromptService2);
  126.         return this.__promptService;
  127.     },
  128.  
  129.  
  130.     __strBundle : null, // String bundle for L10N
  131.     get _strBundle() {
  132.         if (!this.__strBundle) {
  133.             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
  134.                              getService(Ci.nsIStringBundleService);
  135.             this.__strBundle = bunService.createBundle(
  136.                         "chrome://passwordmgr/locale/passwordmgr.properties");
  137.             if (!this.__strBundle)
  138.                 throw "String bundle for Login Manager not present!";
  139.         }
  140.  
  141.         return this.__strBundle;
  142.     },
  143.  
  144.  
  145.     __brandBundle : null, // String bundle for L10N
  146.     get _brandBundle() {
  147.         if (!this.__brandBundle) {
  148.             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
  149.                              getService(Ci.nsIStringBundleService);
  150.             this.__brandBundle = bunService.createBundle(
  151.                         "chrome://branding/locale/brand.properties");
  152.             if (!this.__brandBundle)
  153.                 throw "Branding string bundle not present!";
  154.         }
  155.  
  156.         return this.__brandBundle;
  157.     },
  158.  
  159.  
  160.     __ioService: null, // IO service for string -> nsIURI conversion
  161.     get _ioService() {
  162.         if (!this.__ioService)
  163.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  164.                                getService(Ci.nsIIOService);
  165.         return this.__ioService;
  166.     },
  167.  
  168.  
  169.     /*
  170.      * log
  171.      *
  172.      * Internal function for logging debug messages to the Error Console window.
  173.      */
  174.     log : function (message) {
  175.         if (!this._debug)
  176.             return;
  177.  
  178.         dump("Pwmgr Prompter: " + message + "\n");
  179.         this._logService.logStringMessage("Pwmgr Prompter: " + message);
  180.     },
  181.  
  182.  
  183.  
  184.  
  185.     /* ---------- nsIAuthPrompt prompts ---------- */
  186.  
  187.  
  188.     /*
  189.      * prompt
  190.      *
  191.      * Wrapper around the prompt service prompt. Saving random fields here
  192.      * doesn't really make sense and therefore isn't implemented.
  193.      */
  194.     prompt : function (aDialogTitle, aText, aPasswordRealm,
  195.                        aSavePassword, aDefaultText, aResult) {
  196.         if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
  197.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  198.  
  199.         this.log("===== prompt() called =====");
  200.  
  201.         if (aDefaultText) {
  202.             aResult.value = aDefaultText;
  203.         }
  204.  
  205.         return this._promptService.prompt(this._window,
  206.                aDialogTitle, aText, aResult, null, {});
  207.     },
  208.  
  209.  
  210.     /*
  211.      * promptUsernameAndPassword
  212.      *
  213.      * Looks up a username and password in the database. Will prompt the user
  214.      * with a dialog, even if a username and password are found.
  215.      */
  216.     promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm,
  217.                                          aSavePassword, aUsername, aPassword) {
  218.         this.log("===== promptUsernameAndPassword() called =====");
  219.  
  220.         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
  221.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  222.  
  223.         var selectedLogin = null;
  224.         var checkBox = { value : false };
  225.         var checkBoxLabel = null;
  226.         var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);
  227.  
  228.         // If hostname is null, we can't save this login.
  229.         if (hostname) {
  230.             var canRememberLogin = (aSavePassword ==
  231.                                     Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
  232.                                    this._pwmgr.getLoginSavingEnabled(hostname);
  233.  
  234.             // if checkBoxLabel is null, the checkbox won't be shown at all.
  235.             if (canRememberLogin)
  236.                 checkBoxLabel = this._getLocalizedString("rememberPassword");
  237.  
  238.             // Look for existing logins.
  239.             var foundLogins = this._pwmgr.findLogins({}, hostname, null,
  240.                                                      realm);
  241.  
  242.             // XXX Like the original code, we can't deal with multiple
  243.             // account selection. (bug 227632)
  244.             if (foundLogins.length > 0) {
  245.                 selectedLogin = foundLogins[0];
  246.  
  247.                 // If the caller provided a username, try to use it. If they
  248.                 // provided only a password, this will try to find a password-only
  249.                 // login (or return null if none exists).
  250.                 if (aUsername.value)
  251.                     selectedLogin = this._repickSelectedLogin(foundLogins,
  252.                                                               aUsername.value);
  253.  
  254.                 if (selectedLogin) {
  255.                     checkBox.value = true;
  256.                     aUsername.value = selectedLogin.username;
  257.                     // If the caller provided a password, prefer it.
  258.                     if (!aPassword.value)
  259.                         aPassword.value = selectedLogin.password;
  260.                 }
  261.             }
  262.         }
  263.  
  264.         var ok = this._promptService.promptUsernameAndPassword(this._window,
  265.                     aDialogTitle, aText, aUsername, aPassword,
  266.                     checkBoxLabel, checkBox);
  267.  
  268.         if (!ok || !checkBox.value || !hostname)
  269.             return ok;
  270.  
  271.         var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  272.                        createInstance(Ci.nsILoginInfo);
  273.         newLogin.init(hostname, null, realm, aUsername.value, aPassword.value,
  274.                       "", "");
  275.  
  276.         // XXX We can't prompt with multiple logins yet (bug 227632), so
  277.         // the entered login might correspond to an existing login
  278.         // other than the one we originally selected.
  279.         selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);
  280.  
  281.         // If we didn't find an existing login, or if the username
  282.         // changed, save as a new login.
  283.         if (!selectedLogin) {
  284.             // add as new
  285.             this.log("New login seen for " + realm);
  286.             this._pwmgr.addLogin(newLogin);
  287.         } else if (aPassword.value != selectedLogin.password) {
  288.             // update password
  289.             this.log("Updating password for  " + realm);
  290.             this._pwmgr.modifyLogin(selectedLogin, newLogin);
  291.         } else {
  292.             this.log("Login unchanged, no further action needed.");
  293.         }
  294.  
  295.         return ok;
  296.     },
  297.  
  298.  
  299.     /*
  300.      * promptPassword
  301.      *
  302.      * If a password is found in the database for the password realm, it is
  303.      * returned straight away without displaying a dialog.
  304.      *
  305.      * If a password is not found in the database, the user will be prompted
  306.      * with a dialog with a text field and ok/cancel buttons. If the user
  307.      * allows it, then the password will be saved in the database.
  308.      */
  309.     promptPassword : function (aDialogTitle, aText, aPasswordRealm,
  310.                                aSavePassword, aPassword) {
  311.         this.log("===== promptPassword called() =====");
  312.  
  313.         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
  314.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  315.  
  316.         var checkBox = { value : false };
  317.         var checkBoxLabel = null;
  318.         var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);
  319.  
  320.         // If hostname is null, we can't save this login.
  321.         if (hostname) {
  322.           var canRememberLogin = (aSavePassword ==
  323.                                   Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
  324.                                  this._pwmgr.getLoginSavingEnabled(hostname);
  325.   
  326.           // if checkBoxLabel is null, the checkbox won't be shown at all.
  327.           if (canRememberLogin)
  328.               checkBoxLabel = this._getLocalizedString("rememberPassword");
  329.   
  330.           if (!aPassword.value) {
  331.               // Look for existing logins.
  332.               var foundLogins = this._pwmgr.findLogins({}, hostname, null,
  333.                                                        realm);
  334.   
  335.               // XXX Like the original code, we can't deal with multiple
  336.               // account selection (bug 227632). We can deal with finding the
  337.               // account based on the supplied username - but in this case we'll
  338.               // just return the first match.
  339.               for (var i = 0; i < foundLogins.length; ++i) {
  340.                   if (foundLogins[i].username == username) {
  341.                       aPassword.value = foundLogins[i].password;
  342.                       // wallet returned straight away, so this mimics that code
  343.                       return true;
  344.                   }
  345.               }
  346.           }
  347.         }
  348.  
  349.         var ok = this._promptService.promptPassword(this._window, aDialogTitle,
  350.                                                     aText, aPassword,
  351.                                                     checkBoxLabel, checkBox);
  352.  
  353.         if (ok && checkBox.value && hostname) {
  354.             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  355.                            createInstance(Ci.nsILoginInfo);
  356.             newLogin.init(hostname, null, realm, username,
  357.                           aPassword.value, "", "");
  358.  
  359.             this.log("New login seen for " + realm);
  360.  
  361.             this._pwmgr.addLogin(newLogin);
  362.         }
  363.  
  364.         return ok;
  365.     },
  366.  
  367.     /* ---------- nsIAuthPrompt helpers ---------- */
  368.  
  369.  
  370.     /**
  371.      * Given aRealmString, such as "http://user@example.com/foo", returns an
  372.      * array of:
  373.      *   - the formatted hostname
  374.      *   - the realm (hostname + path)
  375.      *   - the username, if present
  376.      *
  377.      * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
  378.      * channels, e.g. "example.com:80 (httprealm)", null is returned for all
  379.      * arguments to let callers know the login can't be saved because we don't
  380.      * know whether it's http or https.
  381.      */
  382.     _getRealmInfo : function (aRealmString) {
  383.         var httpRealm = /^.+ \(.+\)$/;
  384.         if (httpRealm.test(aRealmString))
  385.             return [null, null, null];
  386.  
  387.         var uri = this._ioService.newURI(aRealmString, null, null);
  388.         var pathname = "";
  389.  
  390.         if (uri.path != "/")
  391.             pathname = uri.path;
  392.  
  393.         var formattedHostname = this._getFormattedHostname(uri);
  394.  
  395.         return [formattedHostname, formattedHostname + pathname, uri.username];
  396.     },
  397.  
  398.     /* ---------- nsIAuthPrompt2 prompts ---------- */
  399.  
  400.  
  401.  
  402.  
  403.     /*
  404.      * promptAuth
  405.      *
  406.      * Implementation of nsIAuthPrompt2.
  407.      *
  408.      * nsIChannel aChannel
  409.      * int        aLevel
  410.      * nsIAuthInformation aAuthInfo
  411.      */
  412.     promptAuth : function (aChannel, aLevel, aAuthInfo) {
  413.         var selectedLogin = null;
  414.         var checkbox = { value : false };
  415.         var checkboxLabel = null;
  416.         var epicfail = false;
  417.  
  418.         try {
  419.  
  420.             this.log("===== promptAuth called =====");
  421.  
  422.             // If the user submits a login but it fails, we need to remove the
  423.             // notification bar that was displayed. Conveniently, the user will
  424.             // be prompted for authentication again, which brings us here.
  425.             var notifyBox = this._getNotifyBox();
  426.             if (notifyBox)
  427.                 this._removeSaveLoginNotification(notifyBox);
  428.  
  429.             var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
  430.  
  431.  
  432.             // Looks for existing logins to prefill the prompt with.
  433.             var foundLogins = this._pwmgr.findLogins({},
  434.                                         hostname, null, httpRealm);
  435.             this.log("found " + foundLogins.length + " matching logins.");
  436.  
  437.             // XXX Can't select from multiple accounts yet. (bug 227632)
  438.             if (foundLogins.length > 0) {
  439.                 selectedLogin = foundLogins[0];
  440.                 this._SetAuthInfo(aAuthInfo, selectedLogin.username,
  441.                                              selectedLogin.password);
  442.                 checkbox.value = true;
  443.             }
  444.  
  445.             var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
  446.         
  447.             // if checkboxLabel is null, the checkbox won't be shown at all.
  448.             if (canRememberLogin && !notifyBox)
  449.                 checkboxLabel = this._getLocalizedString("rememberPassword");
  450.         } catch (e) {
  451.             // Ignore any errors and display the prompt anyway.
  452.             epicfail = true;
  453.             Components.utils.reportError("LoginManagerPrompter: " +
  454.                 "Epic fail in promptAuth: " + e + "\n");
  455.         }
  456.  
  457.         var ok = this._promptService.promptAuth(this._window, aChannel,
  458.                                 aLevel, aAuthInfo, checkboxLabel, checkbox);
  459.  
  460.         // If there's a notification box, use it to allow the user to
  461.         // determine if the login should be saved. If there isn't a
  462.         // notification box, only save the login if the user set the
  463.         // checkbox to do so.
  464.         var rememberLogin = notifyBox ? canRememberLogin : checkbox.value;
  465.         if (!ok || !rememberLogin || epicfail)
  466.             return ok;
  467.  
  468.         try {
  469.             var [username, password] = this._GetAuthInfo(aAuthInfo);
  470.  
  471.             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  472.                            createInstance(Ci.nsILoginInfo);
  473.             newLogin.init(hostname, null, httpRealm,
  474.                           username, password, "", "");
  475.  
  476.             // XXX We can't prompt with multiple logins yet (bug 227632), so
  477.             // the entered login might correspond to an existing login
  478.             // other than the one we originally selected.
  479.             selectedLogin = this._repickSelectedLogin(foundLogins, username);
  480.  
  481.             // If we didn't find an existing login, or if the username
  482.             // changed, save as a new login.
  483.             if (!selectedLogin) {
  484.                 // add as new
  485.                 this.log("New login seen for " + username +
  486.                          " @ " + hostname + " (" + httpRealm + ")");
  487.                 if (notifyBox)
  488.                     this._showSaveLoginNotification(notifyBox, newLogin);
  489.                 else
  490.                     this._pwmgr.addLogin(newLogin);
  491.  
  492.             } else if (password != selectedLogin.password) {
  493.  
  494.                 this.log("Updating password for " + username +
  495.                          " @ " + hostname + " (" + httpRealm + ")");
  496.                 // update password
  497.                 this._pwmgr.modifyLogin(selectedLogin, newLogin);
  498.  
  499.             } else {
  500.                 this.log("Login unchanged, no further action needed.");
  501.             }
  502.         } catch (e) {
  503.             Components.utils.reportError("LoginManagerPrompter: " +
  504.                 "Fail2 in promptAuth: " + e + "\n");
  505.         }
  506.  
  507.         return ok;
  508.     },
  509.  
  510.     asyncPromptAuth : function () {
  511.         return NS_ERROR_NOT_IMPLEMENTED;
  512.     },
  513.  
  514.  
  515.  
  516.  
  517.     /* ---------- nsILoginManagerPrompter prompts ---------- */
  518.  
  519.  
  520.  
  521.  
  522.     /*
  523.      * init
  524.      *
  525.      */
  526.     init : function (aWindow) {
  527.         this._window = aWindow;
  528.  
  529.         var prefBranch = Cc["@mozilla.org/preferences-service;1"].
  530.                          getService(Ci.nsIPrefService).getBranch("signon.");
  531.         this._debug = prefBranch.getBoolPref("debug");
  532.         this.log("===== initialized =====");
  533.     },
  534.  
  535.  
  536.     /*
  537.      * promptToSavePassword
  538.      *
  539.      */
  540.     promptToSavePassword : function (aLogin) {
  541.         var notifyBox = this._getNotifyBox();
  542.  
  543.         if (notifyBox)
  544.             this._showSaveLoginNotification(notifyBox, aLogin);
  545.         else
  546.             this._showSaveLoginDialog(aLogin);
  547.     },
  548.  
  549.  
  550.     /*
  551.      * _showLoginNotification
  552.      *
  553.      * Displays a notification bar.
  554.      *
  555.      */
  556.     _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
  557.         var oldBar = aNotifyBox.getNotificationWithValue(aName);
  558.         const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
  559.  
  560.         this.log("Adding new " + aName + " notification bar");
  561.         var newBar = aNotifyBox.appendNotification(
  562.                                 aText, aName,
  563.                                 "chrome://mozapps/skin/passwordmgr/key.png",
  564.                                 priority, aButtons);
  565.  
  566.         // The page we're going to hasn't loaded yet, so we want to persist
  567.         // across the first location change.
  568.         newBar.persistence++;
  569.  
  570.         // Sites like Gmail perform a funky redirect dance before you end up
  571.         // at the post-authentication page. I don't see a good way to
  572.         // heuristically determine when to ignore such location changes, so
  573.         // we'll try ignoring location changes based on a time interval.
  574.         newBar.timeout = Date.now() + 20000; // 20 seconds
  575.  
  576.         if (oldBar) {
  577.             this.log("(...and removing old " + aName + " notification bar)");
  578.             aNotifyBox.removeNotification(oldBar);
  579.         }
  580.     },
  581.  
  582.  
  583.     /*
  584.      * _showSaveLoginNotification
  585.      *
  586.      * Displays a notification bar (rather than a popup), to allow the user to
  587.      * save the specified login. This allows the user to see the results of
  588.      * their login, and only save a login which they know worked.
  589.      *
  590.      */
  591.     _showSaveLoginNotification : function (aNotifyBox, aLogin) {
  592.  
  593.         // Ugh. We can't use the strings from the popup window, because they
  594.         // have the access key marked in the string (eg "Mo&zilla"), along
  595.         // with some weird rules for handling access keys that do not occur
  596.         // in the string, for L10N. See commonDialog.js's setLabelForNode().
  597.         var neverButtonText =
  598.               this._getLocalizedString("notifyBarNeverForSiteButtonText");
  599.         var neverButtonAccessKey =
  600.               this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey");
  601.         var rememberButtonText =
  602.               this._getLocalizedString("notifyBarRememberButtonText");
  603.         var rememberButtonAccessKey =
  604.               this._getLocalizedString("notifyBarRememberButtonAccessKey");
  605.         var notNowButtonText =
  606.               this._getLocalizedString("notifyBarNotNowButtonText");
  607.         var notNowButtonAccessKey =
  608.               this._getLocalizedString("notifyBarNotNowButtonAccessKey");
  609.  
  610.         var brandShortName =
  611.               this._brandBundle.GetStringFromName("brandShortName");
  612.         var notificationText  = this._getLocalizedString(
  613.                                         "savePasswordText", [brandShortName]);
  614.  
  615.         // The callbacks in |buttons| have a closure to access the variables
  616.         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
  617.         // without a getService() call.
  618.         var pwmgr = this._pwmgr;
  619.  
  620.  
  621.         var buttons = [
  622.             // "Remember" button
  623.             {
  624.                 label:     rememberButtonText,
  625.                 accessKey: rememberButtonAccessKey,
  626.                 popup:     null,
  627.                 callback: function(aNotificationBar, aButton) {
  628.                     pwmgr.addLogin(aLogin);
  629.                 }
  630.             },
  631.  
  632.             // "Never for this site" button
  633.             {
  634.                 label:     neverButtonText,
  635.                 accessKey: neverButtonAccessKey,
  636.                 popup:     null,
  637.                 callback: function(aNotificationBar, aButton) {
  638.                     pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
  639.                 }
  640.             },
  641.  
  642.             // "Not now" button
  643.             {
  644.                 label:     notNowButtonText,
  645.                 accessKey: notNowButtonAccessKey,
  646.                 popup:     null,
  647.                 callback:  function() { /* NOP */ } 
  648.             }
  649.         ];
  650.  
  651.         this._showLoginNotification(aNotifyBox, "password-save",
  652.              notificationText, buttons);
  653.     },
  654.  
  655.  
  656.     /*
  657.      * _removeSaveLoginNotification
  658.      *
  659.      */
  660.     _removeSaveLoginNotification : function (aNotifyBox) {
  661.  
  662.         var oldBar = aNotifyBox.getNotificationWithValue("password-save");
  663.  
  664.         if (oldBar) {
  665.             this.log("Removing save-password notification bar.");
  666.             aNotifyBox.removeNotification(oldBar);
  667.         }
  668.     },
  669.  
  670.  
  671.     /*
  672.      * _showSaveLoginDialog
  673.      *
  674.      * Called when we detect a new login in a form submission,
  675.      * asks the user what to do.
  676.      *
  677.      */
  678.     _showSaveLoginDialog : function (aLogin) {
  679.         const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
  680.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
  681.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
  682.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
  683.  
  684.         var brandShortName =
  685.                 this._brandBundle.GetStringFromName("brandShortName");
  686.  
  687.         var dialogText         = this._getLocalizedString(
  688.                                         "savePasswordText", [brandShortName]);
  689.         var dialogTitle        = this._getLocalizedString(
  690.                                         "savePasswordTitle");
  691.         var neverButtonText    = this._getLocalizedString(
  692.                                         "neverForSiteButtonText");
  693.         var rememberButtonText = this._getLocalizedString(
  694.                                         "rememberButtonText");
  695.         var notNowButtonText   = this._getLocalizedString(
  696.                                         "notNowButtonText");
  697.  
  698.         this.log("Prompting user to save/ignore login");
  699.         var userChoice = this._promptService.confirmEx(this._window,
  700.                                             dialogTitle, dialogText,
  701.                                             buttonFlags, rememberButtonText,
  702.                                             notNowButtonText, neverButtonText,
  703.                                             null, {});
  704.         //  Returns:
  705.         //   0 - Save the login
  706.         //   1 - Ignore the login this time
  707.         //   2 - Never save logins for this site
  708.         if (userChoice == 2) {
  709.             this.log("Disabling " + aLogin.hostname + " logins by request.");
  710.             this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
  711.         } else if (userChoice == 0) {
  712.             this.log("Saving login for " + aLogin.hostname);
  713.             this._pwmgr.addLogin(aLogin);
  714.         } else {
  715.             // userChoice == 1 --> just ignore the login.
  716.             this.log("Ignoring login.");
  717.         }
  718.     },
  719.  
  720.  
  721.     /*
  722.      * promptToChangePassword
  723.      *
  724.      * Called when we think we detect a password change for an existing
  725.      * login, when the form being submitted contains multiple password
  726.      * fields.
  727.      *
  728.      */
  729.     promptToChangePassword : function (aOldLogin, aNewLogin) {
  730.         var notifyBox = this._getNotifyBox();
  731.  
  732.         if (notifyBox)
  733.             this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin);
  734.         else
  735.             this._showChangeLoginDialog(aOldLogin, aNewLogin);
  736.     },
  737.  
  738.  
  739.     /*
  740.      * _showChangeLoginNotification
  741.      *
  742.      * Shows the Change Password notification bar.
  743.      *
  744.      */
  745.     _showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) {
  746.         var notificationText;
  747.         if (aOldLogin.username)
  748.             notificationText  = this._getLocalizedString(
  749.                                           "passwordChangeText",
  750.                                           [aOldLogin.username]);
  751.         else
  752.             notificationText  = this._getLocalizedString(
  753.                                           "passwordChangeTextNoUser");
  754.  
  755.         var changeButtonText =
  756.               this._getLocalizedString("notifyBarChangeButtonText");
  757.         var changeButtonAccessKey =
  758.               this._getLocalizedString("notifyBarChangeButtonAccessKey");
  759.         var dontChangeButtonText =
  760.               this._getLocalizedString("notifyBarDontChangeButtonText");
  761.         var dontChangeButtonAccessKey =
  762.               this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
  763.  
  764.         // The callbacks in |buttons| have a closure to access the variables
  765.         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
  766.         // without a getService() call.
  767.         var pwmgr = this._pwmgr;
  768.  
  769.         var buttons = [
  770.             // "Yes" button
  771.             {
  772.                 label:     changeButtonText,
  773.                 accessKey: changeButtonAccessKey,
  774.                 popup:     null,
  775.                 callback:  function(aNotificationBar, aButton) {
  776.                     pwmgr.modifyLogin(aOldLogin, aNewLogin);
  777.                 }
  778.             },
  779.  
  780.             // "No" button
  781.             {
  782.                 label:     dontChangeButtonText,
  783.                 accessKey: dontChangeButtonAccessKey,
  784.                 popup:     null,
  785.                 callback:  function(aNotificationBar, aButton) {
  786.                     // do nothing
  787.                 }
  788.             }
  789.         ];
  790.  
  791.         this._showLoginNotification(aNotifyBox, "password-change",
  792.              notificationText, buttons);
  793.     },
  794.  
  795.  
  796.     /*
  797.      * _showChangeLoginDialog
  798.      *
  799.      * Shows the Change Password dialog.
  800.      *
  801.      */
  802.     _showChangeLoginDialog : function (aOldLogin, aNewLogin) {
  803.         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  804.  
  805.         var dialogText;
  806.         if (aOldLogin.username)
  807.             dialogText  = this._getLocalizedString(
  808.                                     "passwordChangeText",
  809.                                     [aOldLogin.username]);
  810.         else
  811.             dialogText  = this._getLocalizedString(
  812.                                     "passwordChangeTextNoUser");
  813.  
  814.         var dialogTitle = this._getLocalizedString(
  815.                                     "passwordChangeTitle");
  816.  
  817.         // returns 0 for yes, 1 for no.
  818.         var ok = !this._promptService.confirmEx(this._window,
  819.                                 dialogTitle, dialogText, buttonFlags,
  820.                                 null, null, null,
  821.                                 null, {});
  822.         if (ok) {
  823.             this.log("Updating password for user " + aOldLogin.username);
  824.             this._pwmgr.modifyLogin(aOldLogin, aNewLogin);
  825.         }
  826.     },
  827.  
  828.  
  829.     /*
  830.      * promptToChangePasswordWithUsernames
  831.      *
  832.      * Called when we detect a password change in a form submission, but we
  833.      * don't know which existing login (username) it's for. Asks the user
  834.      * to select a username and confirm the password change.
  835.      *
  836.      * Note: The caller doesn't know the username for aNewLogin, so this
  837.      *       function fills in .username and .usernameField with the values
  838.      *       from the login selected by the user.
  839.      * 
  840.      * Note; XPCOM stupidity: |count| is just |logins.length|.
  841.      */
  842.     promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
  843.         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  844.  
  845.         var usernames = logins.map(function (l) l.username);
  846.         var dialogText  = this._getLocalizedString("userSelectText");
  847.         var dialogTitle = this._getLocalizedString("passwordChangeTitle");
  848.         var selectedIndex = { value: null };
  849.  
  850.         // If user selects ok, outparam.value is set to the index
  851.         // of the selected username.
  852.         var ok = this._promptService.select(this._window,
  853.                                 dialogTitle, dialogText,
  854.                                 usernames.length, usernames,
  855.                                 selectedIndex);
  856.         if (ok) {
  857.             // Now that we know which login to change the password for,
  858.             // update the missing username info in the aNewLogin.
  859.  
  860.             var selectedLogin = logins[selectedIndex.value];
  861.  
  862.             this.log("Updating password for user " + selectedLogin.username);
  863.  
  864.             aNewLogin.username      = selectedLogin.username;
  865.             aNewLogin.usernameField = selectedLogin.usernameField;
  866.  
  867.             this._pwmgr.modifyLogin(selectedLogin, aNewLogin);
  868.         }
  869.     },
  870.  
  871.  
  872.  
  873.  
  874.     /* ---------- Internal Methods ---------- */
  875.  
  876.  
  877.  
  878.  
  879.     /*
  880.      * _getNotifyBox
  881.      *
  882.      * Returns the notification box to this prompter, or null if there isn't
  883.      * a notification box available.
  884.      */
  885.     _getNotifyBox : function () {
  886.         try {
  887.             // Get topmost window, in case we're in a frame.
  888.             var notifyWindow = this._window.top
  889.  
  890.             // Some sites pop up a temporary login window, when disappears
  891.             // upon submission of credentials. We want to put the notification
  892.             // bar in the opener window if this seems to be happening.
  893.             if (notifyWindow.opener) {
  894.                 var webnav = notifyWindow
  895.                                     .QueryInterface(Ci.nsIInterfaceRequestor)
  896.                                     .getInterface(Ci.nsIWebNavigation);
  897.                 var chromeWin = webnav
  898.                                     .QueryInterface(Ci.nsIDocShellTreeItem)
  899.                                     .rootTreeItem
  900.                                     .QueryInterface(Ci.nsIInterfaceRequestor)
  901.                                     .getInterface(Ci.nsIDOMWindow);
  902.                 var chromeDoc = chromeWin.document.documentElement;
  903.  
  904.                 // Check to see if the current window was opened with chrome
  905.                 // disabled, and if so use the opener window. But if the window
  906.                 // has been used to visit other pages (ie, has a history),
  907.                 // assume it'll stick around and *don't* use the opener.
  908.                 if (chromeDoc.getAttribute("chromehidden") &&
  909.                     webnav.sessionHistory.count == 1) {
  910.                     this.log("Using opener window for notification bar.");
  911.                     notifyWindow = notifyWindow.opener;
  912.                 }
  913.             }
  914.  
  915.  
  916.             // Find the <browser> which contains notifyWindow, by looking
  917.             // through all the open windows and all the <browsers> in each.
  918.             var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  919.                      getService(Ci.nsIWindowMediator);
  920.             var enumerator = wm.getEnumerator("navigator:browser");
  921.             var tabbrowser = null;
  922.             var foundBrowser = null;
  923.  
  924.             while (!foundBrowser && enumerator.hasMoreElements()) {
  925.                 var win = enumerator.getNext();
  926.                 tabbrowser = win.getBrowser(); 
  927.                 foundBrowser = tabbrowser.getBrowserForDocument(
  928.                                                   notifyWindow.document);
  929.             }
  930.  
  931.             // Return the notificationBox associated with the browser.
  932.             if (foundBrowser)
  933.                 return tabbrowser.getNotificationBox(foundBrowser)
  934.  
  935.         } catch (e) {
  936.             // If any errors happen, just assume no notification box.
  937.             this.log("No notification box available: " + e)
  938.         }
  939.  
  940.         return null;
  941.     },
  942.  
  943.  
  944.     /*
  945.      * _repickSelectedLogin
  946.      *
  947.      * The user might enter a login that isn't the one we prefilled, but
  948.      * is the same as some other existing login. So, pick a login with a
  949.      * matching username, or return null.
  950.      */
  951.     _repickSelectedLogin : function (foundLogins, username) {
  952.         for (var i = 0; i < foundLogins.length; i++)
  953.             if (foundLogins[i].username == username)
  954.                 return foundLogins[i];
  955.         return null;
  956.     },
  957.  
  958.     
  959.     /*
  960.      * _getLocalizedString
  961.      *
  962.      * Can be called as:
  963.      *   _getLocalizedString("key1");
  964.      *   _getLocalizedString("key2", ["arg1"]);
  965.      *   _getLocalizedString("key3", ["arg1", "arg2"]);
  966.      *   (etc)
  967.      *
  968.      * Returns the localized string for the specified key,
  969.      * formatted if required.
  970.      *
  971.      */ 
  972.     _getLocalizedString : function (key, formatArgs) {
  973.         if (formatArgs)
  974.             return this._strBundle.formatStringFromName(
  975.                                         key, formatArgs, formatArgs.length);
  976.         else
  977.             return this._strBundle.GetStringFromName(key);
  978.     },
  979.  
  980.  
  981.     /*
  982.      * _getFormattedHostname
  983.      *
  984.      * The aURI parameter may either be a string uri, or an nsIURI instance.
  985.      *
  986.      * Returns the hostname to use in a nsILoginInfo object (for example,
  987.      * "http://example.com").
  988.      */
  989.     _getFormattedHostname : function (aURI) {
  990.         var uri;
  991.         if (aURI instanceof Ci.nsIURI) {
  992.             uri = aURI;
  993.         } else {
  994.             uri = this._ioService.newURI(aURI, null, null);
  995.         }
  996.         var scheme = uri.scheme;
  997.  
  998.         var hostname = scheme + "://" + uri.host;
  999.  
  1000.         // If the URI explicitly specified a port, only include it when
  1001.         // it's not the default. (We never want "http://foo.com:80")
  1002.         port = uri.port;
  1003.         if (port != -1) {
  1004.             var handler = this._ioService.getProtocolHandler(scheme);
  1005.             if (port != handler.defaultPort)
  1006.                 hostname += ":" + port;
  1007.         }
  1008.  
  1009.         return hostname;
  1010.     },
  1011.  
  1012.     /*
  1013.      * _getAuthTarget
  1014.      *
  1015.      * Returns the hostname and realm for which authentication is being
  1016.      * requested, in the format expected to be used with nsILoginInfo.
  1017.      */
  1018.     _getAuthTarget : function (aChannel, aAuthInfo) {
  1019.         var hostname, realm;
  1020.  
  1021.         // If our proxy is demanding authentication, don't use the
  1022.         // channel's actual destination.
  1023.         if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
  1024.             this.log("getAuthTarget is for proxy auth");
  1025.             if (!(aChannel instanceof Ci.nsIProxiedChannel))
  1026.                 throw "proxy auth needs nsIProxiedChannel";
  1027.  
  1028.             var info = aChannel.proxyInfo;
  1029.             if (!info)
  1030.                 throw "proxy auth needs nsIProxyInfo";
  1031.  
  1032.             // Proxies don't have a scheme, but we'll use "moz-proxy://"
  1033.             // so that it's more obvious what the login is for.
  1034.             var idnService = Cc["@mozilla.org/network/idn-service;1"].
  1035.                              getService(Ci.nsIIDNService);
  1036.             hostname = "moz-proxy://" +
  1037.                         idnService.convertUTF8toACE(info.host) +
  1038.                         ":" + info.port;
  1039.             realm = aAuthInfo.realm;
  1040.             if (!realm)
  1041.                 realm = hostname;
  1042.  
  1043.             return [hostname, realm];
  1044.         }
  1045.  
  1046.         hostname = this._getFormattedHostname(aChannel.URI);
  1047.  
  1048.         // If a HTTP WWW-Authenticate header specified a realm, that value
  1049.         // will be available here. If it wasn't set or wasn't HTTP, we'll use
  1050.         // the formatted hostname instead.
  1051.         realm = aAuthInfo.realm;
  1052.         if (!realm)
  1053.             realm = hostname;
  1054.  
  1055.         return [hostname, realm];
  1056.     },
  1057.  
  1058.  
  1059.     /**
  1060.      * Returns [username, password] as extracted from aAuthInfo (which
  1061.      * holds this info after having prompted the user).
  1062.      *
  1063.      * If the authentication was for a Windows domain, we'll prepend the
  1064.      * return username with the domain. (eg, "domain\user")
  1065.      */
  1066.     _GetAuthInfo : function (aAuthInfo) {
  1067.         var username, password;
  1068.  
  1069.         var flags = aAuthInfo.flags;
  1070.         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
  1071.             username = aAuthInfo.domain + "\\" + aAuthInfo.username;
  1072.         else
  1073.             username = aAuthInfo.username;
  1074.  
  1075.         password = aAuthInfo.password;
  1076.  
  1077.         return [username, password];
  1078.     },
  1079.  
  1080.  
  1081.     /**
  1082.      * Given a username (possibly in DOMAIN\user form) and password, parses the
  1083.      * domain out of the username if necessary and sets domain, username and
  1084.      * password on the auth information object.
  1085.      */
  1086.     _SetAuthInfo : function (aAuthInfo, username, password) {
  1087.         var flags = aAuthInfo.flags;
  1088.         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
  1089.             // Domain is separated from username by a backslash
  1090.             var idx = username.indexOf("\\");
  1091.             if (idx == -1) {
  1092.                 aAuthInfo.username = username;
  1093.             } else {
  1094.                 aAuthInfo.domain   =  username.substring(0, idx);
  1095.                 aAuthInfo.username =  username.substring(idx+1);
  1096.             }
  1097.         } else {
  1098.             aAuthInfo.username = username;
  1099.         }
  1100.         aAuthInfo.password = password;
  1101.     }
  1102.  
  1103. }; // end of LoginManagerPrompter implementation
  1104.  
  1105.  
  1106. var component = [LoginManagerPromptFactory, LoginManagerPrompter];
  1107. function NSGetModule(compMgr, fileSpec) {
  1108.     return XPCOMUtils.generateModule(component);
  1109. }
  1110.