diff --git a/package-lock.json b/package-lock.json
index f53c786a9..2cfe22abb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24043,7 +24043,7 @@
"graphiql": "3.7.1",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
- "httpsnippet": "^3.0.6",
+ "httpsnippet": "^3.0.9",
"i18next": "24.1.2",
"idb": "^7.0.0",
"immer": "^9.0.15",
diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json
index c824f13a8..7203bb816 100644
--- a/packages/bruno-app/package.json
+++ b/packages/bruno-app/package.json
@@ -33,7 +33,7 @@
"graphiql": "3.7.1",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
- "httpsnippet": "^3.0.6",
+ "httpsnippet": "^3.0.9",
"i18next": "24.1.2",
"idb": "^7.0.0",
"immer": "^9.0.15",
diff --git a/packages/bruno-app/src/components/FilePickerEditor/index.js b/packages/bruno-app/src/components/FilePickerEditor/index.js
index d976a3e79..9387800d2 100644
--- a/packages/bruno-app/src/components/FilePickerEditor/index.js
+++ b/packages/bruno-app/src/components/FilePickerEditor/index.js
@@ -6,10 +6,10 @@ import { IconX } from '@tabler/icons';
import { isWindowsOS } from 'utils/common/platform';
import slash from 'utils/common/slash';
-const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false}) => {
+const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false }) => {
value = value || [];
const dispatch = useDispatch();
- const filenames = value
+ const filenames = (isSingleFilePicker ? [value] : value || [])
.filter((v) => v != null && v != '')
.map((v) => {
const separator = isWindowsOS() ? '\\' : '/';
@@ -20,7 +20,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
const title = filenames.map((v) => `- ${v}`).join('\n');
const browse = () => {
- dispatch(browseFiles([],['']))
+ dispatch(browseFiles([], [!isSingleFilePicker ? "multiSelections": ""]))
.then((filePaths) => {
// If file is in the collection's directory, then we use relative path
// Otherwise, we use the absolute path
@@ -34,7 +34,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
return filePath;
});
- onChange(filePaths);
+ onChange(isSingleFilePicker ? filePaths[0] : filePaths);
})
.catch((error) => {
console.error(error);
@@ -42,7 +42,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
};
const clear = () => {
- onChange([]);
+ onChange(isSingleFilePicker ? '' : []);
};
const renderButtonText = (filenames) => {
@@ -66,9 +66,9 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
) : (
);
};
-export default FilePickerEditor;
+export default FilePickerEditor;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js
new file mode 100644
index 000000000..35adfcc1f
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/FileBody/StyledWrapper.js
@@ -0,0 +1,65 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ font-weight: 600;
+ table-layout: fixed;
+
+ thead,
+ td {
+ border: 1px solid ${(props) => props.theme.table.border};
+ }
+
+ thead {
+ color: ${(props) => props.theme.table.thead.color};
+ font-size: 0.8125rem;
+ user-select: none;
+ }
+ td {
+ padding: 6px 10px;
+
+ &:nth-child(1) {
+ width: 30%;
+ }
+
+ &:nth-child(2) {
+ width: 45%;
+ }
+
+ &:nth-child(3) {
+ width: 25%;
+ }
+
+ &:nth-child(4) {
+ width: 70px;
+ }
+ }
+ }
+
+ .btn-add-param {
+ font-size: 0.8125rem;
+ }
+
+ input[type='text'] {
+ width: 100%;
+ border: solid 1px transparent;
+ outline: none !important;
+ color: ${(props) => props.theme.table.input.color};
+ background: transparent;
+
+ &:focus {
+ outline: none !important;
+ border: solid 1px transparent;
+ }
+ }
+
+ input[type='radio'] {
+ cursor: pointer;
+ position: relative;
+ top: 1px;
+ }
+`;
+
+export default Wrapper;
diff --git a/packages/bruno-app/src/components/RequestPane/FileBody/index.js b/packages/bruno-app/src/components/RequestPane/FileBody/index.js
new file mode 100644
index 000000000..ae1646330
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/FileBody/index.js
@@ -0,0 +1,164 @@
+import React, { useState, useEffect } from 'react';
+import { get, cloneDeep, isArray } from 'lodash';
+import { IconTrash } from '@tabler/icons';
+import { useDispatch } from 'react-redux';
+import { useTheme } from 'providers/Theme';
+import { addFileParam, updateFileParam, deleteFileParam } from 'providers/ReduxStore/slices/collections/index';
+import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
+import StyledWrapper from './StyledWrapper';
+import FilePickerEditor from 'components/FilePickerEditor/index';
+import SingleLineEditor from 'components/SingleLineEditor/index';
+
+const FileBody = ({ item, collection }) => {
+ const dispatch = useDispatch();
+ const { storedTheme } = useTheme();
+ const params = item.draft ? get(item, 'draft.request.body.file') : get(item, 'request.body.file');
+
+ const [enabledFileUid, setEnableFileUid] = useState(params && params.length ? params[0].uid : '');
+
+ const addFile = () => {
+ dispatch(
+ addFileParam({
+ itemUid: item.uid,
+ collectionUid: collection.uid,
+ })
+ );
+ };
+
+ const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
+ const handleRun = () => dispatch(sendRequest(item, collection.uid));
+
+ const handleParamChange = (e, _param, type) => {
+ const param = cloneDeep(_param);
+ switch (type) {
+ case 'filePath': {
+ param.filePath = e.target.filePath;
+ param.contentType = "";
+ break;
+ }
+ case 'contentType': {
+ param.contentType = e.target.contentType;
+ break;
+ }
+ case 'selected': {
+ param.selected = e.target.selected;
+ setEnableFileUid(param.uid)
+ break;
+ }
+ }
+ dispatch(
+ updateFileParam({
+ param: param,
+ itemUid: item.uid,
+ collectionUid: collection.uid
+ })
+ );
+ };
+
+ const handleRemoveParams = (param) => {
+ dispatch(
+ deleteFileParam({
+ paramUid: param.uid,
+ itemUid: item.uid,
+ collectionUid: collection.uid
+ })
+ );
+ };
+
+ return (
+
+
+
+
+ |
+ File
+ |
+
+ Content-Type
+ |
+
+ Selected
+ |
+ |
+
+
+
+ {params && params.length
+ ? params.map((param, index) => {
+ return (
+
+ |
+
+ handleParamChange(
+ {
+ target: {
+ filePath: path
+ }
+ },
+ param,
+ 'filePath'
+ )
+ }
+ collection={collection}
+ />
+ |
+
+
+ handleParamChange(
+ {
+ target: {
+ contentType: newValue
+ }
+ },
+ param,
+ 'contentType'
+ )
+ }
+ onRun={handleRun}
+ collection={collection}
+ />
+ |
+
+
+ handleParamChange(e, param, 'selected')}
+ />
+
+ |
+
+
+
+
+ |
+
+ );
+ })
+ : null}
+
+
+
+
+
+
+ );
+};
+export default FileBody;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js
index 95b3b6a55..db73597df 100644
--- a/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js
+++ b/packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/index.js
@@ -132,10 +132,10 @@ const RequestBodyMode = ({ item, collection }) => {
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
- onModeChange('binaryFile');
+ onModeChange('file');
}}
>
- Binary File
+ File / Binary
{
const dispatch = useDispatch();
@@ -63,8 +63,8 @@ const RequestBody = ({ item, collection }) => {
);
}
- if (bodyMode === 'binaryFile') {
- return
+ if (bodyMode === 'file') {
+ return
}
if (bodyMode === 'formUrlEncoded') {
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index f089dbddc..d251112c7 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -759,7 +759,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
sparql: null,
multipartForm: null,
formUrlEncoded: null,
- binaryFile: null
+ file: null
},
auth: auth ?? {
mode: 'none'
@@ -1040,13 +1040,16 @@ export const browseDirectory = () => (dispatch, getState) => {
export const browseFiles =
(filters = [], properties = ['multiSelections']) =>
- (dispatch, getState) => {
+ (_dispatch, _getState) => {
const { ipcRenderer } = window;
return new Promise((resolve, reject) => {
- ipcRenderer.invoke('renderer:browse-files', undefined, undefined, undefined, filters, properties).then(resolve).catch(reject);
+ ipcRenderer
+ .invoke('renderer:browse-files', filters, properties)
+ .then(resolve)
+ .catch(reject);
});
- };
+};
export const updateBrunoConfig = (brunoConfig, collectionUid) => (dispatch, getState) => {
const state = getState();
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index bdecc1543..dcf130065 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -961,6 +961,76 @@ export const collectionsSlice = createSlice({
}
}
},
+ addFileParam: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection) {
+ const item = findItemInCollection(collection, action.payload.itemUid);
+
+ if (item && isItemARequest(item)) {
+ if (!item.draft) {
+ item.draft = cloneDeep(item);
+ }
+ item.draft.request.body.file = item.draft.request.body.file || [];
+
+ item.draft.request.body.file.push({
+ uid: uuid(),
+ filePath: '',
+ contentType: '',
+ selected: false
+ });
+ }
+ }
+ },
+ updateFileParam: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection) {
+ const item = findItemInCollection(collection, action.payload.itemUid);
+
+ if (item && isItemARequest(item)) {
+ if (!item.draft) {
+ item.draft = cloneDeep(item);
+ }
+
+ const param = find(item.draft.request.body.file, (p) => p.uid === action.payload.param.uid);
+
+ if (param) {
+ const contentType = mime.contentType(path.extname(action.payload.param.filePath));
+ param.filePath = action.payload.param.filePath;
+ param.contentType = action.payload.param.contentType || contentType || '';
+ param.selected = action.payload.param.selected;
+
+ item.draft.request.body.file = item.draft.request.body.file.map((p) => {
+ p.selected = p.uid === param.uid;
+ return p;
+ });
+ }
+ }
+ }
+ },
+ deleteFileParam: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection) {
+ const item = findItemInCollection(collection, action.payload.itemUid);
+
+ if (item && isItemARequest(item)) {
+ if (!item.draft) {
+ item.draft = cloneDeep(item);
+ }
+
+ item.draft.request.body.file = filter(
+ item.draft.request.body.file,
+ (p) => p.uid !== action.payload.paramUid
+ );
+
+ if (item.draft.request.body.file.length > 0) {
+ item.draft.request.body.file[0].selected = true;
+ }
+ }
+ }
+ },
updateRequestAuthMode: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -1017,8 +1087,8 @@ export const collectionsSlice = createSlice({
item.draft.request.body.sparql = action.payload.content;
break;
}
- case 'binaryFile': {
- item.draft.request.body.binaryFile = action.payload.content;
+ case 'file': {
+ item.draft.request.body.file = action.payload.content;
break;
}
case 'formUrlEncoded': {
@@ -2022,9 +2092,9 @@ export const {
addMultipartFormParam,
updateMultipartFormParam,
deleteMultipartFormParam,
- addBinaryFile,
- updateBinaryFile,
- deleteBinaryFile,
+ addFileParam,
+ updateFileParam,
+ deleteFileParam,
moveMultipartFormParam,
updateRequestAuthMode,
updateRequestBodyMode,
diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js
index 19e4ea4de..a4128b0c0 100644
--- a/packages/bruno-app/src/utils/codegenerator/har.js
+++ b/packages/bruno-app/src/utils/codegenerator/har.js
@@ -14,7 +14,7 @@ const createContentType = (mode) => {
return 'application/json';
case 'multipartForm':
return 'multipart/form-data';
- case 'binaryFile':
+ case 'file':
return 'application/octet-stream';
default:
return '';
@@ -65,6 +65,23 @@ const createPostData = (body, type) => {
switch (body.mode) {
case 'formUrlEncoded':
+ return {
+ mimeType: contentType,
+ text: new URLSearchParams(
+ body[body.mode]
+ .filter((param) => param.enabled)
+ .reduce((acc, param) => {
+ acc[param.name] = param.value;
+ return acc;
+ }, {})
+ ).toString(),
+ params: body[body.mode]
+ .filter((param) => param.enabled)
+ .map((param) => ({
+ name: param.name,
+ value: param.value
+ }))
+ };
case 'multipartForm':
return {
mimeType: contentType,
@@ -76,20 +93,15 @@ const createPostData = (body, type) => {
...(param.type === 'file' && { fileName: param.value })
}))
};
- case 'binaryFile':
+ case 'file':
const binary = {
- mimeType: 'application/octet-stream',
- // mimeType: body[body.mode].filter((param) => param.enabled)[0].contentType,
+ mimeType: body[body.mode].filter((param) => param.enabled)[0].contentType,
params: body[body.mode]
- .filter((param) => param.enabled)
+ .filter((param) => param.selected)
.map((param) => ({
- name: param.name,
- value: param.value,
- fileName: param.value
+ value: param.filePath,
}))
};
-
- console.log('curl-binary', binary);
return binary;
default:
return {
diff --git a/packages/bruno-app/src/utils/collections/export.js b/packages/bruno-app/src/utils/collections/export.js
index b7ca9f5ba..3d15fdd07 100644
--- a/packages/bruno-app/src/utils/collections/export.js
+++ b/packages/bruno-app/src/utils/collections/export.js
@@ -14,7 +14,7 @@ export const deleteUidsInItems = (items) => {
each(get(item, 'request.vars.assertions'), (a) => delete a.uid);
each(get(item, 'request.body.multipartForm'), (param) => delete param.uid);
each(get(item, 'request.body.formUrlEncoded'), (param) => delete param.uid);
- each(get(item, 'request.body.binaryFile'), (param) => delete param.uid);
+ each(get(item, 'request.body.file'), (param) => delete param.uid);
}
if (item.items && item.items.length) {
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 309c245eb..eb53cfb48 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -281,15 +281,13 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
});
};
- const copyBinaryFileParams = (params = []) => {
+ const copyFileParams = (params = []) => {
return map(params, (param) => {
return {
uid: param.uid,
- type: param.type,
- name: param.name,
- value: param.value,
+ filePath: param.filePath,
contentType: param.contentType,
- enabled: param.enabled
+ selected: param.selected
}
});
}
@@ -322,7 +320,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
sparql: si.request.body.sparql,
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
multipartForm: copyMultipartFormParams(si.request.body.multipartForm),
- binaryFile: copyBinaryFileParams(si.request.body.binaryFile)
+ file: copyFileParams(si.request.body.file)
},
script: si.request.script,
vars: si.request.vars,
@@ -675,8 +673,8 @@ export const humanizeRequestBodyMode = (mode) => {
label = 'SPARQL';
break;
}
- case 'binaryFile': {
- label = 'Binary File';
+ case 'file': {
+ label = 'File / Binary';
break;
}
case 'formUrlEncoded': {
@@ -779,7 +777,7 @@ export const refreshUidsInItem = (item) => {
each(get(item, 'request.params'), (param) => (param.uid = uuid()));
each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid()));
each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid()));
- each(get(item, 'request.body.binaryFile'), (param) => (param.uid = uuid()));
+ each(get(item, 'request.body.file'), (param) => (param.uid = uuid()));
return item;
};
@@ -790,13 +788,13 @@ export const deleteUidsInItem = (item) => {
const headers = get(item, 'request.headers', []);
const bodyFormUrlEncoded = get(item, 'request.body.formUrlEncoded', []);
const bodyMultipartForm = get(item, 'request.body.multipartForm', []);
- const binaryFile = get(item, 'request.body.binaryFile', []);
+ const file = get(item, 'request.body.file', []);
params.forEach((param) => delete param.uid);
headers.forEach((header) => delete header.uid);
bodyFormUrlEncoded.forEach((param) => delete param.uid);
bodyMultipartForm.forEach((param) => delete param.uid);
- binaryFile.forEach((param) => delete param.uid);
+ file.forEach((param) => delete param.uid);
return item;
};
diff --git a/packages/bruno-app/src/utils/curl/curl-to-json.js b/packages/bruno-app/src/utils/curl/curl-to-json.js
index 5a2c62022..69305b370 100644
--- a/packages/bruno-app/src/utils/curl/curl-to-json.js
+++ b/packages/bruno-app/src/utils/curl/curl-to-json.js
@@ -101,31 +101,24 @@ function getFilesString(request) {
const data = {};
data.data = {};
+ if (request.isDataBinary) {
+ let filePath = '';
- if (request.isDataBinary){
-
- let filePath = ''
-
- if(request.data.startsWith('@')){
+ if (request.data.startsWith('@')) {
filePath = request.data.slice(1);
- }else{
+ } else {
filePath = request.data;
}
- const fileName = path.basename(filePath);
-
data.data = [
{
- name: repr(fileName),
- value: [repr(filePath)],
- enabled: true,
+ filePath: repr(filePath),
contentType: request.headers['Content-Type'],
- type: 'binaryFile'
+ selected: true,
}
];
return data;
-
}
data.files = {};
@@ -190,13 +183,11 @@ const curlToJson = (curlCommand) => {
if (request.query) {
requestJson.queries = getQueries(request);
- }
-
- else if (request.multipartUploads || request.isDataBinary) {
+ } else if (request.multipartUploads || request.isDataBinary) {
Object.assign(requestJson, getFilesString(request));
} else if (typeof request.data === 'string' || typeof request.data === 'number') {
Object.assign(requestJson, getDataString(request));
- }
+ }
if (request.insecure) {
requestJson.insecure = false;
diff --git a/packages/bruno-app/src/utils/curl/curl-to-json.spec.js b/packages/bruno-app/src/utils/curl/curl-to-json.spec.js
index 6f8206139..4c3194c2e 100644
--- a/packages/bruno-app/src/utils/curl/curl-to-json.spec.js
+++ b/packages/bruno-app/src/utils/curl/curl-to-json.spec.js
@@ -117,7 +117,7 @@ describe('curlToJson', () => {
value: ['/path/to/file'],
enabled: true,
contentType: 'application/json;charset=utf-8',
- type: 'binaryFile'
+ type: 'file'
}
]
});
diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js
index d91588178..ad4f1edf6 100644
--- a/packages/bruno-app/src/utils/curl/index.js
+++ b/packages/bruno-app/src/utils/curl/index.js
@@ -51,7 +51,7 @@ export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-reque
multipartForm: null,
formUrlEncoded: null,
graphql: null,
- binaryFile: null
+ file: null
};
if (parsedBody && contentType && typeof contentType === 'string') {
@@ -59,8 +59,8 @@ export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-reque
body.mode = 'graphql';
body.graphql = parseGraphQL(parsedBody);
} else if (requestType === 'http-request' && request.isDataBinary) {
- body.mode = 'binaryFile';
- body.binaryFile = parsedBody;
+ body.mode = 'file';
+ body.file = parsedBody;
}else if (contentType.includes('application/json')) {
body.mode = 'json';
body.json = convertToCodeMirrorJson(parsedBody);
diff --git a/packages/bruno-app/src/utils/importers/common.js b/packages/bruno-app/src/utils/importers/common.js
index af187cc82..9d370a455 100644
--- a/packages/bruno-app/src/utils/importers/common.js
+++ b/packages/bruno-app/src/utils/importers/common.js
@@ -35,7 +35,7 @@ export const updateUidsInCollection = (_collection) => {
each(get(item, 'request.assertions'), (a) => (a.uid = uuid()));
each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid()));
each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid()));
- each(get(item, 'request.body.binaryFile'), (param) => (param.uid = uuid()));
+ each(get(item, 'request.body.file'), (param) => (param.uid = uuid()));
if (item.items && item.items.length) {
updateItemUids(item.items);
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index cdf488e75..cb73d2dc6 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -62,14 +62,11 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
});
// browse directory for file
- ipcMain.handle('renderer:browse-files', async (event, pathname, request, filters, properties) => {
+ ipcMain.handle('renderer:browse-files', async (_, filters, properties) => {
try {
-
- const filePaths = await browseFiles(mainWindow, filters, properties);
-
- return filePaths;
+ return await browseFiles(mainWindow, filters, properties);
} catch (error) {
- return Promise.reject(error);
+ throw error;
}
});
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index dbc36d846..b76113e42 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -28,7 +28,7 @@ const { makeAxiosInstance } = require('./axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { addDigestInterceptor } = require('./digestauth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
-const { chooseFileToSave, writeBinaryFile, writeFile } = require('../../utils/filesystem');
+const { chooseFileToSave, writeFile } = require('../../utils/filesystem');
const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies');
const {
resolveOAuth2AuthorizationCodeAccessToken,
@@ -587,7 +587,6 @@ const registerNetworkIpc = (mainWindow) => {
try {
request.signal = abortController.signal;
-
saveCancelToken(cancelTokenUid, abortController);
await runPreRequest(
@@ -618,7 +617,7 @@ const registerNetworkIpc = (mainWindow) => {
url: request.url,
method: request.method,
headers: request.headers,
- data: request.mode == 'binaryFile'? undefined: safeParseJSON(safeStringifyJSON(request.data)) ,
+ data: request.mode == 'file'? "": safeParseJSON(safeStringifyJSON(request.data)),
timestamp: Date.now()
},
collectionUid,
@@ -1375,7 +1374,7 @@ const registerNetworkIpc = (mainWindow) => {
if (encoding === 'utf-8') {
await writeFile(filePath, data);
} else {
- await writeBinaryFile(filePath, data);
+ await writeFile(filePath, data);
}
}
} catch (error) {
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 297033c6d..137b74440 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -1,4 +1,4 @@
-const { get, each, filter } = require('lodash');
+const { get, each, filter, find } = require('lodash');
const decomment = require('decomment');
const crypto = require('node:crypto');
const fs = require('node:fs/promises');
@@ -253,31 +253,26 @@ const prepareRequest = async (item, collection, abortController) => {
axiosRequest.data = request.body.sparql;
}
- if (request.body.mode === 'binaryFile') {
-
+ if (request.body.mode === 'file') {
if (!contentTypeDefined) {
- axiosRequest.headers['content-type'] = 'application/octet-stream';
+ axiosRequest.headers['content-type'] = 'application/octet-stream'; // Default headers for binary file uploads
}
-
- if (request.body.binaryFile && request.body.binaryFile.length > 0) {
-
- axiosRequest.headers['content-type'] = request.body.binaryFile[0].contentType;
-
- let filePath = request.body.binaryFile[0].value[0];
-
- if (filePath && filePath !== '') {
-
+
+ const file = find(request.body.file, (param) => param.selected);
+ if (file) {
+ let { filePath, contentType } = file;
+
+ axiosRequest.headers['content-type'] = contentType;
+ if (filePath) {
if (!path.isAbsolute(filePath)) {
-
filePath = path.join(collectionPath, filePath);
}
-
- const file = await fs.readFile(filePath, abortController)
-
- axiosRequest.data = file
-
- if(axiosRequest.headers['content-type'].includes('application/json')) {
- axiosRequest.data = JSON.parse(file)
+
+ try {
+ const fileContent = await fs.readFile(filePath);
+ axiosRequest.data = fileContent;
+ } catch (error) {
+ console.error('Error reading file:', error);
}
}
}
diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js
index d37742be4..4bf4c9970 100644
--- a/packages/bruno-electron/src/utils/filesystem.js
+++ b/packages/bruno-electron/src/utils/filesystem.js
@@ -69,16 +69,6 @@ function normalizeWslPath(pathname) {
}
const writeFile = async (pathname, content) => {
- try {
- fs.writeFileSync(pathname, content, {
- encoding: 'utf8'
- });
- } catch (err) {
- return Promise.reject(err);
- }
-};
-
-const writeBinaryFile = async (pathname, content) => {
try {
fs.writeFileSync(pathname, content);
} catch (err) {
@@ -265,7 +255,6 @@ module.exports = {
isWSLPath,
normalizeWslPath,
writeFile,
- writeBinaryFile,
hasJsonExtension,
hasBruExtension,
createDirectory,
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index e7b0c5772..26a670681 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -25,7 +25,7 @@ const grammar = ohm.grammar(`Bru {
BruFile = (meta | http | query | params | headers | auths | bodies | varsandassert | script | tests | docs)*
auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
- bodyforms = bodyformurlencoded | bodymultipart | bodybinaryfile
+ bodyforms = bodyformurlencoded | bodymultipart | bodyfile
params = paramspath | paramsquery
nl = "\\r"? "\\n"
@@ -102,7 +102,7 @@ const grammar = ohm.grammar(`Bru {
bodyformurlencoded = "body:form-urlencoded" dictionary
bodymultipart = "body:multipart-form" dictionary
- bodybinaryfile = "body:binary-file" dictionary
+ bodyfile = "body:file" dictionary
script = scriptreq | scriptres
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
@@ -174,12 +174,12 @@ const multipartExtractContentType = (pair) => {
}
};
-const binaryFileExtractContentType = (pair) => {
+const fileExtractContentType = (pair) => {
if (_.isString(pair.value)) {
const match = pair.value.match(/^(.*?)\s*@contentType\((.*?)\)\s*$/);
- if (match != null && match.length > 2) {
- pair.value = match[1];
- pair.contentType = match[2];
+ if (match && match.length > 2) {
+ pair.value = match[1].trim();
+ pair.contentType = match[2].trim();
} else {
pair.contentType = '';
}
@@ -204,23 +204,27 @@ const mapPairListToKeyValPairsMultipart = (pairList = [], parseEnabled = true) =
});
};
-const mapPairListToKeyValPairsBinaryFile = (pairList = [], parseEnabled = true) => {
+const mapPairListToKeyValPairsFile = (pairList = [], parseEnabled = true) => {
const pairs = mapPairListToKeyValPairs(pairList, parseEnabled);
-
return pairs.map((pair) => {
- binaryFileExtractContentType(pair);
+ fileExtractContentType(pair);
if (pair.value.startsWith('@file(') && pair.value.endsWith(')')) {
- let filestr = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
- pair.type = 'binaryFile';
- pair.value = filestr != '' ? filestr.split('|') : [''];
+ let filePath = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
+ pair.filePath = filePath;
+ pair.selected = pair.enabled
+
+ // Remove pair.value as it only contains the file path reference
+ delete pair.value;
+ // Remove pair.name as it is auto-generated (e.g., file1, file2, file3, etc.)
+ delete pair.name;
+ delete pair.enabled;
}
return pair;
});
};
-
const concatArrays = (objValue, srcValue) => {
if (_.isArray(objValue) && _.isArray(srcValue)) {
return objValue.concat(srcValue);
@@ -605,10 +609,10 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
- bodybinaryfile(_1, dictionary) {
+ bodyfile(_1, dictionary) {
return {
body: {
- binaryFile: mapPairListToKeyValPairsBinaryFile(dictionary.ast)
+ file: mapPairListToKeyValPairsFile(dictionary.ast)
}
};
},
@@ -746,3 +750,4 @@ const parser = (input) => {
};
module.exports = parser;
+
\ No newline at end of file
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index c4e2ba323..12aaa1894 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -2,8 +2,8 @@ const _ = require('lodash');
const { indentString } = require('../../v1/src/utils');
-const enabled = (items = []) => items.filter((item) => item.enabled);
-const disabled = (items = []) => items.filter((item) => !item.enabled);
+const enabled = (items = [], key = "enabled") => items.filter((item) => item[key]);
+const disabled = (items = [], key = "enabled") => items.filter((item) => !item[key]);
// remove the last line if two new lines are found
const stripLastLine = (text) => {
@@ -313,24 +313,21 @@ ${indentString(body.sparql)}
bru += '\n}\n\n';
}
+ if (body && body.file && body.file.length) {
+ bru += `body:file {`;
+ const files = enabled(body.file, "selected").concat(disabled(body.file, "selected"));
- if (body && body.binaryFile && body.binaryFile.length) {
- bru += `body:binary-file {`;
- const binaryFiles = enabled(body.binaryFile).concat(disabled(body.binaryFile));
-
- if (binaryFiles.length) {
+ if (files.length) {
bru += `\n${indentString(
- binaryFiles
+ files
.map((item) => {
- const enabled = item.enabled ? '' : '~';
+ const selected = item.selected ? '' : '~';
const contentType =
item.contentType && item.contentType !== '' ? ' @contentType(' + item.contentType + ')' : '';
-
- if (item.type === 'binaryFile') {
- let filestr = item.value[0] || '';
- const value = `@file(${filestr})`;
- return `${enabled}${item.name}: ${value}${contentType}`;
- }
+ const filePath = item.filePath || '';
+ const value = `@file(${filePath})`;
+ const itemName = "file";
+ return `${selected}${itemName}: ${value}${contentType}`;
})
.join('\n')
)}`;
diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru
index 5f7183f34..ad66c64e8 100644
--- a/packages/bruno-lang/v2/tests/fixtures/request.bru
+++ b/packages/bruno-lang/v2/tests/fixtures/request.bru
@@ -102,9 +102,10 @@ body:multipart-form {
~message: hello
}
-body:binary-file {
+body:file {
file: @file(path/to/file.json) @contentType(application/json)
- ~file2: @file(path/to/file2.json) @contentType(application/json)
+ file: @file(path/to/file.json) @contentType(application/json)
+ ~file: @file(path/to/file2.json) @contentType(application/json)
}
body:graphql {
diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json
index ad7a45495..1cfe98809 100644
--- a/packages/bruno-lang/v2/tests/fixtures/request.json
+++ b/packages/bruno-lang/v2/tests/fixtures/request.json
@@ -138,20 +138,21 @@
"type": "text"
}
],
- "binaryFile" : [
+ "file" : [
{
- "name": "file",
- "value": ["path/to/file.json"],
- "enabled": true,
- "type": "binaryFile",
- "contentType": "application/json"
+ "filePath": "path/to/file.json",
+ "contentType": "application/json",
+ "selected": true
},
{
- "name": "file2",
- "value": ["path/to/file2.json"],
- "enabled": false,
- "type": "binaryFile",
- "contentType": "application/json"
+ "filePath": "path/to/file.json",
+ "contentType": "application/json",
+ "selected": true
+ },
+ {
+ "filePath": "path/to/file2.json",
+ "contentType": "application/json",
+ "selected": false
}
]
},
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 19d98afbb..e0d05d167 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -75,20 +75,18 @@ const multipartFormSchema = Yup.object({
.strict();
-const binaryFileSchema = Yup.object({
+const fileSchema = Yup.object({
uid: uidSchema,
- type: Yup.string().oneOf(['binaryFile']).required('type is required'),
- name: Yup.string().nullable(),
- value: Yup.array().of(Yup.string().nullable()).nullable(),
+ filePath: Yup.string().nullable(),
contentType: Yup.string().nullable(),
- enabled: Yup.boolean()
+ selected: Yup.boolean()
})
.noUnknown(true)
.strict();
const requestBodySchema = Yup.object({
mode: Yup.string()
- .oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql', 'sparql', 'binaryFile'])
+ .oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql', 'sparql', 'file'])
.required('mode is required'),
json: Yup.string().nullable(),
text: Yup.string().nullable(),
@@ -97,7 +95,7 @@ const requestBodySchema = Yup.object({
formUrlEncoded: Yup.array().of(keyValueSchema).nullable(),
multipartForm: Yup.array().of(multipartFormSchema).nullable(),
graphql: graphqlBodySchema.nullable(),
- binaryFile: Yup.array().of(binaryFileSchema).nullable()
+ file: Yup.array().of(fileSchema).nullable()
})
.noUnknown(true)
.strict();
diff --git a/packages/bruno-tests/collection/echo/echo bom json.bru b/packages/bruno-tests/collection/echo/echo bom json.bru
deleted file mode 100644
index 117c329ba..000000000
--- a/packages/bruno-tests/collection/echo/echo bom json.bru
+++ /dev/null
@@ -1,11 +0,0 @@
-meta {
- name: echo bom json
- type: http
- seq: 1
-}
-
-get {
- url: {{host}}/api/echo/bom-json-test
- body: none
- auth: none
-}
\ No newline at end of file
diff --git a/packages/bruno-tests/collection/echo/echo file body/bin.bru b/packages/bruno-tests/collection/echo/echo file body/bin.bru
new file mode 100644
index 000000000..fcb87d9c0
Binary files /dev/null and b/packages/bruno-tests/collection/echo/echo file body/bin.bru differ
diff --git a/packages/bruno-tests/collection/echo/echo file body/bru.bru b/packages/bruno-tests/collection/echo/echo file body/bru.bru
new file mode 100644
index 000000000..8fb60ef28
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo file body/bru.bru
@@ -0,0 +1,40 @@
+meta {
+ name: bru
+ type: http
+ seq: 4
+}
+
+post {
+ url: {{echo-host}}
+ body: file
+ auth: none
+}
+
+body:file {
+ file: @file(ping.bru) @contentType(bru)
+}
+
+tests {
+ test("should return bru file body contents", function() {
+ const data = res.getBody();
+ const expectedData = `meta {
+ name: ping
+ type: http
+ seq: 1
+ }
+
+ get {
+ url: {{host}}/ping
+ body: none
+ auth: none
+ }
+ `;
+ expect(res.getBody()).to.eql(expectedData);
+ });
+
+
+ test("should return proper header", function() {
+ const contentType = res.getHeader('content-type');
+ expect(contentType).to.eql('bru');
+ });
+}
diff --git a/packages/bruno-tests/collection/echo/echo file body/image.bru b/packages/bruno-tests/collection/echo/echo file body/image.bru
new file mode 100644
index 000000000..01c5d408b
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo file body/image.bru
@@ -0,0 +1,22 @@
+meta {
+ name: image
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{echo-host}}
+ body: file
+ auth: none
+}
+
+body:file {
+ file: @file(bruno.png) @contentType(image/png)
+}
+
+tests {
+ test("should return proper header", function() {
+ const contentType = res.getHeader('content-type');
+ expect(contentType).to.eql('image/png');
+ });
+}
diff --git a/packages/bruno-tests/collection/echo/echo file body/json.bru b/packages/bruno-tests/collection/echo/echo file body/json.bru
new file mode 100644
index 000000000..42412038a
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo file body/json.bru
@@ -0,0 +1,55 @@
+meta {
+ name: json
+ type: http
+ seq: 2
+}
+
+post {
+ url: {{echo-host}}
+ body: file
+ auth: none
+}
+
+body:file {
+ file: @file(file.json) @contentType(application/json; charset=utf-8)
+}
+
+tests {
+ test("should return json file body contents buffer", function() {
+ const data = res.getBody();
+ expect(res.getBody()).to.eql({
+ "type": "Buffer",
+ "data": [
+ 123,
+ 10,
+ 32,
+ 32,
+ 34,
+ 104,
+ 101,
+ 108,
+ 108,
+ 111,
+ 34,
+ 58,
+ 32,
+ 34,
+ 98,
+ 114,
+ 117,
+ 110,
+ 111,
+ 34,
+ 10,
+ 125,
+ 10
+ ]
+ });
+ });
+
+ test("should return proper header", function() {
+ const contentType = res.getHeader('content-type');
+ expect(contentType).to.eql('application/json; charset=utf-8');
+ });
+
+}
diff --git a/packages/bruno-tests/collection/echo/echo file body/text.bru b/packages/bruno-tests/collection/echo/echo file body/text.bru
new file mode 100644
index 000000000..663c150f5
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo file body/text.bru
@@ -0,0 +1,30 @@
+meta {
+ name: text
+ type: http
+ seq: 3
+}
+
+post {
+ url: {{echo-host}}
+ body: file
+ auth: none
+}
+
+body:file {
+ file: @file(file.txt) @contentType(text/plain; charset=utf-8)
+}
+
+tests {
+ test("should return json file body contents", function() {
+ const data = res.getBody();
+ const expectedData = `file.txt
+
+ hello, bruno`;
+ expect(res.getBody()).to.eql(expectedData);
+ });
+
+ test("should return proper header", function() {
+ const contentType = res.getHeader('content-type');
+ expect(contentType).to.eql('text/plain; charset=utf-8');
+ });
+}
diff --git a/packages/bruno-tests/collection/echo/echo json.bru b/packages/bruno-tests/collection/echo/echo json.bru
index 1749eda6b..0fa135fe8 100644
--- a/packages/bruno-tests/collection/echo/echo json.bru
+++ b/packages/bruno-tests/collection/echo/echo json.bru
@@ -1,7 +1,7 @@
meta {
name: echo json
type: http
- seq: 2
+ seq: 3
}
post {
diff --git a/packages/bruno-tests/collection/echo/echo numbers.bru b/packages/bruno-tests/collection/echo/echo numbers.bru
index 8f68fe558..0d6126909 100644
--- a/packages/bruno-tests/collection/echo/echo numbers.bru
+++ b/packages/bruno-tests/collection/echo/echo numbers.bru
@@ -1,7 +1,7 @@
meta {
name: echo numbers
type: http
- seq: 1
+ seq: 2
}
post {
diff --git a/packages/bruno-tests/collection/echo/echo plaintext.bru b/packages/bruno-tests/collection/echo/echo plaintext.bru
index 56a23d345..3ace3bb2b 100644
--- a/packages/bruno-tests/collection/echo/echo plaintext.bru
+++ b/packages/bruno-tests/collection/echo/echo plaintext.bru
@@ -1,7 +1,7 @@
meta {
name: echo plaintext
type: http
- seq: 3
+ seq: 4
}
post {
diff --git a/packages/bruno-tests/collection/echo/echo xml parsed(self closing tags).bru b/packages/bruno-tests/collection/echo/echo xml parsed(self closing tags).bru
index d337cebb3..b378fb0c8 100644
--- a/packages/bruno-tests/collection/echo/echo xml parsed(self closing tags).bru
+++ b/packages/bruno-tests/collection/echo/echo xml parsed(self closing tags).bru
@@ -1,7 +1,7 @@
meta {
name: echo xml parsed(self closing tags)
type: http
- seq: 6
+ seq: 7
}
post {
diff --git a/packages/bruno-tests/collection/echo/echo xml parsed.bru b/packages/bruno-tests/collection/echo/echo xml parsed.bru
index acd24a292..71363b71c 100644
--- a/packages/bruno-tests/collection/echo/echo xml parsed.bru
+++ b/packages/bruno-tests/collection/echo/echo xml parsed.bru
@@ -1,7 +1,7 @@
meta {
name: echo xml parsed
type: http
- seq: 4
+ seq: 5
}
post {
diff --git a/packages/bruno-tests/collection/echo/echo xml raw.bru b/packages/bruno-tests/collection/echo/echo xml raw.bru
index 6a02ac238..0f8bec255 100644
--- a/packages/bruno-tests/collection/echo/echo xml raw.bru
+++ b/packages/bruno-tests/collection/echo/echo xml raw.bru
@@ -1,7 +1,7 @@
meta {
name: echo xml raw
type: http
- seq: 5
+ seq: 6
}
post {
diff --git a/packages/bruno-tests/collection/file.bin b/packages/bruno-tests/collection/file.bin
new file mode 100644
index 000000000..8de62184f
Binary files /dev/null and b/packages/bruno-tests/collection/file.bin differ
diff --git a/packages/bruno-tests/collection/file.txt b/packages/bruno-tests/collection/file.txt
new file mode 100644
index 000000000..033b8756d
--- /dev/null
+++ b/packages/bruno-tests/collection/file.txt
@@ -0,0 +1,3 @@
+file.txt
+
+hello, bruno
\ No newline at end of file
diff --git a/packages/bruno-tests/src/echo/index.js b/packages/bruno-tests/src/echo/index.js
index ba9b403ae..89a9208e0 100644
--- a/packages/bruno-tests/src/echo/index.js
+++ b/packages/bruno-tests/src/echo/index.js
@@ -19,6 +19,17 @@ router.post('/xml-raw', (req, res) => {
return res.send(req.rawBody);
});
+router.post('/bin', (req, res) => {
+ const rawBody = req.body;
+
+ if (!rawBody || rawBody.length === 0) {
+ return res.status(400).send('No data received');
+ }
+
+ res.set('Content-Type', req.headers['content-type'] || 'application/octet-stream');
+ res.send(rawBody);
+});
+
router.get('/bom-json-test', (req, res) => {
const jsonData = {
message: 'Hello!',
diff --git a/packages/bruno-tests/src/index.js b/packages/bruno-tests/src/index.js
index a09cb434b..a482fc128 100644
--- a/packages/bruno-tests/src/index.js
+++ b/packages/bruno-tests/src/index.js
@@ -10,6 +10,7 @@ const multipartRouter = require('./multipart');
const app = new express();
const port = process.env.PORT || 8080;
+app.use(express.raw({type: '*/*', limit: '100mb'}));
app.use(cors());
app.use(xmlParser());
app.use(bodyParser.text());