From 705ac5c74de40eec58ad9dfd7bd1ff332e01000b Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Thu, 19 Sep 2024 11:35:51 +0530 Subject: [PATCH 1/4] feat: updates --- .../bruno-cli/src/runner/interpolate-vars.js | 8 +++++ .../bruno-cli/src/runner/prepare-request.js | 10 ++---- .../src/runner/run-single-request.js | 24 ++++++-------- packages/bruno-cli/src/utils/common.js | 30 +++++++++++++++++- .../bruno-electron/src/ipc/network/index.js | 12 ++++++- .../src/ipc/network/interpolate-vars.js | 8 +++++ .../src/ipc/network/prepare-request.js | 20 ++++++------ packages/bruno-tests/collection/bruno.png | Bin 0 -> 795 bytes .../collection/echo/echo form-url-encoded.bru | 24 ++++++++++++++ .../collection/echo/echo multipart.bru | 20 ++++++++++++ .../collection/environments/Prod.bru | 1 + 11 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 packages/bruno-tests/collection/bruno.png create mode 100644 packages/bruno-tests/collection/echo/echo form-url-encoded.bru create mode 100644 packages/bruno-tests/collection/echo/echo multipart.bru diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index c354569939..dd1b4c4e65 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -78,6 +78,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); } diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index e30f8337f3..8ba86472b3 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -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; } diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index b260f6be97..c36a9b97f6 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -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 diff --git a/packages/bruno-cli/src/utils/common.js b/packages/bruno-cli/src/utils/common.js index 704928022f..abeb977354 100644 --- a/packages/bruno-cli/src/utils/common.js +++ b/packages/bruno-cli/src/utils/common.js @@ -1,3 +1,6 @@ +const fs = require('fs'); +const FormData = require('form-data'); + const lpad = (str, width) => { let paddedStr = str; while (paddedStr.length < width) { @@ -14,7 +17,32 @@ 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 == 'object') { + 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)); + }); + } else { + form.append(key, value); + } + }); + return form; +}; + + module.exports = { lpad, - rpad + rpad, + createFormData }; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 768505e271..b3a41ac27f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -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 { @@ -408,6 +410,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; }; diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index b6aeaa078f..0b4e0a0452 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -76,6 +76,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); } diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 6601347baf..d8b1c9013b 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -165,14 +165,12 @@ 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') { + forOwn(datas, (value, key) => { + if (typeof value == 'object') { const filePaths = value || []; filePaths.forEach((filePath) => { let trimmedFilePath = filePath.trim(); @@ -181,10 +179,10 @@ const parseFormData = (datas, collectionPath) => { 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); + form.append(key, value); } }); return form; @@ -382,10 +380,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') { @@ -415,3 +414,4 @@ const prepareRequest = (item, collection) => { module.exports = prepareRequest; module.exports.setAuthHeaders = setAuthHeaders; +module.exports.createFormData = createFormData; diff --git a/packages/bruno-tests/collection/bruno.png b/packages/bruno-tests/collection/bruno.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a7f878fb5bd695f7b67db54fa0ad121740d862 GIT binary patch literal 795 zcmV+$1LXXPP)`Dq^c7q<0*i%&IgAFX`BKTkzNHoGr zR-lAudQ$U^D&V=)7()e5)U9m8?lMzvb4%FfQV z*VoriUGE{*s>+Q?DHk;wO`l9A!*2w? z|pt-p@u=3`Grm_=J zanVXC7((zbshCAVOc-{rlBllUeV$O)*j!#zR76<_x;j5*7o-OrQOHI3A8{aqWs%In z(}jis!CS^;GD+0)9A~1HQnYqDK`HRy#W#GldobP2h5-zXxbeQct`OV+npr zT_1tSut4}Ig^yDBWu+()Bx2#6q!a*2ik*YDwzdu2BxT(Ah2oF8mSLQ|X9YlUmqRb$sk6)dk`Y# zVEgXHv)6JAhHzG~3~NPl zRGWv8wQ~XT>iC*PD>ff+(Xfhwf`S1E2>zkfYQx6*TP{xZ_Z`(LmV{6WTycGJ*hQ+c z)We!{X=!N_n1)~$>Xor4j|Y>&G4CV{!I7y(qfv($Fqp;ts_N_2f$~J1NS<6 Date: Thu, 19 Sep 2024 12:09:26 +0530 Subject: [PATCH 2/4] feat: updates --- .../src/ipc/network/interpolate-vars.js | 1 + .../src/ipc/network/prepare-request.js | 2 +- .../echo/echo multipart scripting.bru | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 packages/bruno-tests/collection/echo/echo multipart scripting.bru diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 0b4e0a0452..da1c9bab35 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -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 = ''; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index d8b1c9013b..4f4900a72f 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -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'); diff --git a/packages/bruno-tests/collection/echo/echo multipart scripting.bru b/packages/bruno-tests/collection/echo/echo multipart scripting.bru new file mode 100644 index 0000000000..750e46d63e --- /dev/null +++ b/packages/bruno-tests/collection/echo/echo multipart scripting.bru @@ -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 bar +} + +script:pre-request { + const FormData = require("form-data"); + const form = new FormData(); + form.append('foo', 'bar'); + req.setBody(form); +} From 5b17b19cb1f53a5b3a734543e26296ecd3db1890 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Thu, 19 Sep 2024 12:21:51 +0530 Subject: [PATCH 3/4] feat: updates --- packages/bruno-cli/src/runner/interpolate-vars.js | 1 + packages/bruno-cli/src/utils/common.js | 2 ++ packages/bruno-tests/collection/bruno.json | 2 +- .../collection/echo/echo form-url-encoded.bru | 9 ++++----- .../collection/echo/echo multipart scripting.bru | 4 ++-- packages/bruno-tests/collection/echo/echo multipart.bru | 8 ++++++-- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index dd1b4c4e65..39e92a6ecd 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -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 = ''; diff --git a/packages/bruno-cli/src/utils/common.js b/packages/bruno-cli/src/utils/common.js index abeb977354..c659ac1d7c 100644 --- a/packages/bruno-cli/src/utils/common.js +++ b/packages/bruno-cli/src/utils/common.js @@ -1,5 +1,7 @@ const fs = require('fs'); const FormData = require('form-data'); +const { forOwn } = require('lodash'); +const path = require('path'); const lpad = (str, width) => { let paddedStr = str; diff --git a/packages/bruno-tests/collection/bruno.json b/packages/bruno-tests/collection/bruno.json index b6d437bbb3..ada36145a9 100644 --- a/packages/bruno-tests/collection/bruno.json +++ b/packages/bruno-tests/collection/bruno.json @@ -15,7 +15,7 @@ "bypassProxy": "" }, "scripts": { - "moduleWhitelist": ["crypto", "buffer"], + "moduleWhitelist": ["crypto", "buffer", "form-data"], "filesystemAccess": { "allow": true } diff --git a/packages/bruno-tests/collection/echo/echo form-url-encoded.bru b/packages/bruno-tests/collection/echo/echo form-url-encoded.bru index 2bce25ade5..a0d2f0afbd 100644 --- a/packages/bruno-tests/collection/echo/echo form-url-encoded.bru +++ b/packages/bruno-tests/collection/echo/echo form-url-encoded.bru @@ -11,14 +11,13 @@ post { } body:form-urlencoded { - foo: {{foo}} + form-data-key: {{form-data-key}} } -body:multipart-form { - foo: bar - file: @file(bruno.png) +script:pre-request { + bru.setVar('form-data-key', 'form-data-value'); } assert { - res.body: eq foo=bar + res.body: eq form-data-key=form-data-value } diff --git a/packages/bruno-tests/collection/echo/echo multipart scripting.bru b/packages/bruno-tests/collection/echo/echo multipart scripting.bru index 750e46d63e..13c1f20515 100644 --- a/packages/bruno-tests/collection/echo/echo multipart scripting.bru +++ b/packages/bruno-tests/collection/echo/echo multipart scripting.bru @@ -11,12 +11,12 @@ post { } assert { - res.body: contains bar + res.body: contains form-data-value } script:pre-request { const FormData = require("form-data"); const form = new FormData(); - form.append('foo', 'bar'); + form.append('form-data-key', 'form-data-value'); req.setBody(form); } diff --git a/packages/bruno-tests/collection/echo/echo multipart.bru b/packages/bruno-tests/collection/echo/echo multipart.bru index 379bdf4d6b..b8fd8abf7a 100644 --- a/packages/bruno-tests/collection/echo/echo multipart.bru +++ b/packages/bruno-tests/collection/echo/echo multipart.bru @@ -11,10 +11,14 @@ post { } body:multipart-form { - foo: {{foo}} + foo: {{form-data-key}} file: @file(bruno.png) } assert { - res.body: contains bar + res.body: contains form-data-value +} + +script:pre-request { + bru.setVar('form-data-key', 'form-data-value'); } From 2209048c64325f134c415275ea0698d427008c8c Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Mon, 23 Sep 2024 12:12:05 +0530 Subject: [PATCH 4/4] feat: updates --- packages/bruno-cli/src/utils/common.js | 25 ++++++++++--------- .../src/ipc/network/prepare-request.js | 25 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/bruno-cli/src/utils/common.js b/packages/bruno-cli/src/utils/common.js index c659ac1d7c..16c2d1a7bf 100644 --- a/packages/bruno-cli/src/utils/common.js +++ b/packages/bruno-cli/src/utils/common.js @@ -24,20 +24,21 @@ const createFormData = (datas, collectionPath) => { // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 const form = new FormData(); forOwn(datas, (value, key) => { - if (typeof value == 'object') { - 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)); - }); - } else { + 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; }; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 4f4900a72f..40c74023c8 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -170,20 +170,21 @@ const createFormData = (datas, collectionPath) => { // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 const form = new FormData(); forOwn(datas, (value, key) => { - if (typeof value == 'object') { - 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)); - }); - } else { + 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; };