Repository: axios/axios (js)

Issue:
`RangeError: Invalid array length` when `data` is set to a large `Buffer`
### Describe the bug

Axios throws `RangeError: Invalid array length` when `data` is set to a large `Buffer`

## Explanation

When axios merges configurations, it checks for empty objects:

```js
function isEmptyObject(val) {
  return val && Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
}
```

Calling `Object.keys` on a `Buffer` instance will try to enumerate every byte as a key. Node.js decides to die when there are more than 100-200 million keys (100-200MB byte length).

## Workaround

```ts
import axios from 'axios'

main()

async function main() {
    const buffer = Buffer.alloc(1024 * 1024 * 200) // 200 MB buffer
    const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
    await axios({
        method: 'POST',
        url: 'http://localhost:1633/bzz',
        data: arrayBuffer
    })
}
```

Passing `ArrayBuffer` instead of `Buffer` solves the issue, but it is not ideal as it requires additional conversion.

### To Reproduce

```ts
import axios from 'axios'

main()

async function main() {
    const buffer = Buffer.alloc(1024 * 1024 * 200) // 200 MB buffer
    await axios({
        method: 'POST',
        url: 'http://localhost:8080',
        data: buffer
    })
}
```

Output:

```
~/node_modules/axios/lib/utils.js:134
  return val && Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
                       ^

RangeError: Invalid array length
    at Function.keys (<anonymous>)
    at Object.isEmptyObject (~/node_modules/axios/lib/utils.js:134:24)
    at getMergedValue (~/node_modules/axios/lib/core/mergeConfig.js:21:22)
    at valueFromConfig2 (~/node_modules/axios/lib/core/mergeConfig.js:43:14)
    at computeConfigValue (~/node_modules/axios/lib/core/mergeConfig.js:98:23)
    at Object.forEach (~/node_modules/axios/lib/utils.js:281:10)
    at mergeConfig (~/node_modules/axios/lib/core/mergeConfig.js:96:9)
    at Axios.request (~/node_modules/axios/lib/core/Axios.js:41:12)
    at wrap (~/node_modules/axios/lib/helpers/bind.js:5:15)
    at main (~/src/axios.ts:7:11)

Node.js v20.18.3
```

### Code snippet

```js

```

### Expected behavior

Since `Buffer` is common, e.g. the default return types of `readFile` or `readFileSync` are Buffers, naively trying to upload a large file in Node.js will trigger this error.

When `typeof Buffer !== 'undefined'`, `axios` should detect and work with it properly during configuration merging.

### Axios Version

0.30.0

### Adapter Version

Node.js default

### Browser

_No response_

### Browser Version

_No response_

### Node.js Version

v20.18.3

### OS

MacOS Sonoma 14.6.1

### Additional Library Versions

```bash

```

### Additional context/Screenshots

```bash

```

Pull request description:
fix #6949: prevent RangeError when using large Buffers in axios requests
## Description

This PR fixes a critical RangeError that occurs when using large Buffers (>100-200MB) as request data in axios. The error was caused by calling `Object.keys()` on Buffer instances during configuration merging, which attempts to enumerate every byte as a key.

## Problem

When axios processes large Buffer data, several utility functions would call `Object.keys()` on the Buffer:

```javascript
// This would crash with large Buffers
function isEmptyObject(val) {
  return val && Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
}
```

**Error:** `RangeError: Invalid array length` when Buffer size exceeds ~100-200MB

## Solution

Added Buffer checks to prevent `Object.keys()` from being called on Buffer instances in:

- ✅ `isEmptyObject()` - Returns `false` for all Buffers (not empty objects)
- ✅ `forEach()` - Skips iteration for Buffers 
- ✅ `findKey()` - Returns `null` for Buffers
- ✅ `toJSONObject()` - Returns Buffer as-is without processing

## Changes Made

### 1. Enhanced `isEmptyObject` function
```javascript
const isEmptyObject = (val) => {
  // Early return for non-objects or Buffers to prevent RangeError
  if (!isObject(val) || isBuffer(val)) {
    return false;
  }
  
  try {
    return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
  } catch (e) {
    // Fallback for any other objects that might cause RangeError with Object.keys()
    return false;
  }
}
```

### 2. Enhanced `forEach` function
```javascript
function forEach(obj, fn, {allOwnKeys = false} = {}) {
  // ... existing code ...
  } else {
    // Buffer check to prevent RangeError
    if (isBuffer(obj)) {
      return;
    }
    // ... rest of object iteration ...
  }
}
```

### 3. Enhanced `findKey` function
```javascript
function findKey(obj, key) {
  if (isBuffer(obj)){
    return null;
  }
  // ... rest of function ...
}
```

### 4. Enhanced `toJSONObject` function
```javascript
const toJSONObject = (obj) => {
  const visit = (source, i) => {
    if (isObject(source)) {
      // ... existing checks ...
      
      // Buffer check to prevent RangeError
      if (isBuffer(source)) {
        return source;
      }
      
      // ... rest of processing ...
    }
    return source;
  }
  return visit(obj, 0);
}
```

## Test Coverage

Added comprehensive tests in `test/unit/utils/utils.js`:

```javascript
describe('Buffer RangeError Fix', function () {
  it('should handle large Buffer in isEmptyObject without RangeError', function () {
    const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
    const result = utils.isEmptyObject(largeBuffer);
    assert.strictEqual(result, false);
  });

  it('should handle large Buffer in forEach without RangeError', function () {
    const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
    let count = 0;
    utils.forEach(largeBuffer, () => count++);
    assert.strictEqual(count, 0); // Should be skipped
  });

  it('should handle large Buffer in findKey without RangeError', function () {
    const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
    const result = utils.findKey(largeBuffer, 'test');
    assert.strictEqual(result, null);
  });
});
```

## Reproduction Case

**Before (throws RangeError):**
```javascript
import axios from 'axios'

const buffer = Buffer.alloc(1024 * 1024 * 200) // 200 MB buffer
await axios({
  method: 'POST',
  url: 'http://localhost:8080',
  data: buffer
})
// RangeError: Invalid array length
```

**After (works correctly):**
```javascript
import axios from 'axios'

const buffer = Buffer.alloc(1024 * 1024 * 200) // 200 MB buffer
await axios({
  method: 'POST',
  url: 'http://localhost:8080',  
  data: buffer
})
// ✅ Works without error
```

## Backward Compatibility

- ✅ No breaking changes
- ✅ All existing functionality preserved
- ✅ Performance impact minimal (fast Buffer checks)
- ✅ Only affects Buffer handling behavior

## Impact

This fix enables axios to handle large file uploads and binary data processing in Node.js environments without crashes, which is essential for:

- File upload services
- Image/video processing APIs
- Binary data streaming
- Large payload handling

## Testing

- ✅ All existing tests pass
- ✅ New Buffer-specific tests added
- ✅ Manual testing with 200MB+ Buffers
- ✅ Integration testing with real HTTP requests

Closes #[#6949]

![Screenshot 2025-07-09 123055](https://github.com/user-attachments/assets/3e0a0bce-ab9c-4b06-9558-976cd00464da)

Patch:
diff --git a/lib/utils.js b/lib/utils.js
index a5ed039..e9a45ff 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -136,6 +136,27 @@ const isPlainObject = (val) => {
   return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(toStringTag in val) && !(iterator in val);
 }
 
+/**
+ * Determine if a value is an empty object (safely handles Buffers)
+ *
+ * @param {*} val The value to test
+ *
+ * @returns {boolean} True if value is an empty object, otherwise false
+ */
+const isEmptyObject = (val) => {
+  // Early return for non-objects or Buffers to prevent RangeError
+  if (!isObject(val) || isBuffer(val)) {
+    return false;
+  }
+  
+  try {
+    return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
+  } catch (e) {
+    // Fallback for any other objects that might cause RangeError with Object.keys()
+    return false;
+  }
+}
+
 /**
  * Determine if a value is a Date
  *
@@ -258,6 +279,11 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
       fn.call(null, obj[i], i, obj);
     }
   } else {
+    // Buffer check
+    if (isBuffer(obj)) {
+      return;
+    }
+
     // Iterate over object keys
     const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
     const len = keys.length;
@@ -271,6 +297,10 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
 }
 
 function findKey(obj, key) {
+  if (isBuffer(obj)){
+    return null;
+  }
+
   key = key.toLowerCase();
   const keys = Object.keys(obj);
   let i = keys.length;
@@ -624,6 +654,11 @@ const toJSONObject = (obj) => {
         return;
       }
 
+      //Buffer check
+      if (isBuffer(source)) {
+        return source;
+      }
+
       if(!('toJSON' in source)) {
         stack[i] = source;
         const target = isArray(source) ? [] : {};
@@ -695,6 +730,7 @@ export default {
   isBoolean,
   isObject,
   isPlainObject,
+  isEmptyObject,
   isReadableStream,
   isRequest,
   isResponse,


Test patch:
diff --git a/test/unit/utils/utils.js b/test/unit/utils/utils.js
index 8d40250..76eebcc 100644
--- a/test/unit/utils/utils.js
+++ b/test/unit/utils/utils.js
@@ -80,4 +80,38 @@ describe('utils', function (){
       assert.strictEqual(JSON.stringify(jsonObject), JSON.stringify({x: 1, y:2, obj: {ok: 1}}))
     });
   });
+
+  describe('Buffer RangeError Fix', function () {
+    it('should handle large Buffer in isEmptyObject without RangeError', function () {
+      // Create a big buffer that used to cause the error
+      const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
+      
+      // This used to throw: RangeError: Invalid array length
+      // Now it should work fine
+      const result = utils.isEmptyObject(largeBuffer);
+      
+      // Buffer should not be considered an empty object
+      assert.strictEqual(result, false);
+    });
+
+    it('should handle large Buffer in forEach without RangeError', function () {
+      const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
+      let count = 0;
+      
+      // This should skip the buffer (not iterate through it)
+      utils.forEach(largeBuffer, () => count++);
+      
+      // Count should be 0 because forEach skips Buffers
+      assert.strictEqual(count, 0);
+    });
+
+    it('should handle large Buffer in findKey without RangeError', function () {
+      const largeBuffer = Buffer.alloc(1024 * 1024 * 200); // 200MB
+      
+      // Should return null for Buffers
+      const result = utils.findKey(largeBuffer, 'test');
+      
+      assert.strictEqual(result, null);
+    });
+  });
 });