Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix "RangeError: Maximum call stack size exceeded" in hasBinary #109

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 26 additions & 15 deletions .github/workflows/ci.yml
Expand Up @@ -6,40 +6,51 @@ on:
schedule:
- cron: '0 0 * * 0'

permissions:
contents: read

jobs:
test-node:
runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [14, 16]

steps:
- uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
env:
CI: true

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

test-browser:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
- name: Checkout repository
uses: actions/checkout@v3

- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: '10.x'
- run: npm ci
node-version: 16

- name: Install dependencies
run: npm ci

- run: npm test
env:
CI: true
BROWSERS: 1
NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }}
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
timeout-minutes: 20
107 changes: 107 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,87 @@
# History

## 2022

- [3.3.3](#333-2022-11-09) (Nov 2022) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [3.4.2](#342-2022-11-09) (Nov 2022) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch)
- [4.0.5](#405-2022-06-27) (Jun 2022) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch)
- [4.2.1](#421-2022-06-27) (Jun 2022)
- [4.2.0](#420-2022-04-17) (Apr 2022)
- [4.1.2](#412-2022-02-17) (Feb 2022)

## 2021

- [4.1.1](#411-2021-10-14) (Oct 2021)
- [4.1.0](#410-2021-10-11) (Oct 2021)
- [4.0.4](#404-2021-01-15) (Jan 2021)
- [3.3.2](#332-2021-01-09) (Jan 2021) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [4.0.3](#403-2021-01-05) (Jan 2021)

## 2020

- [4.0.2](#402-2020-11-25) (Nov 2020)
- [4.0.1](#401-2020-11-05) (Nov 2020)
- [3.3.1](#331-2020-09-30) (Sep 2020) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [**4.0.0**](#400-2020-09-28) (Sep 2020)
- [3.4.1](#341-2020-05-13) (May 2020)

## 2019

- [3.4.0](#340-2019-09-20) (Sep 2019)

## 2018

- [3.3.0](#330-2018-11-07) (Nov 2018)



# Release notes

## [3.3.3](https://github.com/Automattic/socket.io-parser/compare/3.3.2...3.3.3) (2022-11-09)


### Bug Fixes

* check the format of the index of each attachment ([fb21e42](https://github.com/Automattic/socket.io-parser/commit/fb21e422fc193b34347395a33e0f625bebc09983))



## [3.4.2](https://github.com/socketio/socket.io-parser/compare/3.4.1...3.4.2) (2022-11-09)


### Bug Fixes

* check the format of the index of each attachment ([04d23ce](https://github.com/socketio/socket.io-parser/commit/04d23cecafe1b859fb03e0cbf6ba3b74dff56d14))



## [4.2.1](https://github.com/socketio/socket.io-parser/compare/4.2.0...4.2.1) (2022-06-27)


### Bug Fixes

* check the format of the index of each attachment ([b5d0cb7](https://github.com/socketio/socket.io-parser/commit/b5d0cb7dc56a0601a09b056beaeeb0e43b160050))



## [4.0.5](https://github.com/socketio/socket.io-parser/compare/4.0.4...4.0.5) (2022-06-27)


### Bug Fixes

* check the format of the index of each attachment ([b559f05](https://github.com/socketio/socket.io-parser/commit/b559f050ee02bd90bd853b9823f8de7fa94a80d4))



# [4.2.0](https://github.com/socketio/socket.io-parser/compare/4.1.2...4.2.0) (2022-04-17)


### Features

* allow the usage of custom replacer and reviver ([#112](https://github.com/socketio/socket.io-parser/issues/112)) ([b08bc1a](https://github.com/socketio/socket.io-parser/commit/b08bc1a93e8e3194b776c8a0bdedee1e29333680))



## [4.1.2](https://github.com/socketio/socket.io-parser/compare/4.1.1...4.1.2) (2022-02-17)


Expand Down Expand Up @@ -26,6 +110,16 @@
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))



## [3.3.2](https://github.com/Automattic/socket.io-parser/compare/3.3.1...3.3.2) (2021-01-09)


### Bug Fixes

* prevent DoS (OOM) via massive packets ([#95](https://github.com/Automattic/socket.io-parser/issues/95)) ([89197a0](https://github.com/Automattic/socket.io-parser/commit/89197a05c43b18cc4569fd178d56e7bb8f403865))



## [4.0.3](https://github.com/socketio/socket.io-parser/compare/4.0.2...4.0.3) (2021-01-05)


Expand Down Expand Up @@ -104,3 +198,16 @@ There is a breaking API change (see below), but the exchange [protocol](https://
### Bug Fixes

* prevent DoS (OOM) via massive packets ([#95](https://github.com/socketio/socket.io-parser/issues/95)) ([dcb942d](https://github.com/socketio/socket.io-parser/commit/dcb942d24db97162ad16a67c2a0cf30875342d55))



## [3.4.0](https://github.com/socketio/socket.io-parser/compare/3.3.0...3.4.0) (2019-09-20)



## [3.3.0](https://github.com/socketio/socket.io-parser/compare/3.2.0...3.3.0) (2018-11-07)


### Bug Fixes

* remove any reference to the `global` variable ([b47efb2](https://github.com/socketio/socket.io-parser/commit/b47efb2))
3 changes: 3 additions & 0 deletions babel.config.js
@@ -0,0 +1,3 @@
module.exports = {
presets: [["@babel/preset-env"]],
};
32 changes: 25 additions & 7 deletions lib/binary.ts
Expand Up @@ -17,24 +17,34 @@ export function deconstructPacket(packet) {
return { packet: pack, buffers: buffers };
}

function _deconstructPacket(data, buffers) {
function _deconstructPacket(
data,
buffers: Array<any>,
known: object[] = [],
retVals: WeakMap<any, object> = new WeakMap()
) {
if (!data) return data;

if (isBinary(data)) {
const placeholder = { _placeholder: true, num: buffers.length };
buffers.push(data);
return placeholder;
} else if (retVals.has(data)) {
return retVals.get(data);
} else if (known.includes(data)) {
return data;
} else if (Array.isArray(data)) {
const newData = new Array(data.length);
for (let i = 0; i < data.length; i++) {
newData[i] = _deconstructPacket(data[i], buffers);
newData[i] = _deconstructPacket(data[i], buffers, known, retVals);
}
return newData;
} else if (typeof data === "object" && !(data instanceof Date)) {
const newData = {};
retVals.set(data, newData);
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
newData[key] = _deconstructPacket(data[key], buffers);
newData[key] = _deconstructPacket(data[key], buffers, known, retVals);
}
}
return newData;
Expand All @@ -51,17 +61,25 @@ function _deconstructPacket(data, buffers) {
* @public
*/

export function reconstructPacket(packet, buffers) {
export function reconstructPacket(packet, buffers: Array<any>) {
packet.data = _reconstructPacket(packet.data, buffers);
packet.attachments = undefined; // no longer useful
return packet;
}

function _reconstructPacket(data, buffers) {
function _reconstructPacket(data, buffers: Array<any>) {
if (!data) return data;

if (data && data._placeholder) {
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
if (data && data._placeholder === true) {
const isIndexValid =
typeof data.num === "number" &&
data.num >= 0 &&
data.num < buffers.length;
if (isIndexValid) {
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
} else {
throw new Error("illegal attachments");
}
} else if (Array.isArray(data)) {
for (let i = 0; i < data.length; i++) {
data[i] = _reconstructPacket(data[i], buffers);
Expand Down
12 changes: 8 additions & 4 deletions lib/index.ts
@@ -1,4 +1,5 @@
import { Emitter } from "@socket.io/component-emitter";
import { parse, stringify } from "flatted";
import { deconstructPacket, reconstructPacket } from "./binary.js";
import { isBinary, hasBinary } from "./is-binary.js";
import debugModule from "debug"; // debug()
Expand Down Expand Up @@ -39,7 +40,7 @@ export class Encoder {
/**
* Encoder constructor
*
* @param {function} replacer - custom replacer to pass down to JSON.parse
* @param {function} replacer - custom replacer to pass down to JSON.stringify
*/
constructor(private replacer?: (this: any, key: string, value: any) => any) {}
/**
Expand Down Expand Up @@ -92,7 +93,7 @@ export class Encoder {

// json data
if (null != obj.data) {
str += JSON.stringify(obj.data, this.replacer);
str += stringify(obj.data, this.replacer);
}

debug("encoded %j as %s", obj, str);
Expand Down Expand Up @@ -130,7 +131,7 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
/**
* Decoder constructor
*
* @param {function} reviver - custom reviver to pass down to JSON.stringify
* @param {function} reviver - custom reviver to pass down to JSON.parse
*/
constructor(private reviver?: (this: any, key: string, value: any) => any) {
super();
Expand All @@ -145,6 +146,9 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
public add(obj: any) {
let packet;
if (typeof obj === "string") {
if (this.reconstructor) {
throw new Error("got plaintext data when reconstructing a packet");
}
packet = this.decodeString(obj);
if (
packet.type === PacketType.BINARY_EVENT ||
Expand Down Expand Up @@ -253,7 +257,7 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {

private tryParse(str) {
try {
return JSON.parse(str, this.reviver);
return parse(str, this.reviver);
} catch (e) {
return false;
}
Expand Down
26 changes: 15 additions & 11 deletions lib/is-binary.ts
Expand Up @@ -15,6 +15,8 @@ const withNativeFile =
typeof File === "function" ||
(typeof File !== "undefined" &&
toString.call(File) === "[object FileConstructor]");
const withNativeBuffer =
typeof Buffer === "function" && typeof Buffer.isBuffer === "function";

/**
* Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
Expand All @@ -26,18 +28,21 @@ export function isBinary(obj: any) {
return (
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
(withNativeBlob && obj instanceof Blob) ||
(withNativeFile && obj instanceof File)
(withNativeFile && obj instanceof File) ||
(withNativeBuffer && Buffer.isBuffer(obj))
);
}

export function hasBinary(obj: any, toJSON?: boolean) {
if (!obj || typeof obj !== "object") {
export function hasBinary(obj: any, known: object[] = [], toJSON?: boolean) {
if (!obj || typeof obj !== "object" || known.includes(obj)) {
return false;
}

known.push(obj);

if (Array.isArray(obj)) {
for (let i = 0, l = obj.length; i < l; i++) {
if (hasBinary(obj[i])) {
if (hasBinary(obj[i], known)) {
return true;
}
}
Expand All @@ -48,16 +53,15 @@ export function hasBinary(obj: any, toJSON?: boolean) {
return true;
}

if (
obj.toJSON &&
typeof obj.toJSON === "function" &&
arguments.length === 1
) {
return hasBinary(obj.toJSON(), true);
if (obj.toJSON && typeof obj.toJSON === "function" && arguments.length < 3) {
return hasBinary(obj.toJSON(), known, true);
}

for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
if (
(Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key]),
known)
) {
return true;
}
}
Expand Down