UNPKG

@regrapes/access-db-parser

Version:

A pure javascript Microsoft AccessDB files (.mdb, .accdb) parser

813 lines (801 loc) 32.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var createDebug = require('debug'); var util = require('util'); var binaryParser = require('binary-parser'); var UUID = require('uuid'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var createDebug__default = /*#__PURE__*/_interopDefaultLegacy(createDebug); var UUID__default = /*#__PURE__*/_interopDefaultLegacy(UUID); const originalArray = binaryParser.Parser.prototype.array; binaryParser.Parser.prototype.array = function (varName, options) { if (options.length === 0) { return this.setNextParser('array', varName, options); } return originalArray.call(this, varName, options); }; const ACCESSHEADER = new binaryParser.Parser() .seek(4) .string('jetString', { zeroTerminated: true, }) .uint32le('jetVersion') .seek(126); const MEMO = new binaryParser.Parser().uint32le('memoLength').uint32le('recordPointer').uint32le('memoUnknown').saveOffset('memoEnd'); const VERSION_3_FLAGS = new binaryParser.Parser() .bit1('hyperlink') .bit1('autoGUID') .bit1('unk1') .bit1('replication') .bit1('unk2') .bit1('autonumber') .bit1('canBeNull') .bit1('fixedLength'); const VERSION_4_FLAGS = new binaryParser.Parser() .bit1('hyperlink') .bit1('autoGUID') .bit1('unk1') .bit1('replication') .bit1('unk2') .bit1('autonumber') .bit1('canBeNull') .bit1('fixedLength') .bit1('unk3') .bit1('unk4') .bit1('unk5') .bit1('modernPackageType') .bit1('unk6') .bit1('unk7') .bit1('unk8') .bit1('compressedUnicode'); const TDEF_HEADER = new binaryParser.Parser() .seek(2) .uint16le('peekVersion') .seek(-2) .uint16le('tdefVer') .uint32le('nextPagePtr') .saveOffset('headerEnd'); const parseTableHead = (buffer, version = 3) => new binaryParser.Parser() .nest('TDEF_header', { type: TDEF_HEADER }) .uint32le('tableDefinitionLength') // Conditional .uint32le('ver4Unknown') .seek(version > 3 ? 0 : -4) .uint32le('numberOfRows') .uint32le('autonumber') // Conditional .uint32le('autonumberIncrement') .seek(version > 3 ? 0 : -4) // Conditional .uint32le('complexAutonumber') .seek(version > 3 ? 0 : -4) // Conditional .uint32le('ver4Unknown1') .seek(version > 3 ? 0 : -4) // Conditional .uint32le('ver4Unknown2') .seek(version > 3 ? 0 : -4) .uint8('tableTypeFlags') .uint16le('nextColumnID') .uint16le('variableColumns') .uint16le('columnCount') .uint32le('indexCount') .uint32le('realIndexCount') .uint32le('rowPageMap') .uint32le('freeSpacePageMap') .saveOffset('tDefHeaderEnd') .parse(buffer); const parseTableData = (buffer, realIndexCount, columnCount, version = 3) => { const REAL_INDEX = new binaryParser.Parser() .uint32le('unk1') .uint32le('indexRowCount') .seek(version > 3 ? 0 : -4) // Conditional .uint32le('ver4AlwaysZero'); const VARIOUS_TEXT_V3 = new binaryParser.Parser().uint16le('LCID').uint16le('codePage').uint16le('variousText3Unknown'); const VARIOUS_TEXT_V4 = new binaryParser.Parser().uint16le('collation').uint8('variousText4Unknown').uint8('collationVersionNumber'); const VARIOUS_TEXT = version === 3 ? VARIOUS_TEXT_V3 : VARIOUS_TEXT_V4; const VARIOUS_DEC_V3 = new binaryParser.Parser() .uint16le('variousDec3Unknown') .uint8('maxNumberOfDigits') .uint8('numberOfDecimal') .uint16le('variousDec3Unknown2'); const VARIOUS_DEC_V4 = new binaryParser.Parser().uint8('maxNumOfDigits').uint8('numOfDecimalDigits').uint16le('variousDec4Unknown'); const VARIOUS_DEC = version === 3 ? VARIOUS_DEC_V3 : VARIOUS_DEC_V4; const COLUMN = new binaryParser.Parser() .uint8('type') // Conditional .uint32le('ver4Unknown3') .seek(version > 3 ? 0 : -4) .uint16le('columnID') .uint16le('variableColumnNumber') .uint16le('columnIndex') .choice('various', { tag: 'type', choices: { 1: VARIOUS_DEC, 2: VARIOUS_DEC, 3: VARIOUS_DEC, 4: VARIOUS_DEC, 5: VARIOUS_DEC, 6: VARIOUS_DEC, 7: VARIOUS_DEC, 8: VARIOUS_DEC, 9: VARIOUS_TEXT, 10: VARIOUS_TEXT, 11: VARIOUS_TEXT, 12: VARIOUS_TEXT, }, defaultChoice: new binaryParser.Parser().seek(version === 3 ? 6 : 4), }) .choice('columnFlags', { // eslint-disable-next-line no-new-func tag: new Function(`return ${version === 3 ? 1 : 0}`), choices: { 1: VERSION_3_FLAGS, 0: VERSION_4_FLAGS, }, }) // Conditional .uint32le('ver4Unknown4') .seek(version > 3 ? 0 : -4) .uint16le('fixedOffset') .uint16le('length'); const COLUMN_NAMES_V3 = new binaryParser.Parser().uint8('colNamesLen').string('colNameStr', { length: 'colNamesLen', encoding: 'utf8', stripNull: true, }); const COLUMN_NAMES_V4 = new binaryParser.Parser().uint16le('colNamesLen').buffer('colNameStr', { length: 'colNamesLen', }); // .string("colNameStr", { // length: "colNamesLen", // encoding: "utf16", // stripNull: true, // }); const COLUMN_NAMES = version === 3 ? COLUMN_NAMES_V3 : COLUMN_NAMES_V4; const res = new binaryParser.Parser() .array('readIndex', { length: realIndexCount, type: REAL_INDEX, }) .array('column', { length: columnCount, type: COLUMN, }) .array('columnNames', { length: columnCount, type: COLUMN_NAMES, }) .parse(buffer); if (version !== 3) { for (const columnName of res.columnNames) { const buffer = columnName.colNameStr; columnName.colNameStr = new util.TextDecoder('utf-16le').decode(buffer); } } return res; }; const parseDataPageHeader = (buffer, version = 3) => new binaryParser.Parser() .seek(2) .uint16le('dataFreeSpace') .uint32le('owner') .seek(version > 3 ? 0 : -4) // Conditional .uint32le('ver4UnknownData') .uint16le('recordCount') .array('recordOffsets', { length: 'recordCount', type: 'uint16le', }) .parse(buffer); const parseRelativeObjectMetadataStruct = (buffer, variableJumpTablesCNT = 0, version = 3) => { if (version === 3) { return new binaryParser.Parser() .uint8('variableLengthFieldCount') .array('variableLengthJumpTable', { length: variableJumpTablesCNT, type: 'uint8', }) .array('variableLengthFieldOffsets', { length() { return this.variableLengthFieldCount; }, type: 'uint8', }) .uint8('varLenCount') .saveOffset('relativeMetadataEnd') .parse(buffer); } else { const part1 = new binaryParser.Parser() .uint16le('variableLengthFieldCount') .array('variableLengthJumpTable', { length: variableJumpTablesCNT, type: 'uint8', }) .saveOffset('part2StartOffset') .parse(buffer); const part2 = new binaryParser.Parser() .array('variableLengthFieldOffsets', { length: (part1.variableLengthFieldCount & 0xff) >>> 0, type: 'uint16le', }) .uint16le('varLenCount') .saveOffset('relativeMetadataEnd') .parse(buffer.slice(part1.part2StartOffset)); const result = Object.assign(Object.assign({}, part1), part2); return result; } }; var DataType; (function (DataType) { DataType[DataType["Boolean"] = 1] = "Boolean"; DataType[DataType["Int8"] = 2] = "Int8"; DataType[DataType["Int16"] = 3] = "Int16"; DataType[DataType["Int32"] = 4] = "Int32"; DataType[DataType["Money"] = 5] = "Money"; DataType[DataType["Float32"] = 6] = "Float32"; DataType[DataType["Float64"] = 7] = "Float64"; DataType[DataType["DateTime"] = 8] = "DateTime"; DataType[DataType["Binary"] = 9] = "Binary"; DataType[DataType["Text"] = 10] = "Text"; DataType[DataType["OLE"] = 11] = "OLE"; DataType[DataType["Memo"] = 12] = "Memo"; DataType[DataType["GUID"] = 15] = "GUID"; DataType[DataType["Bit96Bytes17"] = 16] = "Bit96Bytes17"; DataType[DataType["Complex"] = 18] = "Complex"; })(DataType || (DataType = {})); const TABLE_PAGE_MAGIC = Buffer.from([0x02, 0x01]); const DATA_PAGE_MAGIC = Buffer.from([0x01, 0x01]); const BOMS = [Buffer.from([0xfe, 0xff]), Buffer.from([0xff, 0xfe])]; const parseType = (dataType, buffer, length, version = 3, textEncoding = 'utf8', sanitizeTextBuffer = (buffer) => buffer) => { switch (dataType) { case DataType.Int8: { return buffer.readInt8(0); } case DataType.Int16: { return buffer.readInt16LE(0); } case DataType.Int32: case DataType.Complex: { return buffer.readInt32LE(0); } case DataType.Float32: { return buffer.readFloatLE(0); } case DataType.Float64: { return buffer.readDoubleLE(0); } case DataType.Money: { return buffer.readUInt32LE(0) + buffer.readUInt32LE(4) * Math.pow(0x10, 8); } case DataType.DateTime: { const daysPassed = Math.floor(buffer.readDoubleLE(0)); // ms access expresses hours in decimals const hoursPassedDecimal = buffer.readDoubleLE(0) % 1; const hours = Math.floor(hoursPassedDecimal * 24); const minutes = Math.floor(((hoursPassedDecimal * 24) % 1) * 60); const seconds = Math.ceil(((((hoursPassedDecimal * 24) % 1) * 60) % 1) * 60); const date = new Date(Date.UTC(1899, 11, 30)); date.setUTCHours(12, 0, 0, 0); date.setUTCDate(date.getUTCDate() + daysPassed); date.setUTCHours(hours, minutes, seconds); return date; } case DataType.Binary: { return buffer.slice(0, length).toString(textEncoding); // Maybe } case DataType.GUID: { return UUID__default["default"].stringify(buffer.slice(0, 16)); } case DataType.Bit96Bytes17: { return buffer.slice(0, 17).toString(textEncoding); // Maybe } case DataType.Text: { if (version > 3) { const sanitzedBuffer = sanitizeTextBuffer(buffer); if (sanitzedBuffer.slice(0, 2).compare(BOMS[0]) === 0 || sanitzedBuffer.slice(0, 2).compare(BOMS[1]) === 0) { return new util.TextDecoder(textEncoding, { ignoreBOM: true }).decode(sanitzedBuffer.slice(2)); } return new util.TextDecoder('utf-16le').decode(buffer); } return buffer.toString(textEncoding); } default: { return null; } } }; const categorizePages = (dbData, pageSize) => { if (dbData.length % pageSize) throw new Error(`DB is not full or pageSize is wrong. pageSize: ${pageSize} dbData.length: ${dbData.length}`); const pages = {}; for (let i = 0; i < dbData.length; i += pageSize) pages[i] = dbData.slice(i, i + pageSize); const dataPages = {}; const tableDefs = {}; for (const page of Object.keys(pages)) { const comp1 = Buffer.compare(DATA_PAGE_MAGIC, pages[page].slice(0, DATA_PAGE_MAGIC.length)) === 0; const comp2 = Buffer.compare(TABLE_PAGE_MAGIC, pages[page].slice(0, TABLE_PAGE_MAGIC.length)) === 0; if (comp1) dataPages[page] = pages[page]; else if (comp2) tableDefs[page] = pages[page]; } return [tableDefs, dataPages, pages]; }; const debug = createDebug__default["default"]('access-db-parser:AccessTable'); class AccessTable { constructor(table, version, pageSize, dataPages, tableDefs, textEncoding, sanitizeTextBuffer) { this.table = table; this.version = version; this.pageSize = pageSize; this.dataPages = dataPages; this.tableDefs = tableDefs; this.textEncoding = textEncoding; this.sanitizeTextBuffer = sanitizeTextBuffer; this.parsedTable = {}; [this.columns, this.tableHeader] = this.getTableColumns(); } getTableColumns() { let tableHeader; let colNames; let columns; try { tableHeader = parseTableHead(this.table.value, this.version); let mergedData = this.table.value.slice(tableHeader.tDefHeaderEnd); if (tableHeader.TDEF_header.nextPagePtr) { mergedData = Buffer.concat([mergedData, this.mergeTableData(tableHeader.TDEF_header.nextPagePtr)]); } const parsedData = parseTableData(mergedData, tableHeader.realIndexCount, tableHeader.columnCount, this.version); columns = parsedData.column; colNames = parsedData.columnNames; // REMOVE FOR NOW // (tableHeader as any).column = parsedData.column; // (tableHeader as any).columnNames = parsedData.columnNames; } catch (err) { throw new Error(`Failed to parse table header`); } // const colNames = tableHeader.columnNames; // const columns = tableHeader.column; columns.forEach((column, index) => { column.colNameStr = colNames[index].colNameStr; }); const offset = Math.min(...columns.map(c => c.columnIndex)); const columnDict = {}; for (const x of columns) columnDict[x.columnIndex - offset] = x; if (Object.keys(columnDict).length !== columns.length) { for (const x of columns) columnDict[x.columnID] = x; } if (Object.keys(columnDict).length !== tableHeader.columnCount) throw new Error(`Expected ${tableHeader.columnCount} columns got ${Object.keys(columnDict).length}`); return [columnDict, tableHeader]; } mergeTableData(firstPage) { let table = this.tableDefs[firstPage * this.pageSize]; let parsedHeader = TDEF_HEADER.parse(table); let data = table.slice(parsedHeader.headerEnd); while (parsedHeader.nextPagePtr) { table = this.tableDefs[parsedHeader.nextPagePtr * this.pageSize]; parsedHeader = TDEF_HEADER.parse(table); data = Buffer.concat([data, table.slice(parsedHeader.headerEnd)]); } return data; } createEmptyTable() { const parsedTable = {}; const [columns] = this.getTableColumns(); for (const i of Object.keys(columns)) { const column = columns[i]; parsedTable[column.colNameStr] = []; } return parsedTable; } getOverflowRecord(recordPointer) { const recordOffset = (recordPointer & 0xff) >>> 0; const pageNum = recordPointer >>> 8; const recordPage = this.dataPages[pageNum * this.pageSize]; if (!recordPage) return; const parsedData = parseDataPageHeader(recordPage, this.version); if (recordOffset > parsedData.recordOffsets.length) return; let start = parsedData.recordOffsets[recordOffset]; if ((start & 0x8000) >>> 0) start = (start & 0xfff) >>> 0; else debug(`Overflow record flag is not present ${start}`); let record; if (recordOffset === 0) { record = recordPage.slice(start); } else { let end = parsedData.recordOffsets[recordOffset - 1]; if (end & 0x8000 && (end & 0xff) !== 0) { end &= 0xfff; } record = recordPage.slice(start, end); } return record; } parseFixedLengthData(originalRecord, column, nullTable) { const columnName = column.colNameStr; let parsedType; if (column.type === DataType.Boolean) { if (column.columnID > nullTable.length) throw new Error(`Failed to parse bool field, Column not found in nullTable column: ${columnName}, column id: ${column.columnID}, nullTable: ${nullTable}`); parsedType = nullTable[column.columnID]; } else { if (column.fixedOffset > originalRecord.length) throw new Error(`Column offset is bigger than the length of the record ${column.fixedOffset}`); const record = originalRecord.slice(column.fixedOffset); parsedType = parseType(column.type, record, this.version, undefined, this.textEncoding, this.sanitizeTextBuffer); } if (this.parsedTable[columnName] === undefined) this.parsedTable[columnName] = []; this.parsedTable[columnName].push(parsedType); return parsedType; } parseDynamicLengthRecordsMetadata(reverseRecord, originalRecord, nullTableLength) { if (this.version > 3) { reverseRecord = reverseRecord.slice(nullTableLength + 1); if (reverseRecord.length > 1 && reverseRecord[0] === 0) reverseRecord = reverseRecord.slice(1); return parseRelativeObjectMetadataStruct(reverseRecord, undefined, this.version); } const variableLengthJumpTableCNT = Math.floor((originalRecord.length - 1) / 256); reverseRecord = reverseRecord.slice(nullTableLength); let relativeRecordMetadata; try { relativeRecordMetadata = parseRelativeObjectMetadataStruct(reverseRecord, variableLengthJumpTableCNT, this.version); relativeRecordMetadata.relativeMetadataEnd += nullTableLength; } catch (_a) { throw new Error('Failed parsing record'); } if (relativeRecordMetadata && relativeRecordMetadata.variableLengthFieldCount !== this.tableHeader.variableColumns) { const tmpBuffer = Buffer.allocUnsafe(2); tmpBuffer.writeUInt16LE(this.tableHeader.variableColumns); const metadataStart = reverseRecord.indexOf(tmpBuffer); if (metadataStart !== 1 && metadataStart < 10) { reverseRecord = reverseRecord.slice(metadataStart); try { relativeRecordMetadata = parseRelativeObjectMetadataStruct(reverseRecord, variableLengthJumpTableCNT, this.version); } catch (_b) { throw new Error(`Failed to parse record metadata: ${originalRecord}`); } relativeRecordMetadata.relativeMetadataEnd += metadataStart; } else { debug(`Record did not parse correctly. Number of columns: ${this.tableHeader.variableColumns}. Number of parsed columns: ${relativeRecordMetadata.variableLengthFieldCount}`); return; } } return relativeRecordMetadata; } parseMemo(relativeObjData) { debug(`Parsing memo field ${relativeObjData}`); const parsedMemo = MEMO.parse(relativeObjData); let memoData; let memoType; if (parsedMemo.memoLength & 0x80000000) { debug('Memo data inline'); memoData = relativeObjData.slice(parsedMemo.memoEnd); memoType = DataType.Text; } else if (parsedMemo.memoLength & 0x40000000) { debug('LVAL type 1'); const tmp = this.getOverflowRecord(parsedMemo.recordPointer); if (tmp === undefined) { throw new Error('LVAL type 1 memoData is undefined'); } memoData = tmp; memoType = DataType.Text; } else { debug('LVAL type 2'); const dataBlocks = []; let { recordPointer } = parsedMemo; while (recordPointer) { const record = this.getOverflowRecord(recordPointer); if (record === undefined) { throw new Error('LVAL type 2 memoData is undefined'); } dataBlocks.push(record.subarray(4)); recordPointer = record.readInt32LE(); } memoData = Buffer.concat(dataBlocks); memoType = DataType.Text; } return parseType(memoType, memoData, memoData.length, this.version, this.textEncoding, this.sanitizeTextBuffer); } parseDynamicLengthData(originalRecord, relativeRecordMetadata, relativeRecordsColumnMap) { const relativeOffsets = relativeRecordMetadata.variableLengthFieldOffsets; let jumpTableAddition = 0; let i = -1; for (const columnIndex of Object.keys(relativeRecordsColumnMap)) { i += 1; const column = relativeRecordsColumnMap[columnIndex]; const colName = column.colNameStr; if (this.version === 3) { if (relativeRecordMetadata.variableLengthJumpTable.includes(i)) jumpTableAddition = (jumpTableAddition + 0x100) >>> 0; } let relStart = relativeOffsets[i]; let relEnd; if (i + 1 === relativeOffsets.length) relEnd = relativeRecordMetadata.varLenCount; else relEnd = relativeOffsets[i + 1]; if (this.version > 3) { if (relEnd > originalRecord.length) relEnd = (relEnd & 0xff) >>> 0; if (relStart > originalRecord.length) relStart = (relStart & 0xff) >>> 0; } if (relStart === relEnd) { if (this.parsedTable[colName] === undefined) this.parsedTable[colName] = []; this.parsedTable[colName].push(''); continue; } const relativeObjData = originalRecord.slice(relStart + jumpTableAddition, relEnd + jumpTableAddition); let parsedType; if (column.type === DataType.Memo) { try { parsedType = this.parseMemo(relativeObjData); } catch (_a) { debug(`Failed to parse memo field. Using data as bytes`); parsedType = relativeObjData.toString(); } } else { parsedType = parseType(column.type, relativeObjData, relativeObjData.length, this.version, this.textEncoding, this.sanitizeTextBuffer); } if (this.parsedTable[colName] === undefined) this.parsedTable[colName] = []; this.parsedTable[colName].push(parsedType); } } parseRow(record) { const originalRecord = Buffer.allocUnsafe(record.length); record.copy(originalRecord); let reverseRecord = Buffer.allocUnsafe(record.length); record.copy(reverseRecord); reverseRecord = reverseRecord.reverse(); const nullTableLen = Math.floor((this.tableHeader.columnCount + 7) / 8); const nullTable = []; if (nullTableLen && nullTableLen < originalRecord.length) { const nullTableBuffer = record.slice(nullTableLen === 0 ? 0 : record.length - nullTableLen); debug(record.slice(record.length - nullTableLen)); for (let i = 0; i < nullTableBuffer.length; i++) { const byte = nullTableBuffer[i]; for (let j = 0; j < 8; j++) { if ((byte & (1 << j)) === 0) nullTable.push(false); else nullTable.push(true); } } } else { debug(`Failed to parse null table column count ${this.tableHeader.columnCount}`); return; } if (this.version > 3) record = record.slice(2); else record = record.slice(1); const relativeRecordsColumnMap = {}; for (const i of Object.keys(this.columns)) { const column = this.columns[i]; if (!column.columnFlags.fixedLength) { relativeRecordsColumnMap[i] = column; continue; } this.parseFixedLengthData(record, column, nullTable); } if (relativeRecordsColumnMap) { const metadata = this.parseDynamicLengthRecordsMetadata(reverseRecord, originalRecord, nullTableLen); if (metadata === undefined) return; this.parseDynamicLengthData(originalRecord, metadata, relativeRecordsColumnMap); } } parse() { if (!this.table.linkedPages) return this.createEmptyTable(); for (const dataChunk of this.table.linkedPages) { const originalData = dataChunk; const parsedData = parseDataPageHeader(originalData, this.version); let lastOffset; for (const recOffset of parsedData.recordOffsets) { if ((recOffset & 0x8000) >>> 0) { lastOffset = (recOffset & 0xfff) >>> 0; continue; } if ((recOffset & 0x4000) >>> 0) { const recPtrOffset = (recOffset & 0xfff) >>> 0; lastOffset = recPtrOffset; const overflowRecPtrBuffer = originalData.slice(recPtrOffset, recPtrOffset + 4); const overflowRecPtr = overflowRecPtrBuffer.readUInt32LE(0); const record = this.getOverflowRecord(overflowRecPtr); if (record !== undefined) this.parseRow(record); continue; } let record; if (!lastOffset) record = originalData.slice(recOffset); else record = originalData.slice(recOffset, lastOffset); lastOffset = recOffset; if (record) this.parseRow(record); } } return this.parsedTable; } } class TableObject { constructor(_offset, value) { this.linkedPages = []; this.value = value; this.linkedPages = []; } } var ALL_VERSIONS; (function (ALL_VERSIONS) { ALL_VERSIONS[ALL_VERSIONS["VERSION_3"] = 3] = "VERSION_3"; ALL_VERSIONS[ALL_VERSIONS["VERSION_4"] = 4] = "VERSION_4"; ALL_VERSIONS[ALL_VERSIONS["VERSION_5"] = 5] = "VERSION_5"; ALL_VERSIONS[ALL_VERSIONS["VERSION_2010"] = 2010] = "VERSION_2010"; })(ALL_VERSIONS || (ALL_VERSIONS = {})); const PAGE_SIZE_V3 = 0x800; const PAGE_SIZE_V4 = 0x1000; // Versions const VERSION_3 = 0x00; const VERSION_4 = 0x01; const VERSION_5 = 0x02; const VERSION_2010 = 0x03; const NEW_VERSIONS = [VERSION_4, VERSION_5, VERSION_2010]; const SYSTEM_TABLE_FLAGS = [-0x80000000, -0x00000002, 0x80000000, 0x00000002]; class AccessParser { constructor(dbData, textEncoding = 'utf8', sanitizeTextBuffer) { this.dbData = dbData; this.textEncoding = textEncoding; this.sanitizeTextBuffer = sanitizeTextBuffer; this.version = ALL_VERSIONS.VERSION_3; this.pageSize = PAGE_SIZE_V3; this.parseFileHeader(); [this.tableDefs, this.dataPages /* this.allPages */] = categorizePages(this.dbData, this.pageSize); this.tablesWithData = this.linkTablesToData(); this.catalog = this.parseCatalog(); } parseFileHeader() { let head; try { head = ACCESSHEADER.parse(this.dbData); } catch (_a) { throw new Error('Failed to parse DB file header. Check it is a valid file header'); } const version = head.jetVersion; if (NEW_VERSIONS.includes(version)) { if (version === VERSION_4) this.version = ALL_VERSIONS.VERSION_4; else if (version === VERSION_5) this.version = ALL_VERSIONS.VERSION_5; else if (version === VERSION_2010) this.version = ALL_VERSIONS.VERSION_2010; this.pageSize = PAGE_SIZE_V4; } else if (version !== VERSION_3) { throw new Error(`Unknown database version ${version} Trying to parse database as version 3`); } } linkTablesToData() { const tablesWithData = {}; for (const i of Object.keys(this.dataPages)) { const data = this.dataPages[i]; let parsedDP; try { parsedDP = parseDataPageHeader(data, this.version); } catch (_a) { console.error(`Failed to parse data page ${data}`); continue; } const pageOffset = parsedDP.owner * this.pageSize; if (Object.keys(this.tableDefs) .map(str => parseInt(str, 10)) .includes(pageOffset)) { const tablePageValue = this.tableDefs[pageOffset]; if (!Object.keys(tablesWithData).includes(pageOffset.toString())) tablesWithData[pageOffset] = new TableObject(pageOffset, tablePageValue); tablesWithData[pageOffset].linkedPages.push(data); } } return tablesWithData; } parseCatalog() { const catalogPage = this.tablesWithData[2 * this.pageSize]; const accessTable = new AccessTable(catalogPage, this.version, this.pageSize, this.dataPages, this.tableDefs, this.textEncoding, this.sanitizeTextBuffer); const catalog = accessTable.parse(); const tablesMapping = {}; let i = -1; const names = catalog.Name; const types = catalog.Type; const flags = catalog.Flags; const ids = catalog.Id; if (names === undefined || types === undefined || flags === undefined || ids === undefined) throw new Error('The catalog is missing required fields'); for (const tableName of names) { if (typeof tableName !== 'string') continue; i += 1; const tableType = 1; if (types[i] === tableType) { if (!SYSTEM_TABLE_FLAGS.includes(flags[i]) && flags[i] === 0) { // TODO: CHECK IF 0 IS THE RIGHT FLAG TO SET // console.log(tableName); // console.log(flags[i]); tablesMapping[tableName] = ids[i]; } } } return tablesMapping; } parseTableUnformatted(tableName) { let tableOffset = this.catalog[tableName]; if (tableOffset === undefined) throw new Error(`Could not find table ${tableName} in Database`); tableOffset *= this.pageSize; const table = this.tablesWithData[tableOffset]; if (table === undefined) { const tableDef = this.tableDefs[tableOffset]; if (tableDef === undefined) { throw new Error(`Could not find table ${tableName} offset ${tableOffset}`); } else { throw new Error('Empty table'); // table = new TableObject(tableOffset, tableDef); } } const accessTable = new AccessTable(table, this.version, this.pageSize, this.dataPages, this.tableDefs, this.textEncoding, this.sanitizeTextBuffer); return accessTable.parse(); } parseTable(name) { const table = this.parseTableUnformatted(name); const fields = Object.keys(table); if (fields.length === 0) { return []; } const linesNumber = table[fields[0]].length; const lines = []; for (let i = 0; i < linesNumber; ++i) { const line = {}; for (const field of fields) { line[field] = table[field][i]; } lines.push(line); } return lines; } getTables() { return Object.keys(this.catalog); } getVersion() { return this.version; } } exports.AccessParser = AccessParser; //# sourceMappingURL=index.cjs.js.map