toolkit/components/passwordmgr/nsLoginManagerPrompter.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 const Cc = Components.classes;
     7 const Ci = Components.interfaces;
     8 const Cr = Components.results;
    10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    11 Components.utils.import("resource://gre/modules/Services.jsm");
    12 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
    14 /*
    15  * LoginManagerPromptFactory
    16  *
    17  * Implements nsIPromptFactory
    18  *
    19  * Invoked by [toolkit/components/prompts/src/nsPrompter.js]
    20  */
    21 function LoginManagerPromptFactory() {
    22     Services.obs.addObserver(this, "quit-application-granted", true);
    23     Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
    24     Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true);
    25 }
    27 LoginManagerPromptFactory.prototype = {
    29     classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
    30     QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]),
    32     _debug : false,
    33     _asyncPrompts : {},
    34     _asyncPromptInProgress : false,
    36     observe : function (subject, topic, data) {
    37         this.log("Observed: " + topic);
    38         if (topic == "quit-application-granted") {
    39             this._cancelPendingPrompts();
    40         } else if (topic == "passwordmgr-crypto-login") {
    41             // Start processing the deferred prompters.
    42             this._doAsyncPrompt();
    43         } else if (topic == "passwordmgr-crypto-loginCanceled") {
    44             // User canceled a Master Password prompt, so go ahead and cancel
    45             // all pending auth prompts to avoid nagging over and over.
    46             this._cancelPendingPrompts();
    47         }
    48     },
    50     getPrompt : function (aWindow, aIID) {
    51         var prefBranch = Services.prefs.getBranch("signon.");
    52         this._debug = prefBranch.getBoolPref("debug");
    54         var prompt = new LoginManagerPrompter().QueryInterface(aIID);
    55         prompt.init(aWindow, this);
    56         return prompt;
    57     },
    59     _doAsyncPrompt : function() {
    60         if (this._asyncPromptInProgress) {
    61             this.log("_doAsyncPrompt bypassed, already in progress");
    62             return;
    63         }
    65         // Find the first prompt key we have in the queue
    66         var hashKey = null;
    67         for (hashKey in this._asyncPrompts)
    68             break;
    70         if (!hashKey) {
    71             this.log("_doAsyncPrompt:run bypassed, no prompts in the queue");
    72             return;
    73         }
    75         // If login manger has logins for this host, defer prompting if we're
    76         // already waiting on a master password entry.
    77         var prompt = this._asyncPrompts[hashKey];
    78         var prompter = prompt.prompter;
    79         var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo);
    80         var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0);
    81         if (hasLogins && prompter._pwmgr.uiBusy) {
    82             this.log("_doAsyncPrompt:run bypassed, master password UI busy");
    83             return;
    84         }
    86         this._asyncPromptInProgress = true;
    87         prompt.inProgress = true;
    89         var self = this;
    91         var runnable = {
    92             run : function() {
    93                 var ok = false;
    94                 try {
    95                     self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
    96                     ok = prompter.promptAuth(prompt.channel,
    97                                              prompt.level,
    98                                              prompt.authInfo);
    99                 } catch (e if (e instanceof Components.Exception) &&
   100                                e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
   101                     self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
   102                 } catch (e) {
   103                     Components.utils.reportError("LoginManagerPrompter: " +
   104                         "_doAsyncPrompt:run: " + e + "\n");
   105                 }
   107                 delete self._asyncPrompts[hashKey];
   108                 prompt.inProgress = false;
   109                 self._asyncPromptInProgress = false;
   111                 for each (var consumer in prompt.consumers) {
   112                     if (!consumer.callback)
   113                         // Not having a callback means that consumer didn't provide it
   114                         // or canceled the notification
   115                         continue;
   117                     self.log("Calling back to " + consumer.callback + " ok=" + ok);
   118                     try {
   119                         if (ok)
   120                             consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
   121                         else
   122                             consumer.callback.onAuthCancelled(consumer.context, true);
   123                     } catch (e) { /* Throw away exceptions caused by callback */ }
   124                 }
   125                 self._doAsyncPrompt();
   126             }
   127         }
   129         Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
   130         this.log("_doAsyncPrompt:run dispatched");
   131     },
   134     _cancelPendingPrompts : function() {
   135         this.log("Canceling all pending prompts...");
   136         var asyncPrompts = this._asyncPrompts;
   137         this.__proto__._asyncPrompts = {};
   139         for each (var prompt in asyncPrompts) {
   140             // Watch out! If this prompt is currently prompting, let it handle
   141             // notifying the callbacks of success/failure, since it's already
   142             // asking the user for input. Reusing a callback can be crashy.
   143             if (prompt.inProgress) {
   144                 this.log("skipping a prompt in progress");
   145                 continue;
   146             }
   148             for each (var consumer in prompt.consumers) {
   149                 if (!consumer.callback)
   150                     continue;
   152                 this.log("Canceling async auth prompt callback " + consumer.callback);
   153                 try {
   154                     consumer.callback.onAuthCancelled(consumer.context, true);
   155                 } catch (e) { /* Just ignore exceptions from the callback */ }
   156             }
   157         }
   158     },
   161     log : function (message) {
   162         if (!this._debug)
   163             return;
   165         dump("Pwmgr PromptFactory: " + message + "\n");
   166         Services.console.logStringMessage("Pwmgr PrompFactory: " + message);
   167     }
   168 }; // end of LoginManagerPromptFactory implementation
   173 /* ==================== LoginManagerPrompter ==================== */
   178 /*
   179  * LoginManagerPrompter
   180  *
   181  * Implements interfaces for prompting the user to enter/save/change auth info.
   182  *
   183  * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
   184  *
   185  * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
   186  * (eg HTTP Authenticate, FTP login).
   187  *
   188  * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
   189  * found in HTML forms.
   190  */
   191 function LoginManagerPrompter() {}
   193 LoginManagerPrompter.prototype = {
   195     classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
   196     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
   197                                             Ci.nsIAuthPrompt2,
   198                                             Ci.nsILoginManagerPrompter]),
   200     _factory       : null,
   201     _window        : null,
   202     _debug         : false, // mirrors signon.debug
   204     __pwmgr : null, // Password Manager service
   205     get _pwmgr() {
   206         if (!this.__pwmgr)
   207             this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
   208                            getService(Ci.nsILoginManager);
   209         return this.__pwmgr;
   210     },
   212     __promptService : null, // Prompt service for user interaction
   213     get _promptService() {
   214         if (!this.__promptService)
   215             this.__promptService =
   216                 Cc["@mozilla.org/embedcomp/prompt-service;1"].
   217                 getService(Ci.nsIPromptService2);
   218         return this.__promptService;
   219     },
   222     __strBundle : null, // String bundle for L10N
   223     get _strBundle() {
   224         if (!this.__strBundle) {
   225             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
   226                              getService(Ci.nsIStringBundleService);
   227             this.__strBundle = bunService.createBundle(
   228                         "chrome://passwordmgr/locale/passwordmgr.properties");
   229             if (!this.__strBundle)
   230                 throw "String bundle for Login Manager not present!";
   231         }
   233         return this.__strBundle;
   234     },
   237     __ellipsis : null,
   238     get _ellipsis() {
   239         if (!this.__ellipsis) {
   240             this.__ellipsis = "\u2026";
   241             try {
   242                 this.__ellipsis = Services.prefs.getComplexValue(
   243                                     "intl.ellipsis", Ci.nsIPrefLocalizedString).data;
   244             } catch (e) { }
   245         }
   246         return this.__ellipsis;
   247     },
   250     // Whether we are in private browsing mode
   251     get _inPrivateBrowsing() {
   252       if (this._window) {
   253         return PrivateBrowsingUtils.isWindowPrivate(this._window);
   254       } else {
   255         // If we don't that we're in private browsing mode if the caller did
   256         // not provide a window.  The callers which really care about this
   257         // will indeed pass down a window to us, and for those who don't,
   258         // we can just assume that we don't want to save the entered login
   259         // information.
   260         return true;
   261       }
   262     },
   265     /*
   266      * log
   267      *
   268      * Internal function for logging debug messages to the Error Console window.
   269      */
   270     log : function (message) {
   271         if (!this._debug)
   272             return;
   274         dump("Pwmgr Prompter: " + message + "\n");
   275         Services.console.logStringMessage("Pwmgr Prompter: " + message);
   276     },
   281     /* ---------- nsIAuthPrompt prompts ---------- */
   284     /*
   285      * prompt
   286      *
   287      * Wrapper around the prompt service prompt. Saving random fields here
   288      * doesn't really make sense and therefore isn't implemented.
   289      */
   290     prompt : function (aDialogTitle, aText, aPasswordRealm,
   291                        aSavePassword, aDefaultText, aResult) {
   292         if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
   293             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   295         this.log("===== prompt() called =====");
   297         if (aDefaultText) {
   298             aResult.value = aDefaultText;
   299         }
   301         return this._promptService.prompt(this._window,
   302                aDialogTitle, aText, aResult, null, {});
   303     },
   306     /*
   307      * promptUsernameAndPassword
   308      *
   309      * Looks up a username and password in the database. Will prompt the user
   310      * with a dialog, even if a username and password are found.
   311      */
   312     promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm,
   313                                          aSavePassword, aUsername, aPassword) {
   314         this.log("===== promptUsernameAndPassword() called =====");
   316         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
   317             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   319         var selectedLogin = null;
   320         var checkBox = { value : false };
   321         var checkBoxLabel = null;
   322         var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);
   324         // If hostname is null, we can't save this login.
   325         if (hostname) {
   326             var canRememberLogin;
   327             if (this._inPrivateBrowsing)
   328                 canRememberLogin = false;
   329             else
   330                 canRememberLogin = (aSavePassword ==
   331                                     Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
   332                                    this._pwmgr.getLoginSavingEnabled(hostname);
   334             // if checkBoxLabel is null, the checkbox won't be shown at all.
   335             if (canRememberLogin)
   336                 checkBoxLabel = this._getLocalizedString("rememberPassword");
   338             // Look for existing logins.
   339             var foundLogins = this._pwmgr.findLogins({}, hostname, null,
   340                                                      realm);
   342             // XXX Like the original code, we can't deal with multiple
   343             // account selection. (bug 227632)
   344             if (foundLogins.length > 0) {
   345                 selectedLogin = foundLogins[0];
   347                 // If the caller provided a username, try to use it. If they
   348                 // provided only a password, this will try to find a password-only
   349                 // login (or return null if none exists).
   350                 if (aUsername.value)
   351                     selectedLogin = this._repickSelectedLogin(foundLogins,
   352                                                               aUsername.value);
   354                 if (selectedLogin) {
   355                     checkBox.value = true;
   356                     aUsername.value = selectedLogin.username;
   357                     // If the caller provided a password, prefer it.
   358                     if (!aPassword.value)
   359                         aPassword.value = selectedLogin.password;
   360                 }
   361             }
   362         }
   364         var ok = this._promptService.promptUsernameAndPassword(this._window,
   365                     aDialogTitle, aText, aUsername, aPassword,
   366                     checkBoxLabel, checkBox);
   368         if (!ok || !checkBox.value || !hostname)
   369             return ok;
   371         if (!aPassword.value) {
   372             this.log("No password entered, so won't offer to save.");
   373             return ok;
   374         }
   376         // XXX We can't prompt with multiple logins yet (bug 227632), so
   377         // the entered login might correspond to an existing login
   378         // other than the one we originally selected.
   379         selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);
   381         // If we didn't find an existing login, or if the username
   382         // changed, save as a new login.
   383         if (!selectedLogin) {
   384             // add as new
   385             this.log("New login seen for " + realm);
   386             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
   387                            createInstance(Ci.nsILoginInfo);
   388             newLogin.init(hostname, null, realm,
   389                           aUsername.value, aPassword.value, "", "");
   390             this._pwmgr.addLogin(newLogin);
   391         } else if (aPassword.value != selectedLogin.password) {
   392             // update password
   393             this.log("Updating password for  " + realm);
   394             this._updateLogin(selectedLogin, aPassword.value);
   395         } else {
   396             this.log("Login unchanged, no further action needed.");
   397             this._updateLogin(selectedLogin);
   398         }
   400         return ok;
   401     },
   404     /*
   405      * promptPassword
   406      *
   407      * If a password is found in the database for the password realm, it is
   408      * returned straight away without displaying a dialog.
   409      *
   410      * If a password is not found in the database, the user will be prompted
   411      * with a dialog with a text field and ok/cancel buttons. If the user
   412      * allows it, then the password will be saved in the database.
   413      */
   414     promptPassword : function (aDialogTitle, aText, aPasswordRealm,
   415                                aSavePassword, aPassword) {
   416         this.log("===== promptPassword called() =====");
   418         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
   419             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   421         var checkBox = { value : false };
   422         var checkBoxLabel = null;
   423         var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);
   425         username = decodeURIComponent(username);
   427         // If hostname is null, we can't save this login.
   428         if (hostname && !this._inPrivateBrowsing) {
   429           var canRememberLogin = (aSavePassword ==
   430                                   Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
   431                                  this._pwmgr.getLoginSavingEnabled(hostname);
   433           // if checkBoxLabel is null, the checkbox won't be shown at all.
   434           if (canRememberLogin)
   435               checkBoxLabel = this._getLocalizedString("rememberPassword");
   437           if (!aPassword.value) {
   438               // Look for existing logins.
   439               var foundLogins = this._pwmgr.findLogins({}, hostname, null,
   440                                                        realm);
   442               // XXX Like the original code, we can't deal with multiple
   443               // account selection (bug 227632). We can deal with finding the
   444               // account based on the supplied username - but in this case we'll
   445               // just return the first match.
   446               for (var i = 0; i < foundLogins.length; ++i) {
   447                   if (foundLogins[i].username == username) {
   448                       aPassword.value = foundLogins[i].password;
   449                       // wallet returned straight away, so this mimics that code
   450                       return true;
   451                   }
   452               }
   453           }
   454         }
   456         var ok = this._promptService.promptPassword(this._window, aDialogTitle,
   457                                                     aText, aPassword,
   458                                                     checkBoxLabel, checkBox);
   460         if (ok && checkBox.value && hostname && aPassword.value) {
   461             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
   462                            createInstance(Ci.nsILoginInfo);
   463             newLogin.init(hostname, null, realm, username,
   464                           aPassword.value, "", "");
   466             this.log("New login seen for " + realm);
   468             this._pwmgr.addLogin(newLogin);
   469         }
   471         return ok;
   472     },
   474     /* ---------- nsIAuthPrompt helpers ---------- */
   477     /**
   478      * Given aRealmString, such as "http://user@example.com/foo", returns an
   479      * array of:
   480      *   - the formatted hostname
   481      *   - the realm (hostname + path)
   482      *   - the username, if present
   483      *
   484      * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
   485      * channels, e.g. "example.com:80 (httprealm)", null is returned for all
   486      * arguments to let callers know the login can't be saved because we don't
   487      * know whether it's http or https.
   488      */
   489     _getRealmInfo : function (aRealmString) {
   490         var httpRealm = /^.+ \(.+\)$/;
   491         if (httpRealm.test(aRealmString))
   492             return [null, null, null];
   494         var uri = Services.io.newURI(aRealmString, null, null);
   495         var pathname = "";
   497         if (uri.path != "/")
   498             pathname = uri.path;
   500         var formattedHostname = this._getFormattedHostname(uri);
   502         return [formattedHostname, formattedHostname + pathname, uri.username];
   503     },
   505     /* ---------- nsIAuthPrompt2 prompts ---------- */
   510     /*
   511      * promptAuth
   512      *
   513      * Implementation of nsIAuthPrompt2.
   514      *
   515      * nsIChannel aChannel
   516      * int        aLevel
   517      * nsIAuthInformation aAuthInfo
   518      */
   519     promptAuth : function (aChannel, aLevel, aAuthInfo) {
   520         var selectedLogin = null;
   521         var checkbox = { value : false };
   522         var checkboxLabel = null;
   523         var epicfail = false;
   524         var canAutologin = false;
   526         try {
   528             this.log("===== promptAuth called =====");
   530             // If the user submits a login but it fails, we need to remove the
   531             // notification bar that was displayed. Conveniently, the user will
   532             // be prompted for authentication again, which brings us here.
   533             this._removeLoginNotifications();
   535             var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
   538             // Looks for existing logins to prefill the prompt with.
   539             var foundLogins = this._pwmgr.findLogins({},
   540                                         hostname, null, httpRealm);
   541             this.log("found " + foundLogins.length + " matching logins.");
   543             // XXX Can't select from multiple accounts yet. (bug 227632)
   544             if (foundLogins.length > 0) {
   545                 selectedLogin = foundLogins[0];
   546                 this._SetAuthInfo(aAuthInfo, selectedLogin.username,
   547                                              selectedLogin.password);
   549                 // Allow automatic proxy login
   550                 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
   551                     !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
   552                     Services.prefs.getBoolPref("signon.autologin.proxy") &&
   553                     !this._inPrivateBrowsing) {
   555                     this.log("Autologin enabled, skipping auth prompt.");
   556                     canAutologin = true;
   557                 }
   559                 checkbox.value = true;
   560             }
   562             var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
   563             if (this._inPrivateBrowsing)
   564               canRememberLogin = false;
   566             // if checkboxLabel is null, the checkbox won't be shown at all.
   567             var notifyBox = this._getNotifyBox();
   568             if (canRememberLogin && !notifyBox)
   569                 checkboxLabel = this._getLocalizedString("rememberPassword");
   570         } catch (e) {
   571             // Ignore any errors and display the prompt anyway.
   572             epicfail = true;
   573             Components.utils.reportError("LoginManagerPrompter: " +
   574                 "Epic fail in promptAuth: " + e + "\n");
   575         }
   577         var ok = canAutologin ||
   578                  this._promptService.promptAuth(this._window,
   579                                                 aChannel, aLevel, aAuthInfo,
   580                                                 checkboxLabel, checkbox);
   582         // If there's a notification box, use it to allow the user to
   583         // determine if the login should be saved. If there isn't a
   584         // notification box, only save the login if the user set the
   585         // checkbox to do so.
   586         var rememberLogin = notifyBox ? canRememberLogin : checkbox.value;
   587         if (!ok || !rememberLogin || epicfail)
   588             return ok;
   590         try {
   591             var [username, password] = this._GetAuthInfo(aAuthInfo);
   593             if (!password) {
   594                 this.log("No password entered, so won't offer to save.");
   595                 return ok;
   596             }
   598             // XXX We can't prompt with multiple logins yet (bug 227632), so
   599             // the entered login might correspond to an existing login
   600             // other than the one we originally selected.
   601             selectedLogin = this._repickSelectedLogin(foundLogins, username);
   603             // If we didn't find an existing login, or if the username
   604             // changed, save as a new login.
   605             if (!selectedLogin) {
   606                 this.log("New login seen for " + username +
   607                          " @ " + hostname + " (" + httpRealm + ")");
   609                 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
   610                                createInstance(Ci.nsILoginInfo);
   611                 newLogin.init(hostname, null, httpRealm,
   612                               username, password, "", "");
   613                 var notifyObj = this._getPopupNote() || notifyBox;
   614                 if (notifyObj)
   615                     this._showSaveLoginNotification(notifyObj, newLogin);
   616                 else
   617                     this._pwmgr.addLogin(newLogin);
   619             } else if (password != selectedLogin.password) {
   621                 this.log("Updating password for " + username +
   622                          " @ " + hostname + " (" + httpRealm + ")");
   623                 var notifyObj = this._getPopupNote() || notifyBox;
   624                 if (notifyObj)
   625                     this._showChangeLoginNotification(notifyObj,
   626                                                       selectedLogin, password);
   627                 else
   628                     this._updateLogin(selectedLogin, password);
   630             } else {
   631                 this.log("Login unchanged, no further action needed.");
   632                 this._updateLogin(selectedLogin);
   633             }
   634         } catch (e) {
   635             Components.utils.reportError("LoginManagerPrompter: " +
   636                 "Fail2 in promptAuth: " + e + "\n");
   637         }
   639         return ok;
   640     },
   642     asyncPromptAuth : function (aChannel, aCallback, aContext, aLevel, aAuthInfo) {
   643         var cancelable = null;
   645         try {
   646             this.log("===== asyncPromptAuth called =====");
   648             // If the user submits a login but it fails, we need to remove the
   649             // notification bar that was displayed. Conveniently, the user will
   650             // be prompted for authentication again, which brings us here.
   651             this._removeLoginNotifications();
   653             cancelable = this._newAsyncPromptConsumer(aCallback, aContext);
   655             var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
   657             var hashKey = aLevel + "|" + hostname + "|" + httpRealm;
   658             this.log("Async prompt key = " + hashKey);
   659             var asyncPrompt = this._factory._asyncPrompts[hashKey];
   660             if (asyncPrompt) {
   661                 this.log("Prompt bound to an existing one in the queue, callback = " + aCallback);
   662                 asyncPrompt.consumers.push(cancelable);
   663                 return cancelable;
   664             }
   666             this.log("Adding new prompt to the queue, callback = " + aCallback);
   667             asyncPrompt = {
   668                 consumers: [cancelable],
   669                 channel: aChannel,
   670                 authInfo: aAuthInfo,
   671                 level: aLevel,
   672                 inProgress : false,
   673                 prompter: this
   674             }
   676             this._factory._asyncPrompts[hashKey] = asyncPrompt;
   677             this._factory._doAsyncPrompt();
   678         }
   679         catch (e) {
   680             Components.utils.reportError("LoginManagerPrompter: " +
   681                 "asyncPromptAuth: " + e + "\nFalling back to promptAuth\n");
   682             // Fail the prompt operation to let the consumer fall back
   683             // to synchronous promptAuth method
   684             throw e;
   685         }
   687         return cancelable;
   688     },
   693     /* ---------- nsILoginManagerPrompter prompts ---------- */
   698     /*
   699      * init
   700      *
   701      */
   702     init : function (aWindow, aFactory) {
   703         this._window = aWindow;
   704         this._factory = aFactory || null;
   706         var prefBranch = Services.prefs.getBranch("signon.");
   707         this._debug = prefBranch.getBoolPref("debug");
   708         this.log("===== initialized =====");
   709     },
   712     /*
   713      * promptToSavePassword
   714      *
   715      */
   716     promptToSavePassword : function (aLogin) {
   717         var notifyObj = this._getPopupNote() || this._getNotifyBox();
   719         if (notifyObj)
   720             this._showSaveLoginNotification(notifyObj, aLogin);
   721         else
   722             this._showSaveLoginDialog(aLogin);
   723     },
   726     /*
   727      * _showLoginNotification
   728      *
   729      * Displays a notification bar.
   730      *
   731      */
   732     _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
   733         var oldBar = aNotifyBox.getNotificationWithValue(aName);
   734         const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
   736         this.log("Adding new " + aName + " notification bar");
   737         var newBar = aNotifyBox.appendNotification(
   738                                 aText, aName,
   739                                 "chrome://mozapps/skin/passwordmgr/key.png",
   740                                 priority, aButtons);
   742         // The page we're going to hasn't loaded yet, so we want to persist
   743         // across the first location change.
   744         newBar.persistence++;
   746         // Sites like Gmail perform a funky redirect dance before you end up
   747         // at the post-authentication page. I don't see a good way to
   748         // heuristically determine when to ignore such location changes, so
   749         // we'll try ignoring location changes based on a time interval.
   750         newBar.timeout = Date.now() + 20000; // 20 seconds
   752         if (oldBar) {
   753             this.log("(...and removing old " + aName + " notification bar)");
   754             aNotifyBox.removeNotification(oldBar);
   755         }
   756     },
   759     /*
   760      * _showSaveLoginNotification
   761      *
   762      * Displays a notification bar or a popup notification, to allow the user
   763      * to save the specified login. This allows the user to see the results of
   764      * their login, and only save a login which they know worked.
   765      *
   766      * @param aNotifyObj
   767      *        A notification box or a popup notification.
   768      */
   769     _showSaveLoginNotification : function (aNotifyObj, aLogin) {
   771         // Ugh. We can't use the strings from the popup window, because they
   772         // have the access key marked in the string (eg "Mo&zilla"), along
   773         // with some weird rules for handling access keys that do not occur
   774         // in the string, for L10N. See commonDialog.js's setLabelForNode().
   775         var neverButtonText =
   776               this._getLocalizedString("notifyBarNeverRememberButtonText");
   777         var neverButtonAccessKey =
   778               this._getLocalizedString("notifyBarNeverRememberButtonAccessKey");
   779         var rememberButtonText =
   780               this._getLocalizedString("notifyBarRememberPasswordButtonText");
   781         var rememberButtonAccessKey =
   782               this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey");
   784         var displayHost = this._getShortDisplayHost(aLogin.hostname);
   785         var notificationText;
   786         if (aLogin.username) {
   787             var displayUser = this._sanitizeUsername(aLogin.username);
   788             notificationText  = this._getLocalizedString(
   789                                         "rememberPasswordMsg",
   790                                         [displayUser, displayHost]);
   791         } else {
   792             notificationText  = this._getLocalizedString(
   793                                         "rememberPasswordMsgNoUsername",
   794                                         [displayHost]);
   795         }
   797         // The callbacks in |buttons| have a closure to access the variables
   798         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
   799         // without a getService() call.
   800         var pwmgr = this._pwmgr;
   802         // Notification is a PopupNotification
   803         if (aNotifyObj == this._getPopupNote()) {
   804             // "Remember" button
   805             var mainAction = {
   806                 label:     rememberButtonText,
   807                 accessKey: rememberButtonAccessKey,
   808                 callback: function(aNotifyObj, aButton) {
   809                     pwmgr.addLogin(aLogin);
   810                     browser.focus();
   811                 }
   812             };
   814             var secondaryActions = [
   815                 // "Never for this site" button
   816                 {
   817                     label:     neverButtonText,
   818                     accessKey: neverButtonAccessKey,
   819                     callback: function(aNotifyObj, aButton) {
   820                         pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
   821                         browser.focus();
   822                     }
   823                 }
   824             ];
   826             var notifyWin = this._getNotifyWindow();
   827             var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
   828             var browser = chromeWin.gBrowser.
   829                                     getBrowserForDocument(notifyWin.top.document);
   831             aNotifyObj.show(browser, "password-save", notificationText,
   832                             "password-notification-icon", mainAction,
   833                             secondaryActions, { timeout: Date.now() + 10000,
   834                                                 persistWhileVisible: true });
   835         } else {
   836             var notNowButtonText =
   837                   this._getLocalizedString("notifyBarNotNowButtonText");
   838             var notNowButtonAccessKey =
   839                   this._getLocalizedString("notifyBarNotNowButtonAccessKey");
   840             var buttons = [
   841                 // "Remember" button
   842                 {
   843                     label:     rememberButtonText,
   844                     accessKey: rememberButtonAccessKey,
   845                     popup:     null,
   846                     callback: function(aNotifyObj, aButton) {
   847                         pwmgr.addLogin(aLogin);
   848                     }
   849                 },
   851                 // "Never for this site" button
   852                 {
   853                     label:     neverButtonText,
   854                     accessKey: neverButtonAccessKey,
   855                     popup:     null,
   856                     callback: function(aNotifyObj, aButton) {
   857                         pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
   858                     }
   859                 },
   861                 // "Not now" button
   862                 {
   863                     label:     notNowButtonText,
   864                     accessKey: notNowButtonAccessKey,
   865                     popup:     null,
   866                     callback:  function() { /* NOP */ }
   867                 }
   868             ];
   870             this._showLoginNotification(aNotifyObj, "password-save",
   871                                         notificationText, buttons);
   872         }
   873     },
   876     /*
   877      * _removeLoginNotifications
   878      *
   879      */
   880     _removeLoginNotifications : function () {
   881         var popupNote = this._getPopupNote();
   882         if (popupNote)
   883             popupNote = popupNote.getNotification("password-save");
   884         if (popupNote)
   885             popupNote.remove();
   887         var notifyBox = this._getNotifyBox();
   888         if (notifyBox) {
   889             var oldBar = notifyBox.getNotificationWithValue("password-save");
   890             if (oldBar) {
   891                 this.log("Removing save-password notification bar.");
   892                 notifyBox.removeNotification(oldBar);
   893             }
   895             oldBar = notifyBox.getNotificationWithValue("password-change");
   896             if (oldBar) {
   897                 this.log("Removing change-password notification bar.");
   898                 notifyBox.removeNotification(oldBar);
   899             }
   900         }
   901     },
   904     /*
   905      * _showSaveLoginDialog
   906      *
   907      * Called when we detect a new login in a form submission,
   908      * asks the user what to do.
   909      *
   910      */
   911     _showSaveLoginDialog : function (aLogin) {
   912         const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
   913             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
   914             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
   915             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
   917         var displayHost = this._getShortDisplayHost(aLogin.hostname);
   919         var dialogText;
   920         if (aLogin.username) {
   921             var displayUser = this._sanitizeUsername(aLogin.username);
   922             dialogText = this._getLocalizedString(
   923                                  "rememberPasswordMsg",
   924                                  [displayUser, displayHost]);
   925         } else {
   926             dialogText = this._getLocalizedString(
   927                                  "rememberPasswordMsgNoUsername",
   928                                  [displayHost]);
   930         }
   931         var dialogTitle        = this._getLocalizedString(
   932                                         "savePasswordTitle");
   933         var neverButtonText    = this._getLocalizedString(
   934                                         "neverForSiteButtonText");
   935         var rememberButtonText = this._getLocalizedString(
   936                                         "rememberButtonText");
   937         var notNowButtonText   = this._getLocalizedString(
   938                                         "notNowButtonText");
   940         this.log("Prompting user to save/ignore login");
   941         var userChoice = this._promptService.confirmEx(this._window,
   942                                             dialogTitle, dialogText,
   943                                             buttonFlags, rememberButtonText,
   944                                             notNowButtonText, neverButtonText,
   945                                             null, {});
   946         //  Returns:
   947         //   0 - Save the login
   948         //   1 - Ignore the login this time
   949         //   2 - Never save logins for this site
   950         if (userChoice == 2) {
   951             this.log("Disabling " + aLogin.hostname + " logins by request.");
   952             this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
   953         } else if (userChoice == 0) {
   954             this.log("Saving login for " + aLogin.hostname);
   955             this._pwmgr.addLogin(aLogin);
   956         } else {
   957             // userChoice == 1 --> just ignore the login.
   958             this.log("Ignoring login.");
   959         }
   960     },
   963     /*
   964      * promptToChangePassword
   965      *
   966      * Called when we think we detect a password change for an existing
   967      * login, when the form being submitted contains multiple password
   968      * fields.
   969      *
   970      */
   971     promptToChangePassword : function (aOldLogin, aNewLogin) {
   972         var notifyObj = this._getPopupNote() || this._getNotifyBox();
   974         if (notifyObj)
   975             this._showChangeLoginNotification(notifyObj, aOldLogin,
   976                                               aNewLogin.password);
   977         else
   978             this._showChangeLoginDialog(aOldLogin, aNewLogin.password);
   979     },
   982     /*
   983      * _showChangeLoginNotification
   984      *
   985      * Shows the Change Password notification bar or popup notification.
   986      *
   987      * @param aNotifyObj
   988      *        A notification box or a popup notification.
   989      */
   990     _showChangeLoginNotification : function (aNotifyObj, aOldLogin, aNewPassword) {
   991         var notificationText;
   992         if (aOldLogin.username) {
   993             var displayUser = this._sanitizeUsername(aOldLogin.username);
   994             notificationText  = this._getLocalizedString(
   995                                           "updatePasswordMsg",
   996                                           [displayUser]);
   997         } else {
   998             notificationText  = this._getLocalizedString(
   999                                           "updatePasswordMsgNoUser");
  1002         var changeButtonText =
  1003               this._getLocalizedString("notifyBarUpdateButtonText");
  1004         var changeButtonAccessKey =
  1005               this._getLocalizedString("notifyBarUpdateButtonAccessKey");
  1007         // The callbacks in |buttons| have a closure to access the variables
  1008         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
  1009         // without a getService() call.
  1010         var self = this;
  1012         // Notification is a PopupNotification
  1013         if (aNotifyObj == this._getPopupNote()) {
  1014             // "Yes" button
  1015             var mainAction = {
  1016                 label:     changeButtonText,
  1017                 accessKey: changeButtonAccessKey,
  1018                 popup:     null,
  1019                 callback:  function(aNotifyObj, aButton) {
  1020                     self._updateLogin(aOldLogin, aNewPassword);
  1022             };
  1024             var notifyWin = this._getNotifyWindow();
  1025             var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
  1026             var browser = chromeWin.gBrowser.
  1027                                     getBrowserForDocument(notifyWin.top.document);
  1029             aNotifyObj.show(browser, "password-change", notificationText,
  1030                             "password-notification-icon", mainAction,
  1031                             null, { timeout: Date.now() + 10000,
  1032                                     persistWhileVisible: true });
  1033         } else {
  1034             var dontChangeButtonText =
  1035                   this._getLocalizedString("notifyBarDontChangeButtonText");
  1036             var dontChangeButtonAccessKey =
  1037                   this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
  1038             var buttons = [
  1039                 // "Yes" button
  1041                     label:     changeButtonText,
  1042                     accessKey: changeButtonAccessKey,
  1043                     popup:     null,
  1044                     callback:  function(aNotifyObj, aButton) {
  1045                         self._updateLogin(aOldLogin, aNewPassword);
  1047                 },
  1049                 // "No" button
  1051                     label:     dontChangeButtonText,
  1052                     accessKey: dontChangeButtonAccessKey,
  1053                     popup:     null,
  1054                     callback:  function(aNotifyObj, aButton) {
  1055                         // do nothing
  1058             ];
  1060             this._showLoginNotification(aNotifyObj, "password-change",
  1061                                         notificationText, buttons);
  1063     },
  1066     /*
  1067      * _showChangeLoginDialog
  1069      * Shows the Change Password dialog.
  1071      */
  1072     _showChangeLoginDialog : function (aOldLogin, aNewPassword) {
  1073         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  1075         var dialogText;
  1076         if (aOldLogin.username)
  1077             dialogText  = this._getLocalizedString(
  1078                                     "updatePasswordMsg",
  1079                                     [aOldLogin.username]);
  1080         else
  1081             dialogText  = this._getLocalizedString(
  1082                                     "updatePasswordMsgNoUser");
  1084         var dialogTitle = this._getLocalizedString(
  1085                                     "passwordChangeTitle");
  1087         // returns 0 for yes, 1 for no.
  1088         var ok = !this._promptService.confirmEx(this._window,
  1089                                 dialogTitle, dialogText, buttonFlags,
  1090                                 null, null, null,
  1091                                 null, {});
  1092         if (ok) {
  1093             this.log("Updating password for user " + aOldLogin.username);
  1094             this._updateLogin(aOldLogin, aNewPassword);
  1096     },
  1099     /*
  1100      * promptToChangePasswordWithUsernames
  1102      * Called when we detect a password change in a form submission, but we
  1103      * don't know which existing login (username) it's for. Asks the user
  1104      * to select a username and confirm the password change.
  1106      * Note: The caller doesn't know the username for aNewLogin, so this
  1107      *       function fills in .username and .usernameField with the values
  1108      *       from the login selected by the user.
  1110      * Note; XPCOM stupidity: |count| is just |logins.length|.
  1111      */
  1112     promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
  1113         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  1115         var usernames = logins.map(function (l) l.username);
  1116         var dialogText  = this._getLocalizedString("userSelectText");
  1117         var dialogTitle = this._getLocalizedString("passwordChangeTitle");
  1118         var selectedIndex = { value: null };
  1120         // If user selects ok, outparam.value is set to the index
  1121         // of the selected username.
  1122         var ok = this._promptService.select(this._window,
  1123                                 dialogTitle, dialogText,
  1124                                 usernames.length, usernames,
  1125                                 selectedIndex);
  1126         if (ok) {
  1127             // Now that we know which login to use, modify its password.
  1128             var selectedLogin = logins[selectedIndex.value];
  1129             this.log("Updating password for user " + selectedLogin.username);
  1130             this._updateLogin(selectedLogin, aNewLogin.password);
  1132     },
  1137     /* ---------- Internal Methods ---------- */
  1142     /*
  1143      * _updateLogin
  1144      */
  1145     _updateLogin : function (login, newPassword) {
  1146         var now = Date.now();
  1147         var propBag = Cc["@mozilla.org/hash-property-bag;1"].
  1148                       createInstance(Ci.nsIWritablePropertyBag);
  1149         if (newPassword) {
  1150             propBag.setProperty("password", newPassword);
  1151             // Explicitly set the password change time here (even though it would
  1152             // be changed automatically), to ensure that it's exactly the same
  1153             // value as timeLastUsed.
  1154             propBag.setProperty("timePasswordChanged", now);
  1156         propBag.setProperty("timeLastUsed", now);
  1157         propBag.setProperty("timesUsedIncrement", 1);
  1158         this._pwmgr.modifyLogin(login, propBag);
  1159     },
  1162     /*
  1163      * _getChromeWindow
  1165      * Given a content DOM window, returns the chrome window it's in.
  1166      */
  1167     _getChromeWindow: function (aWindow) {
  1168         var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  1169                                .getInterface(Ci.nsIWebNavigation)
  1170                                .QueryInterface(Ci.nsIDocShell)
  1171                                .chromeEventHandler.ownerDocument.defaultView;
  1172         return chromeWin;
  1173     },
  1176     /*
  1177      * _getNotifyWindow
  1178      */
  1179     _getNotifyWindow: function () {
  1181         try {
  1182             // Get topmost window, in case we're in a frame.
  1183             var notifyWin = this._window.top;
  1185             // Some sites pop up a temporary login window, when disappears
  1186             // upon submission of credentials. We want to put the notification
  1187             // bar in the opener window if this seems to be happening.
  1188             if (notifyWin.opener) {
  1189                 var chromeDoc = this._getChromeWindow(notifyWin).
  1190                                      document.documentElement;
  1191                 var webnav = notifyWin.
  1192                              QueryInterface(Ci.nsIInterfaceRequestor).
  1193                              getInterface(Ci.nsIWebNavigation);
  1195                 // Check to see if the current window was opened with chrome
  1196                 // disabled, and if so use the opener window. But if the window
  1197                 // has been used to visit other pages (ie, has a history),
  1198                 // assume it'll stick around and *don't* use the opener.
  1199                 if (chromeDoc.getAttribute("chromehidden") &&
  1200                     webnav.sessionHistory.count == 1) {
  1201                     this.log("Using opener window for notification bar.");
  1202                     notifyWin = notifyWin.opener;
  1206             return notifyWin;
  1208         } catch (e) {
  1209             // If any errors happen, just assume no notification box.
  1210             this.log("Unable to get notify window");
  1211             return null;
  1213     },
  1216     /*
  1217      * _getPopupNote
  1219      * Returns the popup notification to this prompter,
  1220      * or null if there isn't one available.
  1221      */
  1222     _getPopupNote : function () {
  1223         let popupNote = null;
  1225         try {
  1226             let notifyWin = this._getNotifyWindow();
  1228             // Get the chrome window for the content window we're using.
  1229             // .wrappedJSObject needed here -- see bug 422974 comment 5.
  1230             let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
  1232             popupNote = chromeWin.PopupNotifications;
  1233         } catch (e) {
  1234             this.log("Popup notifications not available on window");
  1237         return popupNote;
  1238     },
  1241     /*
  1242      * _getNotifyBox
  1244      * Returns the notification box to this prompter, or null if there isn't
  1245      * a notification box available.
  1246      */
  1247     _getNotifyBox : function () {
  1248         let notifyBox = null;
  1250         try {
  1251             let notifyWin = this._getNotifyWindow();
  1253             // Get the chrome window for the content window we're using.
  1254             // .wrappedJSObject needed here -- see bug 422974 comment 5.
  1255             let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
  1257             notifyBox = chromeWin.getNotificationBox(notifyWin);
  1258         } catch (e) {
  1259             this.log("Notification bars not available on window");
  1262         return notifyBox;
  1263     },
  1266     /*
  1267      * _repickSelectedLogin
  1269      * The user might enter a login that isn't the one we prefilled, but
  1270      * is the same as some other existing login. So, pick a login with a
  1271      * matching username, or return null.
  1272      */
  1273     _repickSelectedLogin : function (foundLogins, username) {
  1274         for (var i = 0; i < foundLogins.length; i++)
  1275             if (foundLogins[i].username == username)
  1276                 return foundLogins[i];
  1277         return null;
  1278     },
  1281     /*
  1282      * _getLocalizedString
  1284      * Can be called as:
  1285      *   _getLocalizedString("key1");
  1286      *   _getLocalizedString("key2", ["arg1"]);
  1287      *   _getLocalizedString("key3", ["arg1", "arg2"]);
  1288      *   (etc)
  1290      * Returns the localized string for the specified key,
  1291      * formatted if required.
  1293      */
  1294     _getLocalizedString : function (key, formatArgs) {
  1295         if (formatArgs)
  1296             return this._strBundle.formatStringFromName(
  1297                                         key, formatArgs, formatArgs.length);
  1298         else
  1299             return this._strBundle.GetStringFromName(key);
  1300     },
  1303     /*
  1304      * _sanitizeUsername
  1306      * Sanitizes the specified username, by stripping quotes and truncating if
  1307      * it's too long. This helps prevent an evil site from messing with the
  1308      * "save password?" prompt too much.
  1309      */
  1310     _sanitizeUsername : function (username) {
  1311         if (username.length > 30) {
  1312             username = username.substring(0, 30);
  1313             username += this._ellipsis;
  1315         return username.replace(/['"]/g, "");
  1316     },
  1319     /*
  1320      * _getFormattedHostname
  1322      * The aURI parameter may either be a string uri, or an nsIURI instance.
  1324      * Returns the hostname to use in a nsILoginInfo object (for example,
  1325      * "http://example.com").
  1326      */
  1327     _getFormattedHostname : function (aURI) {
  1328         var uri;
  1329         if (aURI instanceof Ci.nsIURI) {
  1330             uri = aURI;
  1331         } else {
  1332             uri = Services.io.newURI(aURI, null, null);
  1334         var scheme = uri.scheme;
  1336         var hostname = scheme + "://" + uri.host;
  1338         // If the URI explicitly specified a port, only include it when
  1339         // it's not the default. (We never want "http://foo.com:80")
  1340         port = uri.port;
  1341         if (port != -1) {
  1342             var handler = Services.io.getProtocolHandler(scheme);
  1343             if (port != handler.defaultPort)
  1344                 hostname += ":" + port;
  1347         return hostname;
  1348     },
  1351     /*
  1352      * _getShortDisplayHost
  1354      * Converts a login's hostname field (a URL) to a short string for
  1355      * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
  1356      * "ftp://www.site.co.uk" --> "site.co.uk".
  1357      */
  1358     _getShortDisplayHost: function (aURIString) {
  1359         var displayHost;
  1361         var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
  1362                           getService(Ci.nsIEffectiveTLDService);
  1363         var idnService = Cc["@mozilla.org/network/idn-service;1"].
  1364                          getService(Ci.nsIIDNService);
  1365         try {
  1366             var uri = Services.io.newURI(aURIString, null, null);
  1367             var baseDomain = eTLDService.getBaseDomain(uri);
  1368             displayHost = idnService.convertToDisplayIDN(baseDomain, {});
  1369         } catch (e) {
  1370             this.log("_getShortDisplayHost couldn't process " + aURIString);
  1373         if (!displayHost)
  1374             displayHost = aURIString;
  1376         return displayHost;
  1377     },
  1380     /*
  1381      * _getAuthTarget
  1383      * Returns the hostname and realm for which authentication is being
  1384      * requested, in the format expected to be used with nsILoginInfo.
  1385      */
  1386     _getAuthTarget : function (aChannel, aAuthInfo) {
  1387         var hostname, realm;
  1389         // If our proxy is demanding authentication, don't use the
  1390         // channel's actual destination.
  1391         if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
  1392             this.log("getAuthTarget is for proxy auth");
  1393             if (!(aChannel instanceof Ci.nsIProxiedChannel))
  1394                 throw "proxy auth needs nsIProxiedChannel";
  1396             var info = aChannel.proxyInfo;
  1397             if (!info)
  1398                 throw "proxy auth needs nsIProxyInfo";
  1400             // Proxies don't have a scheme, but we'll use "moz-proxy://"
  1401             // so that it's more obvious what the login is for.
  1402             var idnService = Cc["@mozilla.org/network/idn-service;1"].
  1403                              getService(Ci.nsIIDNService);
  1404             hostname = "moz-proxy://" +
  1405                         idnService.convertUTF8toACE(info.host) +
  1406                         ":" + info.port;
  1407             realm = aAuthInfo.realm;
  1408             if (!realm)
  1409                 realm = hostname;
  1411             return [hostname, realm];
  1414         hostname = this._getFormattedHostname(aChannel.URI);
  1416         // If a HTTP WWW-Authenticate header specified a realm, that value
  1417         // will be available here. If it wasn't set or wasn't HTTP, we'll use
  1418         // the formatted hostname instead.
  1419         realm = aAuthInfo.realm;
  1420         if (!realm)
  1421             realm = hostname;
  1423         return [hostname, realm];
  1424     },
  1427     /**
  1428      * Returns [username, password] as extracted from aAuthInfo (which
  1429      * holds this info after having prompted the user).
  1431      * If the authentication was for a Windows domain, we'll prepend the
  1432      * return username with the domain. (eg, "domain\user")
  1433      */
  1434     _GetAuthInfo : function (aAuthInfo) {
  1435         var username, password;
  1437         var flags = aAuthInfo.flags;
  1438         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
  1439             username = aAuthInfo.domain + "\\" + aAuthInfo.username;
  1440         else
  1441             username = aAuthInfo.username;
  1443         password = aAuthInfo.password;
  1445         return [username, password];
  1446     },
  1449     /**
  1450      * Given a username (possibly in DOMAIN\user form) and password, parses the
  1451      * domain out of the username if necessary and sets domain, username and
  1452      * password on the auth information object.
  1453      */
  1454     _SetAuthInfo : function (aAuthInfo, username, password) {
  1455         var flags = aAuthInfo.flags;
  1456         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
  1457             // Domain is separated from username by a backslash
  1458             var idx = username.indexOf("\\");
  1459             if (idx == -1) {
  1460                 aAuthInfo.username = username;
  1461             } else {
  1462                 aAuthInfo.domain   =  username.substring(0, idx);
  1463                 aAuthInfo.username =  username.substring(idx+1);
  1465         } else {
  1466             aAuthInfo.username = username;
  1468         aAuthInfo.password = password;
  1469     },
  1471     _newAsyncPromptConsumer : function(aCallback, aContext) {
  1472         return {
  1473             QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
  1474             callback: aCallback,
  1475             context: aContext,
  1476             cancel: function() {
  1477                 this.callback.onAuthCancelled(this.context, false);
  1478                 this.callback = null;
  1479                 this.context = null;
  1484 }; // end of LoginManagerPrompter implementation
  1487 var component = [LoginManagerPromptFactory, LoginManagerPrompter];
  1488 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);

mercurial