1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 // Test the various ways opening a cookie database can fail in an asynchronous
5 // (i.e. after synchronous initialization) manner, and that the database is
6 // renamed and recreated under each circumstance. These circumstances are, in no
9 // 1) A write operation failing after the database has been read in.
10 // 2) Asynchronous read failure due to a corrupt database.
11 // 3) Synchronous read failure due to a corrupt database, when reading:
12 // a) a single base domain;
13 // b) the entire database.
14 // 4) Asynchronous read failure, followed by another failure during INSERT but
15 // before the database closes for rebuilding. (The additional error should be
17 // 5) Asynchronous read failure, followed by an INSERT failure during rebuild.
18 // This should result in an abort of the database rebuild; the partially-
19 // built database should be moved to 'cookies.sqlite.bak-rebuild'.
26 add_task(async () => {
28 profile = do_get_profile();
29 Services.prefs.setBoolPref("dom.security.https_first", false);
32 Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
33 Services.prefs.setBoolPref(
34 "network.cookieJarSettings.unblocked_for_testing",
38 // Bug 1617611 - Fix all the tests broken by "cookies SameSite=Lax by default"
39 Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
42 const hosts = ["foo.com", "hither.com", "haithur.com", "bar.com"];
43 for (let i = 0; i < 3000; ++i) {
44 hosts.push(i + ".com");
46 CookieXPCShellUtils.createServer({ hosts });
48 // Get the cookie file and the backup file.
49 Assert.ok(!do_get_cookie_file(profile).exists());
50 Assert.ok(!do_get_backup_file().exists());
52 // Create a cookie object for testing.
53 let now = Date.now() * 1000;
54 let futureExpiry = Math.round(now / 1e6 + 1000);
73 Services.prefs.clearUserPref("dom.security.https_first");
74 Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
77 function do_get_backup_file() {
78 let file = profile.clone();
79 file.append("cookies.sqlite.bak");
83 function do_get_rebuild_backup_file() {
84 let file = profile.clone();
85 file.append("cookies.sqlite.bak-rebuild");
89 function do_corrupt_db(file) {
90 // Sanity check: the database size should be larger than 320k, since we've
91 // written about 460k of data. If it's not, let's make it obvious now.
92 let size = file.fileSize;
93 Assert.ok(size > 320e3);
95 // Corrupt the database by writing bad data to the end of the file. We
96 // assume that the important metadata -- table structure etc -- is stored
97 // elsewhere, and that doing this will not cause synchronous failure when
98 // initializing the database connection. This is totally empirical --
99 // overwriting between 1k and 100k of live data seems to work. (Note that the
100 // database file will be larger than the actual content requires, since the
101 // cookie service uses a large growth increment. So we calculate the offset
102 // based on the expected size of the content, not just the file size.)
103 let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
104 Ci.nsIFileOutputStream
106 ostream.init(file, 2, -1, 0);
107 let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
108 let n = size - 320e3 + 20e3;
109 sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
110 for (let i = 0; i < n; ++i) {
111 ostream.write("a", 1);
116 Assert.equal(file.clone().fileSize, size);
120 async function run_test_1() {
121 // Load the profile and populate it.
122 await CookieXPCShellUtils.setCookieToDocument(
124 "oh=hai; max-age=1000"
127 // Close the profile.
128 await promise_close_profile();
130 // Open a database connection now, before we load the profile and begin
131 // asynchronous write operations.
132 let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 12);
133 Assert.equal(do_count_cookies_in_db(db.db), 1);
135 // Load the profile, and wait for async read completion...
136 await promise_load_profile();
139 db.insertCookie(cookie);
142 // Attempt to insert a cookie with the same (name, host, path) triplet.
143 Services.cookies.add(
153 Ci.nsICookie.SAMESITE_NONE,
154 Ci.nsICookie.SCHEME_HTTPS
157 // Check that the cookie service accepted the new cookie.
158 Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
160 let isRebuildingDone = false;
161 let rebuildingObserve = function (subject, topic, data) {
162 isRebuildingDone = true;
163 Services.obs.removeObserver(rebuildingObserve, "cookie-db-rebuilding");
165 Services.obs.addObserver(rebuildingObserve, "cookie-db-rebuilding");
167 // Crash test: we're going to rebuild the cookie database. Close all the db
168 // connections in the main thread and initialize a new database file in the
169 // cookie thread. Trigger some access of cookies to ensure we won't crash in
171 for (let i = 0; i < 10; ++i) {
172 Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
173 await new Promise(resolve => executeSoon(resolve));
176 // Wait for the cookie service to rename the old database and rebuild if not yet.
177 if (!isRebuildingDone) {
178 Services.obs.removeObserver(rebuildingObserve, "cookie-db-rebuilding");
179 await new _promise_observer("cookie-db-rebuilding");
182 await new Promise(resolve => executeSoon(resolve));
184 // At this point, the cookies should still be in memory.
185 Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 1);
186 Assert.equal(Services.cookies.countCookiesFromHost(cookie.host), 1);
187 Assert.equal(do_count_cookies(), 2);
189 // Close the profile.
190 await promise_close_profile();
192 // Check that the original database was renamed, and that it contains the
194 Assert.ok(do_get_backup_file().exists());
195 let backupdb = Services.storage.openDatabase(do_get_backup_file());
196 Assert.equal(do_count_cookies_in_db(backupdb, "foo.com"), 1);
199 // Load the profile, and check that it contains the new cookie.
202 Assert.equal(Services.cookies.countCookiesFromHost("foo.com"), 1);
203 let cookies = Services.cookies.getCookiesFromHost(cookie.host, {});
204 Assert.equal(cookies.length, 1);
205 let dbcookie = cookies[0];
206 Assert.equal(dbcookie.value, "hallo");
208 // Close the profile.
209 await promise_close_profile();
212 do_get_cookie_file(profile).remove(false);
213 do_get_backup_file().remove(false);
214 Assert.ok(!do_get_cookie_file(profile).exists());
215 Assert.ok(!do_get_backup_file().exists());
218 async function run_test_2() {
219 // Load the profile and populate it.
222 Services.cookies.runInTransaction(_ => {
223 let uri = NetUtil.newURI("http://foo.com/");
224 const channel = NetUtil.newChannel({
226 loadUsingSystemPrincipal: true,
227 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
230 for (let i = 0; i < 3000; ++i) {
231 uri = NetUtil.newURI("http://" + i + ".com/");
232 Services.cookies.setCookieStringFromHttp(
234 "oh=hai; max-age=1000",
240 // Close the profile.
241 await promise_close_profile();
243 // Corrupt the database file.
244 let size = do_corrupt_db(do_get_cookie_file(profile));
249 // At this point, the database connection should be open. Ensure that it
251 Assert.ok(!do_get_backup_file().exists());
253 // Recreate a new database since it was corrupted
254 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
255 Assert.equal(do_count_cookies(), 0);
257 // Close the profile.
258 await promise_close_profile();
260 // Check that the original database was renamed.
261 Assert.ok(do_get_backup_file().exists());
262 Assert.equal(do_get_backup_file().fileSize, size);
263 let db = Services.storage.openDatabase(do_get_cookie_file(profile));
267 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
268 Assert.equal(do_count_cookies(), 0);
270 // Close the profile.
271 await promise_close_profile();
274 do_get_cookie_file(profile).remove(false);
275 do_get_backup_file().remove(false);
276 Assert.ok(!do_get_cookie_file(profile).exists());
277 Assert.ok(!do_get_backup_file().exists());
280 async function run_test_3() {
281 // Set the maximum cookies per base domain limit to a large value, so that
282 // corrupting the database is easier.
283 Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
285 // Load the profile and populate it.
287 Services.cookies.runInTransaction(_ => {
288 let uri = NetUtil.newURI("http://hither.com/");
289 let channel = NetUtil.newChannel({
291 loadUsingSystemPrincipal: true,
292 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
294 for (let i = 0; i < 10; ++i) {
295 Services.cookies.setCookieStringFromHttp(
297 "oh" + i + "=hai; max-age=1000",
301 uri = NetUtil.newURI("http://haithur.com/");
302 channel = NetUtil.newChannel({
304 loadUsingSystemPrincipal: true,
305 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
307 for (let i = 10; i < 3000; ++i) {
308 Services.cookies.setCookieStringFromHttp(
310 "oh" + i + "=hai; max-age=1000",
316 // Close the profile.
317 await promise_close_profile();
319 // Corrupt the database file.
320 let size = do_corrupt_db(do_get_cookie_file(profile));
325 // At this point, the database connection should be open. Ensure that it
327 Assert.ok(!do_get_backup_file().exists());
329 // Recreate a new database since it was corrupted
330 Assert.equal(Services.cookies.countCookiesFromHost("hither.com"), 0);
331 Assert.equal(Services.cookies.countCookiesFromHost("haithur.com"), 0);
333 // Close the profile.
334 await promise_close_profile();
336 let db = Services.storage.openDatabase(do_get_cookie_file(profile));
337 Assert.equal(do_count_cookies_in_db(db, "hither.com"), 0);
338 Assert.equal(do_count_cookies_in_db(db), 0);
341 // Check that the original database was renamed.
342 Assert.ok(do_get_backup_file().exists());
343 Assert.equal(do_get_backup_file().fileSize, size);
345 // Rename it back, and try loading the entire database synchronously.
346 do_get_backup_file().moveTo(null, "cookies.sqlite");
349 // At this point, the database connection should be open. Ensure that it
351 Assert.ok(!do_get_backup_file().exists());
353 // Synchronously read in everything.
354 Assert.equal(do_count_cookies(), 0);
356 // Close the profile.
357 await promise_close_profile();
359 db = Services.storage.openDatabase(do_get_cookie_file(profile));
360 Assert.equal(do_count_cookies_in_db(db), 0);
363 // Check that the original database was renamed.
364 Assert.ok(do_get_backup_file().exists());
365 Assert.equal(do_get_backup_file().fileSize, size);
368 do_get_cookie_file(profile).remove(false);
369 do_get_backup_file().remove(false);
370 Assert.ok(!do_get_cookie_file(profile).exists());
371 Assert.ok(!do_get_backup_file().exists());
374 async function run_test_4() {
375 // Load the profile and populate it.
377 Services.cookies.runInTransaction(_ => {
378 let uri = NetUtil.newURI("http://foo.com/");
379 let channel = NetUtil.newChannel({
381 loadUsingSystemPrincipal: true,
382 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
384 for (let i = 0; i < 3000; ++i) {
385 uri = NetUtil.newURI("http://" + i + ".com/");
386 Services.cookies.setCookieStringFromHttp(
388 "oh=hai; max-age=1000",
394 // Close the profile.
395 await promise_close_profile();
397 // Corrupt the database file.
398 let size = do_corrupt_db(do_get_cookie_file(profile));
403 // At this point, the database connection should be open. Ensure that it
405 Assert.ok(!do_get_backup_file().exists());
407 // Recreate a new database since it was corrupted
408 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
410 // Queue up an INSERT for the same base domain. This should also go into
411 // memory and be written out during database rebuild.
412 await CookieXPCShellUtils.setCookieToDocument(
414 "oh2=hai; max-age=1000"
417 // At this point, the cookies should still be in memory.
418 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 1);
419 Assert.equal(do_count_cookies(), 1);
421 // Close the profile.
422 await promise_close_profile();
424 // Check that the original database was renamed.
425 Assert.ok(do_get_backup_file().exists());
426 Assert.equal(do_get_backup_file().fileSize, size);
428 // Load the profile, and check that it contains the new cookie.
430 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 1);
431 Assert.equal(do_count_cookies(), 1);
433 // Close the profile.
434 await promise_close_profile();
437 do_get_cookie_file(profile).remove(false);
438 do_get_backup_file().remove(false);
439 Assert.ok(!do_get_cookie_file(profile).exists());
440 Assert.ok(!do_get_backup_file().exists());
443 async function run_test_5() {
444 // Load the profile and populate it.
446 Services.cookies.runInTransaction(_ => {
447 let uri = NetUtil.newURI("http://bar.com/");
448 const channel = NetUtil.newChannel({
450 loadUsingSystemPrincipal: true,
451 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
453 Services.cookies.setCookieStringFromHttp(
455 "oh=hai; path=/; max-age=1000",
458 for (let i = 0; i < 3000; ++i) {
459 uri = NetUtil.newURI("http://" + i + ".com/");
460 Services.cookies.setCookieStringFromHttp(
462 "oh=hai; max-age=1000",
468 // Close the profile.
469 await promise_close_profile();
471 // Corrupt the database file.
472 let size = do_corrupt_db(do_get_cookie_file(profile));
477 // At this point, the database connection should be open. Ensure that it
479 Assert.ok(!do_get_backup_file().exists());
481 // Recreate a new database since it was corrupted
482 Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 0);
483 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
484 Assert.equal(do_count_cookies(), 0);
485 Assert.ok(do_get_backup_file().exists());
486 Assert.equal(do_get_backup_file().fileSize, size);
487 Assert.ok(!do_get_rebuild_backup_file().exists());
489 // Open a database connection, and write a row that will trigger a constraint
491 let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 12);
492 db.insertCookie(cookie);
493 Assert.equal(do_count_cookies_in_db(db.db, "bar.com"), 1);
494 Assert.equal(do_count_cookies_in_db(db.db), 1);
497 // Check that the original backup and the database itself are gone.
498 Assert.ok(do_get_backup_file().exists());
499 Assert.equal(do_get_backup_file().fileSize, size);
501 Assert.equal(Services.cookies.countCookiesFromHost("bar.com"), 0);
502 Assert.equal(Services.cookies.countCookiesFromHost("0.com"), 0);
503 Assert.equal(do_count_cookies(), 0);
505 // Close the profile. We do not need to wait for completion, because the
506 // database has already been closed. Ensure the cookie file is unlocked.
507 await promise_close_profile();
510 do_get_cookie_file(profile).remove(false);
511 do_get_backup_file().remove(false);
512 Assert.ok(!do_get_cookie_file(profile).exists());
513 Assert.ok(!do_get_backup_file().exists());