Back to index

lightning-sunbird  0.9+nobinonly
nsPasswordManager.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Password Manager.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Brian Ryner.
00019  * Portions created by the Initial Developer are Copyright (C) 2003
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Brian Ryner <bryner@brianryner.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsPasswordManager.h"
00040 #include "nsIFile.h"
00041 #include "nsNetUtil.h"
00042 #include "nsILineInputStream.h"
00043 #include "plbase64.h"
00044 #include "nsISecretDecoderRing.h"
00045 #include "nsIPasswordInternal.h"
00046 #include "nsIPrompt.h"
00047 #include "nsIPrefService.h"
00048 #include "nsIPrefBranch.h"
00049 #include "nsIPrefBranch2.h"
00050 #include "prmem.h"
00051 #include "nsIStringBundle.h"
00052 #include "nsArray.h"
00053 #include "nsICategoryManager.h"
00054 #include "nsIObserverService.h"
00055 #include "nsIDocumentLoader.h"
00056 #include "nsIWebProgress.h"
00057 #include "nsIDOMDocument.h"
00058 #include "nsIDOMWindow.h"
00059 #include "nsIDOMHTMLDocument.h"
00060 #include "nsIDocument.h"
00061 #include "nsIDOMHTMLCollection.h"
00062 #include "nsIForm.h"
00063 #include "nsIDOMHTMLInputElement.h"
00064 #include "nsIContent.h"
00065 #include "nsIFormControl.h"
00066 #include "nsIDOMWindowInternal.h"
00067 #include "nsCURILoader.h"
00068 #include "nsAppDirectoryServiceDefs.h"
00069 #include "nsIDOMEventTarget.h"
00070 #include "nsIDOMHTMLFormElement.h"
00071 #include "nsIAutoCompleteResult.h"
00072 #include "nsIPK11TokenDB.h"
00073 #include "nsIPK11Token.h"
00074 
00075 static const char kPMPropertiesURL[] = "chrome://passwordmgr/locale/passwordmgr.properties";
00076 static PRBool sRememberPasswords = PR_FALSE;
00077 static PRBool sPrefsInitialized = PR_FALSE;
00078 static PRBool sPasswordsLoaded = PR_FALSE;
00079 
00080 static nsIStringBundle* sPMBundle;
00081 static nsISecretDecoderRing* sDecoderRing;
00082 static nsPasswordManager* sPasswordManager;
00083 
00084 class nsPasswordManager::SignonDataEntry
00085 {
00086 public:
00087   nsString userField;
00088   nsString userValue;
00089   nsString passField;
00090   nsString passValue;
00091   nsCString actionOrigin;
00092   SignonDataEntry* next;
00093 
00094   SignonDataEntry() : next(nsnull) { }
00095   ~SignonDataEntry() { delete next; }
00096 };
00097 
00098 class nsPasswordManager::SignonHashEntry
00099 {
00100   // Wraps a pointer to the linked list of SignonDataEntry objects.
00101   // This allows us to adjust the head of the linked list without a
00102   // hashtable operation.
00103 
00104 public:
00105   SignonDataEntry* head;
00106 
00107   SignonHashEntry(SignonDataEntry* aEntry) : head(aEntry) { }
00108   ~SignonHashEntry() { delete head; }
00109 };
00110 
00111 class nsPasswordManager::PasswordEntry : public nsIPasswordInternal
00112 {
00113 public:
00114   PasswordEntry(const nsACString& aKey, SignonDataEntry* aData);
00115   virtual ~PasswordEntry() { }
00116 
00117   NS_DECL_ISUPPORTS
00118   NS_DECL_NSIPASSWORD
00119   NS_DECL_NSIPASSWORDINTERNAL
00120 
00121 protected:
00122 
00123   nsCString mHost;
00124   nsString  mUser;
00125   nsString  mUserField;
00126   nsString  mPassword;
00127   nsString  mPasswordField;
00128   nsCString mActionOrigin;
00129   PRBool    mDecrypted[2];
00130 };
00131 
00132 NS_IMPL_ISUPPORTS2(nsPasswordManager::PasswordEntry, nsIPassword,
00133                    nsIPasswordInternal)
00134 
00135 nsPasswordManager::PasswordEntry::PasswordEntry(const nsACString& aKey,
00136                                                 SignonDataEntry* aData)
00137   : mHost(aKey)
00138 {
00139     mDecrypted[0] = mDecrypted[1] = PR_FALSE;
00140 
00141   if (aData) {
00142     mUser.Assign(aData->userValue);
00143     mUserField.Assign(aData->userField);
00144     mPassword.Assign(aData->passValue);
00145     mPasswordField.Assign(aData->passField);
00146     mActionOrigin.Assign(aData->actionOrigin);
00147   }
00148 }
00149 
00150 NS_IMETHODIMP
00151 nsPasswordManager::PasswordEntry::GetHost(nsACString& aHost)
00152 {
00153   aHost.Assign(mHost);
00154   return NS_OK;
00155 }
00156 
00157 NS_IMETHODIMP
00158 nsPasswordManager::PasswordEntry::GetUser(nsAString& aUser)
00159 {
00160   if (!mUser.IsEmpty() && !mDecrypted[0]) {
00161     if (NS_SUCCEEDED(DecryptData(mUser, mUser)))
00162       mDecrypted[0] = PR_TRUE;
00163     else
00164       return NS_ERROR_FAILURE;
00165   }
00166 
00167   aUser.Assign(mUser);
00168   return NS_OK;
00169 }
00170 
00171 NS_IMETHODIMP
00172 nsPasswordManager::PasswordEntry::GetPassword(nsAString& aPassword)
00173 {
00174   if (!mPassword.IsEmpty() && !mDecrypted[1]) {
00175     if (NS_SUCCEEDED(DecryptData(mPassword, mPassword)))
00176       mDecrypted[1] = PR_TRUE;
00177     else
00178       return NS_ERROR_FAILURE;
00179   }
00180 
00181   aPassword.Assign(mPassword);
00182   return NS_OK;
00183 }
00184 
00185 NS_IMETHODIMP
00186 nsPasswordManager::PasswordEntry::GetUserFieldName(nsAString& aField)
00187 {
00188   aField.Assign(mUserField);
00189   return NS_OK;
00190 }
00191 
00192 NS_IMETHODIMP
00193 nsPasswordManager::PasswordEntry::GetPasswordFieldName(nsAString& aField)
00194 {
00195   aField.Assign(mPasswordField);
00196   return NS_OK;
00197 }
00198 
00199 
00200 
00201 NS_IMPL_ADDREF(nsPasswordManager)
00202 NS_IMPL_RELEASE(nsPasswordManager)
00203 
00204 NS_INTERFACE_MAP_BEGIN(nsPasswordManager)
00205   NS_INTERFACE_MAP_ENTRY(nsIPasswordManager)
00206   NS_INTERFACE_MAP_ENTRY(nsIPasswordManagerInternal)
00207   NS_INTERFACE_MAP_ENTRY(nsIObserver)
00208   NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver)
00209   NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
00210   NS_INTERFACE_MAP_ENTRY(nsIDOMFocusListener)
00211   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMFocusListener)
00212   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
00213   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPasswordManager)
00214 NS_INTERFACE_MAP_END
00215 
00216 nsPasswordManager::nsPasswordManager()
00217   : mAutoCompletingField(nsnull)
00218 {
00219 }
00220 
00221 nsPasswordManager::~nsPasswordManager()
00222 {
00223 }
00224 
00225 
00226 /* static */ nsPasswordManager*
00227 nsPasswordManager::GetInstance()
00228 {
00229   if (!sPasswordManager) {
00230     sPasswordManager = new nsPasswordManager();
00231     if (!sPasswordManager)
00232       return nsnull;
00233 
00234     NS_ADDREF(sPasswordManager);   // addref the global
00235 
00236     if (NS_FAILED(sPasswordManager->Init())) {
00237       NS_RELEASE(sPasswordManager);
00238       return nsnull;
00239     }
00240   }
00241 
00242   // We fail to load passwords during early initialization
00243   // This wrapper function allows us to handle that error and defer
00244   // password loading until later
00245   sPasswordManager->LoadPasswords();
00246 
00247   NS_ADDREF(sPasswordManager);   // addref the return result
00248   return sPasswordManager;
00249 }
00250 
00251 nsresult
00252 nsPasswordManager::Init()
00253 {
00254   mSignonTable.Init();
00255   mRejectTable.Init();
00256   mAutoCompleteInputs.Init();
00257 
00258   sPrefsInitialized = PR_TRUE;
00259 
00260   nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
00261   NS_ASSERTION(prefService, "No pref service");
00262 
00263   prefService->GetBranch("signon.", getter_AddRefs(mPrefBranch));
00264   NS_ASSERTION(mPrefBranch, "No pref branch");
00265 
00266   mPrefBranch->GetBoolPref("rememberSignons", &sRememberPasswords);
00267 
00268   nsCOMPtr<nsIPrefBranch2> branchInternal = do_QueryInterface(mPrefBranch);
00269 
00270   // Have the pref service hold a weak reference; the service manager
00271   // will be holding a strong reference.
00272 
00273   branchInternal->AddObserver("rememberSignons", this, PR_TRUE);
00274 
00275 
00276   // Be a form submit and web progress observer so that we can save and
00277   // prefill passwords.
00278 
00279   nsCOMPtr<nsIObserverService> obsService = do_GetService("@mozilla.org/observer-service;1");
00280   NS_ASSERTION(obsService, "No observer service");
00281 
00282   obsService->AddObserver(this, NS_EARLYFORMSUBMIT_SUBJECT, PR_TRUE);
00283 
00284   nsCOMPtr<nsIDocumentLoader> docLoaderService = do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
00285   NS_ASSERTION(docLoaderService, "No document loader service");
00286 
00287   nsCOMPtr<nsIWebProgress> progress = do_QueryInterface(docLoaderService);
00288   NS_ASSERTION(progress, "docloader service does not implement nsIWebProgress");
00289 
00290   progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
00291 
00292   return NS_OK;
00293 }
00294 
00295 /* static */ PRBool
00296 nsPasswordManager::SingleSignonEnabled()
00297 {
00298   if (!sPrefsInitialized) {
00299     // Create the PasswordManager service to initialize the prefs and callback
00300     nsCOMPtr<nsIPasswordManager> manager = do_GetService(NS_PASSWORDMANAGER_CONTRACTID);
00301   }
00302 
00303   return sRememberPasswords;
00304 }
00305 
00306 /* static */ NS_METHOD
00307 nsPasswordManager::Register(nsIComponentManager* aCompMgr,
00308                             nsIFile* aPath,
00309                             const char* aRegistryLocation,
00310                             const char* aComponentType,
00311                             const nsModuleComponentInfo* aInfo)
00312 {
00313   // By registering in NS_PASSWORDMANAGER_CATEGORY, an instance of the password
00314   // manager will be created when a password input is added to a form.  We
00315   // can then register that singleton instance as a form submission                 .
00316 
00317   nsresult rv;
00318   nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
00319   NS_ENSURE_SUCCESS(rv, rv);
00320 
00321   nsXPIDLCString prevEntry;
00322   catman->AddCategoryEntry(NS_PASSWORDMANAGER_CATEGORY,
00323                            "Password Manager",
00324                            NS_PASSWORDMANAGER_CONTRACTID,
00325                            PR_TRUE,
00326                            PR_TRUE,
00327                            getter_Copies(prevEntry));
00328 
00329   catman->AddCategoryEntry("app-startup",
00330                            "Password Manager",
00331                            NS_PASSWORDMANAGER_CONTRACTID,
00332                            PR_TRUE,
00333                            PR_TRUE,
00334                            getter_Copies(prevEntry));
00335 
00336   return NS_OK;
00337 }
00338 
00339 /* static */ NS_METHOD
00340 nsPasswordManager::Unregister(nsIComponentManager* aCompMgr,
00341                               nsIFile* aPath,
00342                               const char* aRegistryLocation,
00343                               const nsModuleComponentInfo* aInfo)
00344 {
00345   nsresult rv;
00346   nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
00347   NS_ENSURE_SUCCESS(rv, rv);
00348 
00349   catman->DeleteCategoryEntry(NS_PASSWORDMANAGER_CATEGORY,
00350                               NS_PASSWORDMANAGER_CONTRACTID,
00351                               PR_TRUE);
00352 
00353   return NS_OK;
00354 }
00355 
00356 /* static */ void
00357 nsPasswordManager::Shutdown()
00358 {
00359   NS_IF_RELEASE(sDecoderRing);
00360   NS_IF_RELEASE(sPMBundle);
00361   NS_IF_RELEASE(sPasswordManager);
00362 }
00363 
00364 // nsIPasswordManager implementation
00365 
00366 NS_IMETHODIMP
00367 nsPasswordManager::AddUser(const nsACString& aHost,
00368                            const nsAString& aUser,
00369                            const nsAString& aPassword)
00370 {
00371   // Silently ignore an empty username/password entry.
00372   // There's no point in taking up space in the signon file with this.
00373   if (aUser.IsEmpty() && aPassword.IsEmpty())
00374     return NS_OK;
00375 
00376   // Reject values that would cause problems when parsing the storage file
00377   nsresult rv = CheckLoginValues(aHost,
00378                                  EmptyString(), EmptyString(), EmptyCString());
00379   NS_ENSURE_SUCCESS(rv, rv);
00380 
00381   // Check for an existing entry for this host + user
00382   if (!aHost.IsEmpty()) {
00383     SignonHashEntry *hashEnt;
00384     if (mSignonTable.Get(aHost, &hashEnt)) {
00385       nsString empty;
00386       SignonDataEntry *entry = nsnull;
00387       FindPasswordEntryInternal(hashEnt->head, aUser, empty, empty, &entry);
00388       if (entry) {
00389         // Just change the password
00390         return EncryptDataUCS2(aPassword, entry->passValue);
00391       }
00392     }
00393   }
00394 
00395   SignonDataEntry* entry = new SignonDataEntry();
00396   if (NS_FAILED(EncryptDataUCS2(aUser, entry->userValue)) ||
00397       NS_FAILED(EncryptDataUCS2(aPassword, entry->passValue))) {
00398     delete entry;
00399     return NS_ERROR_FAILURE;
00400   }
00401 
00402   AddSignonData(aHost, entry);
00403   WritePasswords(mSignonFile);
00404 
00405   return NS_OK;
00406 }
00407 
00408 NS_IMETHODIMP
00409 nsPasswordManager::RemoveUser(const nsACString& aHost, const nsAString& aUser)
00410 {
00411   SignonDataEntry* entry, *prevEntry = nsnull;
00412   SignonHashEntry* hashEnt;
00413 
00414   if (!mSignonTable.Get(aHost, &hashEnt))
00415     return NS_ERROR_FAILURE;
00416 
00417   for (entry = hashEnt->head; entry; prevEntry = entry, entry = entry->next) {
00418 
00419     nsAutoString ptUser;
00420     if (!entry->userValue.IsEmpty() &&
00421         NS_FAILED(DecryptData(entry->userValue, ptUser)))
00422       break;
00423 
00424     if (ptUser.Equals(aUser)) {
00425       if (prevEntry)
00426         prevEntry->next = entry->next;
00427       else
00428         hashEnt->head = entry->next;
00429 
00430       entry->next = nsnull;
00431       delete entry;
00432 
00433       if (!hashEnt->head)
00434         mSignonTable.Remove(aHost);  // deletes hashEnt
00435 
00436       WritePasswords(mSignonFile);
00437 
00438       return NS_OK;
00439     }
00440   }
00441 
00442   return NS_ERROR_FAILURE;
00443 }
00444 
00445 NS_IMETHODIMP
00446 nsPasswordManager::AddReject(const nsACString& aHost)
00447 {
00448   // Reject values that would cause problems when parsing the storage file
00449   nsresult rv = CheckLoginValues(aHost,
00450                                  EmptyString(), EmptyString(), EmptyCString());
00451   NS_ENSURE_SUCCESS(rv, rv);
00452 
00453   mRejectTable.Put(aHost, 1);
00454   WritePasswords(mSignonFile);
00455   return NS_OK;
00456 }
00457 
00458 NS_IMETHODIMP
00459 nsPasswordManager::RemoveReject(const nsACString& aHost)
00460 {
00461   mRejectTable.Remove(aHost);
00462   WritePasswords(mSignonFile);
00463   return NS_OK;
00464 }
00465 
00466 /* static */ PLDHashOperator PR_CALLBACK
00467 nsPasswordManager::BuildArrayEnumerator(const nsACString& aKey,
00468                                         SignonHashEntry* aEntry,
00469                                         void* aUserData)
00470 {
00471   nsIMutableArray* array = NS_STATIC_CAST(nsIMutableArray*, aUserData);
00472 
00473   for (SignonDataEntry* e = aEntry->head; e; e = e->next)
00474     array->AppendElement(new PasswordEntry(aKey, e), PR_FALSE);
00475 
00476   return PL_DHASH_NEXT;
00477 }
00478 
00479 NS_IMETHODIMP
00480 nsPasswordManager::GetEnumerator(nsISimpleEnumerator** aEnumerator)
00481 {
00482   // Build an array out of the hashtable
00483   nsCOMPtr<nsIMutableArray> signonArray;
00484   NS_NewArray(getter_AddRefs(signonArray));
00485 
00486   mSignonTable.EnumerateRead(BuildArrayEnumerator, signonArray);
00487 
00488   return signonArray->Enumerate(aEnumerator);
00489 }
00490 
00491 /* static */ PLDHashOperator PR_CALLBACK
00492 nsPasswordManager::BuildRejectArrayEnumerator(const nsACString& aKey,
00493                                               PRInt32 aEntry,
00494                                               void* aUserData)
00495 {
00496   nsIMutableArray* array = NS_STATIC_CAST(nsIMutableArray*, aUserData);
00497 
00498   nsCOMPtr<nsIPassword> passwordEntry = new PasswordEntry(aKey, nsnull);
00499   array->AppendElement(passwordEntry, PR_FALSE);
00500 
00501   return PL_DHASH_NEXT;
00502 }
00503 
00504 NS_IMETHODIMP
00505 nsPasswordManager::GetRejectEnumerator(nsISimpleEnumerator** aEnumerator)
00506 {
00507   // Build an array out of the hashtable
00508   nsCOMPtr<nsIMutableArray> rejectArray;
00509   NS_NewArray(getter_AddRefs(rejectArray));
00510 
00511   mRejectTable.EnumerateRead(BuildRejectArrayEnumerator, rejectArray);
00512 
00513   return rejectArray->Enumerate(aEnumerator);
00514 }
00515 
00516 // nsIPasswordManagerInternal implementation
00517 
00518 struct findEntryContext {
00519   nsPasswordManager* manager;
00520   const nsACString& hostURI;
00521   const nsAString&  username;
00522   const nsAString&  password;
00523   nsACString& hostURIFound;
00524   nsAString&  usernameFound;
00525   nsAString&  passwordFound;
00526   PRBool matched;
00527 
00528   findEntryContext(nsPasswordManager* aManager,
00529                    const nsACString& aHostURI,
00530                    const nsAString&  aUsername,
00531                    const nsAString&  aPassword,
00532                    nsACString& aHostURIFound,
00533                    nsAString&  aUsernameFound,
00534                    nsAString&  aPasswordFound)
00535     : manager(aManager), hostURI(aHostURI), username(aUsername),
00536       password(aPassword), hostURIFound(aHostURIFound),
00537       usernameFound(aUsernameFound), passwordFound(aPasswordFound),
00538       matched(PR_FALSE) { }
00539 };
00540 
00541 /* static */ PLDHashOperator PR_CALLBACK
00542 nsPasswordManager::FindEntryEnumerator(const nsACString& aKey,
00543                                        SignonHashEntry* aEntry,
00544                                        void* aUserData)
00545 {
00546   findEntryContext* context = NS_STATIC_CAST(findEntryContext*, aUserData);
00547   nsPasswordManager* manager = context->manager;
00548   nsresult rv;
00549 
00550   SignonDataEntry* entry = nsnull;
00551   rv = manager->FindPasswordEntryInternal(aEntry->head,
00552                                           context->username,
00553                                           context->password,
00554                                           EmptyString(),
00555                                           &entry);
00556 
00557   if (NS_SUCCEEDED(rv) && entry) {
00558     if (NS_SUCCEEDED(DecryptData(entry->userValue, context->usernameFound)) &&
00559         NS_SUCCEEDED(DecryptData(entry->passValue, context->passwordFound))) {
00560       context->matched = PR_TRUE;
00561       context->hostURIFound.Assign(context->hostURI);
00562     }
00563 
00564     return PL_DHASH_STOP;
00565   }
00566 
00567   return PL_DHASH_NEXT;
00568 }
00569 
00570 NS_IMETHODIMP
00571 nsPasswordManager::FindPasswordEntry(const nsACString& aHostURI,
00572                                      const nsAString&  aUsername,
00573                                      const nsAString&  aPassword,
00574                                      nsACString& aHostURIFound,
00575                                      nsAString&  aUsernameFound,
00576                                      nsAString&  aPasswordFound)
00577 {
00578   if (!aHostURI.IsEmpty()) {
00579     SignonHashEntry* hashEnt;
00580     if (mSignonTable.Get(aHostURI, &hashEnt)) {
00581       SignonDataEntry* entry;
00582       nsresult rv = FindPasswordEntryInternal(hashEnt->head,
00583                                               aUsername,
00584                                               aPassword,
00585                                               EmptyString(),
00586                                               &entry);
00587 
00588       if (NS_SUCCEEDED(rv) && entry) {
00589         if (NS_SUCCEEDED(DecryptData(entry->userValue, aUsernameFound)) &&
00590             NS_SUCCEEDED(DecryptData(entry->passValue, aPasswordFound))) {
00591           aHostURIFound.Assign(aHostURI);
00592         } else {
00593           return NS_ERROR_FAILURE;
00594         }
00595       }
00596 
00597       return rv;
00598     }
00599 
00600     return NS_ERROR_FAILURE;
00601   }
00602 
00603   // No host given, so enumerate all entries in the hashtable
00604   findEntryContext context(this, aHostURI, aUsername, aPassword,
00605                            aHostURIFound, aUsernameFound, aPasswordFound);
00606 
00607   mSignonTable.EnumerateRead(FindEntryEnumerator, &context);
00608 
00609   return NS_OK;
00610 }
00611 
00612 NS_IMETHODIMP
00613 nsPasswordManager::AddUserFull(const nsACString& aKey,
00614                                const nsAString& aUser,
00615                                const nsAString& aPassword,
00616                                const nsAString& aUserFieldName,
00617                                const nsAString& aPassFieldName)
00618 {
00619   // Silently ignore an empty username/password entry.
00620   // There's no point in taking up space in the signon file with this.
00621   if (aUser.IsEmpty() && aPassword.IsEmpty())
00622     return NS_OK;
00623 
00624   // Reject values that would cause problems when parsing the storage file
00625   nsresult rv = CheckLoginValues(aKey, aUserFieldName,
00626                                  aPassFieldName, EmptyCString());
00627   NS_ENSURE_SUCCESS(rv, rv);
00628 
00629   // Check for an existing entry for this host + user
00630   if (!aKey.IsEmpty()) {
00631     SignonHashEntry *hashEnt;
00632     if (mSignonTable.Get(aKey, &hashEnt)) {
00633       nsString empty;
00634       SignonDataEntry *entry = nsnull;
00635       FindPasswordEntryInternal(hashEnt->head, aUser, empty, empty, &entry);
00636       if (entry) {
00637         // Just change the password
00638         EncryptDataUCS2(aPassword, entry->passValue);
00639         // ... and update the field names...s
00640         entry->userField.Assign(aUserFieldName);
00641         entry->passField.Assign(aPassFieldName);
00642         return NS_OK;
00643       }
00644     }
00645   }
00646 
00647   SignonDataEntry* entry = new SignonDataEntry();
00648   entry->userField.Assign(aUserFieldName);
00649   entry->passField.Assign(aPassFieldName);
00650   EncryptDataUCS2(aUser, entry->userValue);
00651   EncryptDataUCS2(aPassword, entry->passValue);
00652 
00653   AddSignonData(aKey, entry);
00654   WritePasswords(mSignonFile);
00655 
00656   return NS_OK;
00657 }
00658 
00659 NS_IMETHODIMP
00660 nsPasswordManager::ReadPasswords(nsIFile* aPasswordFile)
00661 {
00662   nsCOMPtr<nsIInputStream> fileStream;
00663   NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aPasswordFile);
00664   if (!fileStream)
00665     return NS_ERROR_OUT_OF_MEMORY;
00666 
00667   nsCOMPtr<nsILineInputStream> lineStream = do_QueryInterface(fileStream);
00668   NS_ASSERTION(lineStream, "File stream is not an nsILineInputStream");
00669 
00670   // Read the header
00671   nsCAutoString utf8Buffer;
00672   PRBool moreData = PR_FALSE;
00673   nsresult rv = lineStream->ReadLine(utf8Buffer, &moreData);
00674   if (NS_FAILED(rv))
00675     return NS_OK;
00676 
00677   PRBool updateEntries = PR_FALSE;
00678   PRBool writeOnFinish = PR_FALSE;
00679 
00680   if (utf8Buffer.Equals("#2c")) {
00681     // we've hit an older version of the file, but we can import it
00682     updateEntries = PR_TRUE;
00683 
00684     // make sure we write the new file out so we only do this once
00685     writeOnFinish = PR_TRUE;
00686   } else if (!utf8Buffer.Equals("#2d")) {
00687     NS_ERROR("Unexpected version header in signon file");
00688     return NS_OK;
00689   }
00690 
00691   enum { STATE_REJECT, STATE_REALM, STATE_USERFIELD,
00692          STATE_USERVALUE, STATE_PASSFIELD, STATE_PASSVALUE,
00693          STATE_ACTION_ORIGIN } state = STATE_REJECT;
00694 
00695   nsCAutoString realm;
00696   SignonDataEntry* entry = nsnull;
00697 
00698   do {
00699     rv = lineStream->ReadLine(utf8Buffer, &moreData);
00700     if (NS_FAILED(rv))
00701       return NS_OK;
00702 
00703     switch (state) {
00704     case STATE_REJECT:
00705       if (utf8Buffer.Equals(NS_LITERAL_CSTRING(".")))
00706         state = STATE_REALM;
00707       else
00708         mRejectTable.Put(utf8Buffer, 1);
00709 
00710       break;
00711 
00712     case STATE_REALM:
00713       realm.Assign(utf8Buffer);
00714       state = STATE_USERFIELD;
00715       break;
00716 
00717     case STATE_USERFIELD:
00718 
00719       // Commit any completed entry
00720       if (entry) {
00721         // Weed out empty username+password entries from corrupted signon files
00722         if (entry->userValue.IsEmpty() && entry->passValue.IsEmpty()) {
00723           NS_WARNING("Discarding empty password entry");
00724           writeOnFinish = PR_TRUE; // so we won't get this on the next startup
00725           delete entry;
00726         } else {
00727           AddSignonData(realm, entry);
00728         }
00729       }
00730 
00731       // If the line is a ., we've reached the end of this realm's entries.
00732       if (utf8Buffer.Equals(NS_LITERAL_CSTRING("."))) {
00733         entry = nsnull;
00734         state = STATE_REALM;
00735       } else {
00736         entry = new SignonDataEntry();
00737         CopyUTF8toUTF16(utf8Buffer, entry->userField);
00738         state = STATE_USERVALUE;
00739       }
00740 
00741       break;
00742 
00743     case STATE_USERVALUE:
00744       NS_ASSERTION(entry, "bad state");
00745 
00746       CopyUTF8toUTF16(utf8Buffer, entry->userValue);
00747 
00748       state = STATE_PASSFIELD;
00749       break;
00750 
00751     case STATE_PASSFIELD:
00752       NS_ASSERTION(entry, "bad state");
00753 
00754       // Strip off the leading "*" character
00755       CopyUTF8toUTF16(Substring(utf8Buffer, 1, utf8Buffer.Length() - 1),
00756                       entry->passField);
00757 
00758       state = STATE_PASSVALUE;
00759       break;
00760 
00761     case STATE_PASSVALUE:
00762       NS_ASSERTION(entry, "bad state");
00763 
00764       CopyUTF8toUTF16(utf8Buffer, entry->passValue);
00765 
00766       // if we're updating entries from an older file skip the action url
00767       if (updateEntries)
00768         state = STATE_USERFIELD;
00769       else
00770         state = STATE_ACTION_ORIGIN;
00771       break;
00772 
00773     case STATE_ACTION_ORIGIN:
00774       NS_ASSERTION(entry, "bad state");
00775 
00776       entry->actionOrigin.Assign(utf8Buffer);
00777 
00778       state = STATE_USERFIELD;
00779 
00780       break;
00781     }
00782   } while (moreData);
00783 
00784   // Don't leak if the file ended unexpectedly
00785   delete entry;
00786 
00787   if (writeOnFinish) {
00788     fileStream->Close();
00789     WritePasswords(mSignonFile);
00790   }
00791 
00792   return NS_OK;
00793 }
00794 
00795 
00796 // nsIObserver implementation
00797 NS_IMETHODIMP
00798 nsPasswordManager::Observe(nsISupports* aSubject,
00799                            const char* aTopic,
00800                            const PRUnichar* aData)
00801 {
00802   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
00803     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(aSubject);
00804     NS_ASSERTION(branch == mPrefBranch, "unexpected pref change notification");
00805 
00806     branch->GetBoolPref("rememberSignons", &sRememberPasswords);
00807   } else if (!strcmp(aTopic, "app-startup")) {
00808     nsCOMPtr<nsIObserverService> obsService = do_GetService("@mozilla.org/observer-service;1");
00809     NS_ASSERTION(obsService, "No observer service");
00810 
00811     obsService->AddObserver(this, "profile-after-change", PR_TRUE);
00812   } else if (!strcmp(aTopic, "profile-after-change"))
00813     LoadPasswords();
00814 
00815   return NS_OK;
00816 }
00817 
00818 // nsIWebProgressListener implementation
00819 NS_IMETHODIMP
00820 nsPasswordManager::OnStateChange(nsIWebProgress* aWebProgress,
00821                                  nsIRequest* aRequest,
00822                                  PRUint32 aStateFlags,
00823                                  nsresult aStatus)
00824 {
00825   if (!(aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) ||
00826       !(aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) ||
00827       NS_FAILED(aStatus))
00828     return NS_OK;
00829 
00830   // Don't do anything if the global signon pref is disabled
00831   if (!SingleSignonEnabled())
00832     return NS_OK;
00833 
00834   nsCOMPtr<nsIDOMWindow> domWin;
00835   nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWin));
00836   NS_ENSURE_SUCCESS(rv, rv);
00837 
00838   nsCOMPtr<nsIDOMDocument> domDoc;
00839   domWin->GetDocument(getter_AddRefs(domDoc));
00840   NS_ASSERTION(domDoc, "DOM window should always have a document!");
00841 
00842   // For now, only prepare to prefill forms in HTML documents.
00843   nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
00844   if (!htmlDoc)
00845     return NS_OK;
00846 
00847   if (aStateFlags & nsIWebProgressListener::STATE_RESTORING)
00848       return FillDocument(domDoc);
00849 
00850   nsCOMPtr<nsIDOMEventTarget> targDoc = do_QueryInterface(domDoc);
00851   nsCOMPtr<nsIDOMEventTarget> targWin = do_QueryInterface(domWin);
00852   nsIDOMEventListener* listener = NS_STATIC_CAST(nsIDOMFocusListener*, this);
00853   targDoc->AddEventListener(NS_LITERAL_STRING("DOMContentLoaded"), listener, PR_FALSE);
00854   targWin->AddEventListener(NS_LITERAL_STRING("pagehide"), listener, PR_FALSE);
00855 
00856   return NS_OK;
00857 }
00858 
00859 NS_IMETHODIMP
00860 nsPasswordManager::OnProgressChange(nsIWebProgress* aWebProgress,
00861                                     nsIRequest* aRequest,
00862                                     PRInt32 aCurSelfProgress,
00863                                     PRInt32 aMaxSelfProgress,
00864                                     PRInt32 aCurTotalProgress,
00865                                     PRInt32 aMaxTotalProgress)
00866 {
00867   return NS_OK;
00868 }
00869 
00870 NS_IMETHODIMP
00871 nsPasswordManager::OnLocationChange(nsIWebProgress* aWebProgress,
00872                                     nsIRequest* aRequest,
00873                                     nsIURI* aLocation)
00874 {
00875   return NS_OK;
00876 }
00877 
00878 NS_IMETHODIMP
00879 nsPasswordManager::OnStatusChange(nsIWebProgress* aWebProgress,
00880                                   nsIRequest* aRequest,
00881                                   nsresult aStatus,
00882                                   const PRUnichar* aMessage)
00883 {
00884   return NS_OK;
00885 }
00886 
00887 NS_IMETHODIMP
00888 nsPasswordManager::OnSecurityChange(nsIWebProgress* aWebProgress,
00889                                     nsIRequest* aRequest,
00890                                     PRUint32 aState)
00891 {
00892   return NS_OK;
00893 }
00894 
00895 
00896 // nsIFormSubmitObserver implementation
00897 NS_IMETHODIMP
00898 nsPasswordManager::Notify(nsIContent* aFormNode,
00899                           nsIDOMWindowInternal* aWindow,
00900                           nsIURI* aActionURL,
00901                           PRBool* aCancelSubmit)
00902 {
00903   // This function must never return a failure code or the form submit
00904   // will be cancelled.
00905 
00906   NS_ENSURE_TRUE(aWindow, NS_OK);
00907 
00908   // Don't do anything if the global signon pref is disabled
00909   if (!SingleSignonEnabled())
00910     return NS_OK;
00911 
00912   // Check the reject list
00913   nsCAutoString realm;
00914   // XXX bug 281125: GetDocument() could sometimes be null here, hinting
00915   // XXX at a problem with document teardown while a modal dialog is posted.
00916   if (!GetPasswordRealm(aFormNode->GetOwnerDoc()->GetDocumentURI(), realm))
00917     return NS_OK;
00918 
00919   PRInt32 rejectValue;
00920   if (mRejectTable.Get(realm, &rejectValue)) {
00921     // The user has opted to never save passwords for this site.
00922     return NS_OK;
00923   }
00924 
00925   nsCOMPtr<nsIForm> formElement = do_QueryInterface(aFormNode);
00926 
00927   PRUint32 numControls;
00928   formElement->GetElementCount(&numControls);
00929 
00930   // Count the number of password fields in the form.
00931 
00932   nsCOMPtr<nsIDOMHTMLInputElement> userField;
00933   nsCOMArray<nsIDOMHTMLInputElement> passFields;
00934 
00935   PRUint32 i, firstPasswordIndex = numControls;
00936 
00937   for (i = 0; i < numControls; ++i) {
00938 
00939     nsCOMPtr<nsIFormControl> control;
00940     formElement->GetElementAt(i, getter_AddRefs(control));
00941 
00942     if (control->GetType() == NS_FORM_INPUT_PASSWORD) {
00943       nsCOMPtr<nsIDOMHTMLInputElement> elem = do_QueryInterface(control);
00944       passFields.AppendObject(elem);
00945       if (firstPasswordIndex == numControls)
00946         firstPasswordIndex = i;
00947     }
00948   }
00949 
00950   nsCOMPtr<nsIPrompt> prompt;
00951   aWindow->GetPrompter(getter_AddRefs(prompt));
00952 
00953   switch (passFields.Count()) {
00954   case 1:  // normal login
00955     {
00956       // Search backwards from the password field to find a username field.
00957       for (PRInt32 j = (PRInt32) firstPasswordIndex - 1; j >= 0; --j) {
00958         nsCOMPtr<nsIFormControl> control;
00959         formElement->GetElementAt(j, getter_AddRefs(control));
00960 
00961         if (control->GetType() == NS_FORM_INPUT_TEXT) {
00962           userField = do_QueryInterface(control);
00963           break;
00964         }
00965       }
00966 
00967       // If the username field or the form has autocomplete=off,
00968       // we don't store the login
00969 
00970       nsAutoString autocomplete;
00971 
00972       if (userField) {
00973         nsCOMPtr<nsIDOMElement> userFieldElement = do_QueryInterface(userField);
00974         userFieldElement->GetAttribute(NS_LITERAL_STRING("autocomplete"),
00975                                        autocomplete);
00976 
00977         if (autocomplete.EqualsIgnoreCase("off"))
00978           return NS_OK;
00979       }
00980 
00981       nsCOMPtr<nsIDOMElement> formDOMEl = do_QueryInterface(aFormNode);
00982       formDOMEl->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete);
00983       if (autocomplete.EqualsIgnoreCase("off"))
00984         return NS_OK;
00985 
00986       nsCOMPtr<nsIDOMElement> passFieldElement = do_QueryInterface(passFields.ObjectAt(0));
00987       passFieldElement->GetAttribute(NS_LITERAL_STRING("autocomplete"), autocomplete);
00988       if (autocomplete.EqualsIgnoreCase("off"))
00989         return NS_OK;
00990 
00991 
00992       // Check whether this signon is already stored.
00993       // Note that we don't prompt the user if only the password doesn't match;
00994       // we instead just silently change the stored password.
00995 
00996       nsAutoString userValue, passValue, userFieldName, passFieldName, actionOrigin;
00997 
00998       if (userField) {
00999         userField->GetValue(userValue);
01000         userField->GetName(userFieldName);
01001       }
01002 
01003       passFields.ObjectAt(0)->GetValue(passValue);
01004       passFields.ObjectAt(0)->GetName(passFieldName);
01005 
01006       // If the password is empty, there is no reason to store this login.
01007       if (passValue.IsEmpty())
01008         return NS_OK;
01009 
01010       SignonHashEntry* hashEnt;
01011       nsCAutoString formActionOrigin;
01012 
01013       if (mSignonTable.Get(realm, &hashEnt)) {
01014 
01015         SignonDataEntry* entry;
01016         nsAutoString buffer;
01017 
01018         for (entry = hashEnt->head; entry; entry = entry->next) {
01019           if (entry->userField.Equals(userFieldName) &&
01020               entry->passField.Equals(passFieldName)) {
01021 
01022             if (NS_FAILED(DecryptData(entry->userValue, buffer)))
01023               return NS_OK;
01024 
01025             if (buffer.Equals(userValue)) {
01026 
01027               if (NS_FAILED(DecryptData(entry->passValue, buffer)))
01028                 return NS_OK;
01029 
01030               PRBool writePasswords = PR_FALSE;
01031               
01032               if (!buffer.Equals(passValue)) {
01033                 if (NS_FAILED(EncryptDataUCS2(passValue, entry->passValue)))
01034                   return NS_OK;
01035 
01036                 writePasswords = PR_TRUE;
01037               }
01038 
01039               if (NS_SUCCEEDED(GetActionRealm(formElement, formActionOrigin)) &&
01040                   !entry->actionOrigin.Equals(formActionOrigin)) {
01041 
01042                 // Reject values that would cause problems when parsing the storage file
01043                 if (NS_SUCCEEDED(CheckLoginValues(EmptyCString(), EmptyString(),
01044                                                   EmptyString(), formActionOrigin))) {
01045                   // update the action URL
01046                   entry->actionOrigin.Assign(formActionOrigin);
01047                   writePasswords = PR_TRUE;
01048                 }
01049               }
01050 
01051               if (writePasswords)
01052                 WritePasswords(mSignonFile);
01053 
01054               return NS_OK;
01055             }
01056           }
01057         }
01058       }
01059 
01060       nsresult rv;
01061       nsCOMPtr<nsIStringBundleService> bundleService =
01062         do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01063       nsCOMPtr<nsIStringBundle> brandBundle;
01064       rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties",
01065                                        getter_AddRefs(brandBundle));
01066       NS_ENSURE_SUCCESS(rv, rv);
01067       nsXPIDLString brandShortName;
01068       rv = brandBundle->GetStringFromName(NS_LITERAL_STRING("brandShortName").get(),
01069                                           getter_Copies(brandShortName));
01070       NS_ENSURE_SUCCESS(rv, rv);
01071       const PRUnichar* formatArgs[1] = { brandShortName.get() };
01072 
01073       nsAutoString dialogText;
01074       GetLocalizedString(NS_LITERAL_STRING("savePasswordText"),
01075                          dialogText,
01076                          PR_TRUE,
01077                          formatArgs,
01078                          1);
01079 
01080       nsAutoString dialogTitle, neverButtonText, rememberButtonText,
01081                    notNowButtonText;
01082       GetLocalizedString(NS_LITERAL_STRING("savePasswordTitle"), dialogTitle);
01083 
01084       GetLocalizedString(NS_LITERAL_STRING("neverForSiteButtonText"),
01085                          neverButtonText);
01086       GetLocalizedString(NS_LITERAL_STRING("rememberButtonText"),
01087                          rememberButtonText);
01088       GetLocalizedString(NS_LITERAL_STRING("notNowButtonText"),
01089                          notNowButtonText);
01090 
01091       PRInt32 selection;
01092       prompt->ConfirmEx(dialogTitle.get(),
01093                         dialogText.get(),
01094                         nsIPrompt::BUTTON_POS_1_DEFAULT +
01095                         (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
01096                         (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_1) +
01097                         (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2),
01098                         rememberButtonText.get(),
01099                         notNowButtonText.get(),
01100                         neverButtonText.get(),
01101                         nsnull, nsnull,
01102                         &selection);
01103 
01104       if (selection == 0) {
01105         SignonDataEntry* entry = new SignonDataEntry();
01106         entry->userField.Assign(userFieldName);
01107         entry->passField.Assign(passFieldName);
01108 
01109         // save the hostname of the action URL
01110         if (NS_FAILED(GetActionRealm(formElement, formActionOrigin))) {
01111           delete entry;
01112           return NS_OK;
01113         }
01114 
01115         entry->actionOrigin.Assign(formActionOrigin);
01116 
01117         if (NS_FAILED(EncryptDataUCS2(userValue, entry->userValue)) ||
01118             NS_FAILED(EncryptDataUCS2(passValue, entry->passValue))) {
01119           delete entry;
01120           return NS_OK;
01121         }
01122 
01123         // Reject values that would cause problems when parsing the storage file
01124         // We do this after prompting, lest any code somehow change the values
01125         // during the prompting.
01126         nsresult rv = CheckLoginValues(realm,
01127                                        entry->userField, entry->passField,
01128                                        entry->actionOrigin);
01129         NS_ENSURE_SUCCESS(rv, NS_OK);
01130 
01131         AddSignonData(realm, entry);
01132         WritePasswords(mSignonFile);
01133       } else if (selection == 2) {
01134         // Reject values that would cause problems when parsing the storage file
01135         // We do this after prompting, lest any code run from prompt context.
01136         nsresult rv = CheckLoginValues(realm, EmptyString(),
01137                                        EmptyString(), EmptyCString());
01138         NS_ENSURE_SUCCESS(rv, NS_OK);
01139 
01140         AddReject(realm);
01141       }
01142     }
01143     break;
01144 
01145   case 2:
01146   case 3:
01147     {
01148       // If the following conditions are true, we guess that this is a
01149       // password change page:
01150       //   - there are 2 or 3 password fields on the page
01151       //   - the fields do not all have the same value
01152       //   - there is already a stored login for this realm
01153       //
01154       // In this situation, prompt the user to confirm that this is a password
01155       // change.
01156 
01157       SignonDataEntry* changeEntry = nsnull;
01158       nsAutoString value0, valueN;
01159       passFields.ObjectAt(0)->GetValue(value0);
01160 
01161       for (PRInt32 k = 1; k < passFields.Count(); ++k) {
01162         passFields.ObjectAt(k)->GetValue(valueN);
01163         if (!value0.Equals(valueN)) {
01164 
01165           SignonHashEntry* hashEnt;
01166 
01167           if (mSignonTable.Get(realm, &hashEnt)) {
01168 
01169             SignonDataEntry* entry = hashEnt->head;
01170 
01171             if (entry->next) {
01172 
01173               // Multiple stored logons, prompt for which username is
01174               // being changed.
01175 
01176               PRUint32 entryCount = 2;
01177               SignonDataEntry* temp = entry->next;
01178               while (temp->next) {
01179                 ++entryCount;
01180                 temp = temp->next;
01181               }
01182 
01183               nsAutoString* ptUsernames = new nsAutoString[entryCount];
01184               const PRUnichar** formatArgs = new const PRUnichar*[entryCount];
01185               temp = entry;
01186 
01187               for (PRUint32 arg = 0; arg < entryCount; ++arg) {
01188                 if (NS_FAILED(DecryptData(temp->userValue, ptUsernames[arg]))) {
01189                   delete [] formatArgs;
01190                   delete [] ptUsernames;
01191                   return NS_OK;
01192                 }
01193 
01194                 formatArgs[arg] = ptUsernames[arg].get();
01195                 temp = temp->next;
01196               }
01197 
01198               nsAutoString dialogTitle, dialogText;
01199               GetLocalizedString(NS_LITERAL_STRING("passwordChangeTitle"),
01200                                  dialogTitle);
01201               GetLocalizedString(NS_LITERAL_STRING("userSelectText"),
01202                                  dialogText);
01203 
01204               PRInt32 selection;
01205               PRBool confirm;
01206               prompt->Select(dialogTitle.get(),
01207                              dialogText.get(),
01208                              entryCount,
01209                              formatArgs,
01210                              &selection,
01211                              &confirm);
01212 
01213               delete[] formatArgs;
01214               delete[] ptUsernames;
01215 
01216               if (confirm && selection >= 0) {
01217                 changeEntry = entry;
01218                 for (PRInt32 m = 0; m < selection; ++m)
01219                   changeEntry = changeEntry->next;
01220               }
01221 
01222             } else {
01223               nsAutoString dialogTitle, dialogText, ptUser;
01224 
01225               if (NS_FAILED(DecryptData(entry->userValue, ptUser)))
01226                 return NS_OK;
01227 
01228               const PRUnichar* formatArgs[1] = { ptUser.get() };
01229 
01230               GetLocalizedString(NS_LITERAL_STRING("passwordChangeTitle"),
01231                                  dialogTitle);
01232               GetLocalizedString(NS_LITERAL_STRING("passwordChangeText"),
01233                                  dialogText,
01234                                  PR_TRUE,
01235                                  formatArgs,
01236                                  1);
01237 
01238               PRInt32 selection;
01239               prompt->ConfirmEx(dialogTitle.get(),
01240                                 dialogText.get(),
01241                                 (nsIPrompt::BUTTON_TITLE_YES * nsIPrompt::BUTTON_POS_0) +
01242                                 (nsIPrompt::BUTTON_TITLE_NO * nsIPrompt::BUTTON_POS_1),
01243                                 nsnull, nsnull, nsnull, nsnull, nsnull,
01244                                 &selection);
01245 
01246               if (selection == 0)
01247                 changeEntry = entry;
01248             }
01249           }
01250           break;
01251         }
01252       }
01253 
01254       if (changeEntry) {
01255         nsAutoString newValue;
01256         passFields.ObjectAt(1)->GetValue(newValue);
01257         if (NS_FAILED(EncryptDataUCS2(newValue, changeEntry->passValue)))
01258           return NS_OK;
01259 
01260         WritePasswords(mSignonFile);
01261       }
01262     }
01263     break;
01264 
01265   default:  // no passwords or something odd; be safe and just don't store anything
01266     break;
01267   }
01268 
01269 
01270   return NS_OK;
01271 }
01272 
01273 // nsIDOMFocusListener implementation
01274 
01275 NS_IMETHODIMP
01276 nsPasswordManager::Focus(nsIDOMEvent* aEvent)
01277 {
01278   return NS_OK;
01279 }
01280 
01281 NS_IMETHODIMP
01282 nsPasswordManager::Blur(nsIDOMEvent* aEvent)
01283 {
01284   return FillPassword(aEvent);
01285 }
01286 
01287 NS_IMETHODIMP
01288 nsPasswordManager::HandleEvent(nsIDOMEvent* aEvent)
01289 {
01290   nsAutoString type;
01291   aEvent->GetType(type);
01292 
01293   if (type.EqualsLiteral("DOMAutoComplete"))
01294     return FillPassword(aEvent);
01295 
01296   nsCOMPtr<nsIDOMEventTarget> target;
01297   aEvent->GetTarget(getter_AddRefs(target));
01298 
01299   nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(target);
01300   if (!domDoc)
01301     return NS_OK;
01302 
01303   if (type.EqualsLiteral("pagehide"))
01304     mAutoCompleteInputs.Enumerate(RemoveForDOMDocumentEnumerator, domDoc);
01305   else if (type.EqualsLiteral("DOMContentLoaded"))
01306     return FillDocument(domDoc);
01307 
01308   return NS_OK;
01309 }
01310 
01311 // Autocomplete implementation
01312 
01313 class UserAutoComplete : public nsIAutoCompleteResult
01314 {
01315 public:
01316   UserAutoComplete(const nsACString& aHost, const nsAString& aSearchString);
01317   virtual ~UserAutoComplete();
01318 
01319   NS_DECL_ISUPPORTS
01320   NS_DECL_NSIAUTOCOMPLETERESULT
01321 
01322   nsVoidArray mArray;
01323   nsCString   mHost;
01324   nsString    mSearchString;
01325   PRInt32     mDefaultIndex;
01326   PRUint16    mResult;
01327 };
01328 
01329 UserAutoComplete::UserAutoComplete(const nsACString& aHost,
01330                                    const nsAString& aSearchString)
01331   : mHost(aHost),
01332     mSearchString(aSearchString),
01333     mDefaultIndex(-1),
01334     mResult(RESULT_FAILURE)
01335 {
01336 }
01337 
01338 UserAutoComplete::~UserAutoComplete()
01339 {
01340   for (PRInt32 i  = 0; i < mArray.Count(); ++i)
01341     nsMemory::Free(mArray.ElementAt(i));
01342 }
01343 
01344 NS_IMPL_ISUPPORTS1(UserAutoComplete, nsIAutoCompleteResult)
01345 
01346 NS_IMETHODIMP
01347 UserAutoComplete::GetSearchString(nsAString& aString)
01348 {
01349   aString.Assign(mSearchString);
01350   return NS_OK;
01351 }
01352 
01353 NS_IMETHODIMP
01354 UserAutoComplete::GetSearchResult(PRUint16* aResult)
01355 {
01356   *aResult = mResult;
01357   return NS_OK;
01358 }
01359 
01360 NS_IMETHODIMP
01361 UserAutoComplete::GetDefaultIndex(PRInt32* aDefaultIndex)
01362 {
01363   *aDefaultIndex = mDefaultIndex;
01364   return NS_OK;
01365 }
01366 
01367 NS_IMETHODIMP
01368 UserAutoComplete::GetErrorDescription(nsAString& aDescription)
01369 {
01370   aDescription.Truncate();
01371   return NS_OK;
01372 }
01373 
01374 NS_IMETHODIMP
01375 UserAutoComplete::GetMatchCount(PRUint32* aCount)
01376 {
01377   *aCount = mArray.Count();
01378   return NS_OK;
01379 }
01380 
01381 NS_IMETHODIMP
01382 UserAutoComplete::GetValueAt(PRInt32 aIndex, nsAString& aValue)
01383 {
01384   aValue.Assign(NS_STATIC_CAST(PRUnichar*, mArray.ElementAt(aIndex)));
01385   return NS_OK;
01386 }
01387 
01388 NS_IMETHODIMP
01389 UserAutoComplete::GetCommentAt(PRInt32 aIndex, nsAString& aComment)
01390 {
01391   aComment.Truncate();
01392   return NS_OK;
01393 }
01394 
01395 NS_IMETHODIMP
01396 UserAutoComplete::GetStyleAt(PRInt32 aIndex, nsAString& aHint)
01397 {
01398   aHint.Truncate();
01399   return NS_OK;
01400 }
01401 
01402 NS_IMETHODIMP
01403 UserAutoComplete::RemoveValueAt(PRInt32 aIndex, PRBool aRemoveFromDB)
01404 {
01405   NS_ENSURE_TRUE(aIndex >= 0 && aIndex < mArray.Count(), NS_ERROR_INVALID_ARG);
01406 
01407   PRUnichar *user = NS_STATIC_CAST(PRUnichar*, mArray.ElementAt(aIndex));
01408   if (aRemoveFromDB)
01409     sPasswordManager->RemoveUser(mHost, nsDependentString(user));
01410 
01411   nsMemory::Free(user);
01412   mArray.RemoveElementAt(aIndex);
01413   return NS_OK;
01414 }
01415 
01416 PR_STATIC_CALLBACK(int)
01417 SortPRUnicharComparator(const void* aElement1,
01418                         const void* aElement2,
01419                         void* aData)
01420 {
01421   return nsCRT::strcmp(NS_STATIC_CAST(const PRUnichar*, aElement1),
01422                        NS_STATIC_CAST(const PRUnichar*, aElement2));
01423 }
01424 
01425 PRBool
01426 nsPasswordManager::AutoCompleteSearch(const nsAString& aSearchString,
01427                                       nsIAutoCompleteResult* aPreviousResult,
01428                                       nsIDOMHTMLInputElement* aElement,
01429                                       nsIAutoCompleteResult** aResult)
01430 {
01431   PRInt32 dummy;
01432   if (!SingleSignonEnabled() || !mAutoCompleteInputs.Get(aElement, &dummy))
01433     return PR_FALSE;
01434 
01435   UserAutoComplete* result = nsnull;
01436 
01437   if (aPreviousResult) {
01438 
01439     // We have a list of results for a shorter search string, so just
01440     // filter them further based on the new search string.
01441 
01442     result = NS_STATIC_CAST(UserAutoComplete*, aPreviousResult);
01443 
01444     if (result->mArray.Count()) {
01445       for (PRInt32 i = result->mArray.Count() - 1; i >= 0; --i) {
01446         nsDependentString match(NS_STATIC_CAST(PRUnichar*, result->mArray.ElementAt(i)));
01447         if (aSearchString.Length() > match.Length() ||
01448             !StringBeginsWith(match, aSearchString)) {
01449           nsMemory::Free(result->mArray.ElementAt(i));
01450           result->mArray.RemoveElementAt(i);
01451         }
01452       }
01453     }
01454   } else {
01455 
01456     nsCOMPtr<nsIDOMDocument> domDoc;
01457     aElement->GetOwnerDocument(getter_AddRefs(domDoc));
01458 
01459     nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
01460 
01461     nsCAutoString realm;
01462     if (!GetPasswordRealm(doc->GetDocumentURI(), realm)) {
01463       *aResult = nsnull;
01464       return NS_OK;
01465     }
01466 
01467     // Get all of the matches into an array that we can sort.
01468 
01469     result = new UserAutoComplete(realm, aSearchString);
01470 
01471     SignonHashEntry* hashEnt;
01472     if (mSignonTable.Get(realm, &hashEnt)) {
01473       // Protect against a reentrant call to DecryptData.  For example, if
01474       // DecryptData causes the Master Password dialog to appear, we don't
01475       // want to respond to a blur on the input element by trying to prefill
01476       // the password.
01477 
01478       mAutoCompletingField = aElement;
01479 
01480       nsCOMPtr<nsIDOMHTMLFormElement> formEl;
01481       aElement->GetForm(getter_AddRefs(formEl));
01482       if (!formEl)
01483         return NS_OK;
01484 
01485       nsCOMPtr<nsIForm> form = do_QueryInterface(formEl);
01486       nsCAutoString formActionOrigin;
01487 
01488       if (NS_FAILED(GetActionRealm(form, formActionOrigin)))
01489         return NS_OK;
01490 
01491       for (SignonDataEntry* e = hashEnt->head; e; e = e->next) {
01492 
01493         nsAutoString userValue;
01494         if (NS_FAILED(DecryptData(e->userValue, userValue)))
01495           return NS_ERROR_FAILURE;
01496 
01497         // if we don't match actionOrigin, don't count this as a match
01498         if (!e->actionOrigin.IsEmpty() &&
01499             !e->actionOrigin.Equals(formActionOrigin))
01500           continue;
01501 
01502         if (aSearchString.Length() <= userValue.Length() &&
01503             StringBeginsWith(userValue, aSearchString)) {
01504           PRUnichar* data = ToNewUnicode(userValue);
01505           if (data)
01506             result->mArray.AppendElement(data);
01507         }
01508       }
01509 
01510       mAutoCompletingField = nsnull;
01511     }
01512 
01513     if (result->mArray.Count()) {
01514       result->mArray.Sort(SortPRUnicharComparator, nsnull);
01515       result->mResult = nsIAutoCompleteResult::RESULT_SUCCESS;
01516       result->mDefaultIndex = 0;
01517     } else {
01518       result->mResult = nsIAutoCompleteResult::RESULT_NOMATCH;
01519       result->mDefaultIndex = -1;
01520     }
01521   }
01522 
01523   *aResult = result;
01524   NS_ADDREF(*aResult);
01525 
01526   return PR_TRUE;
01527 }
01528 
01529  /* static */ PLDHashOperator PR_CALLBACK
01530 nsPasswordManager::RemoveForDOMDocumentEnumerator(nsISupports* aKey,
01531                                                   PRInt32& aEntry,
01532                                                   void* aUserData)
01533 {
01534   nsIDOMDocument* domDoc = NS_STATIC_CAST(nsIDOMDocument*, aUserData);
01535   nsCOMPtr<nsIDOMHTMLInputElement> element = do_QueryInterface(aKey);
01536   nsCOMPtr<nsIDOMDocument> elementDoc;
01537   element->GetOwnerDocument(getter_AddRefs(elementDoc));
01538   if (elementDoc == domDoc)
01539     return PL_DHASH_REMOVE;
01540 
01541   return PL_DHASH_NEXT;
01542 }
01543 
01544 // internal methods
01545 
01546 /*
01547 
01548 Format of the single signon file:
01549 
01550 <1-line version header>
01551 <Reject list URL #1>
01552 <Reject list URL #2>
01553 .
01554 <Saved URL #1 realm>
01555 <Saved URL #1 username field name>
01556 <Encrypted Saved URL #1 username field value>
01557 *<Saved URL #1 password field name>
01558 <Encrypted Saved URL #1 password field value>
01559 <Saved URL #1 username #2 field name>
01560 <.....>
01561 .
01562 <Saved URL #2 realm>
01563 .....
01564 <Encrypted Saved URL #N password field value>
01565 .
01566 <EOF>
01567 
01568 */
01569 
01570 /* static */ PLDHashOperator PR_CALLBACK
01571 nsPasswordManager::WriteRejectEntryEnumerator(const nsACString& aKey,
01572                                               PRInt32 aEntry,
01573                                               void* aUserData)
01574 {
01575   nsIOutputStream* stream = NS_STATIC_CAST(nsIOutputStream*, aUserData);
01576   PRUint32 bytesWritten;
01577 
01578   nsCAutoString buffer(aKey);
01579   buffer.Append(NS_LINEBREAK);
01580   stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01581 
01582   return PL_DHASH_NEXT;
01583 }
01584 
01585 /* static */ PLDHashOperator PR_CALLBACK
01586 nsPasswordManager::WriteSignonEntryEnumerator(const nsACString& aKey,
01587                                               SignonHashEntry* aEntry,
01588                                               void* aUserData)
01589 {
01590   nsIOutputStream* stream = NS_STATIC_CAST(nsIOutputStream*, aUserData);
01591   PRUint32 bytesWritten;
01592 
01593   nsCAutoString buffer(aKey);
01594   buffer.Append(NS_LINEBREAK);
01595   stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01596 
01597   for (SignonDataEntry* e = aEntry->head; e; e = e->next) {
01598     NS_ConvertUCS2toUTF8 userField(e->userField);
01599     userField.Append(NS_LINEBREAK);
01600     stream->Write(userField.get(), userField.Length(), &bytesWritten);
01601 
01602     buffer.Assign(NS_ConvertUCS2toUTF8(e->userValue));
01603     buffer.Append(NS_LINEBREAK);
01604     stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01605 
01606     buffer.Assign("*");
01607     buffer.Append(NS_ConvertUCS2toUTF8(e->passField));
01608     buffer.Append(NS_LINEBREAK);
01609     stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01610 
01611     buffer.Assign(NS_ConvertUCS2toUTF8(e->passValue));
01612     buffer.Append(NS_LINEBREAK);
01613     stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01614 
01615     buffer.Assign(e->actionOrigin);
01616     buffer.Append(NS_LINEBREAK);
01617     stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01618   }
01619 
01620   buffer.Assign("." NS_LINEBREAK);
01621   stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01622 
01623   return PL_DHASH_NEXT;
01624 }
01625 
01626 // Wrapper function for ReadPasswords
01627 void
01628 nsPasswordManager::LoadPasswords()
01629 {
01630   if (sPasswordsLoaded)
01631     return;
01632 
01633   nsXPIDLCString signonFile;
01634   nsresult rv;
01635   rv = mPrefBranch->GetCharPref("SignonFileName2", getter_Copies(signonFile));
01636   if (NS_FAILED(rv))
01637     signonFile.Assign(NS_LITERAL_CSTRING("signons2.txt"));
01638 
01639   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mSignonFile));
01640   if (!mSignonFile)
01641     return;
01642 
01643   mSignonFile->AppendNative(signonFile);
01644 
01645   nsCAutoString path;
01646   mSignonFile->GetNativePath(path);
01647 
01648   PRBool signonExists = PR_FALSE;
01649   mSignonFile->Exists(&signonExists);
01650   if (signonExists) {
01651     if (NS_SUCCEEDED(ReadPasswords(mSignonFile)))
01652       sPasswordsLoaded = PR_TRUE;
01653   } else {
01654     // no current signons file, look for an older version
01655     rv = mPrefBranch->GetCharPref("SignonFileName", getter_Copies(signonFile));
01656     if (NS_FAILED(rv))
01657       signonFile.Assign(NS_LITERAL_CSTRING("signons.txt"));
01658 
01659     nsCOMPtr<nsIFile> oldSignonFile;
01660     mSignonFile->GetParent(getter_AddRefs(oldSignonFile));
01661     oldSignonFile->AppendNative(signonFile);
01662 
01663     if (NS_SUCCEEDED(ReadPasswords(oldSignonFile))) {
01664       sPasswordsLoaded = PR_TRUE;
01665       oldSignonFile->Remove(PR_FALSE);
01666     }
01667   }
01668 }
01669 
01670 void
01671 nsPasswordManager::WritePasswords(nsIFile* aPasswordFile)
01672 {
01673   nsCOMPtr<nsIOutputStream> fileStream;
01674   NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), aPasswordFile, -1,
01675                               0600, 0);
01676 
01677   if (!fileStream)
01678     return;
01679 
01680   PRUint32 bytesWritten;
01681 
01682   // File header
01683   nsCAutoString buffer("#2d" NS_LINEBREAK);
01684   fileStream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01685 
01686   // Write out the reject list.
01687   mRejectTable.EnumerateRead(WriteRejectEntryEnumerator, fileStream);
01688 
01689   buffer.Assign("." NS_LINEBREAK);
01690   fileStream->Write(buffer.get(), buffer.Length(), &bytesWritten);
01691 
01692   // Write out the signon data.
01693   mSignonTable.EnumerateRead(WriteSignonEntryEnumerator, fileStream);
01694 }
01695 
01696 void
01697 nsPasswordManager::AddSignonData(const nsACString& aRealm,
01698                                  SignonDataEntry* aEntry)
01699 {
01700   // See if there is already an entry for this URL
01701   SignonHashEntry* hashEnt;
01702   if (mSignonTable.Get(aRealm, &hashEnt)) {
01703     // Add this one at the front of the linked list
01704     aEntry->next = hashEnt->head;
01705     hashEnt->head = aEntry;
01706   } else {
01707     mSignonTable.Put(aRealm, new SignonHashEntry(aEntry));
01708   }
01709 }
01710 
01711 /* static */ nsresult
01712 nsPasswordManager::DecryptData(const nsAString& aData,
01713                                nsAString& aPlaintext)
01714 {
01715   NS_ConvertUCS2toUTF8 flatData(aData);
01716   char* buffer = nsnull;
01717 
01718   if (flatData.CharAt(0) == '~') {
01719 
01720     // This is a base64-encoded string. Strip off the ~ prefix.
01721     PRUint32 srcLength = flatData.Length() - 1;
01722 
01723     if (!(buffer = PL_Base64Decode(&(flatData.get())[1], srcLength, NULL)))
01724       return NS_ERROR_FAILURE;
01725 
01726   } else {
01727 
01728     // This is encrypted using nsISecretDecoderRing.
01729     EnsureDecoderRing();
01730     if (!sDecoderRing) {
01731       NS_WARNING("Unable to get decoder ring service");
01732       return NS_ERROR_FAILURE;
01733     }
01734 
01735     if (NS_FAILED(sDecoderRing->DecryptString(flatData.get(), &buffer)))
01736       return NS_ERROR_FAILURE;
01737 
01738   }
01739 
01740   aPlaintext.Assign(NS_ConvertUTF8toUCS2(buffer));
01741   PR_Free(buffer);
01742 
01743   return NS_OK;
01744 }
01745 
01746 // Note that nsISecretDecoderRing encryption uses a pseudo-random salt value,
01747 // so it's not possible to test equality of two strings by comparing their
01748 // ciphertexts.  We need to decrypt both strings and compare the plaintext.
01749 
01750 
01751 /* static */ nsresult
01752 nsPasswordManager::EncryptData(const nsAString& aPlaintext,
01753                                nsACString& aEncrypted)
01754 {
01755   EnsureDecoderRing();
01756   NS_ENSURE_TRUE(sDecoderRing, NS_ERROR_FAILURE);
01757 
01758   char* buffer;
01759   if (NS_FAILED(sDecoderRing->EncryptString(NS_ConvertUCS2toUTF8(aPlaintext).get(), &buffer)))
01760     return NS_ERROR_FAILURE;
01761 
01762   aEncrypted.Assign(buffer);
01763   PR_Free(buffer);
01764 
01765   return NS_OK;
01766 }
01767 
01768 /* static */ nsresult
01769 nsPasswordManager::EncryptDataUCS2(const nsAString& aPlaintext,
01770                                    nsAString& aEncrypted)
01771 {
01772   nsCAutoString buffer;
01773   nsresult rv = EncryptData(aPlaintext, buffer);
01774   NS_ENSURE_SUCCESS(rv, rv);
01775 
01776   aEncrypted.Assign(NS_ConvertUTF8toUCS2(buffer));
01777   return NS_OK;
01778 }
01779 
01780 /* static */ void
01781 nsPasswordManager::EnsureDecoderRing()
01782 {
01783   if (!sDecoderRing) {
01784     CallGetService("@mozilla.org/security/sdr;1", &sDecoderRing);
01785 
01786     // Ensure that the master password (internal key) has been initialized.
01787     // If not, set a default empty master password.
01788 
01789     nsCOMPtr<nsIPK11TokenDB> tokenDB = do_GetService(NS_PK11TOKENDB_CONTRACTID);
01790     if (!tokenDB)
01791       return;
01792 
01793     nsCOMPtr<nsIPK11Token> token;
01794     tokenDB->GetInternalKeyToken(getter_AddRefs(token));
01795 
01796     PRBool needUserInit = PR_FALSE;
01797     token->GetNeedsUserInit(&needUserInit);
01798 
01799     if (needUserInit)
01800       token->InitPassword(EmptyString().get());
01801   }
01802 }
01803 
01804 nsresult
01805 nsPasswordManager::FindPasswordEntryInternal(const SignonDataEntry* aEntry,
01806                                              const nsAString&  aUser,
01807                                              const nsAString&  aPassword,
01808                                              const nsAString&  aUserField,
01809                                              SignonDataEntry** aResult)
01810 {
01811   // host has already been checked, so just look for user/password match.
01812   const SignonDataEntry* entry = aEntry;
01813   nsAutoString buffer;
01814 
01815   for (; entry; entry = entry->next) {
01816 
01817     PRBool matched;
01818 
01819     if (aUser.IsEmpty()) {
01820       matched = PR_TRUE;
01821     } else {
01822       if (NS_FAILED(DecryptData(entry->userValue, buffer))) {
01823         *aResult = nsnull;
01824         return NS_ERROR_FAILURE;
01825       }
01826       matched = aUser.Equals(buffer);
01827     }
01828 
01829     if (!matched)
01830       continue;
01831 
01832     if (aPassword.IsEmpty()) {
01833       matched = PR_TRUE;
01834     } else {
01835       if (NS_FAILED(DecryptData(entry->passValue, buffer))) {
01836         *aResult = nsnull;
01837         return NS_ERROR_FAILURE;
01838       }
01839       matched = aPassword.Equals(buffer);
01840     }
01841 
01842     if (!matched)
01843       continue;
01844 
01845     if (aUserField.IsEmpty())
01846       matched = PR_TRUE;
01847     else
01848       matched = entry->userField.Equals(aUserField);
01849 
01850     if (matched)
01851       break;
01852   }
01853 
01854   if (entry) {
01855     *aResult = NS_CONST_CAST(SignonDataEntry*, entry);
01856     return NS_OK;
01857   }
01858 
01859   *aResult = nsnull;
01860   return NS_ERROR_FAILURE;
01861 }
01862 
01863 nsresult
01864 nsPasswordManager::FillDocument(nsIDOMDocument* aDomDoc)
01865 {
01866   nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc);
01867   if (!htmlDoc)
01868     return NS_OK;
01869   nsCOMPtr<nsIDOMHTMLCollection> forms;
01870   htmlDoc->GetForms(getter_AddRefs(forms));
01871 
01872   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDomDoc);
01873 
01874   nsCAutoString realm;
01875   if (!GetPasswordRealm(doc->GetDocumentURI(), realm))
01876     return NS_OK;
01877 
01878   SignonHashEntry* hashEnt;
01879   if (!mSignonTable.Get(realm, &hashEnt))
01880     return NS_OK;
01881 
01882   PRUint32 formCount;
01883   forms->GetLength(&formCount);
01884 
01885   // check to see if we should formfill.  failure is non-fatal
01886   PRBool prefillForm = PR_TRUE;
01887   mPrefBranch->GetBoolPref("prefillForms", &prefillForm);
01888 
01889   nsCAutoString formActionOrigin;
01890 
01891   // We can auto-prefill the username and password if there is only
01892   // one stored login that matches the username and password field names
01893   // on the form in question.  Note that we only need to worry about a
01894   // single login per form.
01895 
01896   for (PRUint32 i = 0; i < formCount; ++i) {
01897     nsCOMPtr<nsIDOMNode> formNode;
01898     forms->Item(i, getter_AddRefs(formNode));
01899 
01900     nsCOMPtr<nsIForm> form = do_QueryInterface(formNode);
01901     SignonDataEntry* firstMatch = nsnull;
01902     PRBool attachedToInput = PR_FALSE;
01903     PRBool prefilledUser = PR_FALSE;
01904     nsCOMPtr<nsIDOMHTMLInputElement> userField, passField;
01905     nsCOMPtr<nsIDOMHTMLInputElement> temp;
01906     nsAutoString fieldType;
01907 
01908     // before we start iterating, make sure we have the action host
01909     if (NS_FAILED(GetActionRealm(form, formActionOrigin)))
01910       return NS_OK;
01911 
01912     for (SignonDataEntry* e = hashEnt->head; e; e = e->next) {
01913 
01914       nsCOMPtr<nsISupports> foundNode;
01915       if (!(e->userField).IsEmpty()) {
01916         form->ResolveName(e->userField, getter_AddRefs(foundNode));
01917         temp = do_QueryInterface(foundNode);
01918       }
01919 
01920       nsAutoString oldUserValue;
01921 
01922       if (temp) {
01923         temp->GetType(fieldType);
01924         if (!fieldType.Equals(NS_LITERAL_STRING("text")))
01925           continue;
01926 
01927         temp->GetValue(oldUserValue);
01928         userField = temp;
01929       } else if ((e->passField).IsEmpty()) {
01930         // Happens sometimes when we import passwords from IE since
01931         // their form name match is case insensitive. In this case,
01932         // we'll just have to do a case insensitive search for the
01933         // userField and hope we get something.
01934         PRUint32 count;
01935         form->GetElementCount(&count);
01936         PRUint32 i;
01937         nsCOMPtr<nsIFormControl> formControl;
01938         for (i = 0; i < count; i++) {
01939           form->GetElementAt(i, getter_AddRefs(formControl));
01940 
01941           if (formControl &&
01942               formControl->GetType() == NS_FORM_INPUT_TEXT) {
01943             nsCOMPtr<nsIDOMHTMLInputElement> inputField = do_QueryInterface(formControl);
01944             nsAutoString name;
01945             inputField->GetName(name);
01946             if (name.EqualsIgnoreCase(NS_ConvertUTF16toUTF8(e->userField).get())) {
01947               inputField->GetValue(oldUserValue);
01948               userField = inputField;
01949               foundNode = inputField;
01950               // Only the case differs, so CheckLoginValues() unneeded.
01951               e->userField.Assign(name);
01952               break;
01953             }
01954           }
01955         }
01956       }
01957 
01958       if (!(e->passField).IsEmpty()) {
01959         form->ResolveName(e->passField, getter_AddRefs(foundNode));
01960         temp = do_QueryInterface(foundNode);
01961       }
01962       else if (userField) {
01963         // No password field name was supplied, try to locate one in the form,
01964         // but only if we have a username field.
01965         nsCOMPtr<nsIFormControl> fc(do_QueryInterface(foundNode));
01966         PRInt32 index = -1;
01967         form->IndexOfControl(fc, &index);
01968         if (index >= 0) {
01969           PRUint32 count;
01970           form->GetElementCount(&count);
01971 
01972           PRUint32 i;
01973           temp = nsnull;
01974 
01975           // Search forwards
01976           nsCOMPtr<nsIFormControl> passField;
01977           for (i = index + 1; i < count; ++i) {
01978             form->GetElementAt(i, getter_AddRefs(passField));
01979 
01980             if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
01981               foundNode = passField;
01982               temp = do_QueryInterface(foundNode);
01983             }
01984           }
01985 
01986           if (!temp && index != 0) {
01987             // Search backwards
01988             i = index;
01989             do {
01990               form->GetElementAt(i, getter_AddRefs(passField));
01991 
01992               if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
01993                 foundNode = passField;
01994                 temp = do_QueryInterface(foundNode);
01995               }
01996             } while (i-- != 0);
01997           }
01998         }
01999       }
02000 
02001       nsAutoString oldPassValue;
02002 
02003       if (temp) {
02004         temp->GetType(fieldType);
02005         if (!fieldType.Equals(NS_LITERAL_STRING("password")))
02006           continue;
02007 
02008         temp->GetValue(oldPassValue);
02009         passField = temp;
02010         if ((e->passField).IsEmpty()) {
02011           nsAutoString passName;
02012           passField->GetName(passName);
02013 
02014           // Reject values that would cause problems when parsing the storage file
02015           if (NS_SUCCEEDED(CheckLoginValues(EmptyCString(), EmptyString(),
02016                                             passName, EmptyCString())))
02017             e->passField.Assign(passName);
02018         }
02019       } else {
02020         continue;
02021       }
02022 
02023       // if we don't match actionOrigin, don't count this as a match
02024       if (!e->actionOrigin.IsEmpty() &&
02025           !e->actionOrigin.Equals(formActionOrigin))
02026         continue;
02027 
02028       if (!oldUserValue.IsEmpty() && prefillForm) {
02029         // The page has prefilled a username.
02030         // If it matches any of our saved usernames, prefill the password
02031         // for that username.  If there are multiple saved usernames,
02032         // we will also attach the autocomplete listener.
02033 
02034         prefilledUser = PR_TRUE;
02035         nsAutoString userValue;
02036         if (NS_FAILED(DecryptData(e->userValue, userValue)))
02037           return NS_OK;
02038 
02039         if (userValue.Equals(oldUserValue)) {
02040           nsAutoString passValue;
02041           if (NS_FAILED(DecryptData(e->passValue, passValue)))
02042             return NS_OK;
02043 
02044           passField->SetValue(passValue);
02045         }
02046       }
02047 
02048       if (firstMatch && userField && !attachedToInput) {
02049         // We've found more than one possible signon for this form.
02050 
02051         // Listen for blur and autocomplete events on the username field so
02052         // that we can attempt to prefill the password after the user has
02053         // entered the username.
02054 
02055         AttachToInput(userField);
02056         attachedToInput = PR_TRUE;
02057       } else {
02058         firstMatch = e;
02059       }
02060     }
02061 
02062     // If we found more than one match, attachedToInput will be true,
02063     // but if we found just one, we need to attach the autocomplete listener,
02064     // and fill in the username and password  only if the HTML didn't prefill
02065     // the username.
02066     if (firstMatch && !attachedToInput) {
02067       if (!prefilledUser && prefillForm) {
02068         nsAutoString buffer;
02069 
02070         if (userField) {
02071           if (NS_FAILED(DecryptData(firstMatch->userValue, buffer)))
02072             return NS_OK;
02073 
02074           userField->SetValue(buffer);
02075         }
02076 
02077         if (NS_FAILED(DecryptData(firstMatch->passValue, buffer)))
02078           return NS_OK;
02079 
02080         passField->SetValue(buffer);
02081       }
02082 
02083       if (userField)
02084         AttachToInput(userField);
02085     }
02086   }
02087 
02088   return NS_OK;
02089 }
02090 
02091 nsresult
02092 nsPasswordManager::FillPassword(nsIDOMEvent* aEvent)
02093 {
02094   // Try to prefill the password for the just-changed username.
02095   nsCOMPtr<nsIDOMEventTarget> target;
02096   aEvent->GetTarget(getter_AddRefs(target));
02097 
02098   nsCOMPtr<nsIDOMHTMLInputElement> userField = do_QueryInterface(target);
02099   if (!userField || userField == mAutoCompletingField)
02100     return NS_OK;
02101 
02102   nsCOMPtr<nsIContent> fieldContent = do_QueryInterface(userField);
02103 
02104   // The document may be null during teardown, for example as Windows
02105   // sends a blur event as a native widget is destroyed.
02106   nsIDocument *doc = fieldContent->GetDocument();
02107   if (!doc)
02108     return NS_OK;
02109 
02110   nsCAutoString realm;
02111   if (!GetPasswordRealm(doc->GetDocumentURI(), realm))
02112     return NS_OK;
02113 
02114   nsAutoString userValue;
02115   userField->GetValue(userValue);
02116 
02117   if (userValue.IsEmpty())
02118     return NS_OK;
02119 
02120   nsAutoString fieldName;
02121   userField->GetName(fieldName);
02122 
02123   SignonHashEntry* hashEnt;
02124   if (!mSignonTable.Get(realm, &hashEnt))
02125     return NS_OK;
02126 
02127   SignonDataEntry* foundEntry;
02128   FindPasswordEntryInternal(hashEnt->head, userValue, EmptyString(),
02129                             fieldName, &foundEntry);
02130 
02131   if (!foundEntry)
02132     return NS_OK;
02133 
02134   nsCOMPtr<nsIDOMHTMLFormElement> formEl;
02135   userField->GetForm(getter_AddRefs(formEl));
02136   if (!formEl)
02137     return NS_OK;
02138 
02139   nsCOMPtr<nsIForm> form = do_QueryInterface(formEl);
02140   nsCAutoString formActionOrigin;
02141   GetActionRealm(form, formActionOrigin);
02142   if (NS_FAILED(GetActionRealm(form, formActionOrigin)))
02143     return NS_OK;
02144   if (!foundEntry->actionOrigin.IsEmpty() && !foundEntry->actionOrigin.Equals(formActionOrigin))
02145     return NS_OK;
02146   
02147   nsCOMPtr<nsISupports> foundNode;
02148   form->ResolveName(foundEntry->passField, getter_AddRefs(foundNode));
02149   nsCOMPtr<nsIDOMHTMLInputElement> passField = do_QueryInterface(foundNode);
02150   if (!passField)
02151     return NS_OK;
02152 
02153   nsAutoString passValue;
02154   if (NS_SUCCEEDED(DecryptData(foundEntry->passValue, passValue)))
02155     passField->SetValue(passValue);
02156 
02157   return NS_OK;
02158 }
02159 
02160 void
02161 nsPasswordManager::AttachToInput(nsIDOMHTMLInputElement* aElement)
02162 {
02163   nsCOMPtr<nsIDOMEventTarget> targ = do_QueryInterface(aElement);
02164   nsIDOMEventListener* listener = NS_STATIC_CAST(nsIDOMFocusListener*, this);
02165 
02166   targ->AddEventListener(NS_LITERAL_STRING("blur"), listener, PR_FALSE);
02167   targ->AddEventListener(NS_LITERAL_STRING("DOMAutoComplete"), listener, PR_FALSE);
02168 
02169   mAutoCompleteInputs.Put(aElement, 1);
02170 }
02171 
02172 PRBool
02173 nsPasswordManager::GetPasswordRealm(nsIURI* aURI, nsACString& aRealm)
02174 {
02175   // Note: this _is_ different from getting the uri's prePath!
02176   // We don't want to include a username or password that's part of the
02177   // URL in the host key... it will cause lookups to work incorrectly, and will
02178   // also cause usernames and passwords to be stored in cleartext.
02179 
02180   nsCAutoString buffer;
02181   aURI->GetScheme(buffer);
02182 
02183   aRealm.Append(buffer);
02184   aRealm.Append(NS_LITERAL_CSTRING("://"));
02185 
02186   aURI->GetHostPort(buffer);
02187   if (buffer.IsEmpty()) {
02188     // The scheme does not support hostnames, so don't attempt to save/restore
02189     // any signon data. (see bug 159484)
02190     return PR_FALSE;
02191   }
02192 
02193   aRealm.Append(buffer);
02194   return PR_TRUE;
02195 }
02196 
02197 /* static */ void
02198 nsPasswordManager::GetLocalizedString(const nsAString& key,
02199                                       nsAString& aResult,
02200                                       PRBool aIsFormatted,
02201                                       const PRUnichar** aFormatArgs,
02202                                       PRUint32 aFormatArgsLength)
02203 {
02204   if (!sPMBundle) {
02205     nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
02206     bundleService->CreateBundle(kPMPropertiesURL,
02207                                 &sPMBundle);
02208 
02209     if (!sPMBundle) {
02210       NS_ERROR("string bundle not present");
02211       return;
02212     }
02213   }
02214 
02215   nsXPIDLString str;
02216   if (aIsFormatted)
02217     sPMBundle->FormatStringFromName(PromiseFlatString(key).get(),
02218                                     aFormatArgs, aFormatArgsLength,
02219                                     getter_Copies(str));
02220   else
02221     sPMBundle->GetStringFromName(PromiseFlatString(key).get(),
02222                                  getter_Copies(str));
02223   aResult.Assign(str);
02224 }
02225 
02226 /* static */ nsresult
02227 nsPasswordManager::GetActionRealm(nsIForm* aForm, nsCString& aURL)
02228 {
02229   nsCOMPtr<nsIURI> actionURI;
02230   nsCAutoString formActionOrigin;
02231 
02232   if (NS_FAILED(aForm->GetActionURL(getter_AddRefs(actionURI))) ||
02233       !actionURI)
02234     return NS_ERROR_FAILURE;
02235 
02236   if (!GetPasswordRealm(actionURI, formActionOrigin))
02237     return NS_ERROR_FAILURE;
02238 
02239   aURL.Assign(formActionOrigin);
02240   return NS_OK;
02241 }
02242 
02243 /* static */ PRBool
02244 nsPasswordManager::BadCharacterPresent(const nsAString &aString)
02245 {
02246   if (aString.FindChar('\r') >= 0)
02247     return PR_TRUE;
02248   if (aString.FindChar('\n') >= 0)
02249     return PR_TRUE;
02250   if (aString.FindChar('\0') >= 0)
02251     return PR_TRUE;
02252 
02253   return PR_FALSE;
02254 }
02255 
02256 /* static */ nsresult
02257 nsPasswordManager::CheckLoginValues(const nsACString &aHost,
02258                                     const nsAString  &aUserField,
02259                                     const nsAString  &aPassField,
02260                                     const nsACString &aActionOrigin)
02261 {
02262   // aHost
02263   if (BadCharacterPresent(NS_ConvertUTF8toUTF16(aHost))) {
02264     NS_WARNING("Login rejected, bad character in aHost");
02265     return NS_ERROR_FAILURE;
02266   }
02267   // The aHost arg is used for both login entry hostnames and reject entry
02268   // hostnames ("never for this site"). A value of "." is not allowed for
02269   // reject entries. It's technically ok for login entries, but to keep the
02270   // code simple we'll disallow it anyway.
02271   if (aHost.EqualsLiteral(".")) {
02272     NS_WARNING("Login rejected, aHost can not be just a period");
02273     return NS_ERROR_FAILURE;
02274   }
02275 
02276 
02277   // aUserField
02278   if (BadCharacterPresent(aUserField)) {
02279     NS_WARNING("Login rejected, bad character in aUserField");
02280     return NS_ERROR_FAILURE;
02281   }
02282   if (aUserField.EqualsLiteral(".")) {
02283     NS_WARNING("Login rejected, aUserField can not be just a period");
02284     return NS_ERROR_FAILURE;
02285   }
02286 
02287 
02288   // aPassField
02289   if (BadCharacterPresent(aPassField)) {
02290     NS_WARNING("Login rejected, bad character in aPassField");
02291     return NS_ERROR_FAILURE;
02292   }
02293 
02294 
02295   // aActionOrigin
02296   if (BadCharacterPresent(NS_ConvertUTF8toUTF16(aActionOrigin))) {
02297     NS_WARNING("Login rejected, bad character in aActionOrigin");
02298     return NS_ERROR_FAILURE;
02299   }
02300   if (aActionOrigin.EqualsLiteral(".")) {
02301     NS_WARNING("Login rejected, aActionOrigin can not be just a period");
02302     return NS_ERROR_FAILURE;
02303   }
02304 
02305   return NS_OK;
02306 }