UNPKG

compound-binary-file-js

Version:

This is an implementation of [Compound Binary File v.3](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b) \ Allows reading existing files, creation of the/write operation

278 lines (233 loc) 12.1 kB
import {CFDataview} from "../dataview/СFDataview"; import {DirectoryEntryChain} from "./DirectoryEntryChain"; import * as Long from 'long'; import "../Long"; import { addTrailingZeros, ENDOFCHAIN_MARK_INT, FREESECT_MARK_OR_NOSTREAM, initializedWidth, isEndOfChain, isFreeSectOrNoStream, toUTF16Bytes, toUTF16WithNoTrailingZeros } from "../utils"; import {Color} from "../tree/Node"; export enum ColorFlag { RED = 0, BLACK = 1 } export enum ObjectType { Storage = 1, Stream = 2, RootStorage = 5, Unknown = 0 } export function toNodeColor(colorFlag: ColorFlag): Color { return colorFlag === ColorFlag.BLACK ? Color.BLACK : Color.RED; } export function toColorFlag(color: Color): ColorFlag { return color === Color.BLACK ? ColorFlag.BLACK : ColorFlag.RED; } export class DirectoryEntry{ public static readonly ENTRY_LENGTH = 128; public static readonly ENTRY_NAME_MAXIMUM_LENGTH_UTF16_STRING = 31; public static readonly ENTRY_NAME_MAXIMUM_LENGTH = 64; protected view: CFDataview; private objectType: ObjectType; private colorFlag: ColorFlag; private id: number; protected directoryEntryChain: DirectoryEntryChain; static FLAG_POSITION = { DIRECTORY_ENTRY_NAME: 0, DIRECTORY_ENTRY_NAME_LENGTH: 64, OBJECT_TYPE: 66, COLOR_FLAG: 67, LEFT_SIBLING: 68, RIGHT_SIBLING: 72, CHILD: 76, CLSID: 80, STATE_BITS: 96, CREATION_TIME: 100, MODIFY_TIME: 108, STARTING_SECTOR_LOCATION: 116, STREAM_SIZE: 120, }; constructor(id: number, directoryEntryChain: DirectoryEntryChain, view: CFDataview, name?: string, colorFlag?: ColorFlag , objectType?: ObjectType) { this.id = id; this.directoryEntryChain = directoryEntryChain; this.view = view; if(name == null) { if (view.getSize() !== DirectoryEntry.ENTRY_LENGTH) throw new Error(); const nameLength = Long.fromBytesLE(view.subView(DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH, DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH + 2).getData()).toNumber(); if (nameLength < 0 || nameLength > DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH) throw new Error(); this.objectType = view.subView(DirectoryEntry.FLAG_POSITION.OBJECT_TYPE, DirectoryEntry.FLAG_POSITION.OBJECT_TYPE + 1).getData()[0] as ObjectType; this.colorFlag = view.subView(DirectoryEntry.FLAG_POSITION.COLOR_FLAG, DirectoryEntry.FLAG_POSITION.COLOR_FLAG + 1).getData()[0] as ColorFlag; this.setStateBits(initializedWidth(4, 0)); this.setCLSID(initializedWidth(16, 0)); this.setModifiedTime(initializedWidth(8, 0)); this.setCreationTime(initializedWidth(8, 0)); } else { this.setObjectType(objectType); this.setColorFlag(colorFlag); const nameLength = name.length * 2 + 2; if(nameLength < 0 || nameLength > DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH) throw new Error(); this.setDirectoryEntryName(name); this.setLeftSibling(null); this.setRightSibling(null); view.subView(DirectoryEntry.FLAG_POSITION.STREAM_SIZE, DirectoryEntry.FLAG_POSITION.STREAM_SIZE + 8).writeAt(0, Long.fromValue(0).toBytesLE()); this.setStreamStartingSector(ENDOFCHAIN_MARK_INT); } } compareTo(o: DirectoryEntry): number { const result = this.getDirectoryEntryName().length - o.getDirectoryEntryName().length; if(result === 0) { if(this.getDirectoryEntryName().toUpperCase() > o.getDirectoryEntryName().toUpperCase()) { return 1; } else if(this.getDirectoryEntryName().toUpperCase() < o.getDirectoryEntryName().toUpperCase()) { return -1; } else { return 0; } } return result; } setRightSibling(rightSibling: DirectoryEntry): void { if(rightSibling == null) { this.view.subView(DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING, DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING + 4).writeAt(0, FREESECT_MARK_OR_NOSTREAM); } else { this.view.subView(DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING, DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING + 4).writeAt(0, Long.fromValue(rightSibling.getId()).to4BytesLE()); } } static setRightSibling(view: CFDataview, position: number) { view.subView(DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING, DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING + 4).writeAt(0, Long.fromValue(position).to4BytesLE()); } setLeftSibling(leftSibling: DirectoryEntry): void { if(leftSibling == null) { this.view.subView(DirectoryEntry.FLAG_POSITION.LEFT_SIBLING, DirectoryEntry.FLAG_POSITION.LEFT_SIBLING + 4).writeAt(0, FREESECT_MARK_OR_NOSTREAM); } else { this.view.subView(DirectoryEntry.FLAG_POSITION.LEFT_SIBLING, DirectoryEntry.FLAG_POSITION.LEFT_SIBLING + 4).writeAt(0, Long.fromValue(leftSibling.getId()).to4BytesLE()); } } static setLeftSibling(view: CFDataview, position: number) { view.subView(DirectoryEntry.FLAG_POSITION.LEFT_SIBLING, DirectoryEntry.FLAG_POSITION.LEFT_SIBLING + 4).writeAt(0, Long.fromValue(position).to4BytesLE()); } setDirectoryEntryName(name: string): void { if(!name) { throw new Error("Directory Entry name should be non-null and non-empty string"); } if(name.length > DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH_UTF16_STRING) { throw new Error("Directory Entry name may contain 31 UTF-16 at most + NULL terminated character"); } this.view .subView(DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME, DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME + DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH) .writeAt(0, addTrailingZeros(toUTF16Bytes(name), DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH)); const lengthInBytesIncludingTerminatorSymbol = name.length; this.view .subView(DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH, DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH + 2) .writeAt(0, Long.fromValue(lengthInBytesIncludingTerminatorSymbol * 2 + 2).to2BytesLE()); } getId(): number { return this.id; } getDirectoryEntryName(): string { return toUTF16WithNoTrailingZeros( this.view .subView(DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME, DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME + DirectoryEntry.ENTRY_NAME_MAXIMUM_LENGTH) .getData() ); } getDirectoryEntryNameLength(): number { return Long.fromBytesLE(this.view .subView(DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH, DirectoryEntry.FLAG_POSITION.DIRECTORY_ENTRY_NAME_LENGTH + 2).getData() ).toNumber(); } getDirectoryEntryNameLengthUTF8(): number { return (this.getDirectoryEntryNameLength() - 2)/2; } getChild(): DirectoryEntry { const childPosition = this.getChildPosition(); return isFreeSectOrNoStream(childPosition) ? null : this.directoryEntryChain.getEntryById(childPosition); } protected getChildPosition(): number { return DirectoryEntry.getChildPosition(this.view); } static getChildPosition(view: CFDataview): number { return Long.fromBytesLE(view.subView(DirectoryEntry.FLAG_POSITION.CHILD, DirectoryEntry.FLAG_POSITION.CHILD + 4).getData()).toNumber(); } private setObjectType(objectType: ObjectType): void { this.objectType = objectType; this.view .subView(DirectoryEntry.FLAG_POSITION.OBJECT_TYPE, DirectoryEntry.FLAG_POSITION.OBJECT_TYPE +1) .writeAt(0, [objectType]); } public getLeftSibling(): DirectoryEntry { const leftSiblingPosition = this.getLeftSiblingPosition(); return isFreeSectOrNoStream(leftSiblingPosition) || isEndOfChain(leftSiblingPosition) ? null : this.directoryEntryChain.getEntryById(leftSiblingPosition); } getLeftSiblingPosition(): number { return DirectoryEntry.getLeftSiblingPosition(this.view); } static getLeftSiblingPosition(view: CFDataview): number { return Long.fromBytesLE(view.subView(DirectoryEntry.FLAG_POSITION.LEFT_SIBLING, DirectoryEntry.FLAG_POSITION.LEFT_SIBLING + 4).getData()).toNumber(); } getRightSibling(): DirectoryEntry { const rightSiblingPosition = this.getRightSiblingPosition(); return isFreeSectOrNoStream(rightSiblingPosition) ? null : this.directoryEntryChain.getEntryById(rightSiblingPosition); } getRightSiblingPosition(): number { return DirectoryEntry.getRightSiblingPosition(this.view); } static getRightSiblingPosition(view: CFDataview): number { return Long.fromBytesLE(view.subView(DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING, DirectoryEntry.FLAG_POSITION.RIGHT_SIBLING + 4).getData()).toNumber(); } getStreamStartingSector(): number { return Long.fromBytesLE(this.view.subView(DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION, DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION + 4).getData()).toNumber(); } setStreamStartingSector(startingSector: number): void { this.view .subView(DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION, DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION + 4) .writeAt(0, Long.fromValue(startingSector).to4BytesLE()); } traverse(action: (d: DirectoryEntry) => void): void { action(this); this.getLeftSibling()?.traverse(action); this.getRightSibling()?.traverse(action); this.getChild()?.traverse(action); } getObjectType(): ObjectType { return this.objectType; } public getColorFlag(): ColorFlag { return this.colorFlag; } setColorFlag(colorFlag: ColorFlag): void { this.colorFlag = colorFlag; this.view .subView(DirectoryEntry.FLAG_POSITION.COLOR_FLAG, DirectoryEntry.FLAG_POSITION.COLOR_FLAG +1) .writeAt(0, [colorFlag]); } invertColor(): void { this.colorFlag === ColorFlag.BLACK ? this.setColorFlag(ColorFlag.RED) : this.setColorFlag(ColorFlag.BLACK); } setCLSID(bytes: number[]): void { this.view.subView(DirectoryEntry.FLAG_POSITION.CLSID, DirectoryEntry.FLAG_POSITION.CLSID + 16).writeAt(0, bytes); } setStateBits(bytes: number[]): void { this.view.subView(DirectoryEntry.FLAG_POSITION.STATE_BITS, DirectoryEntry.FLAG_POSITION.STATE_BITS + 4).writeAt(0, bytes); } setCreationTime(bytes: number[]): void { this.view.subView(DirectoryEntry.FLAG_POSITION.CREATION_TIME, DirectoryEntry.FLAG_POSITION.CREATION_TIME + 8).writeAt(0, bytes); } setModifiedTime(bytes: number[]): void { this.view.subView(DirectoryEntry.FLAG_POSITION.MODIFY_TIME, DirectoryEntry.FLAG_POSITION.MODIFY_TIME + 8).writeAt(0, bytes); } getCLSID(): number[] { return this.view.subView(DirectoryEntry.FLAG_POSITION.CLSID, DirectoryEntry.FLAG_POSITION.CLSID + 16).getData(); } getStateBits(): number[] { return this.view.subView(DirectoryEntry.FLAG_POSITION.STATE_BITS, DirectoryEntry.FLAG_POSITION.STATE_BITS + 4).getData(); } getCreationTime(): number[] { return this.view.subView(DirectoryEntry.FLAG_POSITION.CREATION_TIME, DirectoryEntry.FLAG_POSITION.CREATION_TIME + 8).getData(); } getModifiedTime(): number[] { return this.view.subView(DirectoryEntry.FLAG_POSITION.MODIFY_TIME, DirectoryEntry.FLAG_POSITION.MODIFY_TIME + 8).getData(); } }