1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public 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/
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
14 * The Original Code is mozilla.org code.
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.
21 * Justin Dolske <dolske@mozilla.com> (original author)
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
35 * ***** END LICENSE BLOCK ***** */
38 const Cc
= Components
.classes
;
39 const Ci
= Components
.interfaces
;
41 Components
.utils
.import("resource://gre/modules/XPCOMUtils.jsm");
43 function LoginManager() {
47 LoginManager
.prototype = {
49 classDescription
: "LoginManager",
50 contractID
: "@mozilla.org/login-manager;1",
51 classID
: Components
.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
52 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsILoginManager
,
53 Ci
.nsISupportsWeakReference
]),
56 /* ---------- private memebers ---------- */
59 __logService
: null, // Console logging service, used for debugging.
61 if (!this.__logService
)
62 this.__logService
= Cc
["@mozilla.org/consoleservice;1"].
63 getService(Ci
.nsIConsoleService
);
64 return this.__logService
;
68 __ioService
: null, // IO service for string -> nsIURI conversion
70 if (!this.__ioService
)
71 this.__ioService
= Cc
["@mozilla.org/network/io-service;1"].
72 getService(Ci
.nsIIOService
);
73 return this.__ioService
;
77 __formFillService
: null, // FormFillController, for username autocompleting
78 get _formFillService() {
79 if (!this.__formFillService
)
80 this.__formFillService
=
81 Cc
["@mozilla.org/satchel/form-fill-controller;1"].
82 getService(Ci
.nsIFormFillController
);
83 return this.__formFillService
;
87 __observerService
: null, // Observer Service, for notifications
88 get _observerService() {
89 if (!this.__observerService
)
90 this.__observerService
= Cc
["@mozilla.org/observer-service;1"].
91 getService(Ci
.nsIObserverService
);
92 return this.__observerService
;
96 __storage
: null, // Storage component which contains the saved logins
98 if (!this.__storage
) {
100 var contractID
= "@mozilla.org/login-manager/storage/mozStorage;1";
102 var catMan
= Cc
["@mozilla.org/categorymanager;1"].
103 getService(Ci
.nsICategoryManager
);
104 contractID
= catMan
.getCategoryEntry("login-manager-storage",
105 "nsILoginManagerStorage");
106 this.log("Found alternate nsILoginManagerStorage with " +
107 "contract ID: " + contractID
);
109 this.log("No alternate nsILoginManagerStorage registered");
112 this.__storage
= Cc
[contractID
].
113 createInstance(Ci
.nsILoginManagerStorage
);
115 this.__storage
.init();
117 this.log("Initialization of storage component failed: " + e
);
118 this.__storage
= null;
122 return this.__storage
;
125 _prefBranch
: null, // Preferences service
126 _nsLoginInfo
: null, // Constructor for nsILoginInfo implementation
128 _remember
: true, // mirrors signon.rememberSignons preference
129 _debug
: false, // mirrors signon.debug
135 * Initialize the Login Manager. Automatically called when service
138 * Note: Service created in /browser/base/content/browser.js,
143 // Cache references to current |this| in utility objects
144 this._webProgressListener
._domEventListener
= this._domEventListener
;
145 this._webProgressListener
._pwmgr
= this;
146 this._domEventListener
._pwmgr
= this;
147 this._observer
._pwmgr
= this;
149 // Preferences. Add observer so we get notified of changes.
150 this._prefBranch
= Cc
["@mozilla.org/preferences-service;1"].
151 getService(Ci
.nsIPrefService
).getBranch("signon.");
152 this._prefBranch
.QueryInterface(Ci
.nsIPrefBranch2
);
153 this._prefBranch
.addObserver("", this._observer
, false);
155 // Get current preference values.
156 this._debug
= this._prefBranch
.getBoolPref("debug");
158 this._remember
= this._prefBranch
.getBoolPref("rememberSignons");
161 // Get constructor for nsILoginInfo
162 this._nsLoginInfo
= new Components
.Constructor(
163 "@mozilla.org/login-manager/loginInfo;1", Ci
.nsILoginInfo
);
166 // Form submit observer checks forms for new logins and pw changes.
167 this._observerService
.addObserver(this._observer
, "earlyformsubmit", false);
168 this._observerService
.addObserver(this._observer
, "xpcom-shutdown", false);
170 // WebProgressListener for getting notification of new doc loads.
171 var progress
= Cc
["@mozilla.org/docloaderservice;1"].
172 getService(Ci
.nsIWebProgress
);
173 progress
.addProgressListener(this._webProgressListener
,
174 Ci
.nsIWebProgress
.NOTIFY_STATE_DOCUMENT
);
183 * Internal function for logging debug messages to the Error Console window
185 log : function (message
) {
188 dump("Login Manager: " + message
+ "\n");
189 this._logService
.logStringMessage("Login Manager: " + message
);
193 /* ---------- Utility objects ---------- */
199 * Internal utility object, implements the nsIObserver interface.
200 * Used to receive notification for: form submission, preference changes.
205 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsIObserver
,
206 Ci
.nsIFormSubmitObserver
,
207 Ci
.nsISupportsWeakReference
]),
210 // nsFormSubmitObserver
211 notify : function (formElement
, aWindow
, actionURI
) {
212 this._pwmgr
.log("observer notified for form submission.");
214 // We're invoked before the content's |onsubmit| handlers, so we
215 // can grab form data before it might be modified (see bug 257781).
218 this._pwmgr
._onFormSubmit(formElement
);
220 this._pwmgr
.log("Caught error in onFormSubmit: " + e
);
223 return true; // Always return true, or form submit will be canceled.
227 observe : function (subject
, topic
, data
) {
229 if (topic
== "nsPref:changed") {
231 this._pwmgr
.log("got change to " + prefName
+ " preference");
233 if (prefName
== "debug") {
235 this._pwmgr
._prefBranch
.getBoolPref("debug");
236 } else if (prefName
== "rememberSignons") {
237 this._pwmgr
._remember
=
238 this._pwmgr
._prefBranch
.getBoolPref("rememberSignons");
240 this._pwmgr
.log("Oops! Pref not handled, change ignored.");
242 } else if (topic
== "xpcom-shutdown") {
243 for (let i
in this._pwmgr
) {
245 this._pwmgr
[i
] = null;
250 this._pwmgr
.log("Oops! Unexpected notification: " + topic
);
257 * _webProgressListener object
259 * Internal utility object, implements nsIWebProgressListener interface.
260 * This is attached to the document loader service, so we get
261 * notifications about all page loads.
263 _webProgressListener
: {
265 _domEventListener
: null,
267 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsIWebProgressListener
,
268 Ci
.nsISupportsWeakReference
]),
271 onStateChange : function (aWebProgress
, aRequest
,
272 aStateFlags
, aStatus
) {
274 // STATE_START is too early, doc is still the old page.
275 if (!(aStateFlags
& Ci
.nsIWebProgressListener
.STATE_TRANSFERRING
))
278 if (!this._pwmgr
._remember
)
281 var domWin
= aWebProgress
.DOMWindow
;
282 var domDoc
= domWin
.document
;
284 // Only process things which might have HTML forms.
285 if (!(domDoc
instanceof Ci
.nsIDOMHTMLDocument
))
288 this._pwmgr
.log("onStateChange accepted: req = " +
289 (aRequest
? aRequest
.name
: "(null)") +
290 ", flags = 0x" + aStateFlags
.toString(16));
292 // Fastback doesn't fire DOMContentLoaded, so process forms now.
293 if (aStateFlags
& Ci
.nsIWebProgressListener
.STATE_RESTORING
) {
294 this._pwmgr
.log("onStateChange: restoring document");
295 return this._pwmgr
._fillDocument(domDoc
);
298 // Add event listener to process page when DOM is complete.
299 domDoc
.addEventListener("DOMContentLoaded",
300 this._domEventListener
, false);
304 // stubs for the nsIWebProgressListener interfaces which we don't use.
305 onProgressChange : function() { throw "Unexpected onProgressChange"; },
306 onLocationChange : function() { throw "Unexpected onLocationChange"; },
307 onStatusChange : function() { throw "Unexpected onStatusChange"; },
308 onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
313 * _domEventListener object
315 * Internal utility object, implements nsIDOMEventListener
316 * Used to catch certain DOM events needed to properly implement form fill.
318 _domEventListener
: {
321 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsIDOMEventListener
,
322 Ci
.nsISupportsWeakReference
]),
325 handleEvent : function (event
) {
326 this._pwmgr
.log("domEventListener: got event " + event
.type
);
328 var doc
, inputElement
;
329 switch (event
.type
) {
330 case "DOMContentLoaded":
332 this._pwmgr
._fillDocument(doc
);
335 case "DOMAutoComplete":
337 inputElement
= event
.target
;
338 this._pwmgr
._fillPassword(inputElement
);
342 this._pwmgr
.log("Oops! This event unexpected.");
351 /* ---------- Primary Public interfaces ---------- */
359 * Add a new login to login storage.
361 addLogin : function (login
) {
362 // Sanity check the login
363 if (login
.hostname
== null || login
.hostname
.length
== 0)
364 throw "Can't add a login with a null or empty hostname.";
366 // For logins w/o a username, set to "", not null.
367 if (login
.username
== null)
368 throw "Can't add a login with a null username.";
370 if (login
.password
== null || login
.password
.length
== 0)
371 throw "Can't add a login with a null or empty password.";
373 if (login
.formSubmitURL
|| login
.formSubmitURL
== "") {
374 // We have a form submit URL. Can't have a HTTP realm.
375 if (login
.httpRealm
!= null)
376 throw "Can't add a login with both a httpRealm and formSubmitURL.";
377 } else if (login
.httpRealm
) {
378 // We have a HTTP realm. Can't have a form submit URL.
379 if (login
.formSubmitURL
!= null)
380 throw "Can't add a login with both a httpRealm and formSubmitURL.";
382 // Need one or the other!
383 throw "Can't add a login without a httpRealm or formSubmitURL.";
387 // Look for an existing entry.
388 var logins
= this.findLogins({}, login
.hostname
, login
.formSubmitURL
,
391 if (logins
.some(function(l
) login
.matches(l
, true)))
392 throw "This login already exists.";
394 this.log("Adding login: " + login
);
395 return this._storage
.addLogin(login
);
402 * Remove the specified login from the stored logins.
404 removeLogin : function (login
) {
405 this.log("Removing login: " + login
);
406 return this._storage
.removeLogin(login
);
413 * Change the specified login to match the new login.
415 modifyLogin : function (oldLogin
, newLogin
) {
416 this.log("Modifying oldLogin: " + oldLogin
+ " newLogin: " + newLogin
);
417 return this._storage
.modifyLogin(oldLogin
, newLogin
);
424 * Get a dump of all stored logins. Used by the login manager UI.
426 * |count| is only needed for XPCOM.
428 * Returns an array of logins. If there are no logins, the array is empty.
430 getAllLogins : function (count
) {
431 this.log("Getting a list of all logins");
432 return this._storage
.getAllLogins(count
);
439 * Remove all stored logins.
441 removeAllLogins : function () {
442 this.log("Removing all logins");
443 this._storage
.removeAllLogins();
447 * getAllDisabledHosts
449 * Get a list of all hosts for which logins are disabled.
451 * |count| is only needed for XPCOM.
453 * Returns an array of disabled logins. If there are no disabled logins,
454 * the array is empty.
456 getAllDisabledHosts : function (count
) {
457 this.log("Getting a list of all disabled hosts");
458 return this._storage
.getAllDisabledHosts(count
);
465 * Search for the known logins for entries matching the specified criteria.
467 findLogins : function (count
, hostname
, formSubmitURL
, httpRealm
) {
468 this.log("Searching for logins matching host: " + hostname
+
469 ", formSubmitURL: " + formSubmitURL
+ ", httpRealm: " + httpRealm
);
471 return this._storage
.findLogins(count
, hostname
, formSubmitURL
,
479 * Search for the known logins for entries matching the specified criteria,
480 * returns only the count.
482 countLogins : function (hostname
, formSubmitURL
, httpRealm
) {
483 this.log("Counting logins matching host: " + hostname
+
484 ", formSubmitURL: " + formSubmitURL
+ ", httpRealm: " + httpRealm
);
486 return this._storage
.countLogins(hostname
, formSubmitURL
, httpRealm
);
491 * getLoginSavingEnabled
493 * Check to see if user has disabled saving logins for the host.
495 getLoginSavingEnabled : function (host
) {
496 this.log("Checking if logins to " + host
+ " can be saved.");
500 return this._storage
.getLoginSavingEnabled(host
);
505 * setLoginSavingEnabled
507 * Enable or disable storing logins for the specified host.
509 setLoginSavingEnabled : function (hostname
, enabled
) {
510 // Nulls won't round-trip with getAllDisabledHosts().
511 if (hostname
.indexOf("\0") != -1)
512 throw "Invalid hostname";
514 this.log("Saving logins for " + hostname
+ " enabled? " + enabled
);
515 return this._storage
.setLoginSavingEnabled(hostname
, enabled
);
522 * Yuck. This is called directly by satchel:
523 * nsFormFillController::StartSearch()
524 * [toolkit/components/satchel/src/nsFormFillController.cpp]
526 * We really ought to have a simple way for code to register an
527 * auto-complete provider, and not have satchel calling pwmgr directly.
529 autoCompleteSearch : function (aSearchString
, aPreviousResult
, aElement
) {
530 // aPreviousResult & aResult are nsIAutoCompleteResult,
531 // aElement is nsIDOMHTMLInputElement
536 this.log("AutoCompleteSearch invoked. Search is: " + aSearchString
);
540 if (aPreviousResult
) {
541 this.log("Using previous autocomplete result");
542 result
= aPreviousResult
;
544 // We have a list of results for a shorter search string, so just
545 // filter them further based on the new search string.
546 // Count backwards, because result.matchCount is decremented
547 // when we remove an entry.
548 for (var i
= result
.matchCount
- 1; i
>= 0; i
--) {
549 var match
= result
.getValueAt(i
);
551 // Remove results that are too short, or have different prefix.
552 if (aSearchString
.length
> match
.length
||
553 aSearchString
.toLowerCase() !=
554 match
.substr(0, aSearchString
.length
).toLowerCase())
556 this.log("Removing autocomplete entry '" + match
+ "'");
557 result
.removeValueAt(i
, false);
561 this.log("Creating new autocomplete search result.");
563 var doc
= aElement
.ownerDocument
;
564 var origin
= this._getPasswordOrigin(doc
.documentURI
);
565 var actionOrigin
= this._getActionOrigin(aElement
.form
);
567 var logins
= this.findLogins({}, origin
, actionOrigin
, null);
568 var matchingLogins
= [];
570 for (i
= 0; i
< logins
.length
; i
++) {
571 var username
= logins
[i
].username
.toLowerCase();
572 if (aSearchString
.length
<= username
.length
&&
573 aSearchString
.toLowerCase() ==
574 username
.substr(0, aSearchString
.length
))
576 matchingLogins
.push(logins
[i
]);
579 this.log(matchingLogins
.length
+ " autocomplete logins avail.");
580 result
= new UserAutoCompleteResult(aSearchString
, matchingLogins
);
589 /* ------- Internal methods / callbacks for document integration ------- */
597 * Returns an array of password field elements for the specified form.
598 * If no pw fields are found, or if more than 3 are found, then null
601 * skipEmptyFields can be set to ignore password fields with no value.
603 _getPasswordFields : function (form
, skipEmptyFields
) {
604 // Locate the password fields in the form.
606 for (var i
= 0; i
< form
.elements
.length
; i
++) {
607 if (form
.elements
[i
].type
!= "password")
610 if (skipEmptyFields
&& !form
.elements
[i
].value
)
613 pwFields
[pwFields
.length
] = {
615 element
: form
.elements
[i
]
619 // If too few or too many fields, bail out.
620 if (pwFields
.length
== 0) {
621 this.log("(form ignored -- no password fields.)");
623 } else if (pwFields
.length
> 3) {
624 this.log("(form ignored -- too many password fields. [got " +
625 pwFields
.length
+ "])");
636 * Returns the username and password fields found in the form.
637 * Can handle complex forms by trying to figure out what the
638 * relevant fields are.
640 * Returns: [usernameField, newPasswordField, oldPasswordField]
642 * usernameField may be null.
643 * newPasswordField will always be non-null.
644 * oldPasswordField may be null. If null, newPasswordField is just
645 * "theLoginField". If not null, the form is apparently a
646 * change-password field, with oldPasswordField containing the password
647 * that is being changed.
649 _getFormFields : function (form
, isSubmission
) {
650 var usernameField
= null;
652 // Locate the password field(s) in the form. Up to 3 supported.
653 // If there's no password field, there's nothing for us to do.
654 var pwFields
= this._getPasswordFields(form
, isSubmission
);
656 return [null, null, null];
659 // Locate the username field in the form by searching backwards
660 // from the first passwordfield, assume the first text field is the
661 // username. We might not find a username field if the user is
662 // already logged in to the site.
663 for (var i
= pwFields
[0].index
- 1; i
>= 0; i
--) {
664 if (form
.elements
[i
].type
== "text") {
665 usernameField
= form
.elements
[i
];
671 this.log("(form -- no username field found)");
674 // If we're not submitting a form (it's a page load), there are no
675 // password field values for us to use for identifying fields. So,
676 // just assume the first password field is the one to be filled in.
677 if (!isSubmission
|| pwFields
.length
== 1)
678 return [usernameField
, pwFields
[0].element
, null];
681 // Try to figure out WTF is in the form based on the password values.
682 var oldPasswordField
, newPasswordField
;
683 var pw1
= pwFields
[0].element
.value
;
684 var pw2
= pwFields
[1].element
.value
;
685 var pw3
= (pwFields
[2] ? pwFields
[2].element
.value
: null);
687 if (pwFields
.length
== 3) {
688 // Look for two identical passwords, that's the new password
690 if (pw1
== pw2
&& pw2
== pw3
) {
691 // All 3 passwords the same? Weird! Treat as if 1 pw field.
692 newPasswordField
= pwFields
[0].element
;
693 oldPasswordField
= null;
694 } else if (pw1
== pw2
) {
695 newPasswordField
= pwFields
[0].element
;
696 oldPasswordField
= pwFields
[2].element
;
697 } else if (pw2
== pw3
) {
698 oldPasswordField
= pwFields
[0].element
;
699 newPasswordField
= pwFields
[2].element
;
700 } else if (pw1
== pw3
) {
701 // A bit odd, but could make sense with the right page layout.
702 newPasswordField
= pwFields
[0].element
;
703 oldPasswordField
= pwFields
[1].element
;
705 // We can't tell which of the 3 passwords should be saved.
706 this.log("(form ignored -- all 3 pw fields differ)");
707 return [null, null, null];
709 } else { // pwFields.length == 2
711 // Treat as if 1 pw field
712 newPasswordField
= pwFields
[0].element
;
713 oldPasswordField
= null;
715 // Just assume that the 2nd password is the new password
716 oldPasswordField
= pwFields
[0].element
;
717 newPasswordField
= pwFields
[1].element
;
721 return [usernameField
, newPasswordField
, oldPasswordField
];
726 * _isAutoCompleteDisabled
728 * Returns true if the page requests autocomplete be disabled for the
729 * specified form input.
731 _isAutocompleteDisabled : function (element
) {
732 if (element
&& element
.hasAttribute("autocomplete") &&
733 element
.getAttribute("autocomplete").toLowerCase() == "off")
742 * Called by the our observer when notified of a form submission.
743 * [Note that this happens before any DOM onsubmit handlers are invoked.]
744 * Looks for a password change in the submitted form, so we can update
745 * our stored password.
747 _onFormSubmit : function (form
) {
749 // local helper function
750 function getPrompter(aWindow
) {
751 var prompterSvc
= Cc
["@mozilla.org/login-manager/prompter;1"].
752 createInstance(Ci
.nsILoginManagerPrompter
);
753 prompterSvc
.init(aWindow
);
757 var doc
= form
.ownerDocument
;
758 var win
= doc
.defaultView
;
760 // If password saving is disabled (globally or for host), bail out now.
764 var hostname
= this._getPasswordOrigin(doc
.documentURI
);
765 var formSubmitURL
= this._getActionOrigin(form
)
766 if (!this.getLoginSavingEnabled(hostname
)) {
767 this.log("(form submission ignored -- saving is " +
768 "disabled for: " + hostname
+ ")");
773 // Get the appropriate fields from the form.
774 var [usernameField
, newPasswordField
, oldPasswordField
] =
775 this._getFormFields(form
, true);
777 // Need at least 1 valid password field to do anything.
778 if (newPasswordField
== null)
781 // Check for autocomplete=off attribute. We don't use it to prevent
782 // autofilling (for existing logins), but won't save logins when it's
784 if (this._isAutocompleteDisabled(form
) ||
785 this._isAutocompleteDisabled(usernameField
) ||
786 this._isAutocompleteDisabled(newPasswordField
) ||
787 this._isAutocompleteDisabled(oldPasswordField
)) {
788 this.log("(form submission ignored -- autocomplete=off found)");
793 var formLogin
= new this._nsLoginInfo();
794 formLogin
.init(hostname
, formSubmitURL
, null,
795 (usernameField
? usernameField
.value
: ""),
796 newPasswordField
.value
,
797 (usernameField
? usernameField
.name
: ""),
798 newPasswordField
.name
);
800 // If we didn't find a username field, but seem to be changing a
801 // password, allow the user to select from a list of applicable
802 // logins to update the password for.
803 if (!usernameField
&& oldPasswordField
) {
805 var logins
= this.findLogins({}, hostname
, formSubmitURL
, null);
807 if (logins
.length
== 0) {
808 // Could prompt to save this as a new password-only login.
809 // This seems uncommon, and might be wrong, so ignore.
810 this.log("(no logins for this host -- pwchange ignored)");
814 var prompter
= getPrompter(win
);
816 if (logins
.length
== 1) {
817 var oldLogin
= logins
[0];
818 formLogin
.username
= oldLogin
.username
;
819 formLogin
.usernameField
= oldLogin
.usernameField
;
821 prompter
.promptToChangePassword(oldLogin
, formLogin
);
823 prompter
.promptToChangePasswordWithUsernames(
824 logins
, logins
.length
, formLogin
);
831 // Look for an existing login that matches the form login.
832 var existingLogin
= null;
833 var logins
= this.findLogins({}, hostname
, formSubmitURL
, null);
835 for (var i
= 0; i
< logins
.length
; i
++) {
836 var same
, login
= logins
[i
];
838 // If one login has a username but the other doesn't, ignore
839 // the username when comparing and only match if they have the
840 // same password. Otherwise, compare the logins and match even
841 // if the passwords differ.
842 if (!login
.username
&& formLogin
.username
) {
843 var restoreMe
= formLogin
.username
;
844 formLogin
.username
= "";
845 same
= formLogin
.matches(login
, false);
846 formLogin
.username
= restoreMe
;
847 } else if (!formLogin
.username
&& login
.username
) {
848 formLogin
.username
= login
.username
;
849 same
= formLogin
.matches(login
, false);
850 formLogin
.username
= ""; // we know it's always blank.
852 same
= formLogin
.matches(login
, true);
856 existingLogin
= login
;
862 this.log("Found an existing login matching this form submission");
865 * Change password if needed.
867 * If the login has a username, change the password w/o prompting
868 * (because we can be fairly sure there's only one password
869 * associated with the username). But for logins without a
870 * username, ask the user... Some sites use a password-only "login"
871 * in different contexts (enter your PIN, answer a security
872 * question, etc), and without a username we can't be sure if
873 * modifying an existing login is the right thing to do.
875 if (existingLogin
.password
!= formLogin
.password
) {
876 if (formLogin
.username
) {
877 this.log("...Updating password for existing login.");
878 this.modifyLogin(existingLogin
, formLogin
);
880 this.log("...passwords differ, prompting to change.");
881 prompter
= getPrompter(win
);
882 prompter
.promptToChangePassword(existingLogin
, formLogin
);
890 // Prompt user to save login (via dialog or notification bar)
891 prompter
= getPrompter(win
);
892 prompter
.promptToSavePassword(formLogin
);
899 * Get the parts of the URL we want for identification.
901 _getPasswordOrigin : function (uriString
, allowJS
) {
904 var uri
= this._ioService
.newURI(uriString
, null, null);
906 if (allowJS
&& uri
.scheme
== "javascript")
909 realm
= uri
.scheme
+ "://" + uri
.host
;
911 // If the URI explicitly specified a port, only include it when
912 // it's not the default. (We never want "http://foo.com:80")
915 var handler
= this._ioService
.getProtocolHandler(uri
.scheme
);
916 if (port
!= handler
.defaultPort
)
921 // bug 159484 - disallow url types that don't support a hostPort.
922 // (although we handle "javascript:..." as a special case above.)
923 this.log("Couldn't parse origin for " + uriString
);
930 _getActionOrigin : function (form
) {
931 var uriString
= form
.action
;
933 // A blank or mission action submits to where it came from.
935 uriString
= form
.baseURI
; // ala bug 297761
937 return this._getPasswordOrigin(uriString
, true);
944 * Called when a page has loaded. For each form in the document,
945 * we check to see if it can be filled with a stored login.
947 _fillDocument : function (doc
) {
948 var forms
= doc
.forms
;
949 if (!forms
|| forms
.length
== 0)
952 var formOrigin
= this._getPasswordOrigin(doc
.documentURI
);
954 // If there are no logins for this site, bail out now.
955 if (!this.countLogins(formOrigin
, "", null))
958 this.log("fillDocument processing " + forms
.length
+
959 " forms on " + doc
.documentURI
);
961 var autofillForm
= this._prefBranch
.getBoolPref("autofillForms");
962 var previousActionOrigin
= null;
963 var foundLogins
= null;
965 for (var i
= 0; i
< forms
.length
; i
++) {
968 // Only the actionOrigin might be changing, so if it's the same
969 // as the last form on the page we can reuse the same logins.
970 var actionOrigin
= this._getActionOrigin(form
);
971 if (actionOrigin
!= previousActionOrigin
) {
973 previousActionOrigin
= actionOrigin
;
975 this.log("_fillDocument processing form[" + i
+ "]");
976 foundLogins
= this._fillForm(form
, autofillForm
, false, foundLogins
)[1];
984 * Fill the form with login information if we can find it. This will find
985 * an array of logins if not given any, otherwise it will use the logins
986 * passed in. The logins are returned so they can be reused for
987 * optimization. Success of action is also returned in format
988 * [success, foundLogins]. autofillForm denotes if we should fill the form
989 * in automatically, ignoreAutocomplete denotes if we should ignore
990 * autocomplete=off attributes, and foundLogins is an array of nsILoginInfo
993 _fillForm : function (form
, autofillForm
, ignoreAutocomplete
, foundLogins
) {
994 // Heuristically determine what the user/pass fields are
995 // We do this before checking to see if logins are stored,
996 // so that the user isn't prompted for a master password
998 var [usernameField
, passwordField
, ignored
] =
999 this._getFormFields(form
, false);
1001 // Need a valid password field to do anything.
1002 if (passwordField
== null)
1003 return [false, foundLogins
];
1005 // If the fields are disabled or read-only, there's nothing to do.
1006 if (passwordField
.disabled
|| passwordField
.readOnly
||
1007 usernameField
&& (usernameField
.disabled
||
1008 usernameField
.readOnly
)) {
1009 this.log("not filling form, login fields disabled");
1010 return [false, foundLogins
];
1013 // If there's only a password field and it has a value, there's
1014 // nothing for us to do. (Don't clobber the existing value)
1015 if (!usernameField
&& passwordField
.value
)
1016 return [false, foundLogins
];
1018 // Need to get a list of logins if we weren't given them
1019 if (foundLogins
== null) {
1021 this._getPasswordOrigin(form
.ownerDocument
.documentURI
);
1022 var actionOrigin
= this._getActionOrigin(form
);
1023 foundLogins
= this.findLogins({}, formOrigin
, actionOrigin
, null);
1024 this.log("found " + foundLogins
.length
+ " matching logins.");
1026 this.log("reusing logins from last form.");
1029 // Discard logins which have username/password values that don't
1030 // fit into the fields (as specified by the maxlength attribute).
1031 // The user couldn't enter these values anyway, and it helps
1032 // with sites that have an extra PIN to be entered (bug 391514)
1033 var maxUsernameLen
= Number
.MAX_VALUE
;
1034 var maxPasswordLen
= Number
.MAX_VALUE
;
1036 // If attribute wasn't set, default is -1.
1037 if (usernameField
&& usernameField
.maxLength
>= 0)
1038 maxUsernameLen
= usernameField
.maxLength
;
1039 if (passwordField
.maxLength
>= 0)
1040 maxPasswordLen
= passwordField
.maxLength
;
1042 logins
= foundLogins
.filter(function (l
) {
1043 var fit
= (l
.username
.length
<= maxUsernameLen
&&
1044 l
.password
.length
<= maxPasswordLen
);
1046 this.log("Ignored " + l
.username
+ " login: won't fit");
1052 // Nothing to do if we have no matching logins available.
1053 if (logins
.length
== 0)
1054 return [false, foundLogins
];
1057 // Attach autocomplete stuff to the username field, if we have
1058 // one. This is normally used to select from multiple accounts,
1059 // but even with one account we should refill if the user edits.
1061 this._attachToInput(usernameField
);
1063 // If the form has an autocomplete=off attribute in play, don't
1064 // fill in the login automatically. We check this after attaching
1065 // the autocomplete stuff to the username field, so the user can
1066 // still manually select a login to be filled in.
1067 var isFormDisabled
= false;
1068 if (!ignoreAutocomplete
&&
1069 (this._isAutocompleteDisabled(form
) ||
1070 this._isAutocompleteDisabled(usernameField
) ||
1071 this._isAutocompleteDisabled(passwordField
))) {
1073 isFormDisabled
= true;
1074 this.log("form not filled, has autocomplete=off");
1077 // Variable such that we reduce code duplication and can be sure we
1078 // should be firing notifications if and only if we can fill the form.
1079 var selectedLogin
= null;
1081 if (usernameField
&& usernameField
.value
) {
1082 // If username was specified in the form, only fill in the
1083 // password if we find a matching login.
1085 var username
= usernameField
.value
;
1088 var found
= logins
.some(function(l
) {
1090 return (l
.username
== username
);
1093 selectedLogin
= matchingLogin
;
1095 this.log("Password not filled. None of the stored " +
1096 "logins match the username already present.");
1098 } else if (usernameField
&& logins
.length
== 2) {
1099 // Special case, for sites which have a normal user+pass
1100 // login *and* a password-only login (eg, a PIN)...
1101 // When we have a username field and 1 of 2 available
1102 // logins is password-only, go ahead and prefill the
1103 // one with a username.
1104 if (!logins
[0].username
&& logins
[1].username
)
1105 selectedLogin
= logins
[1];
1106 else if (!logins
[1].username
&& logins
[0].username
)
1107 selectedLogin
= logins
[0];
1108 } else if (logins
.length
== 1) {
1109 selectedLogin
= logins
[0];
1111 this.log("Multiple logins for form, so not filling any.");
1114 var didFillForm
= false;
1115 if (selectedLogin
&& autofillForm
&& !isFormDisabled
) {
1118 usernameField
.value
= selectedLogin
.username
;
1119 passwordField
.value
= selectedLogin
.password
;
1121 } else if (selectedLogin
&& !autofillForm
) {
1122 // For when autofillForm is false, but we still have the information
1123 // to fill a form, we notify observers.
1124 this._observerService
.notifyObservers(form
, "passwordmgr-found-form", "noAutofillForms");
1125 this.log("autofillForms=false but form can be filled; notified observers");
1126 } else if (selectedLogin
&& isFormDisabled
) {
1127 // For when autocomplete is off, but we still have the information
1128 // to fill a form, we notify observers.
1129 this._observerService
.notifyObservers(form
, "passwordmgr-found-form", "autocompleteOff");
1130 this.log("autocomplete=off but form can be filled; notified observers");
1133 return [didFillForm
, foundLogins
];
1140 * Fill the form with login information if we can find it.
1142 fillForm : function (form
) {
1143 this.log("fillForm processing form[id=" + form
.id
+ "]");
1144 return this._fillForm(form
, true, true, null)[0];
1151 * Hooks up autocomplete support to a username field, to allow
1152 * a user editing the field to select an existing login and have
1153 * the password field filled in.
1155 _attachToInput : function (element
) {
1156 this.log("attaching autocomplete stuff");
1157 element
.addEventListener("blur",
1158 this._domEventListener
, false);
1159 element
.addEventListener("DOMAutoComplete",
1160 this._domEventListener
, false);
1161 this._formFillService
.markAsLoginManagerField(element
);
1168 * The user has autocompleted a username field, so fill in the password.
1170 _fillPassword : function (usernameField
) {
1171 this.log("fillPassword autocomplete username: " + usernameField
.value
);
1173 var form
= usernameField
.form
;
1174 var doc
= form
.ownerDocument
;
1176 var hostname
= this._getPasswordOrigin(doc
.documentURI
);
1177 var formSubmitURL
= this._getActionOrigin(form
)
1179 // Find the password field. We should always have at least one,
1180 // or else something has gone rather wrong.
1181 var pwFields
= this._getPasswordFields(form
, false);
1183 const err
= "No password field for autocomplete password fill.";
1185 // We want to know about this even if debugging is disabled.
1194 // If there are multiple passwords fields, we can't really figure
1195 // out what each field is for, so just fill out the last field.
1196 var passwordField
= pwFields
[0].element
;
1198 // Temporary LoginInfo with the info we know.
1199 var currentLogin
= new this._nsLoginInfo();
1200 currentLogin
.init(hostname
, formSubmitURL
, null,
1201 usernameField
.value
, null,
1202 usernameField
.name
, passwordField
.name
);
1204 // Look for a existing login and use its password.
1206 var logins
= this.findLogins({}, hostname
, formSubmitURL
, null);
1208 if (!logins
.some(function(l
) {
1210 return currentLogin
.matches(l
, true);
1213 this.log("Can't find a login for this autocomplete result.");
1217 this.log("Found a matching login, filling in password.");
1218 passwordField
.value
= match
.password
;
1220 }; // end of LoginManager implementation
1225 // nsIAutoCompleteResult implementation
1226 function UserAutoCompleteResult (aSearchString
, matchingLogins
) {
1227 function loginSort(a
,b
) {
1228 var userA
= a
.username
.toLowerCase();
1229 var userB
= b
.username
.toLowerCase();
1240 this.searchString
= aSearchString
;
1241 this.logins
= matchingLogins
.sort(loginSort
);
1242 this.matchCount
= matchingLogins
.length
;
1244 if (this.matchCount
> 0) {
1245 this.searchResult
= Ci
.nsIAutoCompleteResult
.RESULT_SUCCESS
;
1246 this.defaultIndex
= 0;
1250 UserAutoCompleteResult
.prototype = {
1251 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsIAutoCompleteResult
,
1252 Ci
.nsISupportsWeakReference
]),
1257 // Interfaces from idl...
1258 searchString
: null,
1259 searchResult
: Ci
.nsIAutoCompleteResult
.RESULT_NOMATCH
,
1261 errorDescription
: "",
1264 getValueAt : function (index
) {
1265 if (index
< 0 || index
>= this.logins
.length
)
1266 throw "Index out of range.";
1268 return this.logins
[index
].username
;
1271 getCommentAt : function (index
) {
1275 getStyleAt : function (index
) {
1279 getImageAt : function (index
) {
1283 removeValueAt : function (index
, removeFromDB
) {
1284 if (index
< 0 || index
>= this.logins
.length
)
1285 throw "Index out of range.";
1287 var [removedLogin
] = this.logins
.splice(index
, 1);
1290 if (this.defaultIndex
> this.logins
.length
)
1291 this.defaultIndex
--;
1294 var pwmgr
= Cc
["@mozilla.org/login-manager;1"].
1295 getService(Ci
.nsILoginManager
);
1296 pwmgr
.removeLogin(removedLogin
);
1301 var component
= [LoginManager
];
1302 function NSGetModule (compMgr
, fileSpec
) {
1303 return XPCOMUtils
.generateModule(component
);