From 0fd61471dfe302cab1039c206e756a25098f0fe7 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Sun, 11 Oct 2026 19:33:51 +0900 Subject: [PATCH 1/2] Some ts warnings fix --- src/Burner.ts | 14 +++++++------- src/MsgReader.ts | 4 ++-- src/Reader.ts | 29 ++++++++++++++++++++++------- src/utils.ts | 11 +++++++---- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/Burner.ts b/src/Burner.ts index 9e0dc92..0fc122c 100644 --- a/src/Burner.ts +++ b/src/Burner.ts @@ -101,7 +101,7 @@ function repeatValue(value: number, count: number): number[] { class LiteFat { sectors: number[]; - constructor(source) { + constructor(source: number[]) { this.sectors = source; } @@ -233,7 +233,7 @@ class LiteBurner { } while (true) { - const remain = (difat2.length & 127); + const remain: number = (difat2.length & 127); if (remain === 0) { break; } @@ -280,7 +280,7 @@ class LiteBurner { const pos = 512 * (1 + entriesFirstSector) + 128 * x; ds.seek(pos); - ds.writeUCS2String(liteEnt.entry.name, null, null); + ds.writeUCS2String(liteEnt.entry.name, undefined, undefined); const numBytesName = ds.position - pos; ds.seek(pos + 0x40); @@ -314,7 +314,7 @@ class LiteBurner { && it.isMini === false ) ) { - const bytes = liteEnt.entry.binaryProvider(); + const bytes = liteEnt.entry.binaryProvider && liteEnt.entry.binaryProvider() || []; ds.seek(512 * (1 + liteEnt.firstSector)); ds.writeUint8Array(bytes); } @@ -325,7 +325,7 @@ class LiteBurner { && it.isMini === true ) ) { - const bytes = liteEnt.entry.binaryProvider(); + const bytes = liteEnt.entry.binaryProvider && liteEnt.entry.binaryProvider() || []; ds.seek(512 * (1 + firstMiniDataSector) + 64 * liteEnt.firstSector); ds.writeUint8Array(bytes); } @@ -387,7 +387,7 @@ class LiteBurner { } // Array.sort is destructive, so copy it by concat() before changing - const children = liteEntry.entry.children.concat(); + const children = (liteEntry.entry.children || []).concat(); if (1 <= children.length) { children.sort( (a, b) => { @@ -401,7 +401,7 @@ class LiteBurner { // ( | 0 ) // ( 0 | 1 ) // ( 0 | 1 2 ) - + // (left, right), returns first right node const split2 = (start: number, end: number, isRed: boolean): number => { if (start < end) { diff --git a/src/MsgReader.ts b/src/MsgReader.ts index 42bca95..af65a38 100644 --- a/src/MsgReader.ts +++ b/src/MsgReader.ts @@ -20,7 +20,7 @@ import CONST from './const' import DataStream from './DataStream' import { CFileSet, CFolder, Reader } from './Reader'; import { burn, Entry } from './Burner'; -import { bin2HexUpper, emptyToNull, msftUuidStringify, toHex2, toHex4 } from './utils'; +import { bin2HexUpper, emptyToUndefined, msftUuidStringify, toHex2, toHex4 } from './utils'; import { parse as entryStreamParser } from './EntryStreamParser'; import { parse as parseVerbStream } from './VerbStreamParser'; import { parse as parseTZDEFINITION, TzDefinition } from './TZDEFINITIONParser'; @@ -1926,7 +1926,7 @@ export default class MsgReader { { propertyObserver: (this.parserConfig?.propertyObserver) || (() => { }), includeRawProps: this.parserConfig?.includeRawProps ? true : false, - ansiEncoding: emptyToNull(this.parserConfig?.ansiEncoding), + ansiEncoding: emptyToUndefined(this.parserConfig?.ansiEncoding), } ); } diff --git a/src/Reader.ts b/src/Reader.ts index 6e15be0..061049f 100644 --- a/src/Reader.ts +++ b/src/Reader.ts @@ -129,13 +129,27 @@ export class Reader { private xbatStart: number; private xbatCount: number; - private batData?: number[]; - private sbatData?: number[]; - private propertyData?: Property[]; + private batData: number[]; + private sbatData: number[]; + private propertyData: Property[]; private bigBlockTable: number[]; constructor(arrayBuffer: ArrayBuffer | DataView) { this.ds = new DataStream(arrayBuffer, 0, DataStream.LITTLE_ENDIAN); + + this.bigBlockSize = 0; + this.bigBlockLength = 0; + this.xBlockLength = 0; + this.batCount = 0; + this.propertyStart = 0; + this.sbatStart = 0; + this.sbatCount = 0; + this.xbatStart = 0; + this.xbatCount = 0; + this.batData = []; + this.sbatData = []; + this.propertyData = []; + this.bigBlockTable = []; } isMSGFile(): boolean { @@ -222,9 +236,9 @@ export class Reader { } nodeProperty.children = []; - const children = [{ currentMode: "walk", currentIndex: nodeProperty.childProperty }]; + const children: { currentMode: string, currentIndex: number }[] = [{ currentMode: "walk", currentIndex: nodeProperty.childProperty }]; while (children.length != 0) { - const { currentMode, currentIndex } = children.pop(); + const { currentMode, currentIndex } = children.pop() || (() => { throw Error(); })(); const current = props[currentIndex]; if (currentMode === "push") { @@ -503,7 +517,8 @@ export class Reader { if (children) { return children .filter(subIndex => propertyData[subIndex].type == TypeEnum.DIRECTORY) - .map(subIndex => this.folderOf(subIndex)); + .map(subIndex => this.folderOf(subIndex)) + .filter(it => it !== null); } return []; }, @@ -528,6 +543,6 @@ export class Reader { * @returns root folder */ rootFolder(): CFolder { - return this.folderOf(0); + return this.folderOf(0)!; } } diff --git a/src/utils.ts b/src/utils.ts index d90e141..297dbec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,3 @@ -import { stringify } from "querystring"; import DataStream from "./DataStream"; import { TransitionSystemTime } from "./TZDEFINITIONParser"; @@ -122,8 +121,8 @@ export function msftUuidStringify(array: ArrayLike, offset: number): str /** * @internal */ -export function emptyToNull(text: string): string { - return (text === "") ? null : text; +export function emptyToUndefined(text: string): string | undefined { + return (text === "") ? undefined : text; } @@ -131,7 +130,11 @@ export function emptyToNull(text: string): string { * @internal */ function padNumber(value: number, maxLen: number): string { - return ("" + value).padStart(maxLen, '0'); + let text = "" + value; + while (text.length < maxLen) { + text = '0' + text; + } + return text; } /** From 966515b85c78b82406da3b036f756b30b1c1b368 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Sun, 11 Oct 2026 21:55:12 +0900 Subject: [PATCH 2/2] Detect and throw errors on possible infinite loop problems report by mark-bruechner https://github.com/HiraokaHyperTools/msgreader/issues/78 The Reader will throw: - `Cyclic property hierarchy detected` - `Cyclic property chain detected` - `Cyclic small block chain detected` - `Cyclic big block chain detected` --- package.json | 3 ++- src/Reader.ts | 17 +++++++++++++++-- src/utils.ts | 15 +++++++++++++++ test3/cyclicDirTree.msg | Bin 0 -> 1536 bytes test3/cyclicFatRing.msg | Bin 0 -> 1536 bytes test3/test.js | 20 ++++++++++++++++++++ yarn.lock | 5 +++++ 7 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 test3/cyclicDirTree.msg create mode 100644 test3/cyclicFatRing.msg create mode 100644 test3/test.js diff --git a/package.json b/package.json index 55e2038..bb24f46 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "tsc", "clean": "rimraf lib", - "test": "npm run mocha test/ test2/", + "test": "npm run mocha test/ test2/ test3/", "prepare": "npm run clean && npm run build && npm run test", "mocha": "set NODE_ENV=test && mocha", "dr": "npm publish --dry-run" @@ -18,6 +18,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/node": "^26.3.0", + "chai": "^6.3.0", "commander": "^15.0.0", "mocha": "^12.0.2", "rimraf": "^6.1.3", diff --git a/src/Reader.ts b/src/Reader.ts index 061049f..762ddd8 100644 --- a/src/Reader.ts +++ b/src/Reader.ts @@ -1,5 +1,5 @@ import DataStream from "./DataStream"; -import { arraysEqual } from "./utils"; +import { arraysEqual, createVisitTester } from "./utils"; import CONST from './const' /** @@ -236,6 +236,7 @@ export class Reader { } nodeProperty.children = []; + const visit = createVisitTester(new Error("Cyclic property hierarchy detected")); const children: { currentMode: string, currentIndex: number }[] = [{ currentMode: "walk", currentIndex: nodeProperty.childProperty }]; while (children.length != 0) { const { currentMode, currentIndex } = children.pop() || (() => { throw Error(); })(); @@ -253,6 +254,7 @@ export class Reader { children.push({ currentMode: "walk", currentIndex: current.nextProperty, }); } + visit(currentIndex); children.push({ currentMode: "push", currentIndex: currentIndex, }); if (current.previousProperty != CONST.MSG.PROP.NO_INDEX) { @@ -266,9 +268,12 @@ export class Reader { private propertyDataReader(propertyStart: number): Property[] { const props: Property[] = []; + const visit = createVisitTester(new Error("Cyclic property chain detected")); + let currentOffset = propertyStart; while (currentOffset != CONST.MSG.END_OF_CHAIN) { + visit(currentOffset); this.convertBlockToProperties(currentOffset, props); currentOffset = this.getNextBlock(currentOffset); } @@ -339,8 +344,10 @@ export class Reader { private sbatDataReader(): number[] { const result = []; let startIndex = this.sbatStart; + const visit = createVisitTester(new Error("Cyclic small block chain detected")); for (let i = 0; i < this.sbatCount && startIndex && startIndex != CONST.MSG.END_OF_CHAIN; i++) { + visit(startIndex); result.push(startIndex); startIndex = this.getNextBlock(startIndex); } @@ -379,8 +386,10 @@ export class Reader { private getChainByBlockSmall(fieldProperty: Property): number[] { const blockChain = []; + const visit = createVisitTester(new Error("Cyclic small block chain detected")); let nextBlockSmall = fieldProperty.startBlock; while (nextBlockSmall != CONST.MSG.END_OF_CHAIN) { + visit(nextBlockSmall); blockChain.push(nextBlockSmall); nextBlockSmall = this.getNextBlockSmall(nextBlockSmall); } @@ -390,8 +399,10 @@ export class Reader { private readBigBlockTable(): number[] { const rootProp = this.propertyData[0]; const table: number[] = []; + const visit = createVisitTester(new Error("Cyclic big block chain detected")); let nextBlock = rootProp.startBlock; - for (let i = 0; nextBlock != CONST.MSG.END_OF_CHAIN; i++) { + for (; nextBlock != CONST.MSG.END_OF_CHAIN;) { + visit(nextBlock); table.push(nextBlock); nextBlock = this.getNextBlock(nextBlock); } @@ -445,6 +456,7 @@ export class Reader { let nextBlock = fieldProperty.startBlock; let remaining = fieldProperty.sizeBlock; let position = 0; + const visit = createVisitTester(new Error("Cyclic big block chain detected")); const resultData = new Uint8Array(fieldProperty.sizeBlock); while (1 <= remaining) { const blockStartOffset = this.getBlockOffsetAt(nextBlock); @@ -454,6 +466,7 @@ export class Reader { resultData.set(part, position); position += partSize; remaining -= partSize; + visit(nextBlock); nextBlock = this.getNextBlock(nextBlock); } return resultData; diff --git a/src/utils.ts b/src/utils.ts index 297dbec..3b564fd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -198,3 +198,18 @@ export function bin2HexUpper(ds: DataStream): string { } return text.toUpperCase(); } + +/** + * @internal + */ +export function createVisitTester(throwErrorOnDuplicate: Error): (value: number) => void { + const set = new Set(); + return (value: number) => { + if (set.has(value)) { + throw throwErrorOnDuplicate; + } + else { + set.add(value); + } + }; +} diff --git a/test3/cyclicDirTree.msg b/test3/cyclicDirTree.msg new file mode 100644 index 0000000000000000000000000000000000000000..73b689a546c8f49b5bb4efde869b7d95654d97bc GIT binary patch literal 1536 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx2!nwD15p0|e-Il+gV;c!Q80u;;4jdv zp!gdFBQpeo81jLzgh7G9l_3vE7co?VvN6#>jDZzgB!EJXSWU>~1+jYYsY8xAeClW= IH8S;R15kn;?8xsx07+4vRokNU9