1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
14 * The Original Code is mozilla.org code.
16 * The Initial Developer of the Original Code is Mozilla Corporation.
17 * Portions created by the Initial Developer are Copyright (C) 2007
18 * the Initial Developer. All Rights Reserved.
21 * Paul O'Shannessy <poshannessy@mozilla.com> (primary author)
22 * Mrinal Kant <mrinal.kant@gmail.com> (original sqlite related changes)
23 * Justin Dolske <dolske@mozilla.com> (encryption/decryption functions are
24 * a lift from Justin's storage-Legacy.js)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 const Cc
= Components
.classes
;
42 const Ci
= Components
.interfaces
;
44 const DB_VERSION
= 1; // The database schema version
46 Components
.utils
.import("resource://gre/modules/XPCOMUtils.jsm");
48 function LoginManagerStorage_mozStorage() { };
50 LoginManagerStorage_mozStorage
.prototype = {
52 classDescription
: "LoginManagerStorage_mozStorage",
53 contractID
: "@mozilla.org/login-manager/storage/mozStorage;1",
54 classID
: Components
.ID("{8c2023b9-175c-477e-9761-44ae7b549756}"),
55 QueryInterface
: XPCOMUtils
.generateQI([Ci
.nsILoginManagerStorage
]),
57 __logService
: null, // Console logging service, used for debugging.
59 if (!this.__logService
)
60 this.__logService
= Cc
["@mozilla.org/consoleservice;1"].
61 getService(Ci
.nsIConsoleService
);
62 return this.__logService
;
65 __decoderRing
: null, // nsSecretDecoderRing service
67 if (!this.__decoderRing
)
68 this.__decoderRing
= Cc
["@mozilla.org/security/sdr;1"].
69 getService(Ci
.nsISecretDecoderRing
);
70 return this.__decoderRing
;
73 __utfConverter
: null, // UCS2 <--> UTF8 string conversion
75 if (!this.__utfConverter
) {
76 this.__utfConverter
= Cc
["@mozilla.org/intl/scriptableunicodeconverter"].
77 createInstance(Ci
.nsIScriptableUnicodeConverter
);
78 this.__utfConverter
.charset
= "UTF-8";
80 return this.__utfConverter
;
83 __profileDir
: null, // nsIFile for the user's profile dir
85 if (!this.__profileDir
)
86 this.__profileDir
= Cc
["@mozilla.org/file/directory_service;1"].
87 getService(Ci
.nsIProperties
).
88 get("ProfD", Ci
.nsIFile
);
89 return this.__profileDir
;
92 __storageService
: null, // Storage service for using mozStorage
93 get _storageService() {
94 if (!this.__storageService
)
95 this.__storageService
= Cc
["@mozilla.org/storage/service;1"].
96 getService(Ci
.mozIStorageService
);
97 return this.__storageService
;
101 // The current database schema
104 moz_logins
: "id INTEGER PRIMARY KEY," +
105 "hostname TEXT NOT NULL," +
107 "formSubmitURL TEXT," +
108 "usernameField TEXT NOT NULL," +
109 "passwordField TEXT NOT NULL," +
110 "encryptedUsername TEXT NOT NULL," +
111 "encryptedPassword TEXT NOT NULL",
113 moz_disabledHosts
: "id INTEGER PRIMARY KEY," +
114 "hostname TEXT UNIQUE ON CONFLICT REPLACE",
117 moz_logins_hostname_index
: {
119 columns
: ["hostname"]
121 moz_logins_hostname_formSubmitURL_index
: {
123 columns
: ["hostname", "formSubmitURL"]
125 moz_logins_hostname_httpRealm_index
: {
127 columns
: ["hostname", "httpRealm"]
131 _dbConnection
: null, // The database connection
132 _dbStmts
: null, // Database statements for memoization
134 _prefBranch
: null, // Preferences service
135 _signonsFile
: null, // nsIFile for "signons.sqlite"
136 _importFile
: null, // nsIFile for import from legacy
137 _debug
: false, // mirrors signon.debug
138 _initialized
: false, // have we initialized properly (for import failure)
139 _initializing
: false, // prevent concurrent initializations
145 * Internal function for logging debug messages to the Error Console.
147 log : function (message
) {
150 dump("PwMgr mozStorage: " + message
+ "\n");
151 this._logService
.logStringMessage("PwMgr mozStorage: " + message
);
158 * Initialize the component, but override the default filename locations.
159 * This is primarily used to the unit tests and profile migration.
160 * aImportFile is legacy storage file, aDBFile is a sqlite/mozStorage file.
162 initWithFile : function(aImportFile
, aDBFile
) {
164 this._importFile
= aImportFile
;
166 this._signonsFile
= aDBFile
;
175 * Initialize this storage component; import from legacy files, if
176 * necessary. Most of the work is done in _deferredInit.
181 // Connect to the correct preferences branch.
182 this._prefBranch
= Cc
["@mozilla.org/preferences-service;1"].
183 getService(Ci
.nsIPrefService
);
184 this._prefBranch
= this._prefBranch
.getBranch("signon.");
185 this._prefBranch
.QueryInterface(Ci
.nsIPrefBranch2
);
187 this._debug
= this._prefBranch
.getBoolPref("debug");
189 // Check to see if the internal PKCS#11 token has been initialized.
190 // If not, set a blank password.
191 let tokenDB
= Cc
["@mozilla.org/security/pk11tokendb;1"].
192 getService(Ci
.nsIPK11TokenDB
);
194 let token
= tokenDB
.getInternalKeyToken();
195 if (token
.needsUserInit
) {
196 this.log("Initializing key3.db with default blank password.");
197 token
.initPassword("");
200 // Most of the real work is done in _deferredInit, which will get
201 // called upon first use of storage
208 * Tries to initialize the module. Adds protection layer so initialization
209 * from different places will not conflict. Also, ensures that we will try
210 * to import again if import failed, specifically on cancellation of master
213 _deferredInit : function () {
215 // Check that we are not already in an initializing state
216 if (this._initializing
)
217 throw "Already initializing";
219 // Mark that we are initializing
220 this._initializing
= true;
222 // If initWithFile is calling us, _signonsFile may already be set.
223 if (!this._signonsFile
) {
224 // Initialize signons.sqlite
225 this._signonsFile
= this._profileDir
.clone();
226 this._signonsFile
.append("signons.sqlite");
228 this.log("Opening database at " + this._signonsFile
.path
);
230 // Initialize the database (create, migrate as necessary)
231 isFirstRun
= this._dbInit();
233 // On first run we want to import the default legacy storage files.
234 // Otherwise if passed a file, import from that.
235 if (isFirstRun
&& !this._importFile
)
236 this._importLegacySignons();
237 else if (this._importFile
)
238 this._importLegacySignons(this._importFile
);
240 this._initialized
= true;
242 this.log("Initialization failed");
243 // If the import fails on first run, we want to delete the db
244 if (isFirstRun
&& e
== "Import failed")
245 this._dbCleanup(false);
246 throw "Initialization failed";
248 this._initializing
= false;
254 * _checkInitializationState
256 * This snippet is needed in all the public methods. It's essentially only
257 * needed when we try to import a legacy file and the user refuses to enter
258 * the master password. We don't want to start saving new info if there is
259 * old info to import. Throws if attempt to initialize fails.
261 _checkInitializationState : function () {
262 if (!this._initialized
) {
263 this.log("Trying to initialize.");
264 this._deferredInit();
273 addLogin : function (login
) {
274 this._checkInitializationState();
275 this._addLogin(login
);
282 * Private function wrapping core addLogin functionality.
284 _addLogin : function (login
) {
285 // Throws if there are bogus values.
286 this._checkLoginValues(login
);
288 let userCanceled
, encUsername
, encPassword
;
289 // Get the encrypted value of the username and password.
290 [encUsername
, userCanceled
] = this._encrypt(login
.username
);
292 throw "User canceled master password entry, login not added.";
294 [encPassword
, userCanceled
] = this._encrypt(login
.password
);
295 // Probably can't hit this case, but for completeness...
297 throw "User canceled master password entry, login not added.";
300 "INSERT INTO moz_logins " +
301 "(hostname, httpRealm, formSubmitURL, usernameField, " +
302 "passwordField, encryptedUsername, encryptedPassword) " +
303 "VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
304 ":passwordField, :encryptedUsername, :encryptedPassword)";
307 hostname
: login
.hostname
,
308 httpRealm
: login
.httpRealm
,
309 formSubmitURL
: login
.formSubmitURL
,
310 usernameField
: login
.usernameField
,
311 passwordField
: login
.passwordField
,
312 encryptedUsername
: encUsername
,
313 encryptedPassword
: encPassword
318 stmt
= this._dbCreateStatement(query
, params
);
321 this.log("_addLogin failed: " + e
.name
+ " : " + e
.message
);
322 throw "Couldn't write to database, login not added.";
333 removeLogin : function (login
) {
334 this._checkInitializationState();
337 this._searchLogins(login
.hostname
, login
.formSubmitURL
, login
.httpRealm
, false);
340 // The specified login isn't encrypted, so we need to ensure
341 // the logins we're comparing with are decrypted. We decrypt one entry
342 // at a time, lest _decryptLogins return fewer entries and screw up
343 // indices between the two.
344 for (let i
= 0; i
< logins
.length
; i
++) {
345 let [[decryptedLogin
], userCanceled
] =
346 this._decryptLogins([logins
[i
]]);
349 throw "User canceled master password entry, login not removed.";
351 if (!decryptedLogin
|| !decryptedLogin
.equals(login
))
354 // We've found a match, set id and break
360 throw "No matching logins";
362 // Execute the statement & remove from DB
363 let query
= "DELETE FROM moz_logins WHERE id = :id";
364 let params
= { id
: idToDelete
};
367 stmt
= this._dbCreateStatement(query
, params
);
370 this.log("_removeLogin failed: " + e
.name
+ " : " + e
.message
);
371 throw "Couldn't write to database, login not removed.";
382 modifyLogin : function (oldLogin
, newLogin
) {
383 this._checkInitializationState();
385 // Throws if there are bogus values.
386 this._checkLoginValues(newLogin
);
388 // Begin a transaction to wrap remove and add
389 // This will throw if there is a transaction in progress
390 this._dbConnection
.beginTransaction();
392 // Wrap add/remove in try-catch so we can rollback on error
394 this.removeLogin(oldLogin
);
395 this.addLogin(newLogin
);
397 this._dbConnection
.rollbackTransaction();
401 // Commit the transaction
402 this._dbConnection
.commitTransaction();
409 * Returns an array of nsAccountInfo.
411 getAllLogins : function (count
) {
412 this._checkInitializationState();
415 let [logins
, ids
] = this._queryLogins([], {}, false);
417 // decrypt entries for caller.
418 [logins
, userCanceled
] = this._decryptLogins(logins
);
421 throw "User canceled Master Password entry";
423 this.log("_getAllLogins: returning " + logins
.length
+ " logins.");
424 count
.value
= logins
.length
; // needed for XPCOM
432 * Removes all logins from storage.
434 removeAllLogins : function () {
435 this._checkInitializationState();
437 this.log("Removing all logins");
438 // Delete any old, unused files.
439 this._removeOldSignonsFiles();
441 // Disabled hosts kept, as one presumably doesn't want to erase those.
442 let query
= "DELETE FROM moz_logins";
445 stmt
= this._dbCreateStatement(query
);
448 this.log("_removeAllLogins failed: " + e
.name
+ " : " + e
.message
);
449 throw "Couldn't write to database";
457 * getAllDisabledHosts
460 getAllDisabledHosts : function (count
) {
461 this._checkInitializationState();
463 let disabledHosts
= this._queryDisabledHosts(null);
465 this.log("_getAllDisabledHosts: returning " + disabledHosts
.length
+ " disabled hosts.");
466 count
.value
= disabledHosts
.length
; // needed for XPCOM
467 return disabledHosts
;
472 * getLoginSavingEnabled
475 getLoginSavingEnabled : function (hostname
) {
476 this._checkInitializationState();
478 this.log("Getting login saving is enabled for " + hostname
);
479 return this._queryDisabledHosts(hostname
).length
== 0
484 * setLoginSavingEnabled
487 setLoginSavingEnabled : function (hostname
, enabled
) {
488 this._checkInitializationState();
489 this._setLoginSavingEnabled(hostname
, enabled
);
494 * _setLoginSavingEnabled
496 * Private function wrapping core setLoginSavingEnabled functionality.
498 _setLoginSavingEnabled : function (hostname
, enabled
) {
499 // Throws if there are bogus values.
500 this._checkHostnameValue(hostname
);
502 this.log("Setting login saving enabled for " + hostname
+ " to " + enabled
);
505 query
= "DELETE FROM moz_disabledHosts " +
506 "WHERE hostname = :hostname";
508 query
= "INSERT INTO moz_disabledHosts " +
509 "(hostname) VALUES (:hostname)";
510 let params
= { hostname
: hostname
};
514 stmt
= this._dbCreateStatement(query
, params
);
517 this.log("_setLoginSavingEnabled failed: " + e
.name
+ " : " + e
.message
);
518 throw "Couldn't write to database"
529 findLogins : function (count
, hostname
, formSubmitURL
, httpRealm
) {
530 this._checkInitializationState();
533 let [logins
, ids
] = this._searchLogins(hostname
, formSubmitURL
, httpRealm
, false);
535 // Decrypt entries found for the caller.
536 [logins
, userCanceled
] = this._decryptLogins(logins
);
538 // We want to throw in this case, so that the Login Manager
539 // knows to stop processing forms on the page so the user isn't
540 // prompted multiple times.
542 throw "User canceled Master Password entry";
544 this.log("_findLogins: returning " + logins
.length
+ " logins");
545 count
.value
= logins
.length
; // needed for XPCOM
554 countLogins : function (hostname
, formSubmitURL
, httpRealm
) {
555 this._checkInitializationState();
557 // _searchLogins is returning just ids here
558 let [logins
, ids
] = this._searchLogins(hostname
, formSubmitURL
, httpRealm
, true);
559 this.log("_countLogins: counted logins: " + ids
.length
);
567 * Returns array of [logins, ids]. If countOnly is true, call to
568 * _queryLogins will not instantiate logins
570 _searchLogins : function (hostname
, formSubmitURL
, httpRealm
, countOnly
) {
573 // Do checks for null and empty strings, adjust conditions and params
574 if (hostname
== null) {
575 conditions
.push("hostname isnull");
576 } else if (hostname
!= '') {
577 conditions
.push("hostname = :hostname");
578 params
["hostname"] = hostname
;
580 if (formSubmitURL
== null) {
581 conditions
.push("formSubmitURL isnull");
582 } else if (formSubmitURL
!= '') {
583 conditions
.push("formSubmitURL = :formSubmitURL OR formSubmitURL = ''");
584 params
["formSubmitURL"] = formSubmitURL
;
586 if (httpRealm
== null) {
587 conditions
.push("httpRealm isnull");
588 } else if (httpRealm
!= '') {
589 conditions
.push("httpRealm = :httpRealm");
590 params
["httpRealm"] = httpRealm
;
593 return this._queryLogins(conditions
, params
, countOnly
);
600 * Returns [logins, ids] for logins that match the conditions and params,
601 * where logins is an array of encrypted nsLoginInfo and ids is an array of
602 * associated ids in the database. Conditions are joined with AND. If
603 * countOnly is true, we will not instantiate the login objects, so the
604 * logins array returned will be empty. This saves memory and processing
605 * time when we don't need the logins.
607 _queryLogins : function (conditions
, params
, countOnly
) {
608 let logins
= [], ids
= [];
610 let query
= "SELECT * FROM moz_logins";
611 if (conditions
.length
) {
612 conditions
= conditions
.map(function(c
) "(" + c
+ ")");
613 query
+= " WHERE " + conditions
.join(" AND ");
618 stmt
= this._dbCreateStatement(query
, params
);
619 // We can't execute as usual here, since we're iterating over rows
620 while (stmt
.step()) {
621 ids
.push(stmt
.row
.id
);
624 // Create the new nsLoginInfo object, push to array
625 let login
= Cc
["@mozilla.org/login-manager/loginInfo;1"].
626 createInstance(Ci
.nsILoginInfo
);
627 login
.init(stmt
.row
.hostname
, stmt
.row
.formSubmitURL
,
628 stmt
.row
.httpRealm
, stmt
.row
.encryptedUsername
,
629 stmt
.row
.encryptedPassword
, stmt
.row
.usernameField
,
630 stmt
.row
.passwordField
);
634 this.log("_queryLogins failed: " + e
.name
+ " : " + e
.message
);
639 return [logins
, ids
];
644 * _queryDisabledHosts
646 * Returns an array of hostnames from the database according to the
647 * criteria given in the argument. If the argument hostname is null, the
648 * result array contains all hostnames
650 _queryDisabledHosts : function (hostname
) {
651 let disabledHosts
= [];
653 let query
= "SELECT hostname FROM moz_disabledHosts";
656 query
+= " WHERE hostname = :hostname";
657 params
= { hostname
: hostname
};
662 stmt
= this._dbCreateStatement(query
, params
);
664 disabledHosts
.push(stmt
.row
.hostname
);
666 this.log("_queryDisabledHosts failed: " + e
.name
+ " : " + e
.message
);
671 return disabledHosts
;
678 * Due to the way the signons2.txt file is formatted, we need to make
679 * sure certain field values or characters do not cause the file to
680 * be parse incorrectly. Reject logins that we can't store correctly.
682 _checkLoginValues : function (aLogin
) {
683 function badCharacterPresent(l
, c
) {
684 return ((l
.formSubmitURL
&& l
.formSubmitURL
.indexOf(c
) != -1) ||
685 (l
.httpRealm
&& l
.httpRealm
.indexOf(c
) != -1) ||
686 l
.hostname
.indexOf(c
) != -1 ||
687 l
.usernameField
.indexOf(c
) != -1 ||
688 l
.passwordField
.indexOf(c
) != -1);
691 // Nulls are invalid, as they don't round-trip well.
692 // Mostly not a formatting problem, although ".\0" can be quirky.
693 if (badCharacterPresent(aLogin
, "\0"))
694 throw "login values can't contain nulls";
696 // In theory these nulls should just be rolled up into the encrypted
697 // values, but nsISecretDecoderRing doesn't use nsStrings, so the
698 // nulls cause truncation. Check for them here just to avoid
699 // unexpected round-trip surprises.
700 if (aLogin
.username
.indexOf("\0") != -1 ||
701 aLogin
.password
.indexOf("\0") != -1)
702 throw "login values can't contain nulls";
704 // Newlines are invalid for any field stored as plaintext.
705 if (badCharacterPresent(aLogin
, "\r") ||
706 badCharacterPresent(aLogin
, "\n"))
707 throw "login values can't contain newlines";
709 // A line with just a "." can have special meaning.
710 if (aLogin
.usernameField
== "." ||
711 aLogin
.formSubmitURL
== ".")
712 throw "login values can't be periods";
714 // A hostname with "\ \(" won't roundtrip.
715 // eg host="foo (", realm="bar" --> "foo ( (bar)"
716 // vs host="foo", realm=" (bar" --> "foo ( (bar)"
717 if (aLogin
.hostname
.indexOf(" (") != -1)
718 throw "bad parens in hostname";
723 * _checkHostnameValue
725 * Legacy storage prohibited newlines and nulls in hostnames, so we'll keep
726 * that standard here. Throws on illegal format.
728 _checkHostnameValue : function (hostname
) {
729 // File format prohibits certain values. Also, nulls
730 // won't round-trip with getAllDisabledHosts().
731 if (hostname
== "." ||
732 hostname
.indexOf("\r") != -1 ||
733 hostname
.indexOf("\n") != -1 ||
734 hostname
.indexOf("\0") != -1)
735 throw "Invalid hostname";
740 * _importLegacySignons
742 * Imports a file that uses Legacy storage. Will use importFile if provided
743 * else it will attempt to initialize the Legacy storage normally.
746 _importLegacySignons : function (importFile
) {
747 this.log("Importing " + (importFile
? importFile
.path
: "legacy storage"));
749 let legacy
= Cc
["@mozilla.org/login-manager/storage/legacy;1"].
750 createInstance(Ci
.nsILoginManagerStorage
);
752 // Import all logins and disabled hosts
755 legacy
.initWithFile(importFile
, null);
759 // Import logins and disabledHosts
760 let logins
= legacy
.getAllLogins({});
761 for each (let login
in logins
)
762 this._addLogin(login
);
763 let disabledHosts
= legacy
.getAllDisabledHosts({});
764 for each (let hostname
in disabledHosts
)
765 this._setLoginSavingEnabled(hostname
, false);
767 this.log("_importLegacySignons failed: " + e
.name
+ " : " + e
.message
);
768 throw "Import failed";
774 * _removeOldSignonsFiles
776 * Deletes any storage files that we're not using any more.
778 _removeOldSignonsFiles : function () {
779 // We've used a number of prefs over time due to compatibility issues.
780 // We want to delete all files referenced in prefs, which are only for
781 // importing and clearing logins from storage-Legacy.js.
782 filenamePrefs
= ["SignonFileName3", "SignonFileName2", "SignonFileName"];
783 for each (let prefname
in filenamePrefs
) {
784 let filename
= this._prefBranch
.getCharPref(prefname
);
785 let file
= this._profileDir
.clone();
786 file
.append(filename
);
789 this.log("Deleting old " + filename
+ " (" + prefname
+ ")");
793 this.log("NOTICE: Couldn't delete " + filename
+ ": " + e
);
803 * Decrypts username and password fields in the provided array of
806 * The entries specified by the array will be decrypted, if possible.
807 * An array of successfully decrypted logins will be returned. The return
808 * value should be given to external callers (since still-encrypted
809 * entries are useless), whereas internal callers generally don't want
810 * to lose unencrypted entries (eg, because the user clicked Cancel
811 * instead of entering their master password)
813 _decryptLogins : function (logins
) {
814 let result
= [], userCanceled
= false;
816 for each (let login
in logins
) {
817 let decryptedUsername
, decryptedPassword
;
819 [decryptedUsername
, userCanceled
] = this._decrypt(login
.username
);
824 [decryptedPassword
, userCanceled
] = this._decrypt(login
.password
);
826 // Probably can't hit this case, but for completeness...
830 // If decryption failed (corrupt entry?) skip it.
831 // Note that we allow password-only logins, so username con be "".
832 if (decryptedUsername
== null || !decryptedPassword
)
835 login
.username
= decryptedUsername
;
836 login
.password
= decryptedPassword
;
841 return [result
, userCanceled
];
848 * Encrypts the specified string, using the SecretDecoderRing.
850 * Returns [cipherText, userCanceled] where:
851 * cipherText -- the encrypted string, or null if it failed.
852 * userCanceled -- if the encryption failed, this is true if the
853 * user selected Cancel when prompted to enter their
854 * Master Password. The caller should bail out, and not
855 * not request that more things be encrypted (which
856 * results in prompting the user for a Master Password
859 _encrypt : function (plainText
) {
860 let cipherText
= null, userCanceled
= false;
863 let plainOctet
= this._utfConverter
.ConvertFromUnicode(plainText
);
864 plainOctet
+= this._utfConverter
.Finish();
865 cipherText
= this._decoderRing
.encryptString(plainOctet
);
867 this.log("Failed to encrypt string. (" + e
.name
+ ")");
868 // If the user clicks Cancel, we get NS_ERROR_FAILURE.
869 // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
870 if (e
.result
== Components
.results
.NS_ERROR_FAILURE
)
874 return [cipherText
, userCanceled
];
881 * Decrypts the specified string, using the SecretDecoderRing.
883 * Returns [plainText, userCanceled] where:
884 * plainText -- the decrypted string, or null if it failed.
885 * userCanceled -- if the decryption failed, this is true if the
886 * user selected Cancel when prompted to enter their
887 * Master Password. The caller should bail out, and not
888 * not request that more things be decrypted (which
889 * results in prompting the user for a Master Password
892 _decrypt : function (cipherText
) {
893 let plainText
= null, userCanceled
= false;
896 let plainOctet
= this._decoderRing
.decryptString(cipherText
);
897 plainText
= this._utfConverter
.ConvertToUnicode(plainOctet
);
899 this.log("Failed to decrypt string: " + cipherText
+
900 " (" + e
.name
+ ")");
902 // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
903 // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
904 // Wrong passwords are handled by the decoderRing reprompting;
905 // we get no notification.
906 if (e
.result
== Components
.results
.NS_ERROR_NOT_AVAILABLE
)
910 return [plainText
, userCanceled
];
914 //**************************************************************************//
915 // Database Creation & Access
916 // Hijacked from /toolkit/components/contentprefs/src/nsContentPrefService.js
917 // and modified to fit here. Look there for migration samples.
922 * Creates a statement, wraps it, and then does parameter replacement
923 * Returns the wrapped statement for execution. Will use memoization
924 * so that statements can be reused.
926 _dbCreateStatement : function (query
, params
) {
927 // Memoize the statements
928 if (!this._dbStmts
[query
]) {
929 this.log("Creating new statement for query: " + query
);
930 let stmt
= this._dbConnection
.createStatement(query
);
932 let wrappedStmt
= Cc
["@mozilla.org/storage/statement-wrapper;1"].
933 createInstance(Ci
.mozIStorageStatementWrapper
);
934 wrappedStmt
.initialize(stmt
);
935 this._dbStmts
[query
] = wrappedStmt
;
937 // Replace parameters, must be done 1 at a time
939 for (let i
in params
)
940 this._dbStmts
[query
].params
[i
] = params
[i
];
941 return this._dbStmts
[query
];
948 * Attempts to initialize the database. This creates the file if it doesn't
949 * exist, performs any migrations, etc. When database is first created, we
950 * attempt to import legacy signons. Return if this is the first run.
952 _dbInit : function () {
953 this.log("Initializing Database");
954 let isFirstRun
= false;
956 this._dbConnection
= this._storageService
.openDatabase(this._signonsFile
);
957 // schemaVersion will be 0 if the database has not been created yet
958 if (this._dbConnection
.schemaVersion
== 0) {
962 // Get the version of the schema in the file.
963 let version
= this._dbConnection
.schemaVersion
;
965 // Try to migrate the schema in the database to the current schema used by
967 if (version
!= DB_VERSION
) {
969 this._dbMigrate(version
, DB_VERSION
);
972 this.log("Migration Failed");
978 // Database is corrupted, so we backup the database, then throw
979 // causing initialization to fail and a new db to be created next use
980 if (e
.result
== Components
.results
.NS_ERROR_FILE_CORRUPTED
)
981 this._dbCleanup(true);
983 // TODO handle migration failures
989 _dbCreate: function () {
990 this.log("Creating Database");
991 this._dbCreateSchema();
992 this._dbConnection
.schemaVersion
= DB_VERSION
;
996 _dbCreateSchema : function () {
997 this._dbCreateTables();
998 this._dbCreateIndices();
1002 _dbCreateTables : function () {
1003 this.log("Creating Tables");
1004 for (let name
in this._dbSchema
.tables
)
1005 this._dbConnection
.createTable(name
, this._dbSchema
.tables
[name
]);
1009 _dbCreateIndices : function () {
1010 this.log("Creating Indices");
1011 for (let name
in this._dbSchema
.indices
) {
1012 let index
= this._dbSchema
.indices
[name
];
1013 let statement
= "CREATE INDEX IF NOT EXISTS " + name
+ " ON " + index
.table
+
1014 "(" + index
.columns
.join(", ") + ")";
1015 this._dbConnection
.executeSimpleSQL(statement
);
1020 _dbMigrate : function (oldVersion
, newVersion
) {
1021 this.log("Attempting to migrate from v" + oldVersion
+ "to v" + newVersion
);
1022 if (this["_dbMigrate" + oldVersion
+ "To" + newVersion
]) {
1023 this._dbConnection
.beginTransaction();
1025 this["_dbMigrate" + oldVersion
+ "To" + newVersion
]();
1026 this._dbConnection
.schemaVersion
= newVersion
;
1027 this._dbConnection
.commitTransaction();
1030 this._dbConnection
.rollbackTransaction();
1035 throw("no migrator function from version " + oldVersion
+
1036 " to version " + newVersion
);
1044 * Called when database creation fails. Finalizes database statements,
1045 * closes the database connection, deletes the database file.
1047 _dbCleanup : function (backup
) {
1048 this.log("Cleaning up DB file - close & remove & backup=" + backup
)
1050 // Create backup file
1052 let backupFile
= this._signonsFile
.leafName
+ ".corrupt";
1053 this._storageService
.backupDatabaseFile(this._signonsFile
, backupFile
);
1056 // Finalize all statements to free memory, avoid errors later
1057 for (let i
= 0; i
< this._dbStmts
.length
; i
++)
1058 this._dbStmts
[i
].statement
.finalize();
1061 // Close the connection, ignore 'already closed' error
1062 try { this._dbConnection
.close() } catch(e
) {}
1063 this._signonsFile
.remove(false);
1066 }; // end of nsLoginManagerStorage_mozStorage implementation
1068 let component
= [LoginManagerStorage_mozStorage
];
1069 function NSGetModule(compMgr
, fileSpec
) {
1070 return XPCOMUtils
.generateModule(component
);