New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@isomp4/parser

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@isomp4/parser - npm Package Compare versions

Comparing version
0.1.3
to
0.1.4
+227
cjs/parser.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MP4Parser = exports.AbstractMP4Parser = void 0;
const buffer_1 = require("buffer");
const core_1 = require("@isomp4/core");
const EMPTY_BUFFER = buffer_1.Buffer.allocUnsafe(0);
/**
* Parses the ISO BMFF box structure of an MP4 stream.
*/
class AbstractMP4Parser {
/**
* Creates a new parser for an MP4 stream.
*/
constructor() {
this.boxes = new Map();
this.boxStack = [];
this.buffer = EMPTY_BUFFER;
this.bytesNeeded = 0;
this.currentBox = null;
}
/**
*
* @param encodings
*/
registerBox(...encodings) {
for (const encoding of encodings) {
if (this.boxes.has(encoding.type)) {
throw new Error("Box type is already registered: " + encoding.type);
}
this.boxes.set(encoding.type, encoding);
}
}
/**
*
* @param boxType
*/
isBoxRegistered(boxType) {
return this.boxes.has(boxType);
}
/**
* Appends new data to the stream.
* @param data The new data to append. This data does NOT need to be a complete segment (or even a fragment).
*/
append(data) {
let input = buffer_1.Buffer.from(data.buffer, data.byteOffset, data.byteLength);
while (input.length > 0) {
if (this.bytesNeeded > 0) {
// Ensure that buffer is the correct size
this.ensureBuffer(this.bytesNeeded);
// Don't copy more data into the temp buffer than is needed for the next part!
const needed = this.bytesNeeded - this.buffer.byteOffset;
if (needed > 0) {
if (needed <= input.length) {
// Only copy 'needed' number of bytes into the temp buffer
input.copy(this.buffer, 0, 0, needed);
input = input.slice(needed);
}
else {
// All input can be copied into temp buffer
this.buffer = this.buffer.slice(input.copy(this.buffer));
// More input data is needed
return;
}
}
// Temp buffer now contains all bytes needed
// Flip buffer
const bytesNeeded = this.bytesNeeded;
const buf = buffer_1.Buffer.from(this.buffer.buffer, 0, bytesNeeded);
this.bytesNeeded = 0; // This must be reset before calling processBuffer()
const bytesConsumed = this.processBuffer(buf);
if (this.bytesNeeded > 0) {
// More bytes are needed
continue;
}
if (bytesConsumed !== bytesNeeded) {
throw new Error(`bytes consumed(${bytesConsumed}) != bytes needed(${bytesNeeded})`);
}
// Reset buffer
this.buffer = buffer_1.Buffer.from(this.buffer.buffer);
}
else {
// Avoid copying data by using the input buffer directly
const consumed = this.processBuffer(input);
if (consumed > 0) {
input = input.slice(consumed);
}
}
}
}
ensureBuffer(capacity) {
if (this.buffer.buffer.byteLength < capacity) {
const newBuffer = buffer_1.Buffer.alloc(capacity);
// If the byteOffset is zero, then this indicates that there is no data written to the buffer
if (this.buffer.byteOffset > 0) {
// Must copy old buffer to new buffer
buffer_1.Buffer.from(this.buffer.buffer).copy(newBuffer);
}
this.buffer = newBuffer;
}
}
/**
* Processes the given buffer.
* @param buffer The input buffer.
* @return The number of bytes consumed.
*/
processBuffer(buffer) {
stackCheck: if (this.boxStack.length > 0) {
const top = this.boxStack[this.boxStack.length - 1];
const header = top.header;
const needed = header.size - top.offset;
if (needed > 0) {
if (top.children) {
// Skip and parse children normally
break stackCheck;
}
const available = buffer.length;
if (needed > available) {
// Don't have enough data
this.onBoxData(header, buffer);
top.offset += available;
return available;
}
// Have enough data
this.onBoxData(header, buffer.slice(0, needed));
}
// End box
if (top.box != null) {
this.onBoxEnded(header, top.box);
}
else {
this.onBoxEnded(header);
}
// Pop box state
this.boxStack.pop();
// Check next box state
if (this.boxStack.length > 0) {
const next = this.boxStack[this.boxStack.length - 1];
next.offset += header.size;
if (top.box != null && next.box != null && core_1.BoxContainer.isInstance(next.box)) {
core_1.BoxContainer.add(next.box, top.box);
}
}
// Trigger parsing of next box
this.currentBox = null;
return needed;
}
if (this.currentBox == null) {
const header = core_1.BoxHeader.parse(buffer);
if (typeof header === "number") {
this.bytesNeeded = header;
return 0;
}
const headerLength = core_1.BoxHeader.decodedBytes;
// Invoke box started event
const content = this.onBoxStarted(header, buffer.slice(0, headerLength));
// Start box
this.currentBox = {
header,
headerLength,
content,
};
return headerLength;
}
const header = this.currentBox.header;
if (this.currentBox.content) {
const encoding = this.boxes.get(header.type);
if (encoding != null) {
const box = encoding.decode(buffer, header);
if (typeof box === "number") {
this.bytesNeeded = box;
return 0;
}
const consumed = encoding.decodedBytes;
// Invoke box decoded event
const children = this.onBoxDecoded(box, buffer.slice(0, consumed));
// Push box onto stack (and record whether to parse children)
this.boxStack.push({
header,
box,
children,
offset: this.currentBox.headerLength + consumed,
});
// Trigger parsing of child boxes
if (children) {
box.children = {};
this.currentBox = null;
}
return consumed;
}
// No encoding, so reset content boolean
this.currentBox.content = false;
}
// No encoding for box type, must skip entire box and children
this.boxStack.push({
header,
box: null,
children: false,
offset: this.currentBox.headerLength,
});
return this.processBuffer(buffer);
}
}
exports.AbstractMP4Parser = AbstractMP4Parser;
/**
* An implementation of {@link AbstractMP4Parser} that delegates to optional function properties.
*/
class MP4Parser extends AbstractMP4Parser {
/**
* Create a new MP4Parser.
*/
constructor() {
super();
}
onBoxStarted(header, headerData) {
return this.boxStarted ? this.boxStarted(header, headerData) : true;
}
onBoxDecoded(box, boxData) {
return this.boxDecoded ? this.boxDecoded(box, boxData) : true;
}
onBoxData(header, boxData) {
this.boxData?.(header, boxData);
}
onBoxEnded(header, box) {
this.boxEnded?.(header, box);
}
}
exports.MP4Parser = MP4Parser;
import { Buffer } from "buffer";
import type { Box, BoxEncoding, FourCC } from "@isomp4/core";
import { BoxHeader } from "@isomp4/core";
/**
* Parses the ISO BMFF box structure of an MP4 stream.
*/
export declare abstract class AbstractMP4Parser {
/**
* Registered box encodings.
*/
private readonly boxes;
/**
* A stack that keeps track of the current state in the MP4 box structure traversal.
*/
private readonly boxStack;
/**
* A temporary buffer to store appended data before it's parsed.
* This buffer may be resized if a box requires more space before it can be fully parsed.
*/
private buffer;
/**
* The number of bytes needed in the buffer to parse the next part.
*/
private bytesNeeded;
/**
* The current box state.
* If this is <code>null</code>, then a box header will be parsed next.
*/
private currentBox;
/**
* Creates a new parser for an MP4 stream.
*/
protected constructor();
/**
*
* @param encodings
*/
registerBox(...encodings: BoxEncoding[]): void;
/**
*
* @param boxType
*/
isBoxRegistered(boxType: FourCC): boolean;
/**
* Appends new data to the stream.
* @param data The new data to append. This data does NOT need to be a complete segment (or even a fragment).
*/
append(data: ArrayBufferView): void;
private ensureBuffer;
/**
* Processes the given buffer.
* @param buffer The input buffer.
* @return The number of bytes consumed.
*/
private processBuffer;
/**
* Invoked when a new box starts from the source.
* @param header The parsed header data of the box.
* @param headerData The raw header data of the box.
* @return Whether to decode the box content (fields).
*/
protected abstract onBoxStarted(header: BoxHeader, headerData: Buffer): boolean;
/**
* Invoked when the box content is parsed.
* This will be invoked if {@link onBoxStarted} returns <code>true</code> for a box
* and there is a registered box encoding for the box type,
* otherwise {@link onBoxData} will be invoked with the remaining box data.
* @param box The box that was parsed.
* @param boxData The raw content data of the box (excluding header and children).
* @return Whether to decode the children boxes.
*/
protected abstract onBoxDecoded(box: Box, boxData: Buffer): boolean;
/**
* Invoked when new data is received for the current box.
* This will be invoked if either {@link onBoxStarted} or {@link onBoxDecoded} return <code>false</code> for a box.
* @param header The box that the data is for.
* @param boxData The raw data of the box (excluding header).
*/
protected abstract onBoxData(header: BoxHeader, boxData: Buffer): void;
/**
* Invoked when a box ends.
* @param header The header of the box that ended.
* @param box The box that ended, if it was parsed.
*/
protected abstract onBoxEnded(header: BoxHeader, box?: Box): void;
}
/**
* An implementation of {@link AbstractMP4Parser} that delegates to optional function properties.
*/
export declare class MP4Parser extends AbstractMP4Parser {
boxStarted?: typeof MP4Parser.prototype.onBoxStarted;
boxDecoded?: typeof MP4Parser.prototype.onBoxDecoded;
boxData?: typeof MP4Parser.prototype.onBoxData;
boxEnded?: typeof MP4Parser.prototype.onBoxEnded;
/**
* Create a new MP4Parser.
*/
constructor();
protected onBoxStarted(header: BoxHeader, headerData: Buffer): boolean;
protected onBoxDecoded(box: Box, boxData: Buffer): boolean;
protected onBoxData(header: BoxHeader, boxData: Buffer): void;
protected onBoxEnded(header: BoxHeader, box?: Box): void;
}
import { Buffer } from "buffer";
import { BoxContainer, BoxHeader } from "@isomp4/core";
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
/**
* Parses the ISO BMFF box structure of an MP4 stream.
*/
export class AbstractMP4Parser {
/**
* Creates a new parser for an MP4 stream.
*/
constructor() {
this.boxes = new Map();
this.boxStack = [];
this.buffer = EMPTY_BUFFER;
this.bytesNeeded = 0;
this.currentBox = null;
}
/**
*
* @param encodings
*/
registerBox(...encodings) {
for (const encoding of encodings) {
if (this.boxes.has(encoding.type)) {
throw new Error("Box type is already registered: " + encoding.type);
}
this.boxes.set(encoding.type, encoding);
}
}
/**
*
* @param boxType
*/
isBoxRegistered(boxType) {
return this.boxes.has(boxType);
}
/**
* Appends new data to the stream.
* @param data The new data to append. This data does NOT need to be a complete segment (or even a fragment).
*/
append(data) {
let input = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
while (input.length > 0) {
if (this.bytesNeeded > 0) {
// Ensure that buffer is the correct size
this.ensureBuffer(this.bytesNeeded);
// Don't copy more data into the temp buffer than is needed for the next part!
const needed = this.bytesNeeded - this.buffer.byteOffset;
if (needed > 0) {
if (needed <= input.length) {
// Only copy 'needed' number of bytes into the temp buffer
input.copy(this.buffer, 0, 0, needed);
input = input.slice(needed);
}
else {
// All input can be copied into temp buffer
this.buffer = this.buffer.slice(input.copy(this.buffer));
// More input data is needed
return;
}
}
// Temp buffer now contains all bytes needed
// Flip buffer
const bytesNeeded = this.bytesNeeded;
const buf = Buffer.from(this.buffer.buffer, 0, bytesNeeded);
this.bytesNeeded = 0; // This must be reset before calling processBuffer()
const bytesConsumed = this.processBuffer(buf);
if (this.bytesNeeded > 0) {
// More bytes are needed
continue;
}
if (bytesConsumed !== bytesNeeded) {
throw new Error(`bytes consumed(${bytesConsumed}) != bytes needed(${bytesNeeded})`);
}
// Reset buffer
this.buffer = Buffer.from(this.buffer.buffer);
}
else {
// Avoid copying data by using the input buffer directly
const consumed = this.processBuffer(input);
if (consumed > 0) {
input = input.slice(consumed);
}
}
}
}
ensureBuffer(capacity) {
if (this.buffer.buffer.byteLength < capacity) {
const newBuffer = Buffer.alloc(capacity);
// If the byteOffset is zero, then this indicates that there is no data written to the buffer
if (this.buffer.byteOffset > 0) {
// Must copy old buffer to new buffer
Buffer.from(this.buffer.buffer).copy(newBuffer);
}
this.buffer = newBuffer;
}
}
/**
* Processes the given buffer.
* @param buffer The input buffer.
* @return The number of bytes consumed.
*/
processBuffer(buffer) {
stackCheck: if (this.boxStack.length > 0) {
const top = this.boxStack[this.boxStack.length - 1];
const header = top.header;
const needed = header.size - top.offset;
if (needed > 0) {
if (top.children) {
// Skip and parse children normally
break stackCheck;
}
const available = buffer.length;
if (needed > available) {
// Don't have enough data
this.onBoxData(header, buffer);
top.offset += available;
return available;
}
// Have enough data
this.onBoxData(header, buffer.slice(0, needed));
}
// End box
if (top.box != null) {
this.onBoxEnded(header, top.box);
}
else {
this.onBoxEnded(header);
}
// Pop box state
this.boxStack.pop();
// Check next box state
if (this.boxStack.length > 0) {
const next = this.boxStack[this.boxStack.length - 1];
next.offset += header.size;
if (top.box != null && next.box != null && BoxContainer.isInstance(next.box)) {
BoxContainer.add(next.box, top.box);
}
}
// Trigger parsing of next box
this.currentBox = null;
return needed;
}
if (this.currentBox == null) {
const header = BoxHeader.parse(buffer);
if (typeof header === "number") {
this.bytesNeeded = header;
return 0;
}
const headerLength = BoxHeader.decodedBytes;
// Invoke box started event
const content = this.onBoxStarted(header, buffer.slice(0, headerLength));
// Start box
this.currentBox = {
header,
headerLength,
content,
};
return headerLength;
}
const header = this.currentBox.header;
if (this.currentBox.content) {
const encoding = this.boxes.get(header.type);
if (encoding != null) {
const box = encoding.decode(buffer, header);
if (typeof box === "number") {
this.bytesNeeded = box;
return 0;
}
const consumed = encoding.decodedBytes;
// Invoke box decoded event
const children = this.onBoxDecoded(box, buffer.slice(0, consumed));
// Push box onto stack (and record whether to parse children)
this.boxStack.push({
header,
box,
children,
offset: this.currentBox.headerLength + consumed,
});
// Trigger parsing of child boxes
if (children) {
box.children = {};
this.currentBox = null;
}
return consumed;
}
// No encoding, so reset content boolean
this.currentBox.content = false;
}
// No encoding for box type, must skip entire box and children
this.boxStack.push({
header,
box: null,
children: false,
offset: this.currentBox.headerLength,
});
return this.processBuffer(buffer);
}
}
/**
* An implementation of {@link AbstractMP4Parser} that delegates to optional function properties.
*/
export class MP4Parser extends AbstractMP4Parser {
/**
* Create a new MP4Parser.
*/
constructor() {
super();
}
onBoxStarted(header, headerData) {
return this.boxStarted ? this.boxStarted(header, headerData) : true;
}
onBoxDecoded(box, boxData) {
return this.boxDecoded ? this.boxDecoded(box, boxData) : true;
}
onBoxData(header, boxData) {
this.boxData?.(header, boxData);
}
onBoxEnded(header, box) {
this.boxEnded?.(header, box);
}
}
+4
-4
{
"name": "@isomp4/parser",
"version": "0.1.3",
"version": "0.1.4",
"description": "",

@@ -44,6 +44,6 @@ "keywords": [

"dependencies": {
"@isomp4/core": "^0.1.3"
"@isomp4/core": "^0.1.4"
},
"devDependencies": {
"@isomp4/box-moov": "^0.1.3"
"@isomp4/box-moov": "^0.1.4"
},

@@ -57,3 +57,3 @@ "scripts": {

},
"gitHead": "e9a1443618ef08f11ca714fbccde5ad434d5e2ae"
"gitHead": "622a45165f4730d4012e528f98b0b01127a595a5"
}