tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

head_storage.js (10451B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 var { XPCOMUtils } = ChromeUtils.importESModule(
      6  "resource://gre/modules/XPCOMUtils.sys.mjs"
      7 );
      8 var { AppConstants } = ChromeUtils.importESModule(
      9  "resource://gre/modules/AppConstants.sys.mjs"
     10 );
     11 
     12 ChromeUtils.defineESModuleGetters(this, {
     13  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     14  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
     15  TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
     16  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
     17 });
     18 
     19 const TELEMETRY_VALUES = {
     20  success: 0,
     21  failure: 1,
     22  access: 2,
     23  diskio: 3,
     24  corrupt: 4,
     25  busy: 5,
     26  misuse: 6,
     27  diskspace: 7,
     28 };
     29 
     30 do_get_profile();
     31 var gDBConn = null;
     32 
     33 const TEST_DB_NAME = "test_storage.sqlite";
     34 function getTestDB() {
     35  var db = Services.dirsvc.get("ProfD", Ci.nsIFile);
     36  db.append(TEST_DB_NAME);
     37  return db;
     38 }
     39 
     40 /**
     41 * Obtains a corrupt database to test against.
     42 */
     43 function getCorruptDB() {
     44  return do_get_file("corruptDB.sqlite");
     45 }
     46 
     47 /**
     48 * Obtains a fake (non-SQLite format) database to test against.
     49 */
     50 function getFakeDB() {
     51  return do_get_file("fakeDB.sqlite");
     52 }
     53 
     54 /**
     55 * Delete the test database file.
     56 */
     57 function deleteTestDB() {
     58  print("*** Storage Tests: Trying to remove file!");
     59  var dbFile = getTestDB();
     60  if (dbFile.exists()) {
     61    try {
     62      dbFile.remove(false);
     63    } catch (e) {
     64      /* stupid windows box */
     65    }
     66  }
     67 }
     68 
     69 function cleanup() {
     70  // close the connection
     71  print("*** Storage Tests: Trying to close!");
     72  getOpenedDatabase().close();
     73 
     74  // we need to null out the database variable to get a new connection the next
     75  // time getOpenedDatabase is called
     76  gDBConn = null;
     77 
     78  // removing test db
     79  deleteTestDB();
     80 }
     81 
     82 /**
     83 * Use asyncClose to cleanup a connection.  Synchronous by means of internally
     84 * spinning an event loop.
     85 */
     86 function asyncCleanup() {
     87  let closed = false;
     88 
     89  // close the connection
     90  print("*** Storage Tests: Trying to asyncClose!");
     91  getOpenedDatabase().asyncClose(function () {
     92    closed = true;
     93  });
     94 
     95  let tm = Cc["@mozilla.org/thread-manager;1"].getService();
     96  tm.spinEventLoopUntil("Test(head_storage.js:asyncCleanup)", () => closed);
     97 
     98  // we need to null out the database variable to get a new connection the next
     99  // time getOpenedDatabase is called
    100  gDBConn = null;
    101 
    102  // removing test db
    103  deleteTestDB();
    104 }
    105 
    106 /**
    107 * Get a connection to the test database.  Creates and caches the connection
    108 * if necessary, otherwise reuses the existing cached connection. This
    109 * connection shares its cache.
    110 *
    111 * @returns the mozIStorageConnection for the file.
    112 */
    113 function getOpenedDatabase(connectionFlags = 0) {
    114  if (!gDBConn) {
    115    gDBConn = Services.storage.openDatabase(getTestDB(), connectionFlags);
    116  }
    117  return gDBConn;
    118 }
    119 
    120 /**
    121 * Get a connection to the test database.  Creates and caches the connection
    122 * if necessary, otherwise reuses the existing cached connection. This
    123 * connection doesn't share its cache.
    124 *
    125 * @returns the mozIStorageConnection for the file.
    126 */
    127 function getOpenedUnsharedDatabase() {
    128  if (!gDBConn) {
    129    gDBConn = Services.storage.openUnsharedDatabase(getTestDB());
    130  }
    131  return gDBConn;
    132 }
    133 
    134 /**
    135 * Obtains a specific database to use.
    136 *
    137 * @param aFile
    138 *        The nsIFile representing the db file to open.
    139 * @returns the mozIStorageConnection for the file.
    140 */
    141 function getDatabase(aFile) {
    142  return Services.storage.openDatabase(aFile);
    143 }
    144 
    145 function createStatement(aSQL) {
    146  return getOpenedDatabase().createStatement(aSQL);
    147 }
    148 
    149 /**
    150 * Creates an asynchronous SQL statement.
    151 *
    152 * @param aSQL
    153 *        The SQL to parse into a statement.
    154 * @returns a mozIStorageAsyncStatement from aSQL.
    155 */
    156 function createAsyncStatement(aSQL) {
    157  return getOpenedDatabase().createAsyncStatement(aSQL);
    158 }
    159 
    160 /**
    161 * Invoke the given function and assert that it throws an exception expressing
    162 * the provided error code in its 'result' attribute.  JS function expressions
    163 * can be used to do this concisely.
    164 *
    165 * Example:
    166 *  expectError(Cr.NS_ERROR_INVALID_ARG, () => explodingFunction());
    167 *
    168 * @param aErrorCode
    169 *        The error code to expect from invocation of aFunction.
    170 * @param aFunction
    171 *        The function to invoke and expect an XPCOM-style error from.
    172 */
    173 function expectError(aErrorCode, aFunction) {
    174  let exceptionCaught = false;
    175  try {
    176    aFunction();
    177  } catch (e) {
    178    if (e.result != aErrorCode) {
    179      do_throw(
    180        "Got an exception, but the result code was not the expected " +
    181          "one.  Expected " +
    182          aErrorCode +
    183          ", got " +
    184          e.result
    185      );
    186    }
    187    exceptionCaught = true;
    188  }
    189  if (!exceptionCaught) {
    190    do_throw(aFunction + " should have thrown an exception but did not!");
    191  }
    192 }
    193 
    194 /**
    195 * Run a query synchronously and verify that we get back the expected results.
    196 *
    197 * @param aSQLString
    198 *        The SQL string for the query.
    199 * @param aBind
    200 *        The value to bind at index 0.
    201 * @param aResults
    202 *        A list of the expected values returned in the sole result row.
    203 *        Express blobs as lists.
    204 */
    205 function verifyQuery(aSQLString, aBind, aResults) {
    206  let stmt = getOpenedDatabase().createStatement(aSQLString);
    207  stmt.bindByIndex(0, aBind);
    208  try {
    209    Assert.ok(stmt.executeStep());
    210    let nCols = stmt.numEntries;
    211    if (aResults.length != nCols) {
    212      do_throw(
    213        "Expected " +
    214          aResults.length +
    215          " columns in result but " +
    216          "there are only " +
    217          aResults.length +
    218          "!"
    219      );
    220    }
    221    for (let iCol = 0; iCol < nCols; iCol++) {
    222      let expectedVal = aResults[iCol];
    223      let valType = stmt.getTypeOfIndex(iCol);
    224      if (expectedVal === null) {
    225        Assert.equal(stmt.VALUE_TYPE_NULL, valType);
    226        Assert.ok(stmt.getIsNull(iCol));
    227      } else if (typeof expectedVal == "number") {
    228        if (Math.floor(expectedVal) == expectedVal) {
    229          Assert.equal(stmt.VALUE_TYPE_INTEGER, valType);
    230          Assert.equal(expectedVal, stmt.getInt32(iCol));
    231        } else {
    232          Assert.equal(stmt.VALUE_TYPE_FLOAT, valType);
    233          Assert.equal(expectedVal, stmt.getDouble(iCol));
    234        }
    235      } else if (typeof expectedVal == "string") {
    236        Assert.equal(stmt.VALUE_TYPE_TEXT, valType);
    237        Assert.equal(expectedVal, stmt.getUTF8String(iCol));
    238      } else {
    239        // blob
    240        Assert.equal(stmt.VALUE_TYPE_BLOB, valType);
    241        let count = { value: 0 },
    242          blob = { value: null };
    243        stmt.getBlob(iCol, count, blob);
    244        Assert.equal(count.value, expectedVal.length);
    245        for (let i = 0; i < count.value; i++) {
    246          Assert.equal(expectedVal[i], blob.value[i]);
    247        }
    248      }
    249    }
    250  } finally {
    251    stmt.finalize();
    252  }
    253 }
    254 
    255 /**
    256 * Return the number of rows in the able with the given name using a synchronous
    257 * query.
    258 *
    259 * @param aTableName
    260 *        The name of the table.
    261 * @return The number of rows.
    262 */
    263 function getTableRowCount(aTableName) {
    264  var currentRows = 0;
    265  var countStmt = getOpenedDatabase().createStatement(
    266    "SELECT COUNT(1) AS count FROM " + aTableName
    267  );
    268  try {
    269    Assert.ok(countStmt.executeStep());
    270    currentRows = countStmt.row.count;
    271  } finally {
    272    countStmt.finalize();
    273  }
    274  return currentRows;
    275 }
    276 
    277 // Promise-Returning Functions
    278 
    279 function asyncClone(db, readOnly) {
    280  return new Promise((resolve, reject) => {
    281    db.asyncClone(readOnly, function (status, db2) {
    282      if (Components.isSuccessCode(status)) {
    283        resolve(db2.QueryInterface(Ci.mozIStorageAsyncConnection));
    284      } else {
    285        reject(status);
    286      }
    287    });
    288  });
    289 }
    290 
    291 function asyncClose(db) {
    292  return new Promise((resolve, reject) => {
    293    db.asyncClose(function (status) {
    294      if (Components.isSuccessCode(status)) {
    295        resolve();
    296      } else {
    297        reject(status);
    298      }
    299    });
    300  });
    301 }
    302 
    303 function mapOptionsToFlags(aOptions, aMapping) {
    304  let result = aMapping.default;
    305  Object.entries(aOptions || {}).forEach(([optionName, isTrue]) => {
    306    if (aMapping.hasOwnProperty(optionName) && isTrue) {
    307      result |= aMapping[optionName];
    308    }
    309  });
    310  return result;
    311 }
    312 
    313 function getOpenFlagsMap() {
    314  return {
    315    default: Ci.mozIStorageService.OPEN_DEFAULT,
    316    shared: Ci.mozIStorageService.OPEN_SHARED,
    317    readOnly: Ci.mozIStorageService.OPEN_READONLY,
    318    ignoreLockingMode: Ci.mozIStorageService.OPEN_IGNORE_LOCKING_MODE,
    319  };
    320 }
    321 
    322 function getConnectionFlagsMap() {
    323  return {
    324    default: Ci.mozIStorageService.CONNECTION_DEFAULT,
    325    interruptible: Ci.mozIStorageService.CONNECTION_INTERRUPTIBLE,
    326  };
    327 }
    328 
    329 function openAsyncDatabase(file, options) {
    330  return new Promise((resolve, reject) => {
    331    const openFlags = mapOptionsToFlags(options, getOpenFlagsMap());
    332    const connectionFlags = mapOptionsToFlags(options, getConnectionFlagsMap());
    333 
    334    Services.storage.openAsyncDatabase(
    335      file,
    336      openFlags,
    337      connectionFlags,
    338      function (status, db) {
    339        if (Components.isSuccessCode(status)) {
    340          resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection));
    341        } else {
    342          reject(status);
    343        }
    344      }
    345    );
    346  });
    347 }
    348 
    349 function executeAsync(statement, onResult) {
    350  return new Promise((resolve, reject) => {
    351    statement.executeAsync({
    352      handleError(error) {
    353        reject(error);
    354      },
    355      handleResult(result) {
    356        if (onResult) {
    357          onResult(result);
    358        }
    359      },
    360      handleCompletion(result) {
    361        resolve(result);
    362      },
    363    });
    364  });
    365 }
    366 
    367 function executeMultipleStatementsAsync(db, statements, onResult) {
    368  return new Promise((resolve, reject) => {
    369    db.executeAsync(statements, {
    370      handleError(error) {
    371        reject(error);
    372      },
    373      handleResult(result) {
    374        if (onResult) {
    375          onResult(result);
    376        }
    377      },
    378      handleCompletion(result) {
    379        resolve(result);
    380      },
    381    });
    382  });
    383 }
    384 
    385 function executeSimpleSQLAsync(db, query, onResult) {
    386  return new Promise((resolve, reject) => {
    387    db.executeSimpleSQLAsync(query, {
    388      handleError(error) {
    389        reject(error);
    390      },
    391      handleResult(result) {
    392        if (onResult) {
    393          onResult(result);
    394        } else {
    395          do_throw("No results were expected");
    396        }
    397      },
    398      handleCompletion(result) {
    399        resolve(result);
    400      },
    401    });
    402  });
    403 }
    404 
    405 cleanup();