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
text/typescript
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));
}
};
}
}