forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			799 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			799 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
import { ctypes } from "resource://gre/modules/ctypes.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "log", () => {
 | 
						|
  let { ConsoleAPI } = ChromeUtils.importESModule(
 | 
						|
    "resource://gre/modules/Console.sys.mjs"
 | 
						|
  );
 | 
						|
  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:
 | 
						|
export 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
 | 
						|
export 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:
 | 
						|
export 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, 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
 | 
						|
export let gLibs = {};
 | 
						|
 | 
						|
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 -1032 /* JET_errFileAccessDenied */:
 | 
						|
    case -1207 /* JET_errDatabaseLocked */:
 | 
						|
    case -1302 /* JET_errTableLocked */:
 | 
						|
      return "The database or table is locked, error code: " + errorCode;
 | 
						|
    case -1305 /* JET_errObjectNotFound */:
 | 
						|
      return "The table/object was not found.";
 | 
						|
    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) {
 | 
						|
      lazy.log.error("Error calling into ctypes method", methodName, ex);
 | 
						|
      throw ex;
 | 
						|
    }
 | 
						|
    let resultCode = parseInt(rv.toString(10), 10);
 | 
						|
    if (resultCode < 0) {
 | 
						|
      if (errorLog) {
 | 
						|
        lazy.log.error("Got error " + resultCode + " calling " + methodName);
 | 
						|
      }
 | 
						|
      if (shouldThrow) {
 | 
						|
        throw new Error(convertESEError(rv));
 | 
						|
      }
 | 
						|
    } else if (resultCode > 0 && errorLog) {
 | 
						|
      lazy.log.warn("Got warning " + resultCode + " calling " + methodName);
 | 
						|
    }
 | 
						|
    return resultCode;
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
export 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() {
 | 
						|
  lazy.log.debug("Unloading");
 | 
						|
  if (gOpenDBs.size) {
 | 
						|
    lazy.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;
 | 
						|
}
 | 
						|
 | 
						|
export function loadLibraries() {
 | 
						|
  Services.obs.addObserver(unloadLibraries, "xpcom-shutdown");
 | 
						|
  gLibs.ese = ctypes.open("esent.dll");
 | 
						|
  gLibs.kernel = ctypes.open("kernel32.dll");
 | 
						|
  KERNEL.FileTimeToSystemTime = gLibs.kernel.declare(
 | 
						|
    "FileTimeToSystemTime",
 | 
						|
    ctypes.winapi_abi,
 | 
						|
    ctypes.int,
 | 
						|
    KERNEL.FILETIME.ptr,
 | 
						|
    KERNEL.SYSTEMTIME.ptr
 | 
						|
  );
 | 
						|
 | 
						|
  declareESEFunctions();
 | 
						|
}
 | 
						|
 | 
						|
function ESEDB(rootPath, dbPath, logPath) {
 | 
						|
  lazy.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) {
 | 
						|
        console.error(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) {
 | 
						|
      lazy.log.error("Got error " + rv + " calling OpenTableW");
 | 
						|
      throw new Error(convertESEError(rv));
 | 
						|
    }
 | 
						|
 | 
						|
    if (rv > 0) {
 | 
						|
      lazy.log.error("Got warning " + rv + " calling OpenTableW");
 | 
						|
    }
 | 
						|
    ESE.FailSafeCloseTable(this._sessionId, tableId);
 | 
						|
    return true;
 | 
						|
  },
 | 
						|
 | 
						|
  *tableItems(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 {
 | 
						|
        console.error(
 | 
						|
          "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) {
 | 
						|
        console.error(
 | 
						|
          "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) {
 | 
						|
      lazy.log.debug("close db");
 | 
						|
      ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
 | 
						|
      lazy.log.debug("finished close db");
 | 
						|
      this._opened = false;
 | 
						|
    }
 | 
						|
    if (this._attached) {
 | 
						|
      lazy.log.debug("detach db");
 | 
						|
      ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath);
 | 
						|
      this._attached = false;
 | 
						|
    }
 | 
						|
    if (this._sessionCreated) {
 | 
						|
      lazy.log.debug("end session");
 | 
						|
      ESE.FailSafeEndSession(this._sessionId, 0);
 | 
						|
      this._sessionCreated = false;
 | 
						|
    }
 | 
						|
    if (this._instanceCreated) {
 | 
						|
      lazy.log.debug("term");
 | 
						|
      ESE.FailSafeTerm(this._instanceId);
 | 
						|
      this._instanceCreated = false;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  incrementReferenceCounter() {
 | 
						|
    this._references++;
 | 
						|
  },
 | 
						|
 | 
						|
  decrementReferenceCounter() {
 | 
						|
    this._references--;
 | 
						|
    if (this._references <= 0) {
 | 
						|
      this._close();
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
export 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 + "\\");
 | 
						|
  },
 | 
						|
 | 
						|
  async dbLocked(dbFile) {
 | 
						|
    const utils = Cc[
 | 
						|
      "@mozilla.org/profile/migrator/edgemigrationutils;1"
 | 
						|
    ].createInstance(Ci.nsIEdgeMigrationUtils);
 | 
						|
 | 
						|
    const locked = await utils.isDbLocked(dbFile);
 | 
						|
 | 
						|
    if (locked) {
 | 
						|
      console.error(`ESE DB at ${dbFile.path} is locked.`);
 | 
						|
    }
 | 
						|
 | 
						|
    return locked;
 | 
						|
  },
 | 
						|
 | 
						|
  closeDB(db) {
 | 
						|
    db.decrementReferenceCounter();
 | 
						|
  },
 | 
						|
 | 
						|
  COLUMN_TYPES,
 | 
						|
};
 |