summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/ESEDBReader.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/migration/ESEDBReader.jsm')
-rw-r--r--browser/components/migration/ESEDBReader.jsm590
1 files changed, 0 insertions, 590 deletions
diff --git a/browser/components/migration/ESEDBReader.jsm b/browser/components/migration/ESEDBReader.jsm
deleted file mode 100644
index 0768c65aa..000000000
--- a/browser/components/migration/ESEDBReader.jsm
+++ /dev/null
@@ -1,590 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["ESEDBReader"]; /* exported ESEDBReader */
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/ctypes.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyGetter(this, "log", () => {
- let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
- let consoleOptions = {
- maxLogLevelPref: "browser.esedbreader.loglevel",
- prefix: "ESEDBReader",
- };
- return new ConsoleAPI(consoleOptions);
-});
-
-// We have a globally unique identifier for ESE instances. A new one
-// is used for each different database opened.
-let gESEInstanceCounter = 0;
-
-// We limit the length of strings that we read from databases.
-const MAX_STR_LENGTH = 64 * 1024;
-
-// Kernel-related types:
-const KERNEL = {};
-KERNEL.FILETIME = new ctypes.StructType("FILETIME", [
- {dwLowDateTime: ctypes.uint32_t},
- {dwHighDateTime: ctypes.uint32_t}
-]);
-KERNEL.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
- {wYear: ctypes.uint16_t},
- {wMonth: ctypes.uint16_t},
- {wDayOfWeek: ctypes.uint16_t},
- {wDay: ctypes.uint16_t},
- {wHour: ctypes.uint16_t},
- {wMinute: ctypes.uint16_t},
- {wSecond: ctypes.uint16_t},
- {wMilliseconds: ctypes.uint16_t}
-]);
-
-// DB column types, cribbed from the ESE header
-var COLUMN_TYPES = {
- JET_coltypBit: 1, /* True, False, or NULL */
- JET_coltypUnsignedByte: 2, /* 1-byte integer, unsigned */
- JET_coltypShort: 3, /* 2-byte integer, signed */
- JET_coltypLong: 4, /* 4-byte integer, signed */
- JET_coltypCurrency: 5, /* 8 byte integer, signed */
- JET_coltypIEEESingle: 6, /* 4-byte IEEE single precision */
- JET_coltypIEEEDouble: 7, /* 8-byte IEEE double precision */
- JET_coltypDateTime: 8, /* Integral date, fractional time */
- JET_coltypBinary: 9, /* Binary data, < 255 bytes */
- JET_coltypText: 10, /* ANSI text, case insensitive, < 255 bytes */
- JET_coltypLongBinary: 11, /* Binary data, long value */
- JET_coltypLongText: 12, /* ANSI text, long value */
-
- JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */
- JET_coltypLongLong: 15, /* 8-byte signed integer */
- JET_coltypGUID: 16, /* 16-byte globally unique identifier */
-};
-
-// Not very efficient, but only used for error messages
-function getColTypeName(numericValue) {
- return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown";
-}
-
-// All type constants and method wrappers go on this object:
-const ESE = {};
-ESE.JET_ERR = ctypes.long;
-ESE.JET_PCWSTR = ctypes.char16_t.ptr;
-// The ESE header calls this JET_API_PTR, but because it isn't ever used as a
-// pointer and because OS.File code implies that the name you give a type
-// matters, I opted for a different name.
-// Note that this is defined differently on 32 vs. 64-bit in the header.
-ESE.JET_API_ITEM = ctypes.voidptr_t.size == 4 ? ctypes.unsigned_long : ctypes.uint64_t;
-ESE.JET_INSTANCE = ESE.JET_API_ITEM;
-ESE.JET_SESID = ESE.JET_API_ITEM;
-ESE.JET_TABLEID = ESE.JET_API_ITEM;
-ESE.JET_COLUMNID = ctypes.unsigned_long;
-ESE.JET_GRBIT = ctypes.unsigned_long;
-ESE.JET_COLTYP = ctypes.unsigned_long;
-ESE.JET_DBID = ctypes.unsigned_long;
-
-ESE.JET_COLUMNDEF = new ctypes.StructType("JET_COLUMNDEF", [
- {"cbStruct": ctypes.unsigned_long},
- {"columnid": ESE.JET_COLUMNID },
- {"coltyp": ESE.JET_COLTYP },
- {"wCountry": ctypes.unsigned_short }, // sepcifies the country/region for the column definition
- {"langid": ctypes.unsigned_short },
- {"cp": ctypes.unsigned_short },
- {"wCollate": ctypes.unsigned_short }, /* Must be 0 */
- {"cbMax": ctypes.unsigned_long },
- {"grbit": ESE.JET_GRBIT }
-]);
-
-// Track open databases
-let gOpenDBs = new Map();
-
-// Track open libraries
-let gLibs = {};
-this.ESE = ESE; // Required for tests.
-this.KERNEL = KERNEL; // ditto
-this.gLibs = gLibs; // ditto
-
-function convertESEError(errorCode) {
- switch (errorCode) {
- case -1213 /* JET_errPageSizeMismatch */:
- case -1002 /* JET_errInvalidName*/:
- case -1507 /* JET_errColumnNotFound */:
- // The DB format has changed and we haven't updated this migration code:
- return "The database format has changed, error code: " + errorCode;
- case -1207 /* JET_errDatabaseLocked */:
- case -1302 /* JET_errTableLocked */:
- return "The database or table is locked, error code: " + errorCode;
- case -1809 /* JET_errPermissionDenied*/:
- case -1907 /* JET_errAccessDenied */:
- return "Access or permission denied, error code: " + errorCode;
- case -1044 /* JET_errInvalidFilename */:
- return "Invalid file name";
- case -1811 /* JET_errFileNotFound */:
- return "File not found";
- case -550 /* JET_errDatabaseDirtyShutdown */:
- return "Database in dirty shutdown state (without the requisite logs?)";
- case -514 /* JET_errBadLogVersion */:
- return "Database log version does not match the version of ESE in use.";
- default:
- return "Unknown error: " + errorCode;
- }
-}
-
-function handleESEError(method, methodName, shouldThrow = true, errorLog = true) {
- return function () {
- let rv;
- try {
- rv = method.apply(null, arguments);
- } catch (ex) {
- log.error("Error calling into ctypes method", methodName, ex);
- throw ex;
- }
- let resultCode = parseInt(rv.toString(10), 10);
- if (resultCode < 0) {
- if (errorLog) {
- log.error("Got error " + resultCode + " calling " + methodName);
- }
- if (shouldThrow) {
- throw new Error(convertESEError(rv));
- }
- } else if (resultCode > 0 && errorLog) {
- log.warn("Got warning " + resultCode + " calling " + methodName);
- }
- return resultCode;
- };
-}
-
-
-function declareESEFunction(methodName, ...args) {
- let declaration = ["Jet" + methodName, ctypes.winapi_abi, ESE.JET_ERR].concat(args);
- let ctypeMethod = gLibs.ese.declare.apply(gLibs.ese, declaration);
- ESE[methodName] = handleESEError(ctypeMethod, methodName);
- ESE["FailSafe" + methodName] = handleESEError(ctypeMethod, methodName, false);
- ESE["Manual" + methodName] = handleESEError(ctypeMethod, methodName, false, false);
-}
-
-function declareESEFunctions() {
- declareESEFunction("GetDatabaseFileInfoW", ESE.JET_PCWSTR, ctypes.voidptr_t,
- ctypes.unsigned_long, ctypes.unsigned_long);
-
- declareESEFunction("GetSystemParameterW", ESE.JET_INSTANCE, ESE.JET_SESID,
- ctypes.unsigned_long, ESE.JET_API_ITEM.ptr,
- ESE.JET_PCWSTR, ctypes.unsigned_long);
- declareESEFunction("SetSystemParameterW", ESE.JET_INSTANCE.ptr,
- ESE.JET_SESID, ctypes.unsigned_long, ESE.JET_API_ITEM,
- ESE.JET_PCWSTR);
- declareESEFunction("CreateInstanceW", ESE.JET_INSTANCE.ptr, ESE.JET_PCWSTR);
- declareESEFunction("Init", ESE.JET_INSTANCE.ptr);
-
- declareESEFunction("BeginSessionW", ESE.JET_INSTANCE, ESE.JET_SESID.ptr,
- ESE.JET_PCWSTR, ESE.JET_PCWSTR);
- declareESEFunction("AttachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
- ESE.JET_GRBIT);
- declareESEFunction("DetachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR);
- declareESEFunction("OpenDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
- ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
- declareESEFunction("OpenTableW", ESE.JET_SESID, ESE.JET_DBID, ESE.JET_PCWSTR,
- ctypes.voidptr_t, ctypes.unsigned_long, ESE.JET_GRBIT,
- ESE.JET_TABLEID.ptr);
-
- declareESEFunction("GetColumnInfoW", ESE.JET_SESID, ESE.JET_DBID,
- ESE.JET_PCWSTR, ESE.JET_PCWSTR, ctypes.voidptr_t,
- ctypes.unsigned_long, ctypes.unsigned_long);
-
- declareESEFunction("Move", ESE.JET_SESID, ESE.JET_TABLEID, ctypes.long,
- ESE.JET_GRBIT);
-
- declareESEFunction("RetrieveColumn", ESE.JET_SESID, ESE.JET_TABLEID,
- ESE.JET_COLUMNID, ctypes.voidptr_t, ctypes.unsigned_long,
- ctypes.unsigned_long.ptr, ESE.JET_GRBIT, ctypes.voidptr_t);
-
- declareESEFunction("CloseTable", ESE.JET_SESID, ESE.JET_TABLEID);
- declareESEFunction("CloseDatabase", ESE.JET_SESID, ESE.JET_DBID,
- ESE.JET_GRBIT);
-
- declareESEFunction("EndSession", ESE.JET_SESID, ESE.JET_GRBIT);
-
- declareESEFunction("Term", ESE.JET_INSTANCE);
-}
-
-function unloadLibraries() {
- log.debug("Unloading");
- if (gOpenDBs.size) {
- log.error("Shouldn't unload libraries before DBs are closed!");
- for (let db of gOpenDBs.values()) {
- db._close();
- }
- }
- for (let k of Object.keys(ESE)) {
- delete ESE[k];
- }
- gLibs.ese.close();
- gLibs.kernel.close();
- delete gLibs.ese;
- delete gLibs.kernel;
-}
-
-function loadLibraries() {
- Services.obs.addObserver(unloadLibraries, "xpcom-shutdown", false);
- gLibs.ese = ctypes.open("esent.dll");
- gLibs.kernel = ctypes.open("kernel32.dll");
- KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
- ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
-
- declareESEFunctions();
-}
-
-function ESEDB(rootPath, dbPath, logPath) {
- log.info("Created db");
- this.rootPath = rootPath;
- this.dbPath = dbPath;
- this.logPath = logPath;
- this._references = 0;
- this._init();
-}
-
-ESEDB.prototype = {
- rootPath: null,
- dbPath: null,
- logPath: null,
- _opened: false,
- _attached: false,
- _sessionCreated: false,
- _instanceCreated: false,
- _dbId: null,
- _sessionId: null,
- _instanceId: null,
-
- _init() {
- if (!gLibs.ese) {
- loadLibraries();
- }
- this.incrementReferenceCounter();
- this._internalOpen();
- },
-
- _internalOpen() {
- try {
- let dbinfo = new ctypes.unsigned_long();
- ESE.GetDatabaseFileInfoW(this.dbPath, dbinfo.address(),
- ctypes.unsigned_long.size, 17);
-
- let pageSize = ctypes.UInt64.lo(dbinfo.value);
- ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/,
- pageSize, null);
-
- this._instanceId = new ESE.JET_INSTANCE();
- ESE.CreateInstanceW(this._instanceId.address(),
- "firefox-dbreader-" + (gESEInstanceCounter++));
- this._instanceCreated = true;
-
- ESE.SetSystemParameterW(this._instanceId.address(), 0,
- 0 /* JET_paramSystemPath*/, 0, this.rootPath);
- ESE.SetSystemParameterW(this._instanceId.address(), 0,
- 1 /* JET_paramTempPath */, 0, this.rootPath);
- ESE.SetSystemParameterW(this._instanceId.address(), 0,
- 2 /* JET_paramLogFilePath*/, 0, this.logPath);
-
- // Shouldn't try to call JetTerm if the following call fails.
- this._instanceCreated = false;
- ESE.Init(this._instanceId.address());
- this._instanceCreated = true;
- this._sessionId = new ESE.JET_SESID();
- ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
- null);
- this._sessionCreated = true;
-
- const JET_bitDbReadOnly = 1;
- ESE.AttachDatabaseW(this._sessionId, this.dbPath, JET_bitDbReadOnly);
- this._attached = true;
- this._dbId = new ESE.JET_DBID();
- ESE.OpenDatabaseW(this._sessionId, this.dbPath, null,
- this._dbId.address(), JET_bitDbReadOnly);
- this._opened = true;
- } catch (ex) {
- try {
- this._close();
- } catch (innerException) {
- Cu.reportError(innerException);
- }
- // Make sure caller knows we failed.
- throw ex;
- }
- gOpenDBs.set(this.dbPath, this);
- },
-
- checkForColumn(tableName, columnName) {
- if (!this._opened) {
- throw new Error("The database was closed!");
- }
-
- let columnInfo;
- try {
- columnInfo = this._getColumnInfo(tableName, [{name: columnName}]);
- } catch (ex) {
- return null;
- }
- return columnInfo[0];
- },
-
- tableExists(tableName) {
- if (!this._opened) {
- throw new Error("The database was closed!");
- }
-
- let tableId = new ESE.JET_TABLEID();
- let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null,
- 0, 4 /* JET_bitTableReadOnly */,
- tableId.address());
- if (rv == -1305 /* JET_errObjectNotFound */) {
- return false;
- }
- if (rv < 0) {
- log.error("Got error " + rv + " calling OpenTableW");
- throw new Error(convertESEError(rv));
- }
-
- if (rv > 0) {
- log.error("Got warning " + rv + " calling OpenTableW");
- }
- ESE.FailSafeCloseTable(this._sessionId, tableId);
- return true;
- },
-
- tableItems: function*(tableName, columns) {
- if (!this._opened) {
- throw new Error("The database was closed!");
- }
-
- let tableOpened = false;
- let tableId;
- try {
- tableId = this._openTable(tableName);
- tableOpened = true;
-
- let columnInfo = this._getColumnInfo(tableName, columns);
-
- let rv = ESE.ManualMove(this._sessionId, tableId,
- -2147483648 /* JET_MoveFirst */, 0);
- if (rv == -1603 /* JET_errNoCurrentRecord */) {
- // There are no rows in the table.
- this._closeTable(tableId);
- return;
- }
- if (rv != 0) {
- throw new Error(convertESEError(rv));
- }
-
- do {
- let rowContents = {};
- for (let column of columnInfo) {
- let [buffer, bufferSize] = this._getBufferForColumn(column);
- // We handle errors manually so we accurately deal with NULL values.
- let err = ESE.ManualRetrieveColumn(this._sessionId, tableId,
- column.id, buffer.address(),
- bufferSize, null, 0, null);
- rowContents[column.name] = this._convertResult(column, buffer, err);
- }
- yield rowContents;
- } while (ESE.ManualMove(this._sessionId, tableId, 1 /* JET_MoveNext */, 0) === 0);
- } catch (ex) {
- if (tableOpened) {
- this._closeTable(tableId);
- }
- throw ex;
- }
- this._closeTable(tableId);
- },
-
- _openTable(tableName) {
- let tableId = new ESE.JET_TABLEID();
- ESE.OpenTableW(this._sessionId, this._dbId, tableName, null,
- 0, 4 /* JET_bitTableReadOnly */, tableId.address());
- return tableId;
- },
-
- _getBufferForColumn(column) {
- let buffer;
- if (column.type == "string") {
- let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
- // size on the column is in bytes, 2 bytes to a wchar, so:
- let charCount = column.dbSize >> 1;
- buffer = new wchar_tArray(charCount);
- } else if (column.type == "boolean") {
- buffer = new ctypes.uint8_t();
- } else if (column.type == "date") {
- buffer = new KERNEL.FILETIME();
- } else if (column.type == "guid") {
- let byteArray = ctypes.ArrayType(ctypes.uint8_t);
- buffer = new byteArray(column.dbSize);
- } else {
- throw new Error("Unknown type " + column.type);
- }
- return [buffer, buffer.constructor.size];
- },
-
- _convertResult(column, buffer, err) {
- if (err != 0) {
- if (err == 1004) {
- // Deal with null values:
- buffer = null;
- } else {
- Cu.reportError("Unexpected JET error: " + err + ";" + " retrieving value for column " + column.name);
- throw new Error(convertESEError(err));
- }
- }
- if (column.type == "string") {
- return buffer ? buffer.readString() : "";
- }
- if (column.type == "boolean") {
- return buffer ? (buffer.value == 255) : false;
- }
- if (column.type == "guid") {
- if (buffer.length != 16) {
- Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!");
- return "";
- }
- let rv = "{";
- for (let i = 0; i < 16; i++) {
- if (i == 4 || i == 6 || i == 8 || i == 10) {
- rv += "-";
- }
- let byteValue = buffer.addressOfElement(i).contents;
- // Ensure there's a leading 0
- rv += ("0" + byteValue.toString(16)).substr(-2);
- }
- return rv + "}";
- }
- if (column.type == "date") {
- if (!buffer) {
- return null;
- }
- let systemTime = new KERNEL.SYSTEMTIME();
- let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address());
- if (result == 0) {
- throw new Error(ctypes.winLastError);
- }
-
- // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
- // then divide by 1000 to get seconds, and round down:
- return new Date(Date.UTC(systemTime.wYear,
- systemTime.wMonth - 1,
- systemTime.wDay,
- systemTime.wHour,
- systemTime.wMinute,
- systemTime.wSecond,
- systemTime.wMilliseconds));
- }
- return undefined;
- },
-
- _getColumnInfo(tableName, columns) {
- let rv = [];
- for (let column of columns) {
- let columnInfoFromDB = new ESE.JET_COLUMNDEF();
- ESE.GetColumnInfoW(this._sessionId, this._dbId, tableName, column.name,
- columnInfoFromDB.address(), ESE.JET_COLUMNDEF.size, 0 /* JET_ColInfo */);
- let dbType = parseInt(columnInfoFromDB.coltyp.toString(10), 10);
- let dbSize = parseInt(columnInfoFromDB.cbMax.toString(10), 10);
- if (column.type == "string") {
- if (dbType != COLUMN_TYPES.JET_coltypLongText &&
- dbType != COLUMN_TYPES.JET_coltypText) {
- throw new Error("Invalid column type for column " + column.name +
- "; expected text type, got type " + getColTypeName(dbType));
- }
- if (dbSize > MAX_STR_LENGTH) {
- throw new Error("Column " + column.name + " has more than 64k data in it. This API is not designed to handle data that large.");
- }
- } else if (column.type == "boolean") {
- if (dbType != COLUMN_TYPES.JET_coltypBit) {
- throw new Error("Invalid column type for column " + column.name +
- "; expected bit type, got type " + getColTypeName(dbType));
- }
- } else if (column.type == "date") {
- if (dbType != COLUMN_TYPES.JET_coltypLongLong) {
- throw new Error("Invalid column type for column " + column.name +
- "; expected long long type, got type " + getColTypeName(dbType));
- }
- } else if (column.type == "guid") {
- if (dbType != COLUMN_TYPES.JET_coltypGUID) {
- throw new Error("Invalid column type for column " + column.name +
- "; expected guid type, got type " + getColTypeName(dbType));
- }
- } else if (column.type) {
- throw new Error("Unknown column type " + column.type + " requested for column " +
- column.name + ", don't know what to do.");
- }
-
- rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType});
- }
- return rv;
- },
-
- _closeTable(tableId) {
- ESE.FailSafeCloseTable(this._sessionId, tableId);
- },
-
- _close() {
- this._internalClose();
- gOpenDBs.delete(this.dbPath);
- },
-
- _internalClose() {
- if (this._opened) {
- log.debug("close db");
- ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
- log.debug("finished close db");
- this._opened = false;
- }
- if (this._attached) {
- log.debug("detach db");
- ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath);
- this._attached = false;
- }
- if (this._sessionCreated) {
- log.debug("end session");
- ESE.FailSafeEndSession(this._sessionId, 0);
- this._sessionCreated = false;
- }
- if (this._instanceCreated) {
- log.debug("term");
- ESE.FailSafeTerm(this._instanceId);
- this._instanceCreated = false;
- }
- },
-
- incrementReferenceCounter() {
- this._references++;
- },
-
- decrementReferenceCounter() {
- this._references--;
- if (this._references <= 0) {
- this._close();
- }
- },
-};
-
-let ESEDBReader = {
- openDB(rootDir, dbFile, logDir) {
- let dbFilePath = dbFile.path;
- if (gOpenDBs.has(dbFilePath)) {
- let db = gOpenDBs.get(dbFilePath);
- db.incrementReferenceCounter();
- return db;
- }
- // ESE is really picky about the trailing slashes according to the docs,
- // so we do as we're told and ensure those are there:
- return new ESEDB(rootDir.path + "\\", dbFilePath, logDir.path + "\\");
- },
-
- closeDB(db) {
- db.decrementReferenceCounter();
- },
-
- COLUMN_TYPES,
-};
-