feat: added multipart data formatting in timeline (#6185)

refactor: remove escapeHeaderValue function and enhance formatMultipartData utility
This commit is contained in:
Sanjai Kumar
2025-11-27 18:12:20 +05:30
committed by GitHub
parent 6e88671788
commit bb0096eb38
4 changed files with 140 additions and 4 deletions

View File

@@ -1,6 +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');
// a customized version of nanoid without using _ and -
const uuid = () => {
@@ -128,7 +130,18 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
};
const parseDataFromRequest = (request) => {
const requestDataString = request.mode == 'file'? "<request body redacted>": (typeof request?.data === 'string' ? request?.data : safeStringifyJSON(request?.data));
let requestDataString;
// 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)) {
const boundary = request.data._boundary || 'boundary';
requestDataString = formatMultipartData(request._originalMultipartData, boundary);
} else {
requestDataString = typeof request?.data === 'string' ? request?.data : safeStringifyJSON(request?.data);
}
const requestCopy = cloneDeep(request);
if (!requestCopy.data) {
return { data: null, dataBuffer: null };

View File

@@ -3,6 +3,61 @@ const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
const formatMultipartData = (multipartData, boundary) => {
if (!Array.isArray(multipartData) || multipartData.length === 0) {
return '';
}
const normalizeBoundary = (b) => {
const value = b || 'boundary';
return value.replace(/^--+/, '').replace(/--+$/, '');
};
const getFileName = (filePath) => {
if (typeof filePath === 'string' && filePath.trim()) {
return path.basename(filePath) || 'file';
}
return 'file';
};
const formatValue = (value) => {
if (Array.isArray(value)) {
return value.map((v) => String(v ?? '')).join(', ');
}
return String(value ?? '');
};
const boundaryValue = normalizeBoundary(boundary);
const parts = [];
multipartData.forEach((field) => {
if (!field || !field.name) return;
parts.push(`----${boundaryValue}`);
parts.push('Content-Disposition: form-data');
if (field.type === 'file') {
const filePaths = Array.isArray(field.value) ? field.value : (field.value ? [field.value] : ['']);
filePaths.forEach((filePath) => {
parts.push(`----${boundaryValue}`);
parts.push('Content-Disposition: form-data');
const fileName = getFileName(filePath);
parts.push(`name: ${field.name}`);
parts.push(`value: [File: ${fileName}]`);
parts.push('');
});
} else {
const value = formatValue(field.value);
parts.push(`name: ${field.name}`);
parts.push(`value: ${value}`);
parts.push('');
}
});
parts.push(`----${boundaryValue}--`);
return parts.join('\n');
};
const createFormData = (data, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
@@ -38,5 +93,6 @@ const createFormData = (data, collectionPath) => {
};
module.exports = {
createFormData
createFormData,
formatMultipartData
};

View File

@@ -1,4 +1,5 @@
const { flattenDataForDotNotation } = require('../../src/utils/common');
const { flattenDataForDotNotation, parseDataFromRequest } = require('../../src/utils/common');
const FormData = require('form-data');
describe('utils: flattenDataForDotNotation', () => {
test('Flatten a simple object with dot notation', () => {
@@ -82,4 +83,24 @@ describe('utils: flattenDataForDotNotation', () => {
expect(flattenDataForDotNotation(input)).toEqual(expectedOutput);
});
});
});
describe('utils: parseDataFromRequest', () => {
test('should format multipart FormData', () => {
const formData = new FormData();
formData._boundary = 'boundary123';
const request = {
data: formData,
_originalMultipartData: [
{ name: 'description', type: 'text', value: 'dfv' },
{ name: 'file', type: 'file', value: ['Dumy.xml'] }
],
headers: {}
};
const result = parseDataFromRequest(request);
expect(result.data).toContain('name: description');
expect(result.data).toContain('value: dfv');
expect(result.data).toContain('value: [File: Dumy.xml]');
});
});

View File

@@ -0,0 +1,46 @@
const { formatMultipartData } = require('../../src/utils/form-data');
describe('utils: formatMultipartData', () => {
test('should format text field', () => {
const data = [{ name: 'description', type: 'text', value: 'dfv' }];
const result = formatMultipartData(data, 'boundary');
expect(result).toContain('----boundary');
expect(result).toContain('Content-Disposition: form-data');
expect(result).toContain('name: description');
expect(result).toContain('value: dfv');
expect(result).toContain('----boundary--');
});
test('should format file field', () => {
const data = [{ name: 'file', type: 'file', value: ['Dumy.xml'] }];
const result = formatMultipartData(data, 'boundary');
expect(result).toContain('name: file');
expect(result).toContain('value: [File: Dumy.xml]');
});
test('should format multiple fields', () => {
const data = [
{ name: 'description', type: 'text', value: 'dfv' },
{ name: 'file', type: 'file', value: ['Dumy.xml'] }
];
const result = formatMultipartData(data, 'boundary');
expect(result).toContain('name: description');
expect(result).toContain('value: dfv');
expect(result).toContain('name: file');
expect(result).toContain('value: [File: Dumy.xml]');
});
test('should return empty string for invalid input', () => {
expect(formatMultipartData([], 'boundary')).toBe('');
expect(formatMultipartData(null, 'boundary')).toBe('');
});
test('should normalize boundary', () => {
const data = [{ name: 'field', type: 'text', value: 'value' }];
expect(formatMultipartData(data, '--boundary')).toContain('----boundary');
expect(formatMultipartData(data, 'boundary--')).toContain('----boundary');
});
});