fix: multipart/form-data body interpolation (#3142)

* feat: updates

* feat: updates

* feat: updates

* feat: updates
This commit is contained in:
lohit 2024-09-23 15:54:54 +05:30 committed by GitHub
parent eb33504f19
commit ed20eccc25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 165 additions and 47 deletions

View File

@ -1,5 +1,6 @@
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash');
const FormData = require('form-data');
const getContentType = (headers = {}) => {
let contentType = '';
@ -78,6 +79,14 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
request.data = JSON.parse(parsed);
} catch (err) {}
}
} else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request?.data instanceof FormData)) {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
} catch (err) {}
}
} else {
request.data = _interpolate(request.data);
}

View File

@ -120,16 +120,10 @@ const prepareRequest = (request, collectionRoot) => {
}
if (request.body.mode === 'multipartForm') {
axiosRequest.headers['content-type'] = 'multipart/form-data';
const params = {};
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
each(enabledParams, (p) => {
if (p.type === 'file') {
params[p.name] = p.value.map((path) => fs.createReadStream(path));
} else {
params[p.name] = p.value;
}
});
axiosRequest.headers['content-type'] = 'multipart/form-data';
each(enabledParams, (p) => (params[p.name] = p.value));
axiosRequest.data = params;
}

View File

@ -19,6 +19,7 @@ const { makeAxiosInstance } = require('../utils/axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
const path = require('path');
const { createFormData } = require('../utils/common');
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const onConsoleLog = (type, args) => {
@ -45,21 +46,6 @@ const runSingleRequest = async function (
const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = runtime;
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
const form = new FormData();
forOwn(request.data, (value, key) => {
if (value instanceof Array) {
each(value, (v) => form.append(key, v));
} else {
form.append(key, value);
}
});
extend(request.headers, form.getHeaders());
request.data = form;
}
// run pre request script
const requestScriptFile = compact([
get(collectionRoot, 'request.script.req'),
@ -195,6 +181,14 @@ const runSingleRequest = async function (
request.data = qs.stringify(request.data);
}
if (request?.headers?.['content-type'] === 'multipart/form-data') {
if (!(request?.data instanceof FormData)) {
let form = createFormData(request.data, collectionPath);
request.data = form;
extend(request.headers, form.getHeaders());
}
}
let response, responseTime;
try {
// run request

View File

@ -1,3 +1,8 @@
const fs = require('fs');
const FormData = require('form-data');
const { forOwn } = require('lodash');
const path = require('path');
const lpad = (str, width) => {
let paddedStr = str;
while (paddedStr.length < width) {
@ -14,7 +19,33 @@ const rpad = (str, width) => {
return paddedStr;
};
const createFormData = (datas, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
forOwn(datas, (value, key) => {
if (typeof value == 'string') {
form.append(key, value);
return;
}
const filePaths = value || [];
filePaths?.forEach?.((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
form.append(key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
});
});
return form;
};
module.exports = {
lpad,
rpad
rpad,
createFormData
};

View File

@ -9,7 +9,7 @@ const decomment = require('decomment');
const contentDispositionParser = require('content-disposition');
const mime = require('mime-types');
const { ipcMain } = require('electron');
const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash');
const { isUndefined, isNull, each, get, compact, cloneDeep, forOwn, extend } = require('lodash');
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
const prepareRequest = require('./prepare-request');
const prepareCollectionRequest = require('./prepare-collection-request');
@ -37,6 +37,8 @@ const {
} = require('./oauth2-helper');
const Oauth2Store = require('../../store/oauth2');
const iconv = require('iconv-lite');
const FormData = require('form-data');
const { createFormData } = prepareRequest;
const safeStringifyJSON = (data) => {
try {
@ -423,6 +425,14 @@ const registerNetworkIpc = (mainWindow) => {
request.data = qs.stringify(request.data);
}
if (request.headers['content-type'] === 'multipart/form-data') {
if (!(request.data instanceof FormData)) {
let form = createFormData(request.data, collectionPath);
request.data = form;
extend(request.headers, form.getHeaders());
}
}
return scriptResult;
};

View File

@ -1,5 +1,6 @@
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash');
const FormData = require('form-data');
const getContentType = (headers = {}) => {
let contentType = '';
@ -76,6 +77,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.data = JSON.parse(parsed);
} catch (err) {}
}
} else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request.data instanceof FormData)) {
try {
let parsed = JSON.stringify(request.data);
parsed = _interpolate(parsed);
request.data = JSON.parse(parsed);
} catch (err) {}
}
} else {
request.data = _interpolate(request.data);
}

View File

@ -1,5 +1,5 @@
const os = require('os');
const { get, each, filter, extend, compact } = require('lodash');
const { get, each, filter, compact, forOwn } = require('lodash');
const decomment = require('decomment');
const FormData = require('form-data');
const fs = require('fs');
@ -165,27 +165,26 @@ const mergeFolderLevelScripts = (request, requestTreePath, scriptFlow) => {
}
};
const parseFormData = (datas, collectionPath) => {
const createFormData = (datas, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
datas.forEach((item) => {
const value = item.value;
const name = item.name;
if (item.type === 'file') {
const filePaths = value || [];
filePaths.forEach((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
form.append(name, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
});
} else {
form.append(name, value);
forOwn(datas, (value, key) => {
if (typeof value == 'string') {
form.append(key, value);
return;
}
const filePaths = value || [];
filePaths?.forEach?.((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
form.append(key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
});
});
return form;
};
@ -400,10 +399,11 @@ const prepareRequest = (item, collection) => {
}
if (request.body.mode === 'multipartForm') {
axiosRequest.headers['content-type'] = 'multipart/form-data';
const params = {};
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
const form = parseFormData(enabledParams, collectionPath);
extend(axiosRequest.headers, form.getHeaders());
axiosRequest.data = form;
each(enabledParams, (p) => (params[p.name] = p.value));
axiosRequest.data = params;
}
if (request.body.mode === 'graphql') {
@ -433,3 +433,4 @@ const prepareRequest = (item, collection) => {
module.exports = prepareRequest;
module.exports.setAuthHeaders = setAuthHeaders;
module.exports.createFormData = createFormData;

View File

@ -15,7 +15,7 @@
"bypassProxy": ""
},
"scripts": {
"moduleWhitelist": ["crypto", "buffer"],
"moduleWhitelist": ["crypto", "buffer", "form-data"],
"filesystemAccess": {
"allow": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

View File

@ -0,0 +1,23 @@
meta {
name: echo form-url-encoded
type: http
seq: 9
}
post {
url: {{echo-host}}
body: formUrlEncoded
auth: none
}
body:form-urlencoded {
form-data-key: {{form-data-key}}
}
script:pre-request {
bru.setVar('form-data-key', 'form-data-value');
}
assert {
res.body: eq form-data-key=form-data-value
}

View File

@ -0,0 +1,22 @@
meta {
name: echo multipart via scripting
type: http
seq: 10
}
post {
url: {{echo-host}}
body: multipartForm
auth: none
}
assert {
res.body: contains form-data-value
}
script:pre-request {
const FormData = require("form-data");
const form = new FormData();
form.append('form-data-key', 'form-data-value');
req.setBody(form);
}

View File

@ -0,0 +1,24 @@
meta {
name: echo multipart
type: http
seq: 8
}
post {
url: {{echo-host}}
body: multipartForm
auth: none
}
body:multipart-form {
foo: {{form-data-key}}
file: @file(bruno.png)
}
assert {
res.body: contains form-data-value
}
script:pre-request {
bru.setVar('form-data-key', 'form-data-value');
}

View File

@ -7,4 +7,5 @@ vars {
bark: {{process.env.PROC_ENV_VAR}}
foo: bar
testSetEnvVar: bruno-29653
echo-host: https://echo.usebruno.com
}