mirror of
https://github.com/usebruno/bruno.git
synced 2025-06-25 14:31:44 +02:00
fix: multipart/form-data body interpolation (#3142)
* feat: updates * feat: updates * feat: updates * feat: updates
This commit is contained in:
parent
eb33504f19
commit
ed20eccc25
@ -1,5 +1,6 @@
|
|||||||
const { interpolate } = require('@usebruno/common');
|
const { interpolate } = require('@usebruno/common');
|
||||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
|
||||||
const getContentType = (headers = {}) => {
|
const getContentType = (headers = {}) => {
|
||||||
let contentType = '';
|
let contentType = '';
|
||||||
@ -78,6 +79,14 @@ const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEn
|
|||||||
request.data = JSON.parse(parsed);
|
request.data = JSON.parse(parsed);
|
||||||
} catch (err) {}
|
} 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 {
|
} else {
|
||||||
request.data = _interpolate(request.data);
|
request.data = _interpolate(request.data);
|
||||||
}
|
}
|
||||||
|
@ -120,16 +120,10 @@ const prepareRequest = (request, collectionRoot) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.body.mode === 'multipartForm') {
|
if (request.body.mode === 'multipartForm') {
|
||||||
|
axiosRequest.headers['content-type'] = 'multipart/form-data';
|
||||||
const params = {};
|
const params = {};
|
||||||
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
||||||
each(enabledParams, (p) => {
|
each(enabledParams, (p) => (params[p.name] = p.value));
|
||||||
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';
|
|
||||||
axiosRequest.data = params;
|
axiosRequest.data = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ const { makeAxiosInstance } = require('../utils/axios-instance');
|
|||||||
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { createFormData } = require('../utils/common');
|
||||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
@ -45,21 +46,6 @@ const runSingleRequest = async function (
|
|||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
scriptingConfig.runtime = runtime;
|
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
|
// run pre request script
|
||||||
const requestScriptFile = compact([
|
const requestScriptFile = compact([
|
||||||
get(collectionRoot, 'request.script.req'),
|
get(collectionRoot, 'request.script.req'),
|
||||||
@ -195,6 +181,14 @@ const runSingleRequest = async function (
|
|||||||
request.data = qs.stringify(request.data);
|
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;
|
let response, responseTime;
|
||||||
try {
|
try {
|
||||||
// run request
|
// run request
|
||||||
|
@ -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) => {
|
const lpad = (str, width) => {
|
||||||
let paddedStr = str;
|
let paddedStr = str;
|
||||||
while (paddedStr.length < width) {
|
while (paddedStr.length < width) {
|
||||||
@ -14,7 +19,33 @@ const rpad = (str, width) => {
|
|||||||
return paddedStr;
|
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 = {
|
module.exports = {
|
||||||
lpad,
|
lpad,
|
||||||
rpad
|
rpad,
|
||||||
|
createFormData
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,7 @@ const decomment = require('decomment');
|
|||||||
const contentDispositionParser = require('content-disposition');
|
const contentDispositionParser = require('content-disposition');
|
||||||
const mime = require('mime-types');
|
const mime = require('mime-types');
|
||||||
const { ipcMain } = require('electron');
|
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 { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
const prepareCollectionRequest = require('./prepare-collection-request');
|
const prepareCollectionRequest = require('./prepare-collection-request');
|
||||||
@ -37,6 +37,8 @@ const {
|
|||||||
} = require('./oauth2-helper');
|
} = require('./oauth2-helper');
|
||||||
const Oauth2Store = require('../../store/oauth2');
|
const Oauth2Store = require('../../store/oauth2');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const { createFormData } = prepareRequest;
|
||||||
|
|
||||||
const safeStringifyJSON = (data) => {
|
const safeStringifyJSON = (data) => {
|
||||||
try {
|
try {
|
||||||
@ -423,6 +425,14 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
request.data = qs.stringify(request.data);
|
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;
|
return scriptResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const { interpolate } = require('@usebruno/common');
|
const { interpolate } = require('@usebruno/common');
|
||||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
|
||||||
const getContentType = (headers = {}) => {
|
const getContentType = (headers = {}) => {
|
||||||
let contentType = '';
|
let contentType = '';
|
||||||
@ -76,6 +77,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
|||||||
request.data = JSON.parse(parsed);
|
request.data = JSON.parse(parsed);
|
||||||
} catch (err) {}
|
} 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 {
|
} else {
|
||||||
request.data = _interpolate(request.data);
|
request.data = _interpolate(request.data);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const os = require('os');
|
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 decomment = require('decomment');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const fs = require('fs');
|
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
|
// make axios work in node using form data
|
||||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
datas.forEach((item) => {
|
forOwn(datas, (value, key) => {
|
||||||
const value = item.value;
|
if (typeof value == 'string') {
|
||||||
const name = item.name;
|
form.append(key, value);
|
||||||
if (item.type === 'file') {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filePaths = value || [];
|
const filePaths = value || [];
|
||||||
filePaths.forEach((filePath) => {
|
filePaths?.forEach?.((filePath) => {
|
||||||
let trimmedFilePath = filePath.trim();
|
let trimmedFilePath = filePath.trim();
|
||||||
|
|
||||||
if (!path.isAbsolute(trimmedFilePath)) {
|
if (!path.isAbsolute(trimmedFilePath)) {
|
||||||
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
|
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.append(name, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
|
form.append(key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
form.append(name, value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return form;
|
return form;
|
||||||
};
|
};
|
||||||
@ -400,10 +399,11 @@ const prepareRequest = (item, collection) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.body.mode === 'multipartForm') {
|
if (request.body.mode === 'multipartForm') {
|
||||||
|
axiosRequest.headers['content-type'] = 'multipart/form-data';
|
||||||
|
const params = {};
|
||||||
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
||||||
const form = parseFormData(enabledParams, collectionPath);
|
each(enabledParams, (p) => (params[p.name] = p.value));
|
||||||
extend(axiosRequest.headers, form.getHeaders());
|
axiosRequest.data = params;
|
||||||
axiosRequest.data = form;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.body.mode === 'graphql') {
|
if (request.body.mode === 'graphql') {
|
||||||
@ -433,3 +433,4 @@ const prepareRequest = (item, collection) => {
|
|||||||
|
|
||||||
module.exports = prepareRequest;
|
module.exports = prepareRequest;
|
||||||
module.exports.setAuthHeaders = setAuthHeaders;
|
module.exports.setAuthHeaders = setAuthHeaders;
|
||||||
|
module.exports.createFormData = createFormData;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"bypassProxy": ""
|
"bypassProxy": ""
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"moduleWhitelist": ["crypto", "buffer"],
|
"moduleWhitelist": ["crypto", "buffer", "form-data"],
|
||||||
"filesystemAccess": {
|
"filesystemAccess": {
|
||||||
"allow": true
|
"allow": true
|
||||||
}
|
}
|
||||||
|
BIN
packages/bruno-tests/collection/bruno.png
Normal file
BIN
packages/bruno-tests/collection/bruno.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 795 B |
@ -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
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
24
packages/bruno-tests/collection/echo/echo multipart.bru
Normal file
24
packages/bruno-tests/collection/echo/echo multipart.bru
Normal 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');
|
||||||
|
}
|
@ -7,4 +7,5 @@ vars {
|
|||||||
bark: {{process.env.PROC_ENV_VAR}}
|
bark: {{process.env.PROC_ENV_VAR}}
|
||||||
foo: bar
|
foo: bar
|
||||||
testSetEnvVar: bruno-29653
|
testSetEnvVar: bruno-29653
|
||||||
|
echo-host: https://echo.usebruno.com
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user