extensions/cookie/test/unit/test_cookies_async_failure.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 /* 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
     7 // particular order:
     8 //
     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
    16 //    ignored.)
    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'.
    21 let test_generator = do_run_test();
    23 function run_test() {
    24   do_test_pending();
    25   do_run_generator(test_generator);
    26 }
    28 function finish_test() {
    29   do_execute_soon(function() {
    30     test_generator.close();
    31     do_test_finished();
    32   });
    33 }
    35 function do_run_test() {
    36   // Set up a profile.
    37   this.profile = do_get_profile();
    39   // Allow all cookies.
    40   Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
    42   // Get the cookie file and the backup file.
    43   do_check_false(do_get_cookie_file(profile).exists());
    44   do_check_false(do_get_backup_file(profile).exists());
    46   // Create a cookie object for testing.
    47   this.now = Date.now() * 1000;
    48   this.futureExpiry = Math.round(this.now / 1e6 + 1000);
    49   this.cookie = new Cookie("oh", "hai", "bar.com", "/", this.futureExpiry,
    50     this.now, this.now, false, false, false);
    52   this.sub_generator = run_test_1(test_generator);
    53   sub_generator.next();
    54   yield;
    56   this.sub_generator = run_test_2(test_generator);
    57   sub_generator.next();
    58   yield;
    60   this.sub_generator = run_test_3(test_generator);
    61   sub_generator.next();
    62   yield;
    64   this.sub_generator = run_test_4(test_generator);
    65   sub_generator.next();
    66   yield;
    68   this.sub_generator = run_test_5(test_generator);
    69   sub_generator.next();
    70   yield;
    72   finish_test();
    73   return;
    74 }
    76 function do_get_backup_file(profile)
    77 {
    78   let file = profile.clone();
    79   file.append("cookies.sqlite.bak");
    80   return file;
    81 }
    83 function do_get_rebuild_backup_file(profile)
    84 {
    85   let file = profile.clone();
    86   file.append("cookies.sqlite.bak-rebuild");
    87   return file;
    88 }
    90 function do_corrupt_db(file)
    91 {
    92   // Sanity check: the database size should be larger than 450k, since we've
    93   // written about 460k of data. If it's not, let's make it obvious now.
    94   let size = file.fileSize;
    95   do_check_true(size > 450e3);
    97   // Corrupt the database by writing bad data to the end of the file. We
    98   // assume that the important metadata -- table structure etc -- is stored
    99   // elsewhere, and that doing this will not cause synchronous failure when
   100   // initializing the database connection. This is totally empirical --
   101   // overwriting between 1k and 100k of live data seems to work. (Note that the
   102   // database file will be larger than the actual content requires, since the
   103   // cookie service uses a large growth increment. So we calculate the offset
   104   // based on the expected size of the content, not just the file size.)
   105   let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
   106                 createInstance(Ci.nsIFileOutputStream);
   107   ostream.init(file, 2, -1, 0);
   108   let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
   109   let n = size - 450e3 + 20e3;
   110   sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
   111   for (let i = 0; i < n; ++i) {
   112     ostream.write("a", 1);
   113   }
   114   ostream.flush();
   115   ostream.close();
   117   do_check_eq(file.clone().fileSize, size);
   118   return size;
   119 }
   121 function run_test_1(generator)
   122 {
   123   // Load the profile and populate it.
   124   let uri = NetUtil.newURI("http://foo.com/");
   125   Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   127   // Close the profile.
   128   do_close_profile(sub_generator);
   129   yield;
   131   // Open a database connection now, before we load the profile and begin
   132   // asynchronous write operations. In order to tell when the async delete
   133   // statement has completed, we do something tricky: open a schema 2 connection
   134   // and add a cookie with null baseDomain. We can then wait until we see it
   135   // deleted in the new database.
   136   let db2 = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
   137   db2.db.executeSimpleSQL("INSERT INTO moz_cookies (baseDomain) VALUES (NULL)");
   138   db2.close();
   139   let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
   140   do_check_eq(do_count_cookies_in_db(db.db), 2);
   142   // Load the profile, and wait for async read completion...
   143   do_load_profile(sub_generator);
   144   yield;
   146   // ... and the DELETE statement to finish.
   147   while (do_count_cookies_in_db(db.db) == 2) {
   148     do_execute_soon(function() {
   149       do_run_generator(sub_generator);
   150     });
   151     yield;
   152   }
   153   do_check_eq(do_count_cookies_in_db(db.db), 1);
   155   // Insert a row.
   156   db.insertCookie(cookie);
   157   db.close();
   159   // Attempt to insert a cookie with the same (name, host, path) triplet.
   160   Services.cookiemgr.add(cookie.host, cookie.path, cookie.name, "hallo",
   161     cookie.isSecure, cookie.isHttpOnly, cookie.isSession, cookie.expiry);
   163   // Check that the cookie service accepted the new cookie.
   164   do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
   166   // Wait for the cookie service to rename the old database and rebuild.
   167   new _observer(sub_generator, "cookie-db-rebuilding");
   168   yield;
   169   do_execute_soon(function() { do_run_generator(sub_generator); });
   170   yield;
   172   // At this point, the cookies should still be in memory.
   173   do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
   174   do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
   175   do_check_eq(do_count_cookies(), 2);
   177   // Close the profile.
   178   do_close_profile(sub_generator);
   179   yield;
   181   // Check that the original database was renamed, and that it contains the
   182   // original cookie.
   183   do_check_true(do_get_backup_file(profile).exists());
   184   let backupdb = Services.storage.openDatabase(do_get_backup_file(profile));
   185   do_check_eq(do_count_cookies_in_db(backupdb, "foo.com"), 1);
   186   backupdb.close();
   188   // Load the profile, and check that it contains the new cookie.
   189   do_load_profile();
   191   do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
   192   let enumerator = Services.cookiemgr.getCookiesFromHost(cookie.host);
   193   do_check_true(enumerator.hasMoreElements());
   194   let dbcookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
   195   do_check_eq(dbcookie.value, "hallo");
   196   do_check_false(enumerator.hasMoreElements());
   198   // Close the profile.
   199   do_close_profile(sub_generator);
   200   yield;
   202   // Clean up.
   203   do_get_cookie_file(profile).remove(false);
   204   do_get_backup_file(profile).remove(false);
   205   do_check_false(do_get_cookie_file(profile).exists());
   206   do_check_false(do_get_backup_file(profile).exists());
   207   do_run_generator(generator);
   208 }
   210 function run_test_2(generator)
   211 {
   212   // Load the profile and populate it.
   213   do_load_profile();
   214   for (let i = 0; i < 3000; ++i) {
   215     let uri = NetUtil.newURI("http://" + i + ".com/");
   216     Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   217   }
   219   // Close the profile.
   220   do_close_profile(sub_generator);
   221   yield;
   223   // Corrupt the database file.
   224   let size = do_corrupt_db(do_get_cookie_file(profile));
   226   // Load the profile.
   227   do_load_profile();
   229   // At this point, the database connection should be open. Ensure that it
   230   // succeeded.
   231   do_check_false(do_get_backup_file(profile).exists());
   233   // Synchronously read in the first cookie. This will cause it to go into the
   234   // cookie table, whereupon it will be written out during database rebuild.
   235   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   237   // Wait for the asynchronous read to choke, at which point the backup file
   238   // will be created and the database rebuilt.
   239   new _observer(sub_generator, "cookie-db-rebuilding");
   240   yield;
   241   do_execute_soon(function() { do_run_generator(sub_generator); });
   242   yield;
   244   // At this point, the cookies should still be in memory.
   245   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   246   do_check_eq(do_count_cookies(), 1);
   248   // Close the profile.
   249   do_close_profile(sub_generator);
   250   yield;
   252   // Check that the original database was renamed.
   253   do_check_true(do_get_backup_file(profile).exists());
   254   do_check_eq(do_get_backup_file(profile).fileSize, size);
   255   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
   256   do_check_eq(do_count_cookies_in_db(db, "0.com"), 1);
   257   db.close();
   259   // Load the profile, and check that it contains the new cookie.
   260   do_load_profile();
   261   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   262   do_check_eq(do_count_cookies(), 1);
   264   // Close the profile.
   265   do_close_profile(sub_generator);
   266   yield;
   268   // Clean up.
   269   do_get_cookie_file(profile).remove(false);
   270   do_get_backup_file(profile).remove(false);
   271   do_check_false(do_get_cookie_file(profile).exists());
   272   do_check_false(do_get_backup_file(profile).exists());
   273   do_run_generator(generator);
   274 }
   276 function run_test_3(generator)
   277 {
   278   // Set the maximum cookies per base domain limit to a large value, so that
   279   // corrupting the database is easier.
   280   Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
   282   // Load the profile and populate it.
   283   do_load_profile();
   284   for (let i = 0; i < 10; ++i) {
   285     let uri = NetUtil.newURI("http://hither.com/");
   286     Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
   287       null);
   288   }
   289   for (let i = 10; i < 3000; ++i) {
   290     let uri = NetUtil.newURI("http://haithur.com/");
   291     Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
   292       null);
   293   }
   295   // Close the profile.
   296   do_close_profile(sub_generator);
   297   yield;
   299   // Corrupt the database file.
   300   let size = do_corrupt_db(do_get_cookie_file(profile));
   302   // Load the profile.
   303   do_load_profile();
   305   // At this point, the database connection should be open. Ensure that it
   306   // succeeded.
   307   do_check_false(do_get_backup_file(profile).exists());
   309   // Synchronously read in the cookies for our two domains. The first should
   310   // succeed, but the second should fail midway through, resulting in none of
   311   // those cookies being present.
   312   do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 10);
   313   do_check_eq(Services.cookiemgr.countCookiesFromHost("haithur.com"), 0);
   315   // Wait for the backup file to be created and the database rebuilt.
   316   do_check_false(do_get_backup_file(profile).exists());
   317   new _observer(sub_generator, "cookie-db-rebuilding");
   318   yield;
   319   do_execute_soon(function() { do_run_generator(sub_generator); });
   320   yield;
   322   // Close the profile.
   323   do_close_profile(sub_generator);
   324   yield;
   325   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
   326   do_check_eq(do_count_cookies_in_db(db, "hither.com"), 10);
   327   do_check_eq(do_count_cookies_in_db(db), 10);
   328   db.close();
   330   // Check that the original database was renamed.
   331   do_check_true(do_get_backup_file(profile).exists());
   332   do_check_eq(do_get_backup_file(profile).fileSize, size);
   334   // Rename it back, and try loading the entire database synchronously.
   335   do_get_backup_file(profile).moveTo(null, "cookies.sqlite");
   336   do_load_profile();
   338   // At this point, the database connection should be open. Ensure that it
   339   // succeeded.
   340   do_check_false(do_get_backup_file(profile).exists());
   342   // Synchronously read in everything.
   343   do_check_eq(do_count_cookies(), 0);
   345   // Wait for the backup file to be created and the database rebuilt.
   346   do_check_false(do_get_backup_file(profile).exists());
   347   new _observer(sub_generator, "cookie-db-rebuilding");
   348   yield;
   349   do_execute_soon(function() { do_run_generator(sub_generator); });
   350   yield;
   352   // Close the profile.
   353   do_close_profile(sub_generator);
   354   yield;
   355   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
   356   do_check_eq(do_count_cookies_in_db(db), 0);
   357   db.close();
   359   // Check that the original database was renamed.
   360   do_check_true(do_get_backup_file(profile).exists());
   361   do_check_eq(do_get_backup_file(profile).fileSize, size);
   363   // Clean up.
   364   do_get_cookie_file(profile).remove(false);
   365   do_get_backup_file(profile).remove(false);
   366   do_check_false(do_get_cookie_file(profile).exists());
   367   do_check_false(do_get_backup_file(profile).exists());
   368   do_run_generator(generator);
   369 }
   371 function run_test_4(generator)
   372 {
   373   // Load the profile and populate it.
   374   do_load_profile();
   375   for (let i = 0; i < 3000; ++i) {
   376     let uri = NetUtil.newURI("http://" + i + ".com/");
   377     Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   378   }
   380   // Close the profile.
   381   do_close_profile(sub_generator);
   382   yield;
   384   // Corrupt the database file.
   385   let size = do_corrupt_db(do_get_cookie_file(profile));
   387   // Load the profile.
   388   do_load_profile();
   390   // At this point, the database connection should be open. Ensure that it
   391   // succeeded.
   392   do_check_false(do_get_backup_file(profile).exists());
   394   // Synchronously read in the first cookie. This will cause it to go into the
   395   // cookie table, whereupon it will be written out during database rebuild.
   396   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   398   // Queue up an INSERT for the same base domain. This should also go into
   399   // memory and be written out during database rebuild.
   400   let uri = NetUtil.newURI("http://0.com/");
   401   Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
   403   // Wait for the asynchronous read to choke and the insert to fail shortly
   404   // thereafter, at which point the backup file will be created and the database
   405   // rebuilt.
   406   new _observer(sub_generator, "cookie-db-rebuilding");
   407   yield;
   408   do_execute_soon(function() { do_run_generator(sub_generator); });
   409   yield;
   411   // Close the profile.
   412   do_close_profile(sub_generator);
   413   yield;
   415   // Check that the original database was renamed.
   416   do_check_true(do_get_backup_file(profile).exists());
   417   do_check_eq(do_get_backup_file(profile).fileSize, size);
   418   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
   419   do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
   420   db.close();
   422   // Load the profile, and check that it contains the new cookie.
   423   do_load_profile();
   424   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
   425   do_check_eq(do_count_cookies(), 2);
   427   // Close the profile.
   428   do_close_profile(sub_generator);
   429   yield;
   431   // Clean up.
   432   do_get_cookie_file(profile).remove(false);
   433   do_get_backup_file(profile).remove(false);
   434   do_check_false(do_get_cookie_file(profile).exists());
   435   do_check_false(do_get_backup_file(profile).exists());
   436   do_run_generator(generator);
   437 }
   439 function run_test_4(generator)
   440 {
   441   // Load the profile and populate it.
   442   do_load_profile();
   443   for (let i = 0; i < 3000; ++i) {
   444     let uri = NetUtil.newURI("http://" + i + ".com/");
   445     Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   446   }
   448   // Close the profile.
   449   do_close_profile(sub_generator);
   450   yield;
   452   // Corrupt the database file.
   453   let size = do_corrupt_db(do_get_cookie_file(profile));
   455   // Load the profile.
   456   do_load_profile();
   458   // At this point, the database connection should be open. Ensure that it
   459   // succeeded.
   460   do_check_false(do_get_backup_file(profile).exists());
   462   // Synchronously read in the first cookie. This will cause it to go into the
   463   // cookie table, whereupon it will be written out during database rebuild.
   464   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   466   // Queue up an INSERT for the same base domain. This should also go into
   467   // memory and be written out during database rebuild.
   468   let uri = NetUtil.newURI("http://0.com/");
   469   Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
   471   // Wait for the asynchronous read to choke and the insert to fail shortly
   472   // thereafter, at which point the backup file will be created and the database
   473   // rebuilt.
   474   new _observer(sub_generator, "cookie-db-rebuilding");
   475   yield;
   476   do_execute_soon(function() { do_run_generator(sub_generator); });
   477   yield;
   479   // At this point, the cookies should still be in memory.
   480   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
   481   do_check_eq(do_count_cookies(), 2);
   483   // Close the profile.
   484   do_close_profile(sub_generator);
   485   yield;
   487   // Check that the original database was renamed.
   488   do_check_true(do_get_backup_file(profile).exists());
   489   do_check_eq(do_get_backup_file(profile).fileSize, size);
   490   let db = Services.storage.openDatabase(do_get_cookie_file(profile));
   491   do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
   492   db.close();
   494   // Load the profile, and check that it contains the new cookie.
   495   do_load_profile();
   496   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
   497   do_check_eq(do_count_cookies(), 2);
   499   // Close the profile.
   500   do_close_profile(sub_generator);
   501   yield;
   503   // Clean up.
   504   do_get_cookie_file(profile).remove(false);
   505   do_get_backup_file(profile).remove(false);
   506   do_check_false(do_get_cookie_file(profile).exists());
   507   do_check_false(do_get_backup_file(profile).exists());
   508   do_run_generator(generator);
   509 }
   511 function run_test_5(generator)
   512 {
   513   // Load the profile and populate it.
   514   do_load_profile();
   515   let uri = NetUtil.newURI("http://bar.com/");
   516   Services.cookies.setCookieString(uri, null, "oh=hai; path=/; max-age=1000",
   517     null);
   518   for (let i = 0; i < 3000; ++i) {
   519     let uri = NetUtil.newURI("http://" + i + ".com/");
   520     Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
   521   }
   523   // Close the profile.
   524   do_close_profile(sub_generator);
   525   yield;
   527   // Corrupt the database file.
   528   let size = do_corrupt_db(do_get_cookie_file(profile));
   530   // Load the profile.
   531   do_load_profile();
   533   // At this point, the database connection should be open. Ensure that it
   534   // succeeded.
   535   do_check_false(do_get_backup_file(profile).exists());
   537   // Synchronously read in the first two cookies. This will cause them to go
   538   // into the cookie table, whereupon it will be written out during database
   539   // rebuild.
   540   do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
   541   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   543   // Wait for the asynchronous read to choke, at which point the backup file
   544   // will be created and a new connection opened.
   545   new _observer(sub_generator, "cookie-db-rebuilding");
   546   yield;
   548   // At this point, the cookies should still be in memory. (Note that these
   549   // calls are re-entrant into the cookie service, but it's OK!)
   550   do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
   551   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   552   do_check_eq(do_count_cookies(), 2);
   553   do_check_true(do_get_backup_file(profile).exists());
   554   do_check_eq(do_get_backup_file(profile).fileSize, size);
   555   do_check_false(do_get_rebuild_backup_file(profile).exists());
   557   // Open a database connection, and write a row that will trigger a constraint
   558   // violation.
   559   let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
   560   db.insertCookie(cookie);
   561   do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
   562   do_check_eq(do_count_cookies_in_db(db.db), 1);
   563   db.close();
   565   // Wait for the rebuild to bail and the database to be closed.
   566   new _observer(sub_generator, "cookie-db-closed");
   567   yield;
   569   // Check that the original backup and the database itself are gone.
   570   do_check_true(do_get_rebuild_backup_file(profile).exists());
   571   do_check_true(do_get_backup_file(profile).exists());
   572   do_check_eq(do_get_backup_file(profile).fileSize, size);
   573   do_check_false(do_get_cookie_file(profile).exists());
   575   // Check that the rebuild backup has the original bar.com cookie, and possibly
   576   // a 0.com cookie depending on whether it got written out first or second.
   577   db = new CookieDatabaseConnection(do_get_rebuild_backup_file(profile), 4);
   578   do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
   579   let count = do_count_cookies_in_db(db.db);
   580   do_check_true(count == 1 ||
   581     count == 2 && do_count_cookies_in_db(db.db, "0.com") == 1);
   582   db.close();
   584   do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
   585   do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
   586   do_check_eq(do_count_cookies(), 2);
   588   // Close the profile. We do not need to wait for completion, because the
   589   // database has already been closed.
   590   do_close_profile();
   592   // Clean up.
   593   do_get_backup_file(profile).remove(false);
   594   do_get_rebuild_backup_file(profile).remove(false);
   595   do_check_false(do_get_cookie_file(profile).exists());
   596   do_check_false(do_get_backup_file(profile).exists());
   597   do_check_false(do_get_rebuild_backup_file(profile).exists());
   598   do_run_generator(generator);
   599 }

mercurial