toolkit/components/passwordmgr/storage-mozStorage.js

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

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

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

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* vim: set sw=4 ts=4 et lcs=trail\:.,tab\:>~ : */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     8 const Cc = Components.classes;
     9 const Ci = Components.interfaces;
    10 const Cr = Components.results;
    12 const DB_VERSION = 5; // The database schema version
    14 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    15 Components.utils.import("resource://gre/modules/Services.jsm");
    17 /**
    18  * Object that manages a database transaction properly so consumers don't have
    19  * to worry about it throwing.
    20  *
    21  * @param aDatabase
    22  *        The mozIStorageConnection to start a transaction on.
    23  */
    24 function Transaction(aDatabase) {
    25     this._db = aDatabase;
    27     this._hasTransaction = false;
    28     try {
    29         this._db.beginTransaction();
    30         this._hasTransaction = true;
    31     }
    32     catch(e) { /* om nom nom exceptions */ }
    33 }
    35 Transaction.prototype = {
    36     commit : function() {
    37         if (this._hasTransaction)
    38             this._db.commitTransaction();
    39     },
    41     rollback : function() {
    42         if (this._hasTransaction)
    43             this._db.rollbackTransaction();
    44     },
    45 };
    48 function LoginManagerStorage_mozStorage() { };
    50 LoginManagerStorage_mozStorage.prototype = {
    52     classID : Components.ID("{8c2023b9-175c-477e-9761-44ae7b549756}"),
    53     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage,
    54                                             Ci.nsIInterfaceRequestor]),
    55     getInterface : function(aIID) {
    56         if (aIID.equals(Ci.mozIStorageConnection)) {
    57             return this._dbConnection;
    58         }
    60         throw Cr.NS_ERROR_NO_INTERFACE;
    61     },
    63     __crypto : null,  // nsILoginManagerCrypto service
    64     get _crypto() {
    65         if (!this.__crypto)
    66             this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
    67                             getService(Ci.nsILoginManagerCrypto);
    68         return this.__crypto;
    69     },
    71     __profileDir: null,  // nsIFile for the user's profile dir
    72     get _profileDir() {
    73         if (!this.__profileDir)
    74             this.__profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    75         return this.__profileDir;
    76     },
    78     __storageService: null, // Storage service for using mozStorage
    79     get _storageService() {
    80         if (!this.__storageService)
    81             this.__storageService = Cc["@mozilla.org/storage/service;1"].
    82                                     getService(Ci.mozIStorageService);
    83         return this.__storageService;
    84     },
    86     __uuidService: null,
    87     get _uuidService() {
    88         if (!this.__uuidService)
    89             this.__uuidService = Cc["@mozilla.org/uuid-generator;1"].
    90                                  getService(Ci.nsIUUIDGenerator);
    91         return this.__uuidService;
    92     },
    95     // The current database schema.
    96     _dbSchema: {
    97         tables: {
    98             moz_logins:         "id                  INTEGER PRIMARY KEY," +
    99                                 "hostname            TEXT NOT NULL,"       +
   100                                 "httpRealm           TEXT,"                +
   101                                 "formSubmitURL       TEXT,"                +
   102                                 "usernameField       TEXT NOT NULL,"       +
   103                                 "passwordField       TEXT NOT NULL,"       +
   104                                 "encryptedUsername   TEXT NOT NULL,"       +
   105                                 "encryptedPassword   TEXT NOT NULL,"       +
   106                                 "guid                TEXT,"                +
   107                                 "encType             INTEGER,"             +
   108                                 "timeCreated         INTEGER,"             +
   109                                 "timeLastUsed        INTEGER,"             +
   110                                 "timePasswordChanged INTEGER,"             +
   111                                 "timesUsed           INTEGER",
   112             // Changes must be reflected in this._dbAreExpectedColumnsPresent(),
   113             // this._searchLogins(), and this.modifyLogin().
   115             moz_disabledHosts:  "id                 INTEGER PRIMARY KEY," +
   116                                 "hostname           TEXT UNIQUE ON CONFLICT REPLACE",
   118             moz_deleted_logins: "id                  INTEGER PRIMARY KEY," +
   119                                 "guid                TEXT,"                +
   120                                 "timeDeleted         INTEGER",
   121         },
   122         indices: {
   123           moz_logins_hostname_index: {
   124             table: "moz_logins",
   125             columns: ["hostname"]
   126           },
   127           moz_logins_hostname_formSubmitURL_index: {
   128             table: "moz_logins",
   129             columns: ["hostname", "formSubmitURL"]
   130           },
   131           moz_logins_hostname_httpRealm_index: {
   132               table: "moz_logins",
   133               columns: ["hostname", "httpRealm"]
   134           },
   135           moz_logins_guid_index: {
   136               table: "moz_logins",
   137               columns: ["guid"]
   138           },
   139           moz_logins_encType_index: {
   140               table: "moz_logins",
   141               columns: ["encType"]
   142           }
   143         }
   144     },
   145     _dbConnection : null,  // The database connection
   146     _dbStmts      : null,  // Database statements for memoization
   148     _prefBranch   : null,  // Preferences service
   149     _signonsFile  : null,  // nsIFile for "signons.sqlite"
   150     _debug        : false, // mirrors signon.debug
   153     /*
   154      * log
   155      *
   156      * Internal function for logging debug messages to the Error Console.
   157      */
   158     log : function (message) {
   159         if (!this._debug)
   160             return;
   161         dump("PwMgr mozStorage: " + message + "\n");
   162         Services.console.logStringMessage("PwMgr mozStorage: " + message);
   163     },
   166     /*
   167      * initWithFile
   168      *
   169      * Initialize the component, but override the default filename locations.
   170      * This is primarily used to the unit tests and profile migration.
   171      */
   172     initWithFile : function(aDBFile) {
   173         if (aDBFile)
   174             this._signonsFile = aDBFile;
   176         this.init();
   177     },
   180     /*
   181      * init
   182      *
   183      */
   184     init : function () {
   185         this._dbStmts = {};
   187         // Connect to the correct preferences branch.
   188         this._prefBranch = Services.prefs.getBranch("signon.");
   189         this._debug = this._prefBranch.getBoolPref("debug");
   191         let isFirstRun;
   192         try {
   193             // Force initialization of the crypto module.
   194             // See bug 717490 comment 17.
   195             this._crypto;
   197             // If initWithFile is calling us, _signonsFile may already be set.
   198             if (!this._signonsFile) {
   199                 // Initialize signons.sqlite
   200                 this._signonsFile = this._profileDir.clone();
   201                 this._signonsFile.append("signons.sqlite");
   202             }
   203             this.log("Opening database at " + this._signonsFile.path);
   205             // Initialize the database (create, migrate as necessary)
   206             isFirstRun = this._dbInit();
   208             this._initialized = true;
   209         } catch (e) {
   210             this.log("Initialization failed: " + e);
   211             // If the import fails on first run, we want to delete the db
   212             if (isFirstRun && e == "Import failed")
   213                 this._dbCleanup(false);
   214             throw "Initialization failed";
   215         }
   216     },
   219     /*
   220      * addLogin
   221      *
   222      */
   223     addLogin : function (login) {
   224         let encUsername, encPassword;
   226         // Throws if there are bogus values.
   227         this._checkLoginValues(login);
   229         [encUsername, encPassword, encType] = this._encryptLogin(login);
   231         // Clone the login, so we don't modify the caller's object.
   232         let loginClone = login.clone();
   234         // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
   235         loginClone.QueryInterface(Ci.nsILoginMetaInfo);
   236         if (loginClone.guid) {
   237             if (!this._isGuidUnique(loginClone.guid))
   238                 throw "specified GUID already exists";
   239         } else {
   240             loginClone.guid = this._uuidService.generateUUID().toString();
   241         }
   243         // Set timestamps
   244         let currentTime = Date.now();
   245         if (!loginClone.timeCreated)
   246             loginClone.timeCreated = currentTime;
   247         if (!loginClone.timeLastUsed)
   248             loginClone.timeLastUsed = currentTime;
   249         if (!loginClone.timePasswordChanged)
   250             loginClone.timePasswordChanged = currentTime;
   251         if (!loginClone.timesUsed)
   252             loginClone.timesUsed = 1;
   254         let query =
   255             "INSERT INTO moz_logins " +
   256             "(hostname, httpRealm, formSubmitURL, usernameField, " +
   257              "passwordField, encryptedUsername, encryptedPassword, " +
   258              "guid, encType, timeCreated, timeLastUsed, timePasswordChanged, " +
   259              "timesUsed) " +
   260             "VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
   261                     ":passwordField, :encryptedUsername, :encryptedPassword, " +
   262                     ":guid, :encType, :timeCreated, :timeLastUsed, " +
   263                     ":timePasswordChanged, :timesUsed)";
   265         let params = {
   266             hostname:            loginClone.hostname,
   267             httpRealm:           loginClone.httpRealm,
   268             formSubmitURL:       loginClone.formSubmitURL,
   269             usernameField:       loginClone.usernameField,
   270             passwordField:       loginClone.passwordField,
   271             encryptedUsername:   encUsername,
   272             encryptedPassword:   encPassword,
   273             guid:                loginClone.guid,
   274             encType:             encType,
   275             timeCreated:         loginClone.timeCreated,
   276             timeLastUsed:        loginClone.timeLastUsed,
   277             timePasswordChanged: loginClone.timePasswordChanged,
   278             timesUsed:           loginClone.timesUsed
   279         };
   281         let stmt;
   282         try {
   283             stmt = this._dbCreateStatement(query, params);
   284             stmt.execute();
   285         } catch (e) {
   286             this.log("addLogin failed: " + e.name + " : " + e.message);
   287             throw "Couldn't write to database, login not added.";
   288         } finally {
   289             if (stmt) {
   290                 stmt.reset();
   291             }
   292         }
   294         // Send a notification that a login was added.
   295         this._sendNotification("addLogin", loginClone);
   296     },
   299     /*
   300      * removeLogin
   301      *
   302      */
   303     removeLogin : function (login) {
   304         let [idToDelete, storedLogin] = this._getIdForLogin(login);
   305         if (!idToDelete)
   306             throw "No matching logins";
   308         // Execute the statement & remove from DB
   309         let query  = "DELETE FROM moz_logins WHERE id = :id";
   310         let params = { id: idToDelete };
   311         let stmt;
   312         let transaction = new Transaction(this._dbConnection);
   313         try {
   314             stmt = this._dbCreateStatement(query, params);
   315             stmt.execute();
   316             this.storeDeletedLogin(storedLogin);
   317             transaction.commit();
   318         } catch (e) {
   319             this.log("_removeLogin failed: " + e.name + " : " + e.message);
   320             throw "Couldn't write to database, login not removed.";
   321             transaction.rollback();
   322         } finally {
   323             if (stmt) {
   324                 stmt.reset();
   325             }
   326         }
   327         this._sendNotification("removeLogin", storedLogin);
   328     },
   331     /*
   332      * modifyLogin
   333      *
   334      */
   335     modifyLogin : function (oldLogin, newLoginData) {
   336         let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
   337         if (!idToModify)
   338             throw "No matching logins";
   339         oldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
   341         let newLogin;
   342         if (newLoginData instanceof Ci.nsILoginInfo) {
   343             // Clone the existing login to get its nsILoginMetaInfo, then init it
   344             // with the replacement nsILoginInfo data from the new login.
   345             newLogin = oldStoredLogin.clone();
   346             newLogin.init(newLoginData.hostname,
   347                           newLoginData.formSubmitURL, newLoginData.httpRealm,
   348                           newLoginData.username, newLoginData.password,
   349                           newLoginData.usernameField, newLoginData.passwordField);
   350             newLogin.QueryInterface(Ci.nsILoginMetaInfo);
   352             // Automatically update metainfo when password is changed.
   353             if (newLogin.password != oldLogin.password)
   354                 newLogin.timePasswordChanged = Date.now();
   355         } else if (newLoginData instanceof Ci.nsIPropertyBag) {
   356             function _bagHasProperty(aPropName) {
   357                 try {
   358                     newLoginData.getProperty(aPropName);
   359                     return true;
   360                 } catch (e) {
   361                     return false;
   362                 }
   363             }
   365             // Clone the existing login, along with all its properties.
   366             newLogin = oldStoredLogin.clone();
   367             newLogin.QueryInterface(Ci.nsILoginMetaInfo);
   369             // Automatically update metainfo when password is changed.
   370             // (Done before the main property updates, lest the caller be
   371             // explicitly updating both .password and .timePasswordChanged)
   372             if (_bagHasProperty("password")) {
   373                 let newPassword = newLoginData.getProperty("password");
   374                 if (newPassword != oldLogin.password)
   375                     newLogin.timePasswordChanged = Date.now();
   376             }
   378             let propEnum = newLoginData.enumerator;
   379             while (propEnum.hasMoreElements()) {
   380                 let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
   381                 switch (prop.name) {
   382                     // nsILoginInfo properties...
   383                     case "hostname":
   384                     case "httpRealm":
   385                     case "formSubmitURL":
   386                     case "username":
   387                     case "password":
   388                     case "usernameField":
   389                     case "passwordField":
   390                     // nsILoginMetaInfo properties...
   391                     case "guid":
   392                     case "timeCreated":
   393                     case "timeLastUsed":
   394                     case "timePasswordChanged":
   395                     case "timesUsed":
   396                         newLogin[prop.name] = prop.value;
   397                         if (prop.name == "guid" && !this._isGuidUnique(newLogin.guid))
   398                             throw "specified GUID already exists";
   399                         break;
   401                     // Fake property, allows easy incrementing.
   402                     case "timesUsedIncrement":
   403                         newLogin.timesUsed += prop.value;
   404                         break;
   406                     // Fail if caller requests setting an unknown property.
   407                     default:
   408                         throw "Unexpected propertybag item: " + prop.name;
   409                 }
   410             }
   411         } else {
   412             throw "newLoginData needs an expected interface!";
   413         }
   415         // Throws if there are bogus values.
   416         this._checkLoginValues(newLogin);
   418         // Get the encrypted value of the username and password.
   419         let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);
   421         let query =
   422             "UPDATE moz_logins " +
   423             "SET hostname = :hostname, " +
   424                 "httpRealm = :httpRealm, " +
   425                 "formSubmitURL = :formSubmitURL, " +
   426                 "usernameField = :usernameField, " +
   427                 "passwordField = :passwordField, " +
   428                 "encryptedUsername = :encryptedUsername, " +
   429                 "encryptedPassword = :encryptedPassword, " +
   430                 "guid = :guid, " +
   431                 "encType = :encType, " +
   432                 "timeCreated = :timeCreated, " +
   433                 "timeLastUsed = :timeLastUsed, " +
   434                 "timePasswordChanged = :timePasswordChanged, " +
   435                 "timesUsed = :timesUsed " +
   436             "WHERE id = :id";
   438         let params = {
   439             id:                  idToModify,
   440             hostname:            newLogin.hostname,
   441             httpRealm:           newLogin.httpRealm,
   442             formSubmitURL:       newLogin.formSubmitURL,
   443             usernameField:       newLogin.usernameField,
   444             passwordField:       newLogin.passwordField,
   445             encryptedUsername:   encUsername,
   446             encryptedPassword:   encPassword,
   447             guid:                newLogin.guid,
   448             encType:             encType,
   449             timeCreated:         newLogin.timeCreated,
   450             timeLastUsed:        newLogin.timeLastUsed,
   451             timePasswordChanged: newLogin.timePasswordChanged,
   452             timesUsed:           newLogin.timesUsed
   453         };
   455         let stmt;
   456         try {
   457             stmt = this._dbCreateStatement(query, params);
   458             stmt.execute();
   459         } catch (e) {
   460             this.log("modifyLogin failed: " + e.name + " : " + e.message);
   461             throw "Couldn't write to database, login not modified.";
   462         } finally {
   463             if (stmt) {
   464                 stmt.reset();
   465             }
   466         }
   468         this._sendNotification("modifyLogin", [oldStoredLogin, newLogin]);
   469     },
   472     /*
   473      * getAllLogins
   474      *
   475      * Returns an array of nsILoginInfo.
   476      */
   477     getAllLogins : function (count) {
   478         let [logins, ids] = this._searchLogins({});
   480         // decrypt entries for caller.
   481         logins = this._decryptLogins(logins);
   483         this.log("_getAllLogins: returning " + logins.length + " logins.");
   484         if (count)
   485             count.value = logins.length; // needed for XPCOM
   486         return logins;
   487     },
   490     /*
   491      * getAllEncryptedLogins
   492      *
   493      * Not implemented. This interface was added to extract logins from the
   494      * legacy storage module without decrypting them. Now that logins are in
   495      * mozStorage, if the encrypted data is really needed it can be easily
   496      * obtained with SQL and the mozStorage APIs.
   497      */
   498     getAllEncryptedLogins : function (count) {
   499         throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   500     },
   503     /*
   504      * searchLogins
   505      *
   506      * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   507      * JavaScript object and decrypt the results.
   508      *
   509      * Returns an array of decrypted nsILoginInfo.
   510      */
   511     searchLogins : function(count, matchData) {
   512         let realMatchData = {};
   513         // Convert nsIPropertyBag to normal JS object
   514         let propEnum = matchData.enumerator;
   515         while (propEnum.hasMoreElements()) {
   516             let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
   517             realMatchData[prop.name] = prop.value;
   518         }
   520         let [logins, ids] = this._searchLogins(realMatchData);
   522         // Decrypt entries found for the caller.
   523         logins = this._decryptLogins(logins);
   525         count.value = logins.length; // needed for XPCOM
   526         return logins;
   527     },
   530     /*
   531      * _searchLogins
   532      *
   533      * Private method to perform arbitrary searches on any field. Decryption is
   534      * left to the caller.
   535      *
   536      * Returns [logins, ids] for logins that match the arguments, where logins
   537      * is an array of encrypted nsLoginInfo and ids is an array of associated
   538      * ids in the database.
   539      */
   540     _searchLogins : function (matchData) {
   541         let conditions = [], params = {};
   543         for (let field in matchData) {
   544             let value = matchData[field];
   545             switch (field) {
   546                 // Historical compatibility requires this special case
   547                 case "formSubmitURL":
   548                     if (value != null) {
   549                         conditions.push("formSubmitURL = :formSubmitURL OR formSubmitURL = ''");
   550                         params["formSubmitURL"] = value;
   551                         break;
   552                     }
   553                 // Normal cases.
   554                 case "hostname":
   555                 case "httpRealm":
   556                 case "id":
   557                 case "usernameField":
   558                 case "passwordField":
   559                 case "encryptedUsername":
   560                 case "encryptedPassword":
   561                 case "guid":
   562                 case "encType":
   563                 case "timeCreated":
   564                 case "timeLastUsed":
   565                 case "timePasswordChanged":
   566                 case "timesUsed":
   567                     if (value == null) {
   568                         conditions.push(field + " isnull");
   569                     } else {
   570                         conditions.push(field + " = :" + field);
   571                         params[field] = value;
   572                     }
   573                     break;
   574                 // Fail if caller requests an unknown property.
   575                 default:
   576                     throw "Unexpected field: " + field;
   577             }
   578         }
   580         // Build query
   581         let query = "SELECT * FROM moz_logins";
   582         if (conditions.length) {
   583             conditions = conditions.map(function(c) "(" + c + ")");
   584             query += " WHERE " + conditions.join(" AND ");
   585         }
   587         let stmt;
   588         let logins = [], ids = [];
   589         try {
   590             stmt = this._dbCreateStatement(query, params);
   591             // We can't execute as usual here, since we're iterating over rows
   592             while (stmt.executeStep()) {
   593                 // Create the new nsLoginInfo object, push to array
   594                 let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
   595                             createInstance(Ci.nsILoginInfo);
   596                 login.init(stmt.row.hostname, stmt.row.formSubmitURL,
   597                            stmt.row.httpRealm, stmt.row.encryptedUsername,
   598                            stmt.row.encryptedPassword, stmt.row.usernameField,
   599                            stmt.row.passwordField);
   600                 // set nsILoginMetaInfo values
   601                 login.QueryInterface(Ci.nsILoginMetaInfo);
   602                 login.guid = stmt.row.guid;
   603                 login.timeCreated = stmt.row.timeCreated;
   604                 login.timeLastUsed = stmt.row.timeLastUsed;
   605                 login.timePasswordChanged = stmt.row.timePasswordChanged;
   606                 login.timesUsed = stmt.row.timesUsed;
   607                 logins.push(login);
   608                 ids.push(stmt.row.id);
   609             }
   610         } catch (e) {
   611             this.log("_searchLogins failed: " + e.name + " : " + e.message);
   612         } finally {
   613             if (stmt) {
   614                 stmt.reset();
   615             }
   616         }
   618         this.log("_searchLogins: returning " + logins.length + " logins");
   619         return [logins, ids];
   620     },
   622     /* storeDeletedLogin
   623      *
   624      * Moves a login to the deleted logins table
   625      *
   626      */
   627      storeDeletedLogin : function(aLogin) {
   628 #ifdef ANDROID
   629           let stmt = null; 
   630           try {
   631               this.log("Storing " + aLogin.guid + " in deleted passwords\n");
   632               let query = "INSERT INTO moz_deleted_logins (guid, timeDeleted) VALUES (:guid, :timeDeleted)";
   633               let params = { guid: aLogin.guid,
   634                              timeDeleted: Date.now() };
   635               let stmt = this._dbCreateStatement(query, params);
   636               stmt.execute();
   637           } catch(ex) {
   638               throw ex;
   639           } finally {
   640               if (stmt)
   641                   stmt.reset();
   642           }		
   643 #endif
   644      },
   647     /*
   648      * removeAllLogins
   649      *
   650      * Removes all logins from storage.
   651      */
   652     removeAllLogins : function () {
   653         this.log("Removing all logins");
   654         let query;
   655         let stmt;
   656         let transaction = new Transaction(this._dbConnection);
   658         // Disabled hosts kept, as one presumably doesn't want to erase those.
   659         // TODO: Add these items to the deleted items table once we've sorted
   660         //       out the issues from bug 756701
   661         query = "DELETE FROM moz_logins";
   662         try {
   663             stmt = this._dbCreateStatement(query);
   664             stmt.execute();
   665             transaction.commit();
   666         } catch (e) {
   667             this.log("_removeAllLogins failed: " + e.name + " : " + e.message);
   668             transaction.rollback();
   669             throw "Couldn't write to database";
   670         } finally {
   671             if (stmt) {
   672                 stmt.reset();
   673             }
   674         }
   676         this._sendNotification("removeAllLogins", null);
   677    },
   680     /*
   681      * getAllDisabledHosts
   682      *
   683      */
   684     getAllDisabledHosts : function (count) {
   685         let disabledHosts = this._queryDisabledHosts(null);
   687         this.log("_getAllDisabledHosts: returning " + disabledHosts.length + " disabled hosts.");
   688         if (count)
   689             count.value = disabledHosts.length; // needed for XPCOM
   690         return disabledHosts;
   691     },
   694     /*
   695      * getLoginSavingEnabled
   696      *
   697      */
   698     getLoginSavingEnabled : function (hostname) {
   699         this.log("Getting login saving is enabled for " + hostname);
   700         return this._queryDisabledHosts(hostname).length == 0
   701     },
   704     /*
   705      * setLoginSavingEnabled
   706      *
   707      */
   708     setLoginSavingEnabled : function (hostname, enabled) {
   709         // Throws if there are bogus values.
   710         this._checkHostnameValue(hostname);
   712         this.log("Setting login saving enabled for " + hostname + " to " + enabled);
   713         let query;
   714         if (enabled)
   715             query = "DELETE FROM moz_disabledHosts " +
   716                     "WHERE hostname = :hostname";
   717         else
   718             query = "INSERT INTO moz_disabledHosts " +
   719                     "(hostname) VALUES (:hostname)";
   720         let params = { hostname: hostname };
   722         let stmt
   723         try {
   724             stmt = this._dbCreateStatement(query, params);
   725             stmt.execute();
   726         } catch (e) {
   727             this.log("setLoginSavingEnabled failed: " + e.name + " : " + e.message);
   728             throw "Couldn't write to database"
   729         } finally {
   730             if (stmt) {
   731                 stmt.reset();
   732             }
   733         }
   735         this._sendNotification(enabled ? "hostSavingEnabled" : "hostSavingDisabled", hostname);
   736     },
   739     /*
   740      * findLogins
   741      *
   742      */
   743     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
   744         let loginData = {
   745             hostname: hostname,
   746             formSubmitURL: formSubmitURL,
   747             httpRealm: httpRealm
   748         };
   749         let matchData = { };
   750         for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
   751           if (loginData[field] != '')
   752               matchData[field] = loginData[field];
   753         let [logins, ids] = this._searchLogins(matchData);
   755         // Decrypt entries found for the caller.
   756         logins = this._decryptLogins(logins);
   758         this.log("_findLogins: returning " + logins.length + " logins");
   759         count.value = logins.length; // needed for XPCOM
   760         return logins;
   761     },
   764     /*
   765      * countLogins
   766      *
   767      */
   768     countLogins : function (hostname, formSubmitURL, httpRealm) {
   769         // Do checks for null and empty strings, adjust conditions and params
   770         let [conditions, params] =
   771             this._buildConditionsAndParams(hostname, formSubmitURL, httpRealm);
   773         let query = "SELECT COUNT(1) AS numLogins FROM moz_logins";
   774         if (conditions.length) {
   775             conditions = conditions.map(function(c) "(" + c + ")");
   776             query += " WHERE " + conditions.join(" AND ");
   777         }
   779         let stmt, numLogins;
   780         try {
   781             stmt = this._dbCreateStatement(query, params);
   782             stmt.executeStep();
   783             numLogins = stmt.row.numLogins;
   784         } catch (e) {
   785             this.log("_countLogins failed: " + e.name + " : " + e.message);
   786         } finally {
   787             if (stmt) {
   788                 stmt.reset();
   789             }
   790         }
   792         this.log("_countLogins: counted logins: " + numLogins);
   793         return numLogins;
   794     },
   797     /*
   798      * uiBusy
   799      */
   800     get uiBusy() {
   801         return this._crypto.uiBusy;
   802     },
   805     /*
   806      * isLoggedIn
   807      */
   808     get isLoggedIn() {
   809         return this._crypto.isLoggedIn;
   810     },
   813     /*
   814      * _sendNotification
   815      *
   816      * Send a notification when stored data is changed.
   817      */
   818     _sendNotification : function (changeType, data) {
   819         let dataObject = data;
   820         // Can't pass a raw JS string or array though notifyObservers(). :-(
   821         if (data instanceof Array) {
   822             dataObject = Cc["@mozilla.org/array;1"].
   823                          createInstance(Ci.nsIMutableArray);
   824             for (let i = 0; i < data.length; i++)
   825                 dataObject.appendElement(data[i], false);
   826         } else if (typeof(data) == "string") {
   827             dataObject = Cc["@mozilla.org/supports-string;1"].
   828                          createInstance(Ci.nsISupportsString);
   829             dataObject.data = data;
   830         }
   831         Services.obs.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
   832     },
   835     /*
   836      * _getIdForLogin
   837      *
   838      * Returns an array with two items: [id, login]. If the login was not
   839      * found, both items will be null. The returned login contains the actual
   840      * stored login (useful for looking at the actual nsILoginMetaInfo values).
   841      */
   842     _getIdForLogin : function (login) {
   843         let matchData = { };
   844         for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
   845             if (login[field] != '')
   846                 matchData[field] = login[field];
   847         let [logins, ids] = this._searchLogins(matchData);
   849         let id = null;
   850         let foundLogin = null;
   852         // The specified login isn't encrypted, so we need to ensure
   853         // the logins we're comparing with are decrypted. We decrypt one entry
   854         // at a time, lest _decryptLogins return fewer entries and screw up
   855         // indices between the two.
   856         for (let i = 0; i < logins.length; i++) {
   857             let [decryptedLogin] = this._decryptLogins([logins[i]]);
   859             if (!decryptedLogin || !decryptedLogin.equals(login))
   860                 continue;
   862             // We've found a match, set id and break
   863             foundLogin = decryptedLogin;
   864             id = ids[i];
   865             break;
   866         }
   868         return [id, foundLogin];
   869     },
   872     /*
   873      * _queryDisabledHosts
   874      *
   875      * Returns an array of hostnames from the database according to the
   876      * criteria given in the argument. If the argument hostname is null, the
   877      * result array contains all hostnames
   878      */
   879     _queryDisabledHosts : function (hostname) {
   880         let disabledHosts = [];
   882         let query = "SELECT hostname FROM moz_disabledHosts";
   883         let params = {};
   884         if (hostname) {
   885             query += " WHERE hostname = :hostname";
   886             params = { hostname: hostname };
   887         }
   889         let stmt;
   890         try {
   891             stmt = this._dbCreateStatement(query, params);
   892             while (stmt.executeStep())
   893                 disabledHosts.push(stmt.row.hostname);
   894         } catch (e) {
   895             this.log("_queryDisabledHosts failed: " + e.name + " : " + e.message);
   896         } finally {
   897             if (stmt) {
   898                 stmt.reset();
   899             }
   900         }
   902         return disabledHosts;
   903     },
   906     /*
   907      * _buildConditionsAndParams
   908      *
   909      * Adjusts the WHERE conditions and parameters for statements prior to the
   910      * statement being created. This fixes the cases where nulls are involved
   911      * and the empty string is supposed to be a wildcard match
   912      */
   913     _buildConditionsAndParams : function (hostname, formSubmitURL, httpRealm) {
   914         let conditions = [], params = {};
   916         if (hostname == null) {
   917             conditions.push("hostname isnull");
   918         } else if (hostname != '') {
   919             conditions.push("hostname = :hostname");
   920             params["hostname"] = hostname;
   921         }
   923         if (formSubmitURL == null) {
   924             conditions.push("formSubmitURL isnull");
   925         } else if (formSubmitURL != '') {
   926             conditions.push("formSubmitURL = :formSubmitURL OR formSubmitURL = ''");
   927             params["formSubmitURL"] = formSubmitURL;
   928         }
   930         if (httpRealm == null) {
   931             conditions.push("httpRealm isnull");
   932         } else if (httpRealm != '') {
   933             conditions.push("httpRealm = :httpRealm");
   934             params["httpRealm"] = httpRealm;
   935         }
   937         return [conditions, params];
   938     },
   941     /*
   942      * _checkLoginValues
   943      *
   944      * Due to the way the signons2.txt file is formatted, we need to make
   945      * sure certain field values or characters do not cause the file to
   946      * be parse incorrectly. Reject logins that we can't store correctly.
   947      */
   948     _checkLoginValues : function (aLogin) {
   949         function badCharacterPresent(l, c) {
   950             return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
   951                     (l.httpRealm     && l.httpRealm.indexOf(c)     != -1) ||
   952                                         l.hostname.indexOf(c)      != -1  ||
   953                                         l.usernameField.indexOf(c) != -1  ||
   954                                         l.passwordField.indexOf(c) != -1);
   955         }
   957         // Nulls are invalid, as they don't round-trip well.
   958         // Mostly not a formatting problem, although ".\0" can be quirky.
   959         if (badCharacterPresent(aLogin, "\0"))
   960             throw "login values can't contain nulls";
   962         // In theory these nulls should just be rolled up into the encrypted
   963         // values, but nsISecretDecoderRing doesn't use nsStrings, so the
   964         // nulls cause truncation. Check for them here just to avoid
   965         // unexpected round-trip surprises.
   966         if (aLogin.username.indexOf("\0") != -1 ||
   967             aLogin.password.indexOf("\0") != -1)
   968             throw "login values can't contain nulls";
   970         // Newlines are invalid for any field stored as plaintext.
   971         if (badCharacterPresent(aLogin, "\r") ||
   972             badCharacterPresent(aLogin, "\n"))
   973             throw "login values can't contain newlines";
   975         // A line with just a "." can have special meaning.
   976         if (aLogin.usernameField == "." ||
   977             aLogin.formSubmitURL == ".")
   978             throw "login values can't be periods";
   980         // A hostname with "\ \(" won't roundtrip.
   981         // eg host="foo (", realm="bar" --> "foo ( (bar)"
   982         // vs host="foo", realm=" (bar" --> "foo ( (bar)"
   983         if (aLogin.hostname.indexOf(" (") != -1)
   984             throw "bad parens in hostname";
   985     },
   988     /*
   989      * _checkHostnameValue
   990      *
   991      * Legacy storage prohibited newlines and nulls in hostnames, so we'll keep
   992      * that standard here. Throws on illegal format.
   993      */
   994     _checkHostnameValue : function (hostname) {
   995         // File format prohibits certain values. Also, nulls
   996         // won't round-trip with getAllDisabledHosts().
   997         if (hostname == "." ||
   998             hostname.indexOf("\r") != -1 ||
   999             hostname.indexOf("\n") != -1 ||
  1000             hostname.indexOf("\0") != -1)
  1001             throw "Invalid hostname";
  1002     },
  1005     /*
  1006      * _isGuidUnique
  1008      * Checks to see if the specified GUID already exists.
  1009      */
  1010     _isGuidUnique : function (guid) {
  1011         let query = "SELECT COUNT(1) AS numLogins FROM moz_logins WHERE guid = :guid";
  1012         let params = { guid: guid };
  1014         let stmt, numLogins;
  1015         try {
  1016             stmt = this._dbCreateStatement(query, params);
  1017             stmt.executeStep();
  1018             numLogins = stmt.row.numLogins;
  1019         } catch (e) {
  1020             this.log("_isGuidUnique failed: " + e.name + " : " + e.message);
  1021         } finally {
  1022             if (stmt) {
  1023                 stmt.reset();
  1027         return (numLogins == 0);
  1028     },
  1031     /*
  1032      * _encryptLogin
  1034      * Returns the encrypted username, password, and encrypton type for the specified
  1035      * login. Can throw if the user cancels a master password entry.
  1036      */
  1037     _encryptLogin : function (login) {
  1038         let encUsername = this._crypto.encrypt(login.username);
  1039         let encPassword = this._crypto.encrypt(login.password);
  1040         let encType     = this._crypto.defaultEncType;
  1042         return [encUsername, encPassword, encType];
  1043     },
  1046     /*
  1047      * _decryptLogins
  1049      * Decrypts username and password fields in the provided array of
  1050      * logins.
  1052      * The entries specified by the array will be decrypted, if possible.
  1053      * An array of successfully decrypted logins will be returned. The return
  1054      * value should be given to external callers (since still-encrypted
  1055      * entries are useless), whereas internal callers generally don't want
  1056      * to lose unencrypted entries (eg, because the user clicked Cancel
  1057      * instead of entering their master password)
  1058      */
  1059     _decryptLogins : function (logins) {
  1060         let result = [];
  1062         for each (let login in logins) {
  1063             try {
  1064                 login.username = this._crypto.decrypt(login.username);
  1065                 login.password = this._crypto.decrypt(login.password);
  1066             } catch (e) {
  1067                 // If decryption failed (corrupt entry?), just skip it.
  1068                 // Rethrow other errors (like canceling entry of a master pw)
  1069                 if (e.result == Cr.NS_ERROR_FAILURE)
  1070                     continue;
  1071                 throw e;
  1073             result.push(login);
  1076         return result;
  1077     },
  1080     //**************************************************************************//
  1081     // Database Creation & Access
  1083     /*
  1084      * _dbCreateStatement
  1086      * Creates a statement, wraps it, and then does parameter replacement
  1087      * Returns the wrapped statement for execution.  Will use memoization
  1088      * so that statements can be reused.
  1089      */
  1090     _dbCreateStatement : function (query, params) {
  1091         let wrappedStmt = this._dbStmts[query];
  1092         // Memoize the statements
  1093         if (!wrappedStmt) {
  1094             this.log("Creating new statement for query: " + query);
  1095             wrappedStmt = this._dbConnection.createStatement(query);
  1096             this._dbStmts[query] = wrappedStmt;
  1098         // Replace parameters, must be done 1 at a time
  1099         if (params)
  1100             for (let i in params)
  1101                 wrappedStmt.params[i] = params[i];
  1102         return wrappedStmt;
  1103     },
  1106     /*
  1107      * _dbInit
  1109      * Attempts to initialize the database. This creates the file if it doesn't
  1110      * exist, performs any migrations, etc. Return if this is the first run.
  1111      */
  1112     _dbInit : function () {
  1113         this.log("Initializing Database");
  1114         let isFirstRun = false;
  1115         try {
  1116             this._dbConnection = this._storageService.openDatabase(this._signonsFile);
  1117             // Get the version of the schema in the file. It will be 0 if the
  1118             // database has not been created yet.
  1119             let version = this._dbConnection.schemaVersion;
  1120             if (version == 0) {
  1121                 this._dbCreate();
  1122                 isFirstRun = true;
  1123             } else if (version != DB_VERSION) {
  1124                 this._dbMigrate(version);
  1126         } catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
  1127             // Database is corrupted, so we backup the database, then throw
  1128             // causing initialization to fail and a new db to be created next use
  1129             this._dbCleanup(true);
  1130             throw e;
  1133         Services.obs.addObserver(this, "profile-before-change", false);
  1134         return isFirstRun;
  1135     },
  1137     observe: function (subject, topic, data) {
  1138         switch (topic) {
  1139             case "profile-before-change":
  1140                 Services.obs.removeObserver(this, "profile-before-change");
  1141                 this._dbClose();
  1142             break;
  1144     },
  1146     _dbCreate: function () {
  1147         this.log("Creating Database");
  1148         this._dbCreateSchema();
  1149         this._dbConnection.schemaVersion = DB_VERSION;
  1150     },
  1153     _dbCreateSchema : function () {
  1154         this._dbCreateTables();
  1155         this._dbCreateIndices();
  1156     },
  1159     _dbCreateTables : function () {
  1160         this.log("Creating Tables");
  1161         for (let name in this._dbSchema.tables)
  1162             this._dbConnection.createTable(name, this._dbSchema.tables[name]);
  1163     },
  1166     _dbCreateIndices : function () {
  1167         this.log("Creating Indices");
  1168         for (let name in this._dbSchema.indices) {
  1169             let index = this._dbSchema.indices[name];
  1170             let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
  1171                             "(" + index.columns.join(", ") + ")";
  1172             this._dbConnection.executeSimpleSQL(statement);
  1174     },
  1177     _dbMigrate : function (oldVersion) {
  1178         this.log("Attempting to migrate from version " + oldVersion);
  1180         if (oldVersion > DB_VERSION) {
  1181             this.log("Downgrading to version " + DB_VERSION);
  1182             // User's DB is newer. Sanity check that our expected columns are
  1183             // present, and if so mark the lower version and merrily continue
  1184             // on. If the columns are borked, something is wrong so blow away
  1185             // the DB and start from scratch. [Future incompatible upgrades
  1186             // should swtich to a different table or file.]
  1188             if (!this._dbAreExpectedColumnsPresent())
  1189                 throw Components.Exception("DB is missing expected columns",
  1190                                            Cr.NS_ERROR_FILE_CORRUPTED);
  1192             // Change the stored version to the current version. If the user
  1193             // runs the newer code again, it will see the lower version number
  1194             // and re-upgrade (to fixup any entries the old code added).
  1195             this._dbConnection.schemaVersion = DB_VERSION;
  1196             return;
  1199         // Upgrade to newer version...
  1201         let transaction = new Transaction(this._dbConnection);
  1203         try {
  1204             for (let v = oldVersion + 1; v <= DB_VERSION; v++) {
  1205                 this.log("Upgrading to version " + v + "...");
  1206                 let migrateFunction = "_dbMigrateToVersion" + v;
  1207                 this[migrateFunction]();
  1209         } catch (e) {
  1210             this.log("Migration failed: "  + e);
  1211             transaction.rollback();
  1212             throw e;
  1215         this._dbConnection.schemaVersion = DB_VERSION;
  1216         transaction.commit();
  1217         this.log("DB migration completed.");
  1218     },
  1221     /*
  1222      * _dbMigrateToVersion2
  1224      * Version 2 adds a GUID column. Existing logins are assigned a random GUID.
  1225      */
  1226     _dbMigrateToVersion2 : function () {
  1227         // Check to see if GUID column already exists, add if needed
  1228         let query;
  1229         if (!this._dbColumnExists("guid")) {
  1230             query = "ALTER TABLE moz_logins ADD COLUMN guid TEXT";
  1231             this._dbConnection.executeSimpleSQL(query);
  1233             query = "CREATE INDEX IF NOT EXISTS moz_logins_guid_index ON moz_logins (guid)";
  1234             this._dbConnection.executeSimpleSQL(query);
  1237         // Get a list of IDs for existing logins
  1238         let ids = [];
  1239         let query = "SELECT id FROM moz_logins WHERE guid isnull";
  1240         let stmt;
  1241         try {
  1242             stmt = this._dbCreateStatement(query);
  1243             while (stmt.executeStep())
  1244                 ids.push(stmt.row.id);
  1245         } catch (e) {
  1246             this.log("Failed getting IDs: " + e);
  1247             throw e;
  1248         } finally {
  1249             if (stmt) {
  1250                 stmt.reset();
  1254         // Generate a GUID for each login and update the DB.
  1255         query = "UPDATE moz_logins SET guid = :guid WHERE id = :id";
  1256         for each (let id in ids) {
  1257             let params = {
  1258                 id:   id,
  1259                 guid: this._uuidService.generateUUID().toString()
  1260             };
  1262             try {
  1263                 stmt = this._dbCreateStatement(query, params);
  1264                 stmt.execute();
  1265             } catch (e) {
  1266                 this.log("Failed setting GUID: " + e);
  1267                 throw e;
  1268             } finally {
  1269                 if (stmt) {
  1270                     stmt.reset();
  1274     },
  1277     /*
  1278      * _dbMigrateToVersion3
  1280      * Version 3 adds a encType column.
  1281      */
  1282     _dbMigrateToVersion3 : function () {
  1283         // Check to see if encType column already exists, add if needed
  1284         let query;
  1285         if (!this._dbColumnExists("encType")) {
  1286             query = "ALTER TABLE moz_logins ADD COLUMN encType INTEGER";
  1287             this._dbConnection.executeSimpleSQL(query);
  1289             query = "CREATE INDEX IF NOT EXISTS " +
  1290                         "moz_logins_encType_index ON moz_logins (encType)";
  1291             this._dbConnection.executeSimpleSQL(query);
  1294         // Get a list of existing logins
  1295         let logins = [];
  1296         let stmt;
  1297         query = "SELECT id, encryptedUsername, encryptedPassword " +
  1298                     "FROM moz_logins WHERE encType isnull";
  1299         try {
  1300             stmt = this._dbCreateStatement(query);
  1301             while (stmt.executeStep()) {
  1302                 let params = { id: stmt.row.id };
  1303                 // We will tag base64 logins correctly, but no longer support their use.
  1304                 if (stmt.row.encryptedUsername.charAt(0) == '~' ||
  1305                     stmt.row.encryptedPassword.charAt(0) == '~')
  1306                     params.encType = Ci.nsILoginManagerCrypto.ENCTYPE_BASE64;
  1307                 else
  1308                     params.encType = Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
  1309                 logins.push(params);
  1311         } catch (e) {
  1312             this.log("Failed getting logins: " + e);
  1313             throw e;
  1314         } finally {
  1315             if (stmt) {
  1316                 stmt.reset();
  1320         // Determine encryption type for each login and update the DB.
  1321         query = "UPDATE moz_logins SET encType = :encType WHERE id = :id";
  1322         for each (let params in logins) {
  1323             try {
  1324                 stmt = this._dbCreateStatement(query, params);
  1325                 stmt.execute();
  1326             } catch (e) {
  1327                 this.log("Failed setting encType: " + e);
  1328                 throw e;
  1329             } finally {
  1330                 if (stmt) {
  1331                     stmt.reset();
  1335     },
  1338     /*
  1339      * _dbMigrateToVersion4
  1341      * Version 4 adds timeCreated, timeLastUsed, timePasswordChanged,
  1342      * and timesUsed columns
  1343      */
  1344     _dbMigrateToVersion4 : function () {
  1345         let query;
  1346         // Add the new columns, if needed.
  1347         for each (let column in ["timeCreated", "timeLastUsed", "timePasswordChanged", "timesUsed"]) {
  1348             if (!this._dbColumnExists(column)) {
  1349                 query = "ALTER TABLE moz_logins ADD COLUMN " + column + " INTEGER";
  1350                 this._dbConnection.executeSimpleSQL(query);
  1354         // Get a list of IDs for existing logins.
  1355         let ids = [];
  1356         let stmt;
  1357         query = "SELECT id FROM moz_logins WHERE timeCreated isnull OR " +
  1358                 "timeLastUsed isnull OR timePasswordChanged isnull OR timesUsed isnull";
  1359         try {
  1360             stmt = this._dbCreateStatement(query);
  1361             while (stmt.executeStep())
  1362                 ids.push(stmt.row.id);
  1363         } catch (e) {
  1364             this.log("Failed getting IDs: " + e);
  1365             throw e;
  1366         } finally {
  1367             if (stmt) {
  1368                 stmt.reset();
  1372         // Initialize logins with current time.
  1373         query = "UPDATE moz_logins SET timeCreated = :initTime, timeLastUsed = :initTime, " +
  1374                 "timePasswordChanged = :initTime, timesUsed = 1 WHERE id = :id";
  1375         let params = {
  1376             id:       null,
  1377             initTime: Date.now()
  1378         };
  1379         for each (let id in ids) {
  1380             params.id = id;
  1381             try {
  1382                 stmt = this._dbCreateStatement(query, params);
  1383                 stmt.execute();
  1384             } catch (e) {
  1385                 this.log("Failed setting timestamps: " + e);
  1386                 throw e;
  1387             } finally {
  1388                 if (stmt) {
  1389                     stmt.reset();
  1393     },
  1396     /*
  1397      * _dbMigrateToVersion5
  1399      * Version 5 adds the moz_deleted_logins table
  1400      */
  1401     _dbMigrateToVersion5 : function () {
  1402         if (!this._dbConnection.tableExists("moz_deleted_logins")) {
  1403           this._dbConnection.createTable("moz_deleted_logins", this._dbSchema.tables.moz_deleted_logins);
  1405     },
  1407     /*
  1408      * _dbAreExpectedColumnsPresent
  1410      * Sanity check to ensure that the columns this version of the code expects
  1411      * are present in the DB we're using.
  1412      */
  1413     _dbAreExpectedColumnsPresent : function () {
  1414         let query = "SELECT " +
  1415                        "id, " +
  1416                        "hostname, " +
  1417                        "httpRealm, " +
  1418                        "formSubmitURL, " +
  1419                        "usernameField, " +
  1420                        "passwordField, " +
  1421                        "encryptedUsername, " +
  1422                        "encryptedPassword, " +
  1423                        "guid, " +
  1424                        "encType, " +
  1425                        "timeCreated, " +
  1426                        "timeLastUsed, " +
  1427                        "timePasswordChanged, " +
  1428                        "timesUsed " +
  1429                     "FROM moz_logins";
  1430         try {
  1431             let stmt = this._dbConnection.createStatement(query);
  1432             // (no need to execute statement, if it compiled we're good)
  1433             stmt.finalize();
  1434         } catch (e) {
  1435             return false;
  1438         query = "SELECT " +
  1439                    "id, " +
  1440                    "hostname " +
  1441                 "FROM moz_disabledHosts";
  1442         try {
  1443             let stmt = this._dbConnection.createStatement(query);
  1444             // (no need to execute statement, if it compiled we're good)
  1445             stmt.finalize();
  1446         } catch (e) {
  1447             return false;
  1450         this.log("verified that expected columns are present in DB.");
  1451         return true;
  1452     },
  1455     /*
  1456      * _dbColumnExists
  1458      * Checks to see if the named column already exists.
  1459      */
  1460     _dbColumnExists : function (columnName) {
  1461         let query = "SELECT " + columnName + " FROM moz_logins";
  1462         try {
  1463             let stmt = this._dbConnection.createStatement(query);
  1464             // (no need to execute statement, if it compiled we're good)
  1465             stmt.finalize();
  1466             return true;
  1467         } catch (e) {
  1468             return false;
  1470     },
  1472     _dbClose : function () {
  1473         this.log("Closing the DB connection.");
  1474         // Finalize all statements to free memory, avoid errors later
  1475         for each (let stmt in this._dbStmts) {
  1476             stmt.finalize();
  1478         this._dbStmts = {};
  1480         if (this._dbConnection !== null) {
  1481             try {
  1482                 this._dbConnection.close();
  1483             } catch (e) {
  1484                 Components.utils.reportError(e);
  1487         this._dbConnection = null;
  1488     },
  1490     /*
  1491      * _dbCleanup
  1493      * Called when database creation fails. Finalizes database statements,
  1494      * closes the database connection, deletes the database file.
  1495      */
  1496     _dbCleanup : function (backup) {
  1497         this.log("Cleaning up DB file - close & remove & backup=" + backup)
  1499         // Create backup file
  1500         if (backup) {
  1501             let backupFile = this._signonsFile.leafName + ".corrupt";
  1502             this._storageService.backupDatabaseFile(this._signonsFile, backupFile);
  1505         this._dbClose();
  1506         this._signonsFile.remove(false);
  1509 }; // end of nsLoginManagerStorage_mozStorage implementation
  1511 let component = [LoginManagerStorage_mozStorage];
  1512 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);

mercurial