Back to index

lightning-sunbird  0.9+nobinonly
nsFormHistory.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 Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Joe Hewitt <hewitt@netscape.com> (Original Author)
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 "nsFormHistory.h"
00040 
00041 #include "nsIServiceManager.h"
00042 #include "nsIObserverService.h"
00043 #include "nsICategoryManager.h"
00044 #include "nsIDirectoryService.h"
00045 #include "nsAppDirectoryServiceDefs.h"
00046 #include "nsMorkCID.h"
00047 #include "nsIMdbFactoryFactory.h"
00048 #include "nsQuickSort.h"
00049 #include "nsCRT.h"
00050 #include "nsString.h"
00051 #include "nsUnicharUtils.h"
00052 #include "nsReadableUtils.h"
00053 #include "nsIContent.h"
00054 #include "nsIDOMNode.h"
00055 #include "nsIDOMHTMLFormElement.h"
00056 #include "nsIDOMHTMLInputElement.h"
00057 #include "nsIDOMHTMLCollection.h"
00058 #include "nsIPrefService.h"
00059 #include "nsIPrefBranch.h"
00060 #include "nsIPrefBranch2.h"
00061 #include "nsVoidArray.h"
00062 #include "nsCOMArray.h"
00063 
00064 static void SwapBytes(PRUnichar* aDest, const PRUnichar* aSrc, PRUint32 aLen)
00065 {
00066   for(PRUint32 i = 0; i < aLen; i++)
00067   {
00068     PRUnichar aChar = *aSrc++;
00069     *aDest++ = (0xff & (aChar >> 8)) | (aChar << 8);
00070   }
00071 }
00072 
00073 #define PREF_FORMFILL_BRANCH "browser.formfill."
00074 #define PREF_FORMFILL_ENABLE "enable"
00075 
00076 // upper bounds on saved form data, more isn't useful.
00077 #define FORMFILL_NAME_MAX_LEN  1000
00078 #define FORMFILL_VALUE_MAX_LEN 4000
00079 
00080 static const char *kFormHistoryFileName = "formhistory.dat";
00081 
00082 NS_INTERFACE_MAP_BEGIN(nsFormHistory)
00083   NS_INTERFACE_MAP_ENTRY(nsIFormHistory2)
00084   NS_INTERFACE_MAP_ENTRY(nsIObserver)
00085   NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver)
00086   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
00087   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
00088 NS_INTERFACE_MAP_END_THREADSAFE
00089 
00090 NS_IMPL_THREADSAFE_ADDREF(nsFormHistory)
00091 NS_IMPL_THREADSAFE_RELEASE(nsFormHistory)
00092 
00093 mdb_column nsFormHistory::kToken_ValueColumn = 0;
00094 mdb_column nsFormHistory::kToken_NameColumn = 0;
00095 
00096 PRBool nsFormHistory::gFormHistoryEnabled = PR_FALSE;
00097 PRBool nsFormHistory::gPrefsInitialized = PR_FALSE;
00098 
00099 nsFormHistory::nsFormHistory() :
00100   mEnv(nsnull),
00101   mStore(nsnull),
00102   mTable(nsnull),
00103   mReverseByteOrder(PR_FALSE)
00104 {
00105   NS_ASSERTION(!gFormHistory, "nsFormHistory must be used as a service");
00106   gFormHistory = this;
00107 }
00108 
00109 nsFormHistory::~nsFormHistory()
00110 {
00111   NS_ASSERTION(gFormHistory == this,
00112                "nsFormHistory must be used as a service");
00113   CloseDatabase();
00114   gFormHistory = nsnull;
00115 }
00116 
00117 nsresult
00118 nsFormHistory::Init()
00119 {
00120   nsCOMPtr<nsIObserverService> service = do_GetService("@mozilla.org/observer-service;1");
00121   if (service)
00122     service->AddObserver(this, NS_EARLYFORMSUBMIT_SUBJECT, PR_TRUE);
00123   
00124   return NS_OK;
00125 }
00126 
00127 nsFormHistory *nsFormHistory::gFormHistory = nsnull;
00128 
00129 /* static */ PRBool
00130 nsFormHistory::FormHistoryEnabled()
00131 {
00132   if (!gPrefsInitialized) {
00133     nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
00134 
00135     prefService->GetBranch(PREF_FORMFILL_BRANCH,
00136                            getter_AddRefs(gFormHistory->mPrefBranch));
00137     gFormHistory->mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE,
00138                                            &gFormHistoryEnabled);
00139 
00140     nsCOMPtr<nsIPrefBranch2> branchInternal =
00141       do_QueryInterface(gFormHistory->mPrefBranch);
00142     branchInternal->AddObserver(PREF_FORMFILL_ENABLE, gFormHistory, PR_TRUE);
00143 
00144     gPrefsInitialized = PR_TRUE;
00145   }
00146 
00147   return gFormHistoryEnabled;
00148 }
00149 
00150 
00153 
00154 NS_IMETHODIMP
00155 nsFormHistory::GetHasEntries(PRBool *aHasEntries)
00156 {
00157   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00158   NS_ENSURE_SUCCESS(rv, rv);
00159 
00160   PRUint32 rowCount;
00161   mdb_err err = mTable->GetCount(mEnv, &rowCount);
00162   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00163 
00164   *aHasEntries = rowCount != 0;
00165   return NS_OK;
00166 }
00167 
00168 NS_IMETHODIMP
00169 nsFormHistory::AddEntry(const nsAString &aName, const nsAString &aValue)
00170 {
00171   if (!FormHistoryEnabled())
00172     return NS_OK;
00173 
00174   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00175   NS_ENSURE_SUCCESS(rv, rv);
00176 
00177   nsCOMPtr<nsIMdbRow> row;
00178   AppendRow(aName, aValue, getter_AddRefs(row));
00179   return NS_OK;
00180 }
00181 
00182 NS_IMETHODIMP
00183 nsFormHistory::EntryExists(const nsAString &aName, const nsAString &aValue, PRBool *_retval)
00184 {
00185   return EntriesExistInternal(&aName, &aValue, _retval);
00186 }
00187 
00188 NS_IMETHODIMP
00189 nsFormHistory::NameExists(const nsAString &aName, PRBool *_retval)
00190 {
00191   return EntriesExistInternal(&aName, nsnull, _retval);
00192 }
00193 
00194 NS_IMETHODIMP
00195 nsFormHistory::RemoveEntry(const nsAString &aName, const nsAString &aValue)
00196 {
00197   return NS_ERROR_NOT_IMPLEMENTED;
00198 }
00199 
00200 NS_IMETHODIMP
00201 nsFormHistory::RemoveEntriesForName(const nsAString &aName)
00202 {
00203   return RemoveEntriesInternal(&aName);
00204 }
00205 
00206 NS_IMETHODIMP
00207 nsFormHistory::RemoveAllEntries()
00208 {
00209   nsresult rv = RemoveEntriesInternal(nsnull);
00210 
00211   if (NS_SUCCEEDED(rv))
00212     rv = InitByteOrder(PR_TRUE);
00213   
00214   rv |= Flush();
00215   
00216   return rv;
00217 }
00218 
00221 
00222 NS_IMETHODIMP
00223 nsFormHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) 
00224 {
00225   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
00226     mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE, &gFormHistoryEnabled);
00227   }
00228 
00229   return NS_OK;
00230 }
00231 
00234 
00235 NS_IMETHODIMP
00236 nsFormHistory::Notify(nsIContent* aFormNode, nsIDOMWindowInternal* aWindow, nsIURI* aActionURL, PRBool* aCancelSubmit)
00237 {
00238   if (!FormHistoryEnabled())
00239     return NS_OK;
00240 
00241   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00242   NS_ENSURE_SUCCESS(rv, rv);
00243   
00244   nsCOMPtr<nsIDOMHTMLFormElement> formElt = do_QueryInterface(aFormNode);
00245   NS_ENSURE_TRUE(formElt, NS_ERROR_FAILURE);
00246 
00247   NS_NAMED_LITERAL_STRING(kAutoComplete, "autocomplete");
00248   nsAutoString autocomplete;
00249   formElt->GetAttribute(kAutoComplete, autocomplete);
00250   if (autocomplete.LowerCaseEqualsLiteral("off"))
00251     return NS_OK;
00252 
00253   nsCOMPtr<nsIDOMHTMLCollection> elts;
00254   formElt->GetElements(getter_AddRefs(elts));
00255 
00256   PRUint32 length;
00257   elts->GetLength(&length);
00258   for (PRUint32 i = 0; i < length; ++i) {
00259     nsCOMPtr<nsIDOMNode> node;
00260     elts->Item(i, getter_AddRefs(node));
00261     nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(node);
00262     if (inputElt) {
00263       // Filter only inputs that are of type "text" without autocomplete="off"
00264       nsAutoString type;
00265       inputElt->GetType(type);
00266       if (!type.LowerCaseEqualsLiteral("text"))
00267         continue;
00268 
00269       nsAutoString autocomplete;
00270       inputElt->GetAttribute(kAutoComplete, autocomplete);
00271       if (!autocomplete.LowerCaseEqualsLiteral("off")) {
00272         // If this input has a name/id and value, add it to the database
00273         nsAutoString value;
00274         inputElt->GetValue(value);
00275         if (!value.IsEmpty()) {
00276           nsAutoString name;
00277           inputElt->GetName(name);
00278           if (name.IsEmpty())
00279             inputElt->GetId(name);
00280           if (!name.IsEmpty())
00281             AppendRow(name, value, nsnull);
00282         }
00283       }
00284     }
00285   }
00286 
00287   return NS_OK;
00288 }
00289 
00292 
00293 class SatchelErrorHook : public nsIMdbErrorHook
00294 {
00295 public:
00296   NS_DECL_ISUPPORTS
00297 
00298   // nsIMdbErrorHook
00299   NS_IMETHOD OnErrorString(nsIMdbEnv* ev, const char* inAscii);
00300   NS_IMETHOD OnErrorYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
00301   NS_IMETHOD OnWarningString(nsIMdbEnv* ev, const char* inAscii);
00302   NS_IMETHOD OnWarningYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
00303   NS_IMETHOD OnAbortHintString(nsIMdbEnv* ev, const char* inAscii);
00304   NS_IMETHOD OnAbortHintYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
00305 };
00306 
00307 // nsIMdbErrorHook has no IID!
00308 NS_IMPL_ISUPPORTS0(SatchelErrorHook)
00309 
00310 NS_IMETHODIMP
00311 SatchelErrorHook::OnErrorString(nsIMdbEnv *ev, const char *inAscii)
00312 {
00313   printf("mork error: %s\n", inAscii);
00314   return NS_OK;
00315 }
00316 
00317 NS_IMETHODIMP
00318 SatchelErrorHook::OnErrorYarn(nsIMdbEnv *ev, const mdbYarn* inYarn)
00319 {
00320   printf("mork error yarn: %p\n", (void*)inYarn);
00321   return NS_OK;
00322 }
00323 
00324 NS_IMETHODIMP
00325 SatchelErrorHook::OnWarningString(nsIMdbEnv *ev, const char *inAscii)
00326 {
00327   printf("mork warning: %s\n", inAscii);
00328   return NS_OK;
00329 }
00330 
00331 NS_IMETHODIMP
00332 SatchelErrorHook::OnWarningYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
00333 {
00334   printf("mork warning yarn: %p\n", (void*)inYarn);
00335   return NS_OK;
00336 }
00337 
00338 NS_IMETHODIMP
00339 SatchelErrorHook::OnAbortHintString(nsIMdbEnv *ev, const char *inAscii)
00340 {
00341   printf("mork abort: %s\n", inAscii);
00342   return NS_OK;
00343 }
00344 
00345 NS_IMETHODIMP
00346 SatchelErrorHook::OnAbortHintYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
00347 {
00348   printf("mork abort yarn: %p\n", (void*)inYarn);
00349   return NS_OK;
00350 }
00351 
00352 nsresult
00353 nsFormHistory::OpenDatabase()
00354 {
00355   if (mStore)
00356     return NS_OK;
00357   
00358   // Get a handle to the database file
00359   nsCOMPtr <nsIFile> historyFile;
00360   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(historyFile));
00361   NS_ENSURE_SUCCESS(rv, rv);
00362   historyFile->Append(NS_ConvertUTF8toUCS2(kFormHistoryFileName));
00363 
00364   // Get an Mdb Factory
00365   static NS_DEFINE_CID(kMorkCID, NS_MORK_CID);
00366   nsCOMPtr<nsIMdbFactoryFactory> mdbFactory = do_CreateInstance(kMorkCID, &rv);
00367   NS_ENSURE_SUCCESS(rv, rv);
00368   rv = mdbFactory->GetMdbFactory(getter_AddRefs(mMdbFactory));
00369   NS_ENSURE_SUCCESS(rv, rv);
00370 
00371   // Create the Mdb environment
00372   mdb_err err = mMdbFactory->MakeEnv(nsnull, &mEnv);
00373   NS_ASSERTION(err == 0, "ERROR: Unable to create Form History mdb");
00374   mEnv->SetAutoClear(PR_TRUE);
00375   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00376   mEnv->SetErrorHook(new SatchelErrorHook());
00377 
00378   nsCAutoString filePath;
00379   historyFile->GetNativePath(filePath);
00380   PRBool exists = PR_TRUE;
00381   historyFile->Exists(&exists);
00382 
00383   PRBool createdNew = PR_FALSE;
00384   
00385   if (!exists || NS_FAILED(rv = OpenExistingFile(filePath.get()))) {
00386     // If the file doesn't exist, or we fail trying to open it,
00387     // then make sure it is deleted and then create an empty database file
00388     historyFile->Remove(PR_FALSE);
00389     rv = CreateNewFile(filePath.get());
00390     createdNew = PR_TRUE;
00391   }
00392   NS_ENSURE_SUCCESS(rv, rv);
00393 
00394   // Get the initial size of the file, needed later for Commit
00395   historyFile->GetFileSize(&mFileSizeOnDisk);
00396 
00397   rv = InitByteOrder(createdNew);
00398 
00399   /* // TESTING: Add a row to the database
00400   nsAutoString foopy;
00401   foopy.AssignWithConversion("foopy");
00402   nsAutoString oogly;
00403   oogly.AssignWithConversion("oogly");
00404   AppendRow(foopy, oogly, nsnull);
00405   Flush(); */
00406   
00407   /* // TESTING: Dump the contents of the database
00408   PRUint32 count = 0;
00409   mdb_err err = mTable->GetCount(mEnv, &count);
00410   printf("%d rows in form history\n", count);
00411 
00412   for (mdb_pos pos = count - 1; pos >= 0; --pos) {
00413     nsCOMPtr<nsIMdbRow> row;
00414     err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
00415     
00416     nsAutoString name;
00417     GetRowValue(row, kToken_NameColumn, name);
00418     nsAutoString value;
00419     GetRowValue(row, kToken_ValueColumn, value);
00420     printf("ROW: %s - %s\n", ToNewCString(name), ToNewCString(value));
00421   } */
00422 
00423   return rv;
00424 }
00425 
00426 nsresult
00427 nsFormHistory::OpenExistingFile(const char *aPath)
00428 {
00429   nsCOMPtr<nsIMdbFile> oldFile;
00430   nsIMdbHeap* dbHeap = 0;
00431   mdb_err err = mMdbFactory->OpenOldFile(mEnv, dbHeap, aPath, mdbBool_kFalse, getter_AddRefs(oldFile));
00432   NS_ENSURE_TRUE(!err && oldFile, NS_ERROR_FAILURE);
00433 
00434   mdb_bool canOpen = 0;
00435   mdbYarn outFormat = {nsnull, 0, 0, 0, 0, nsnull};
00436   err = mMdbFactory->CanOpenFilePort(mEnv, oldFile, &canOpen, &outFormat);
00437   NS_ENSURE_TRUE(!err && canOpen, NS_ERROR_FAILURE);
00438 
00439   nsCOMPtr<nsIMdbThumb> thumb;
00440   mdbOpenPolicy policy = {{0, 0}, 0, 0};
00441   err = mMdbFactory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, getter_AddRefs(thumb));
00442   NS_ENSURE_TRUE(!err && thumb, NS_ERROR_FAILURE);
00443 
00444   PRBool done;
00445   mdb_err thumbErr = UseThumb(thumb, &done);
00446 
00447   if (err == 0 && done)
00448     err = mMdbFactory->ThumbToOpenStore(mEnv, thumb, &mStore);
00449   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00450 
00451   nsresult rv = CreateTokens();
00452   NS_ENSURE_SUCCESS(rv, rv);
00453 
00454   mdbOid oid = {kToken_RowScope, 1};
00455   err = mStore->GetTable(mEnv, &oid, &mTable);
00456   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00457   if (!mTable) {
00458     NS_WARNING("ERROR: Form history file is corrupt, now deleting it.");
00459     return NS_ERROR_FAILURE;
00460   }
00461 
00462   err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
00463   if (err)
00464     NS_WARNING("Could not get meta row");
00465 
00466   if (NS_FAILED(thumbErr))
00467     err = thumbErr;
00468 
00469   return err ? NS_ERROR_FAILURE : NS_OK;
00470 }
00471 
00472 nsresult
00473 nsFormHistory::CreateNewFile(const char *aPath)
00474 {
00475   nsIMdbHeap* dbHeap = 0;
00476   nsCOMPtr<nsIMdbFile> newFile;
00477   mdb_err err = mMdbFactory->CreateNewFile(mEnv, dbHeap, aPath, getter_AddRefs(newFile));
00478   NS_ENSURE_TRUE(!err && newFile, NS_ERROR_FAILURE);
00479 
00480   nsCOMPtr <nsIMdbTable> oldTable = mTable;;
00481   nsCOMPtr <nsIMdbStore> oldStore = mStore;
00482   mdbOpenPolicy policy = {{0, 0}, 0, 0};
00483   err = mMdbFactory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore);
00484   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00485   
00486   nsresult rv = CreateTokens();
00487   NS_ENSURE_SUCCESS(rv, rv);
00488 
00489   // Create the one and only table in the database
00490   err = mStore->NewTable(mEnv, kToken_RowScope, kToken_Kind, PR_TRUE, nsnull, &mTable);
00491   NS_ENSURE_TRUE(!err && mTable, NS_ERROR_FAILURE);
00492 
00493   mdbOid oid = {kToken_RowScope, 1};
00494   err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
00495   if (err) {
00496     NS_WARNING("Could not get meta row");
00497     return NS_ERROR_FAILURE;
00498   }
00499 
00500    // oldTable will only be set if we detected a corrupt db, and are 
00501    // trying to restore data from it.
00502   if (oldTable)
00503     CopyRowsFromTable(oldTable);
00504 
00505   // Force a commit now to get it written out.
00506   nsCOMPtr<nsIMdbThumb> thumb;
00507   err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
00508   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00509 
00510   PRBool done;
00511   err = UseThumb(thumb, &done);
00512 
00513   return err || !done ? NS_ERROR_FAILURE : NS_OK;
00514 }
00515 
00516 nsresult
00517 nsFormHistory::CloseDatabase()
00518 {
00519   Flush();
00520 
00521   mMetaRow = nsnull;
00522 
00523   if (mTable)
00524     mTable->Release();
00525 
00526   if (mStore)
00527     mStore->Release();
00528 
00529   if (mEnv)
00530     mEnv->Release();
00531 
00532   mTable = nsnull;
00533   mEnv = nsnull;
00534   mStore = nsnull;
00535 
00536   return NS_OK;
00537 }
00538 
00539 nsresult
00540 nsFormHistory::CreateTokens()
00541 {
00542   mdb_err err;
00543 
00544   if (!mStore)
00545     return NS_ERROR_NOT_INITIALIZED;
00546 
00547   err = mStore->StringToToken(mEnv, "ns:formhistory:db:row:scope:formhistory:all", &kToken_RowScope);
00548   if (err != 0) return NS_ERROR_FAILURE;
00549   
00550   err = mStore->StringToToken(mEnv, "ns:formhistory:db:table:kind:formhistory", &kToken_Kind);
00551   if (err != 0) return NS_ERROR_FAILURE;
00552   
00553   err = mStore->StringToToken(mEnv, "Value", &kToken_ValueColumn);
00554   if (err != 0) return NS_ERROR_FAILURE;
00555 
00556   err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn);
00557   if (err != 0) return NS_ERROR_FAILURE;
00558 
00559   err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder);
00560   if (err != 0) return NS_ERROR_FAILURE;
00561 
00562   return NS_OK;
00563 }
00564 
00565 nsresult
00566 nsFormHistory::Flush()
00567 {
00568   if (!mStore || !mTable)
00569     return NS_OK;
00570 
00571   mdb_err err;
00572 
00573   nsCOMPtr<nsIMdbThumb> thumb;
00574   err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
00575 
00576   if (err == 0)
00577     err = UseThumb(thumb, nsnull);
00578   
00579   return err ? NS_ERROR_FAILURE : NS_OK;
00580 }
00581 
00582 mdb_err
00583 nsFormHistory::UseThumb(nsIMdbThumb *aThumb, PRBool *aDone)
00584 {
00585   mdb_count total;
00586   mdb_count current;
00587   mdb_bool done;
00588   mdb_bool broken;
00589   mdb_err err;
00590   
00591   do {
00592     err = aThumb->DoMore(mEnv, &total, &current, &done, &broken);
00593   } while ((err == 0) && !broken && !done);
00594   
00595   if (aDone)
00596     *aDone = done;
00597   
00598   return err ? NS_ERROR_FAILURE : NS_OK;
00599 }
00600 
00601 nsresult
00602 nsFormHistory::CopyRowsFromTable(nsIMdbTable *sourceTable)
00603 {
00604   nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
00605   mdb_err err = sourceTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
00606   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00607   
00608   nsCOMPtr<nsIMdbRow> row;
00609   mdb_pos pos;
00610   do {
00611     rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
00612     if (!row)
00613       break;
00614 
00615     mdbOid rowId;
00616     rowId.mOid_Scope = kToken_RowScope;
00617     rowId.mOid_Id = mdb_id(-1);
00618 
00619     nsCOMPtr<nsIMdbRow> newRow;
00620     mTable->NewRow(mEnv, &rowId, getter_AddRefs(newRow));
00621     newRow->SetRow(mEnv, row);
00622     mTable->AddRow(mEnv, newRow);
00623   } while (row);
00624   return NS_OK;
00625 }
00626 
00627 nsresult
00628 nsFormHistory::AppendRow(const nsAString &aName, const nsAString &aValue, nsIMdbRow **aResult)
00629 {  
00630   if (!mTable)
00631     return NS_ERROR_NOT_INITIALIZED;
00632 
00633   if (aName.Length() > FORMFILL_NAME_MAX_LEN ||
00634       aValue.Length() > FORMFILL_VALUE_MAX_LEN)
00635     return NS_ERROR_INVALID_ARG;
00636 
00637   PRBool exists = PR_TRUE;
00638   EntryExists(aName, aValue, &exists);
00639   if (exists)
00640     return NS_OK;
00641 
00642   mdbOid rowId;
00643   rowId.mOid_Scope = kToken_RowScope;
00644   rowId.mOid_Id = mdb_id(-1);
00645 
00646   nsCOMPtr<nsIMdbRow> row;
00647   mdb_err err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row));
00648   if (err != 0)
00649     return NS_ERROR_FAILURE;
00650 
00651   SetRowValue(row, kToken_NameColumn, aName);
00652   SetRowValue(row, kToken_ValueColumn, aValue);
00653 
00654   if (aResult) {
00655     *aResult = row;
00656     NS_ADDREF(*aResult);
00657   }
00658   
00659   return NS_OK;  
00660 }
00661 
00662 nsresult
00663 nsFormHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const nsAString &aValue)
00664 {
00665   PRInt32 len = aValue.Length() * sizeof(PRUnichar);
00666   PRUnichar *swapval = nsnull;
00667   mdbYarn yarn = {nsnull, len, len, 0, 0, nsnull};
00668   const nsPromiseFlatString& buffer = PromiseFlatString(aValue);
00669 
00670   if (mReverseByteOrder) {
00671     swapval = new PRUnichar[aValue.Length()];
00672     if (!swapval)
00673       return NS_ERROR_OUT_OF_MEMORY;
00674     SwapBytes(swapval, buffer.get(), aValue.Length());
00675     yarn.mYarn_Buf = swapval;
00676   }
00677   else
00678     yarn.mYarn_Buf = (void*)buffer.get();
00679 
00680   mdb_err err = aRow->AddColumn(mEnv, aCol, &yarn);
00681 
00682   delete swapval;
00683   
00684   return err ? NS_ERROR_FAILURE : NS_OK;
00685 }
00686 
00687 nsresult
00688 nsFormHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsAString &aValue)
00689 {
00690   mdbYarn yarn;
00691   mdb_err err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
00692   if (err != 0)
00693     return NS_ERROR_FAILURE;
00694 
00695   aValue.Truncate(0);
00696   if (!yarn.mYarn_Fill)
00697     return NS_OK;
00698   
00699   switch (yarn.mYarn_Form) {
00700     case 0: { // unicode
00701       PRUint32 len = yarn.mYarn_Fill / sizeof(PRUnichar);
00702       if (mReverseByteOrder) {
00703         PRUnichar *swapval = new PRUnichar[len];
00704         if (!swapval)
00705           return NS_ERROR_OUT_OF_MEMORY;
00706         SwapBytes(swapval, (const PRUnichar*)yarn.mYarn_Buf, len);
00707         aValue.Assign(swapval, len);
00708         delete swapval;
00709       }
00710       else
00711         aValue.Assign((const PRUnichar *)yarn.mYarn_Buf, len);
00712       break;
00713     }
00714     default:
00715       return NS_ERROR_UNEXPECTED;
00716   }
00717   
00718   return NS_OK;
00719 }
00720 
00721 nsresult
00722 nsFormHistory::AutoCompleteSearch(const nsAString &aInputName,
00723                                   const nsAString &aInputValue,
00724                                   nsIAutoCompleteMdbResult2 *aPrevResult,
00725                                   nsIAutoCompleteResult **aResult)
00726 {
00727   if (!FormHistoryEnabled())
00728     return NS_OK;
00729 
00730   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00731   NS_ENSURE_SUCCESS(rv, rv);
00732 
00733   nsCOMPtr<nsIAutoCompleteMdbResult2> result;
00734   
00735   if (aPrevResult) {
00736     result = aPrevResult;
00737     
00738     PRUint32 rowCount;
00739     result->GetMatchCount(&rowCount);
00740     
00741     for (PRInt32 i = rowCount-1; i >= 0; --i) {
00742       nsIMdbRow *row;
00743       result->GetRowAt(i, &row);
00744       if (!RowMatch(row, aInputName, aInputValue, nsnull))
00745         result->RemoveValueAt(i, PR_FALSE);
00746     }
00747   } else {
00748     result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1");
00749 
00750     result->SetSearchString(aInputValue);
00751     result->Init(mEnv, mTable);
00752     result->SetTokens(kToken_ValueColumn, nsIAutoCompleteMdbResult2::kUnicharType, nsnull, nsIAutoCompleteMdbResult2::kUnicharType);
00753     result->SetReverseByteOrder(mReverseByteOrder);
00754 
00755     // Get a cursor to iterate through all rows in the database
00756     nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
00757     mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
00758     NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00759     
00760     // Store only the matching values
00761     nsAutoVoidArray matchingValues;
00762     nsCOMArray<nsIMdbRow> matchingRows;
00763 
00764     nsCOMPtr<nsIMdbRow> row;
00765     mdb_pos pos;
00766     do {
00767       rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
00768       if (!row)
00769         break;
00770 
00771       PRUnichar *value = 0; // We will own the allocated string value
00772       if (RowMatch(row, aInputName, aInputValue, &value)) {
00773         matchingRows.AppendObject(row);
00774         matchingValues.AppendElement(value);
00775       }
00776     } while (row);
00777 
00778     // Turn auto array into flat array for quick sort, now that we
00779     // know how many items there are
00780     PRUint32 count = matchingRows.Count();
00781 
00782     if (count > 0) {
00783       PRUint32* items = new PRUint32[count];
00784       PRUint32 i;
00785       for (i = 0; i < count; ++i)
00786         items[i] = i;
00787 
00788       NS_QuickSort(items, count, sizeof(PRUint32),
00789                    SortComparison, &matchingValues);
00790 
00791       for (i = 0; i < count; ++i) {
00792         // Place the sorted result into the autocomplete result
00793         result->AddRow(matchingRows[items[i]]);
00794 
00795         // Free up these strings we owned.
00796         NS_Free(matchingValues[i]);
00797       }
00798 
00799       delete[] items;
00800     }
00801 
00802     PRUint32 matchCount;
00803     result->GetMatchCount(&matchCount);
00804     if (matchCount > 0) {
00805       result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS);
00806       result->SetDefaultIndex(0);
00807     } else {
00808       result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH);
00809       result->SetDefaultIndex(-1);
00810     }
00811   }
00812   
00813   *aResult = result;
00814   NS_IF_ADDREF(*aResult);
00815 
00816   return NS_OK;
00817 }
00818 
00819 int PR_CALLBACK 
00820 nsFormHistory::SortComparison(const void *v1, const void *v2, void *closureVoid) 
00821 {
00822   PRUint32 *index1 = (PRUint32 *)v1;
00823   PRUint32 *index2 = (PRUint32 *)v2;
00824   nsAutoVoidArray *array = (nsAutoVoidArray *)closureVoid;
00825   
00826   PRUnichar *s1 = (PRUnichar *)array->ElementAt(*index1);
00827   PRUnichar *s2 = (PRUnichar *)array->ElementAt(*index2);
00828   
00829   return nsCRT::strcmp(s1, s2);
00830 }
00831 
00832 PRBool
00833 nsFormHistory::RowMatch(nsIMdbRow *aRow, const nsAString &aInputName, const nsAString &aInputValue, PRUnichar **aValue)
00834 {
00835   nsAutoString name;
00836   GetRowValue(aRow, kToken_NameColumn, name);
00837 
00838   if (name.Equals(aInputName)) {
00839     nsAutoString value;
00840     GetRowValue(aRow, kToken_ValueColumn, value);
00841     if (Compare(Substring(value, 0, aInputValue.Length()), aInputValue, nsCaseInsensitiveStringComparator()) == 0) {
00842       if (aValue)
00843         *aValue = ToNewUnicode(value);
00844       return PR_TRUE;
00845     }
00846   }
00847   
00848   return PR_FALSE;
00849 }
00850 
00851 nsresult
00852 nsFormHistory::EntriesExistInternal(const nsAString *aName, const nsAString *aValue, PRBool *_retval)
00853 {
00854   // Unfortunately we have to do a brute force search through the database
00855   // because mork didn't bother to implement any indexing functionality
00856   
00857   *_retval = PR_FALSE;
00858   
00859   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00860   NS_ENSURE_SUCCESS(rv, rv);
00861 
00862   // Get a cursor to iterate through all rows in the database
00863   nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
00864   mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
00865   NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
00866   
00867   nsCOMPtr<nsIMdbRow> row;
00868   mdb_pos pos;
00869   do {
00870     rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
00871     if (!row)
00872       break;
00873 
00874     // Check if the name and value combination match this row
00875     nsAutoString name;
00876     GetRowValue(row, kToken_NameColumn, name);
00877 
00878     if (Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
00879       nsAutoString value;
00880       GetRowValue(row, kToken_ValueColumn, value);
00881       if (!aValue || Compare(value, *aValue, nsCaseInsensitiveStringComparator()) == 0) {
00882         *_retval = PR_TRUE;
00883         break;
00884       }
00885     }
00886   } while (1);
00887   
00888   return NS_OK;
00889 }
00890 
00891 nsresult
00892 nsFormHistory::RemoveEntriesInternal(const nsAString *aName)
00893 {
00894   nsresult rv = OpenDatabase(); // lazily ensure that the database is open
00895   NS_ENSURE_SUCCESS(rv, rv);
00896 
00897   if (!mTable) return NS_OK;
00898 
00899   mdb_err err;
00900   mdb_count count;
00901   nsAutoString name;
00902   err = mTable->GetCount(mEnv, &count);
00903   if (err != 0) return NS_ERROR_FAILURE;
00904 
00905   // Begin the batch.
00906   int marker;
00907   err = mTable->StartBatchChangeHint(mEnv, &marker);
00908   NS_ASSERTION(err == 0, "unable to start batch");
00909   if (err != 0) return NS_ERROR_FAILURE;
00910 
00911   for (mdb_pos pos = count - 1; pos >= 0; --pos) {
00912     nsCOMPtr<nsIMdbRow> row;
00913     err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
00914     NS_ASSERTION(err == 0, "unable to get row");
00915     if (err != 0)
00916       break;
00917 
00918     NS_ASSERTION(row != nsnull, "no row");
00919     if (! row)
00920       continue;
00921 
00922     // Check if the name matches this row
00923     GetRowValue(row, kToken_NameColumn, name);
00924     
00925     if (!aName || Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
00926 
00927       // Officially cut the row *now*, before notifying any observers:
00928       // that way, any re-entrant calls won't find the row.
00929       err = mTable->CutRow(mEnv, row);
00930       NS_ASSERTION(err == 0, "couldn't cut row");
00931       if (err != 0)
00932         continue;
00933   
00934       // possibly avoid leakage
00935       err = row->CutAllColumns(mEnv);
00936       NS_ASSERTION(err == 0, "couldn't cut all columns");
00937       // we'll notify regardless of whether we could successfully
00938       // CutAllColumns or not.
00939     }
00940 
00941   }
00942   
00943   // Finish the batch.
00944   err = mTable->EndBatchChangeHint(mEnv, &marker);
00945   NS_ASSERTION(err == 0, "error ending batch");
00946 
00947   return (err == 0) ? NS_OK : NS_ERROR_FAILURE;
00948 
00949 }
00950 
00951 nsresult
00952 nsFormHistory::InitByteOrder(PRBool aForce)
00953 {
00954   // bigEndian and littleEndian are endianness markers that are stored in
00955   // the formhistory db as UTF-16.  Define them to be strings easily
00956   // recognized in either endianness.
00957   nsAutoString bigEndianByteOrder((PRUnichar*)"BBBB", 2);
00958   nsAutoString littleEndianByteOrder((PRUnichar*)"llll", 2);
00959 #ifdef IS_BIG_ENDIAN
00960   nsAutoString nativeByteOrder(bigEndianByteOrder);
00961 #else
00962   nsAutoString nativeByteOrder(littleEndianByteOrder);
00963 #endif
00964 
00965   nsAutoString fileByteOrder;
00966   nsresult rv = NS_OK;
00967 
00968   if (!aForce)
00969     rv = GetByteOrder(fileByteOrder);
00970 
00971   if (aForce || NS_FAILED(rv) ||
00972       !(fileByteOrder.Equals(bigEndianByteOrder) ||
00973         fileByteOrder.Equals(littleEndianByteOrder))) {
00974 #if defined(XP_MACOSX) && defined(IS_LITTLE_ENDIAN)
00975     // The formhistory db did not carry endiannes information until the
00976     // initial x86 Mac release.  There are a lot of users out there who
00977     // will be switching from ppc versions to x86, and their unmarked
00978     // formhistory files are big-endian.  On x86 Macs, unless aForce is set
00979     // (indicating formhistory reset or a brand-new db), use big-endian byte
00980     // ordering and turn swapping on.
00981     if (aForce) {
00982       mReverseByteOrder = PR_FALSE;
00983       rv = SaveByteOrder(nativeByteOrder);
00984     }
00985     else {
00986       mReverseByteOrder = PR_TRUE;
00987       rv = SaveByteOrder(bigEndianByteOrder);
00988     }
00989 #else
00990     mReverseByteOrder = PR_FALSE;
00991     rv = SaveByteOrder(nativeByteOrder);
00992 #endif
00993   }
00994   else
00995     mReverseByteOrder = !fileByteOrder.Equals(nativeByteOrder);
00996 
00997   return rv;
00998 }
00999 
01000 nsresult
01001 nsFormHistory::GetByteOrder(nsAString& aByteOrder)
01002 {
01003   NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
01004 
01005   mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
01006   NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
01007 
01008   return NS_OK;
01009 }
01010 
01011 nsresult
01012 nsFormHistory::SaveByteOrder(const nsAString& aByteOrder)
01013 {
01014   NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
01015 
01016   mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
01017   NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
01018 
01019   return NS_OK;
01020 }