From 91e7812fb23c59f87dc79dd5798edf9b61aad422 Mon Sep 17 00:00:00 2001 From: Alakh Chandarana <36898190+alakhpc@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:45:18 -0500 Subject: [PATCH] fix --- src/input-format.ts | 5 +- src/matroska/ebml.ts | 108 +++++++++++++++++++++++-------- src/matroska/matroska-demuxer.ts | 4 +- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/input-format.ts b/src/input-format.ts index 1748f27..1c0e292 100644 --- a/src/input-format.ts +++ b/src/input-format.ts @@ -154,10 +154,11 @@ export class MatroskaInputFormat extends InputFormat { return false; } - const dataSize = readElementSize(headerSlice); - if (dataSize === null) { + const dataSizeResult = readElementSize(headerSlice); + if (dataSizeResult === null || dataSizeResult.size === null) { return false; // Miss me with that shit } + const dataSize = dataSizeResult.size; let dataSlice = input._reader.requestSlice(headerSlice.filePos, dataSize); if (dataSlice instanceof Promise) dataSlice = await dataSlice; diff --git a/src/matroska/ebml.ts b/src/matroska/ebml.ts index eb27c40..0df632d 100644 --- a/src/matroska/ebml.ts +++ b/src/matroska/ebml.ts @@ -470,6 +470,11 @@ export const MIN_HEADER_SIZE = 2; // 1-byte ID and 1-byte size export const MAX_HEADER_SIZE = 2 * MAX_VAR_INT_SIZE; // 8-byte ID and 8-byte size export const readVarIntSize = (slice: FileSlice) => { + // Check if we have at least one byte to read + if (slice.remainingLength < 1) { + return null; + } + const firstByte = readU8(slice); slice.skip(-1); @@ -484,10 +489,20 @@ export const readVarIntSize = (slice: FileSlice) => { mask >>= 1; } + // Check if we have enough bytes to read the full varint + if (slice.remainingLength < width) { + return null; + } + return width; }; export const readVarInt = (slice: FileSlice) => { + // Check if we have at least one byte + if (slice.remainingLength < 1) { + return null; + } + // Read the first byte to determine the width of the variable-length integer const firstByte = readU8(slice); @@ -503,6 +518,13 @@ export const readVarInt = (slice: FileSlice) => { mask >>= 1; } + // Check if we have enough bytes remaining for the full varint + if (slice.remainingLength < width - 1) { + // Not enough bytes, rewind and return null + slice.skip(-1); + return null; + } + // First byte's value needs the marker bit cleared let value = firstByte & (mask - 1); @@ -567,37 +589,69 @@ export const readElementId = (slice: FileSlice) => { return id; }; -export const readElementSize = (slice: FileSlice) => { - let size: number | null = readU8(slice); - - if (size === 0xff) { - size = null; - } else { - slice.skip(-1); - size = readVarInt(slice); - - // In some (livestreamed) files, this is the value of the size field. While this technically is just a very - // large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We - // catch the number here. Note that it cannot be perfectly represented as a double, but the comparison works - // nonetheless. - // eslint-disable-next-line no-loss-of-precision - if (size === 0x00ffffffffffffff) { - size = null; - } - } - - return size; -}; - -export const readElementHeader = (slice: FileSlice) => { - const id = readElementId(slice); - if (id === null) { +/** + * Reads the size field of an EBML element. + * + * @returns + * - `{ size: number }` - Successfully read a definite size + * - `{ size: null }` - Successfully read an undefined size (0xFF marker, used in streaming) + * - `null` - Couldn't read (insufficient bytes or invalid data) + */ +export const readElementSize = (slice: FileSlice): { size: number | null } | null => { + // Need at least 1 byte to read the size + if (slice.remainingLength < 1) { return null; } - const size = readElementSize(slice); + const firstByte = readU8(slice); - return { id, size }; + if (firstByte === 0xff) { + // Legitimate undefined size marker + return { size: null }; + } + + slice.skip(-1); + const size = readVarInt(slice); + + if (size === null) { + // Couldn't read the varint (insufficient bytes or invalid) + return null; + } + + // In some (livestreamed) files, this is the value of the size field. While this technically is just a very + // large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We + // catch the number here. Note that it cannot be perfectly represented as a double, but the comparison works + // nonetheless. + // eslint-disable-next-line no-loss-of-precision + if (size === 0x00ffffffffffffff) { + return { size: null }; + } + + return { size }; +}; + +export const readElementHeader = (slice: FileSlice) => { + // We need at least MIN_HEADER_SIZE bytes to read a header + if (slice.remainingLength < MIN_HEADER_SIZE) { + return null; + } + + const startPos = slice.filePos; + + const id = readElementId(slice); + if (id === null) { + slice.filePos = startPos; + return null; + } + + const sizeResult = readElementSize(slice); + if (sizeResult === null) { + // Couldn't read size - rewind to start + slice.filePos = startPos; + return null; + } + + return { id, size: sizeResult.size }; }; export const readAsciiString = (slice: FileSlice, length: number) => { diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts index db421b6..ab2db28 100644 --- a/src/matroska/matroska-demuxer.ts +++ b/src/matroska/matroska-demuxer.ts @@ -908,9 +908,7 @@ export class MatroskaDemuxer extends Demuxer { } readContiguousElements(slice: FileSlice, stopIds?: number[]) { - const startIndex = slice.filePos; - - while (slice.filePos - startIndex <= slice.length - MIN_HEADER_SIZE) { + while (slice.remainingLength >= MIN_HEADER_SIZE) { const startPos = slice.filePos; const foundElement = this.traverseElement(slice, stopIds);