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

153 lines (132 loc) 7.22 kB
import {Header} from "./Header"; import {DIFAT} from "./alloc/DIFAT"; import {Sectors} from "./Sectors"; import {FAT} from "./alloc/FAT"; import {MiniFAT} from "./alloc/MiniFAT"; import {DirectoryEntryChain} from "./directory/DirectoryEntryChain"; import {CFDataview} from "./dataview/СFDataview"; import {FATtoDIFATFacade} from "./alloc/FATtoDIFATFacade"; import {MiniStreamRW} from "./stream/MiniStreamRW"; import {StreamHolder} from "./stream/StreamHolder"; import {RegularStreamRW} from "./stream/RegularStream"; import {StreamRW} from "./stream/StreamRW"; import {FixedSizeChunkedDataview} from "./dataview/FixedSizeChunkedDataview"; import {ENDOFCHAIN_MARK, ENDOFCHAIN_MARK_INT} from "./utils"; import { DirectoryEntry } from "./directory/DirectoryEntry"; import * as Long from "long"; import "./Long"; import {RootStorageDirectoryEntry} from "./directory/RootStorageDirectoryEntry"; import {StorageDirectoryEntry} from "./directory/StorageDirectoryEntry"; import {StreamDirectoryEntry} from "./directory/StreamDirectoryEntry"; export class CompoundFile { private readonly header: Header; private readonly difat: DIFAT; private readonly sectors: Sectors; private readonly fat: FAT; private readonly miniFat: MiniFAT; private readonly directoryEntryChain: DirectoryEntryChain; private dataView: CFDataview; public static fromBytes(bytes: number[]): CompoundFile { return new CompoundFile(new FixedSizeChunkedDataview(512, bytes)); } public static fromUint8Array(bytes: Uint8Array): CompoundFile { return new CompoundFile(new FixedSizeChunkedDataview(512, [].slice.call(bytes))); } constructor(dataView?: CFDataview) { const emptyFile = dataView == null; if(emptyFile) { dataView = CompoundFile.empty(); } this.dataView = dataView; this.header = new Header(dataView.subView(0, Header.HEADER_LENGTH)); this.sectors = new Sectors(dataView, this.header); const faTtoDIFATFacade = new FATtoDIFATFacade(); this.difat = new DIFAT(this.sectors, this.header, faTtoDIFATFacade); faTtoDIFATFacade.setDifat(this.difat); this.fat = new FAT(this.sectors, this.header, faTtoDIFATFacade); faTtoDIFATFacade.setFat(this.fat); this.miniFat = new MiniFAT(this.sectors, this.header, this.fat); const miniStreamRW = new MiniStreamRW(this.miniFat, this.fat, this.getMiniStreamFirstSectorLocation(), this.getMiniStreamLength(), this.sectors, this.header); const me = this; const listenableMiniStream: StreamRW = { read: (startingSector: number, lengthOrFromIncl: number, toExcl?: number) => miniStreamRW.read(startingSector, lengthOrFromIncl, toExcl), write: (data: number[]) => { const firstSectorLocation = miniStreamRW.write(data); me.setMiniStreamFirstSectorLocation(miniStreamRW.getMiniStreamFirstSectorPosition()); me.setMiniStreamLength(miniStreamRW.getMiniStreamLength()); return firstSectorLocation; }, writeAt: (startingSector: number, position: number, data: number[]) => miniStreamRW.writeAt(startingSector, position, data), append: (startingSector: number, currentSize: number, data: number[]) => { const firstSectorLocation = miniStreamRW.append(startingSector, currentSize, data); me.setMiniStreamFirstSectorLocation(miniStreamRW.getMiniStreamFirstSectorPosition()); me.setMiniStreamLength(miniStreamRW.getMiniStreamLength()); return firstSectorLocation; } }; const streamReader = new StreamHolder( new RegularStreamRW(this.fat, this.sectors, this.header), listenableMiniStream, this.header.getMiniStreamCutoffSize() ); this.directoryEntryChain = new DirectoryEntryChain(this.sectors, this.fat, this.header, streamReader); if(emptyFile) { this.directoryEntryChain.createRootStorage(); } } private static empty(): CFDataview { const dataView = new FixedSizeChunkedDataview(Header.SECTOR_SHIFT_VERSION_3_INT); Header.empty(dataView.allocate(Header.HEADER_LENGTH)); return dataView; } getMiniStreamFirstSectorLocation(): number { if(ENDOFCHAIN_MARK_INT === this.header.getFirstDirectorySectorLocation()) { return ENDOFCHAIN_MARK_INT; } else { return Long.fromBytesLE(this.sectors.sector(this.header.getFirstDirectorySectorLocation()).subView(DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION, DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION + 4).getData()).toNumber(); } } getMiniStreamLength(): number { if(ENDOFCHAIN_MARK_INT === this.header.getFirstDirectorySectorLocation()) { return 0; } else { return Long.fromBytesLE(this.sectors.sector(this.header.getFirstDirectorySectorLocation()).subView(DirectoryEntry.FLAG_POSITION.STREAM_SIZE, DirectoryEntry.FLAG_POSITION.STREAM_SIZE + 4).getData()).toNumber(); } } setMiniStreamFirstSectorLocation(position: number): void { this.sectors.sector(this.header.getFirstDirectorySectorLocation()) .subView(DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION, DirectoryEntry.FLAG_POSITION.STARTING_SECTOR_LOCATION + 4) .writeAt(0, position >= 0 ? Long.fromValue(position).to4BytesLE() : ENDOFCHAIN_MARK); } setMiniStreamLength(size: number): void { this.sectors.sector(this.header.getFirstDirectorySectorLocation()) .subView(DirectoryEntry.FLAG_POSITION.STREAM_SIZE, DirectoryEntry.FLAG_POSITION.STREAM_SIZE + 4) .writeAt(0, Long.fromValue(size).to4BytesLE()); } getRootStorage(): RootStorageDirectoryEntry { return this.directoryEntryChain.getRootStorage(); } asBytes(): number[] { return this.dataView.getData(); } rewrite(): CompoundFile { const copy = new CompoundFile(); const rootStorage = this.getRootStorage(); const rootStorageCopy = copy.getRootStorage(); rootStorage.eachChild(this.copyConsumer(rootStorageCopy)); return copy; } private copyConsumer(this: CompoundFile, parent: StorageDirectoryEntry): (directoryEntry: DirectoryEntry) => void { const consumer = this.copyConsumer.bind(this); return (directoryEntry: DirectoryEntry) => { if(directoryEntry instanceof StorageDirectoryEntry) { const copy = parent.addStorage(directoryEntry.getDirectoryEntryName()); directoryEntry.eachChild(consumer(copy)); } else if (directoryEntry instanceof StreamDirectoryEntry) { parent.addStream(directoryEntry.getDirectoryEntryName(), directoryEntry.getStreamData()); } else { throw new Error('Unsupported object type: ' + (typeof directoryEntry)); } }; } }