Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions javascript/packages/fory/lib/fory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ export default class {

private initConfig(config: Partial<Config> | undefined) {
return {
hps: config?.hps,
refTracking: config?.refTracking !== null ? Boolean(config?.refTracking) : null,
useSliceString: Boolean(config?.useSliceString),
hooks: config?.hooks || {},
compatible: Boolean(config?.compatible),
maxBinarySize: config?.maxBinarySize,
maxCollectionSize: config?.maxCollectionSize,
maxMapEntries: config?.maxMapEntries,
};
}

Expand Down
13 changes: 13 additions & 0 deletions javascript/packages/fory/lib/gen/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class CollectionAnySerializer {
read(accessor: (result: any, index: number, v: any) => void, createCollection: (len: number) => any, fromRef: boolean): any {
void fromRef;
const len = this.fory.binaryReader.readVarUint32Small7();
const maxSize = this.fory.config.maxCollectionSize;
if (typeof maxSize === "number" && maxSize > 0 && len > maxSize) {
throw new Error(`Collection length ${len} exceeds configured maxCollectionSize ${maxSize}`);
}
const flags = this.fory.binaryReader.readUint8();
const isSame = flags & CollectionFlags.SAME_TYPE;
const includeNone = flags & CollectionFlags.HAS_NULL;
Expand Down Expand Up @@ -295,8 +299,17 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera
const flags = this.scope.uniqueName("flags");
const idx = this.scope.uniqueName("idx");
const refFlag = this.scope.uniqueName("refFlag");
const maxSizeConfig = this.builder.fory.config.maxCollectionSize;
const limitCheck = typeof maxSizeConfig === "number" && maxSizeConfig > 0
? `
if (${len} > ${maxSizeConfig}) {
throw new Error(\`Collection length \${${len}} exceeds configured maxCollectionSize ${maxSizeConfig}\`);
}
`
: "";
return `
const ${len} = ${this.builder.reader.readVarUint32Small7()};
${limitCheck}
const ${flags} = ${this.builder.reader.readUint8()};
const ${result} = ${this.newCollection(len)};
${this.maybeReference(result, refState)}
Expand Down
13 changes: 13 additions & 0 deletions javascript/packages/fory/lib/gen/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ class MapAnySerializer {

read(fromRef: boolean): any {
let count = this.fory.binaryReader.readVarUint32Small7();
const maxEntries = this.fory.config.maxMapEntries;
if (typeof maxEntries === "number" && maxEntries > 0 && count > maxEntries) {
throw new Error(`Map entry count ${count} exceeds configured maxMapEntries ${maxEntries}`);
}
const result = new Map();
if (fromRef) {
this.fory.referenceResolver.reference(result);
Expand Down Expand Up @@ -400,8 +404,17 @@ export class MapSerializerGenerator extends BaseSerializerGenerator {
const count = this.scope.uniqueName("count");
const result = this.scope.uniqueName("result");

const maxEntriesConfig = this.builder.fory.config.maxMapEntries;
const limitCheck = typeof maxEntriesConfig === "number" && maxEntriesConfig > 0
? `
if (${count} > ${maxEntriesConfig}) {
throw new Error(\`Map entry count \${${count}} exceeds configured maxMapEntries ${maxEntriesConfig}\`);
}
`
: "";
return `
let ${count} = ${this.builder.reader.readVarUint32Small7()};
${limitCheck}
const ${result} = new Map();
if (${refState}) {
${this.builder.referenceResolver.reference(result)}
Expand Down
6 changes: 6 additions & 0 deletions javascript/packages/fory/lib/reader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ export class BinaryReader {
private platformBuffer!: PlatformBuffer;
private bigString = "";
private byteLength = 0;
private maxBinarySize?: number;

constructor(config: {
useSliceString?: boolean;
maxBinarySize?: number;
}) {
this.sliceStringEnable = isNodeEnv && config.useSliceString;
this.maxBinarySize = config.maxBinarySize;
}

reset(ab: Uint8Array) {
Expand Down Expand Up @@ -189,6 +192,9 @@ export class BinaryReader {
const header = this.readVarUint36Small();
const type = header & 0b11;
const len = header >>> 2;
if (typeof this.maxBinarySize === "number" && this.maxBinarySize > 0 && len > this.maxBinarySize) {
throw new Error(`String byte length ${len} exceeds configured maxBinarySize ${this.maxBinarySize}`);
}
switch (type) {
case LATIN1:
return this.stringLatin1(len);
Expand Down
3 changes: 3 additions & 0 deletions javascript/packages/fory/lib/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ export interface Config {
afterCodeGenerated?: (code: string) => string;
};
compatible?: boolean;
maxBinarySize?: number;
maxCollectionSize?: number;
maxMapEntries?: number;
}

export interface WithForyClsInfo {
Expand Down
16 changes: 16 additions & 0 deletions javascript/test/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,22 @@ describe('array', () => {
expect(result.a7[1].toFloat32()).toBeCloseTo(-2.5, 2);
expect(result.a7[2].toFloat32()).toBe(0);
});

test('should enforce maxCollectionSize for list field', () => {
const typeinfo = Type.struct({
typeName: "example.limit"
}, {
items: Type.array(Type.string()),
});
const fory = new Fory({ refTracking: true, maxCollectionSize: 2 });
const { serialize, deserialize } = fory.registerSerializer(typeinfo);

const okBin = serialize({ items: ["a", "b"] });
expect(deserialize(okBin)).toEqual({ items: ["a", "b"] });

const tooManyBin = serialize({ items: ["a", "b", "c"] });
expect(() => deserialize(tooManyBin)).toThrow(/maxCollectionSize/);
});
});


10 changes: 10 additions & 0 deletions javascript/test/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ describe('map', () => {
const result = deserialize(bin);
expect(result).toEqual({ f1: new Map([["hello", 123],["world", 456]])})
});

test('should enforce maxMapEntries when configured', () => {
const fory = new Fory({ refTracking: true, maxMapEntries: 1 });

const okInput = fory.serialize(new Map([["foo", "bar"]]));
expect(fory.deserialize(okInput)).toEqual(new Map([["foo", "bar"]]));

const tooManyInput = fory.serialize(new Map([["foo", "bar"], ["baz", "qux"]]));
expect(() => fory.deserialize(tooManyInput)).toThrow(/maxMapEntries/);
});
});


12 changes: 12 additions & 0 deletions javascript/test/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,16 @@ describe('string', () => {
);
expect(result).toEqual(str)
});

test('should enforce maxBinarySize when configured', () => {
const short = "abcd";
const long = "abcdef";
const fory = new Fory({ ...config, maxBinarySize: 4 });

const ok = fory.serialize(short);
expect(fory.deserialize(ok)).toEqual(short);

const tooLong = fory.serialize(long);
expect(() => fory.deserialize(tooLong)).toThrow(/maxBinarySize/);
});
});
Loading