fix: update logic for checking formdata instances (#6643)

* fix: update logic for checking formdata instance

* fix: isFormData logic update

* fix: review comment fix, add isFormData to @usebruno/common package

* fix: review comment fix
This commit is contained in:
lohit
2026-01-04 21:27:07 +05:30
committed by GitHub
parent b6a27bc66c
commit 97c700beba
10 changed files with 78 additions and 14 deletions

1
package-lock.json generated
View File

@@ -33081,6 +33081,7 @@
"@rollup/plugin-typescript": "^12.1.2",
"@types/jest": "^29.5.14",
"babel-jest": "^29.7.0",
"form-data": "^4.0.0",
"is-ip": "^5.0.1",
"moment": "^2.29.4",
"rollup": "3.29.5",

View File

@@ -1,6 +1,6 @@
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash');
const FormData = require('form-data');
const { isFormData } = require('@usebruno/common').utils;
const getContentType = (headers = {}) => {
let contentType = '';
@@ -87,7 +87,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
}));
}
} else if (contentType === 'multipart/form-data') {
if (Array.isArray(request?.data) && !(request.data instanceof FormData)) {
if (Array.isArray(request?.data) && !isFormData(request.data)) {
try {
request.data = request?.data?.map((d) => ({
...d,

View File

@@ -3,7 +3,6 @@ const chalk = require('chalk');
const decomment = require('decomment');
const fs = require('fs');
const { forOwn, isUndefined, isNull, each, extend, get, compact } = require('lodash');
const FormData = require('form-data');
const prepareRequest = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
const { interpolateString } = require('./interpolate-string');
@@ -25,7 +24,7 @@ const { NtlmClient } = require('axios-ntlm');
const { addDigestInterceptor } = require('@usebruno/requests');
const { getCACertificates } = require('@usebruno/requests');
const { getOAuth2Token } = require('../utils/oauth2');
const { encodeUrl, buildFormUrlEncodedPayload, extractPromptVariables } = require('@usebruno/common').utils;
const { encodeUrl, buildFormUrlEncodedPayload, extractPromptVariables, isFormData } = require('@usebruno/common').utils;
const onConsoleLog = (type, args) => {
console[type](...args);
@@ -429,7 +428,7 @@ const runSingleRequest = async function (
}
if (contentTypeHeader && request.headers[contentTypeHeader] === 'multipart/form-data') {
if (!(request?.data instanceof FormData)) {
if (!isFormData(request?.data)) {
request._originalMultipartData = request.data;
request.collectionPath = collectionPath;
let form = createFormData(request.data, collectionPath);

View File

@@ -46,6 +46,7 @@
"@rollup/plugin-typescript": "^12.1.2",
"@types/jest": "^29.5.14",
"babel-jest": "^29.7.0",
"form-data": "^4.0.0",
"is-ip": "^5.0.1",
"moment": "^2.29.4",
"rollup": "3.29.5",

View File

@@ -1,5 +1,6 @@
import { describe, it, expect } from '@jest/globals';
import { buildFormUrlEncodedPayload } from './form-data';
import { buildFormUrlEncodedPayload, isFormData } from './form-data';
import FormData from 'form-data';
describe('buildFormUrlEncodedPayload', () => {
it('should handle single key-value pair', () => {
@@ -110,3 +111,53 @@ describe('buildFormUrlEncodedPayload', () => {
expect(result).toEqual(expected);
});
});
describe('isFormData', () => {
it('should return true for objects with FormData constructor name', () => {
const mockFormData = {
constructor: { name: 'FormData' }
};
expect(isFormData(mockFormData)).toBe(true);
});
it('should return false for null', () => {
expect(isFormData(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isFormData(undefined)).toBe(false);
});
it('should return false for plain objects', () => {
expect(isFormData({})).toBe(false);
expect(isFormData({ key: 'value' })).toBe(false);
});
it('should return false for arrays', () => {
expect(isFormData([])).toBe(false);
expect(isFormData([1, 2, 3])).toBe(false);
});
it('should return false for primitives', () => {
expect(isFormData('string')).toBe(false);
expect(isFormData(123)).toBe(false);
expect(isFormData(true)).toBe(false);
});
it('should return false for objects with different constructor names', () => {
class CustomClass {}
const customObj = new CustomClass();
expect(isFormData(customObj)).toBe(false);
});
it('should return false for objects without constructor', () => {
const obj = Object.create(null);
expect(isFormData(obj)).toBe(false);
});
it('should return true for actual FormData instance from form-data library', () => {
const formData = new FormData();
formData.append('key', 'value');
expect(isFormData(formData)).toBe(true);
});
});

View File

@@ -31,3 +31,15 @@ export const buildFormUrlEncodedPayload = (params: Array<{ name: string; value:
return resultParams.toString();
};
/**
* Determines if the given object is a FormData instance.
* Supports native FormData (Node 18+, browser) and the 'form-data' npm package.
* @param obj - Object to check.
* @returns True if obj is a FormData instance, false otherwise.
*/
export const isFormData = (obj: unknown): boolean => {
// Check constructor name (works for both native FormData and form-data npm package)
// todo: checking constructor.name can produce false positives for objects that have a constructor.name property set to 'FormData', but this is rare.
return obj?.constructor?.name === 'FormData';
};

View File

@@ -5,7 +5,8 @@ export {
} from './url';
export {
buildFormUrlEncodedPayload
buildFormUrlEncodedPayload,
isFormData
} from './form-data';
export {

View File

@@ -5,7 +5,6 @@ const qs = require('qs');
const decomment = require('decomment');
const contentDispositionParser = require('content-disposition');
const mime = require('mime-types');
const FormData = require('form-data');
const { ipcMain } = require('electron');
const { each, get, extend, cloneDeep, merge } = require('lodash');
const { NtlmClient } = require('axios-ntlm');
@@ -36,7 +35,7 @@ const { cookiesStore } = require('../../store/cookies');
const registerGrpcEventHandlers = require('./grpc-event-handlers');
const { registerWsEventHandlers } = require('./ws-event-handlers');
const { getCertsAndProxyConfig } = require('./cert-utils');
const { buildFormUrlEncodedPayload } = require('@usebruno/common').utils;
const { buildFormUrlEncodedPayload, isFormData } = require('@usebruno/common').utils;
const ERROR_OCCURRED_WHILE_EXECUTING_REQUEST = 'Error occurred while executing the request!';
@@ -474,7 +473,7 @@ const registerNetworkIpc = (mainWindow) => {
}
if (contentTypeHeader && request.headers[contentTypeHeader] === 'multipart/form-data') {
if (!(request.data instanceof FormData)) {
if (!isFormData(request.data)) {
request._originalMultipartData = request.data;
request.collectionPath = collectionPath;
let form = createFormData(request.data, collectionPath);

View File

@@ -1,6 +1,6 @@
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep } = require('lodash');
const FormData = require('form-data');
const { isFormData } = require('@usebruno/common').utils;
const getContentType = (headers = {}) => {
let contentType = '';
@@ -132,7 +132,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
}));
}
} else if (contentType === 'multipart/form-data') {
if (Array.isArray(request?.data) && !(request.data instanceof FormData)) {
if (Array.isArray(request?.data) && !isFormData(request.data)) {
try {
request.data = request?.data?.map((d) => ({
...d,

View File

@@ -1,8 +1,8 @@
const { customAlphabet } = require('nanoid');
const iconv = require('iconv-lite');
const { cloneDeep } = require('lodash');
const FormData = require('form-data');
const { formatMultipartData } = require('./form-data');
const { isFormData } = require('@usebruno/common').utils;
// a customized version of nanoid without using _ and -
const uuid = () => {
@@ -135,7 +135,7 @@ const parseDataFromRequest = (request) => {
// File uploads are redacted, multipart FormData is formatted from original data for readability, and other types are stringified as-is.
if (request.mode === 'file') {
requestDataString = '<request body redacted>';
} else if (request?.data instanceof FormData && Array.isArray(request._originalMultipartData)) {
} else if (isFormData(request?.data) && Array.isArray(request._originalMultipartData)) {
const boundary = request.data._boundary || 'boundary';
requestDataString = formatMultipartData(request._originalMultipartData, boundary);
} else {