From 249f31878775f1dff9d9c7dfe029c20e9a4d5906 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:55:15 +0200 Subject: [PATCH 1/8] fix: Fix name validation consistency --- .../EnvironmentSettings/CreateEnvironment/index.js | 2 ++ .../EnvironmentSettings/RenameEnvironment/index.js | 2 ++ .../Collection/CollectionItem/RenameCollectionItem/index.js | 2 ++ .../src/components/Sidebar/CreateCollection/index.js | 3 ++- packages/bruno-app/src/components/Sidebar/NewFolder/index.js | 2 ++ .../bruno-app/src/components/Sidebar/NewRequest/index.js | 2 ++ packages/bruno-app/src/utils/common/regex.js | 3 +++ packages/bruno-electron/src/ipc/collection.js | 5 ----- 8 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 packages/bruno-app/src/utils/common/regex.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js index d412687e25..567ce9957e 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js @@ -6,6 +6,7 @@ import { useFormik } from 'formik'; import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import * as Yup from 'yup'; import { useDispatch } from 'react-redux'; +import { filenameRegex } from 'utils/common/regex'; const CreateEnvironment = ({ collection, onClose }) => { const dispatch = useDispatch(); @@ -19,6 +20,7 @@ const CreateEnvironment = ({ collection, onClose }) => { name: Yup.string() .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') + .matches(filenameRegex, 'Folder name contains invalid characters') .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js index dc928d4c68..dd10b33651 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js @@ -6,6 +6,7 @@ import { useFormik } from 'formik'; import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import * as Yup from 'yup'; import { useDispatch } from 'react-redux'; +import { filenameRegex } from 'utils/common/regex'; const RenameEnvironment = ({ onClose, environment, collection }) => { const dispatch = useDispatch(); @@ -19,6 +20,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => { name: Yup.string() .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') + .matches(filenameRegex, 'Folder name contains invalid characters') .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index 9b485e9928..8acfeb3b4f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -5,6 +5,7 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { renameItem } from 'providers/ReduxStore/slices/collections/actions'; +import { filenameRegex } from 'utils/common/regex'; const RenameCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -19,6 +20,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { name: Yup.string() .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') + .matches(filenameRegex, `${isFolder ? 'folder' : 'request'} name contains invalid characters`) .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 9b56ca1b87..0dcba27bab 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -7,6 +7,7 @@ import { createCollection } from 'providers/ReduxStore/slices/collections/action import toast from 'react-hot-toast'; import Tooltip from 'components/Tooltip'; import Modal from 'components/Modal'; +import { filenameRegex } from 'utils/common/regex'; const CreateCollection = ({ onClose }) => { const inputRef = useRef(); @@ -27,7 +28,7 @@ const CreateCollection = ({ onClose }) => { collectionFolderName: Yup.string() .min(1, 'must be atleast 1 characters') .max(50, 'must be 50 characters or less') - .matches(/^[\w\-. ]+$/, 'Folder name contains invalid characters') + .matches(filenameRegex, 'Folder name contains invalid characters') .required('folder name is required'), collectionLocation: Yup.string() .min(1, 'location is required') diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index 9245d7abc6..1513b811b6 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -5,6 +5,7 @@ import * as Yup from 'yup'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; +import { filenameRegex } from 'utils/common/regex'; const NewFolder = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -18,6 +19,7 @@ const NewFolder = ({ collection, item, onClose }) => { folderName: Yup.string() .min(1, 'must be atleast 1 characters') .required('name is required') + .matches(filenameRegex, 'Folder name contains invalid characters') .test({ name: 'folderName', message: 'The folder name "environments" at the root of the collection is reserved in bruno', diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index f5753aced0..764ee1e29d 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; +import { filenameRegex } from 'utils/common/regex'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -27,6 +28,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: Yup.string() .min(1, 'must be atleast 1 characters') .required('name is required') + .matches(filenameRegex, 'request name contains invalid characters') .test({ name: 'requestName', message: 'The request name "index" is reserved in bruno', diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js new file mode 100644 index 0000000000..f9c109580d --- /dev/null +++ b/packages/bruno-app/src/utils/common/regex.js @@ -0,0 +1,3 @@ +// Regex for validating filenames that covers most cases +// See https://github.com/usebruno/bruno/pull/349 for more info +export const filenameRegex = /^(?!CON|PRN|AUX|NUL|COM\d|LPT\d|^ |^\-)[\w\-\. \(\)\[\]]+[^\. ]$/ diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ae85558afe..5e7b4b55d2 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -5,7 +5,6 @@ const { ipcMain, shell } = require('electron'); const { envJsonToBru, bruToJson, jsonToBru } = require('../bru'); const { - isValidPathname, writeFile, hasBruExtension, isDirectory, @@ -51,10 +50,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection throw new Error(`collection: ${dirPath} already exists`); } - if (!isValidPathname(dirPath)) { - throw new Error(`collection: invalid pathname - ${dir}`); - } - await createDirectory(dirPath); const uid = generateUidBasedOnHash(dirPath); From ccfff8870ef4fd7614fc2809d1c909239c84a579 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:59:49 +0200 Subject: [PATCH 2/8] feat: Allow special characters in request and environment names --- .../CreateEnvironment/index.js | 4 +- .../RenameEnvironment/index.js | 4 +- .../RenameCollectionItem/index.js | 7 +- .../Sidebar/CreateCollection/index.js | 7 +- .../src/components/Sidebar/NewFolder/index.js | 8 +- .../components/Sidebar/NewRequest/index.js | 1 - .../ReduxStore/slices/collections/actions.js | 26 ++--- .../ReduxStore/slices/collections/index.js | 5 + packages/bruno-app/src/utils/common/regex.js | 4 +- packages/bruno-electron/src/app/watcher.js | 12 ++- packages/bruno-electron/src/ipc/collection.js | 100 ++++++++++++------ .../bruno-electron/src/utils/filesystem.js | 7 +- packages/bruno-lang/v2/src/envToJson.js | 19 +++- packages/bruno-lang/v2/src/jsonToEnv.js | 8 +- 14 files changed, 136 insertions(+), 76 deletions(-) diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js index 567ce9957e..15c4efa008 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js @@ -6,7 +6,6 @@ import { useFormik } from 'formik'; import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import * as Yup from 'yup'; import { useDispatch } from 'react-redux'; -import { filenameRegex } from 'utils/common/regex'; const CreateEnvironment = ({ collection, onClose }) => { const dispatch = useDispatch(); @@ -19,8 +18,7 @@ const CreateEnvironment = ({ collection, onClose }) => { validationSchema: Yup.object({ name: Yup.string() .min(1, 'must be atleast 1 characters') - .max(50, 'must be 50 characters or less') - .matches(filenameRegex, 'Folder name contains invalid characters') + .max(250, 'must be 250 characters or less') .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js index dd10b33651..121c90b073 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js @@ -6,7 +6,6 @@ import { useFormik } from 'formik'; import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import * as Yup from 'yup'; import { useDispatch } from 'react-redux'; -import { filenameRegex } from 'utils/common/regex'; const RenameEnvironment = ({ onClose, environment, collection }) => { const dispatch = useDispatch(); @@ -19,8 +18,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => { validationSchema: Yup.object({ name: Yup.string() .min(1, 'must be atleast 1 characters') - .max(50, 'must be 50 characters or less') - .matches(filenameRegex, 'Folder name contains invalid characters') + .max(250, 'must be 250 characters or less') .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index 8acfeb3b4f..5211efdc80 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -5,7 +5,7 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { renameItem } from 'providers/ReduxStore/slices/collections/actions'; -import { filenameRegex } from 'utils/common/regex'; +import { dirnameRegex } from 'utils/common/regex'; const RenameCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -19,8 +19,9 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { validationSchema: Yup.object({ name: Yup.string() .min(1, 'must be atleast 1 characters') - .max(50, 'must be 50 characters or less') - .matches(filenameRegex, `${isFolder ? 'folder' : 'request'} name contains invalid characters`) + .max(250, 'must be 250 characters or less') + .trim() + .matches(isFolder ? dirnameRegex : /.*/g, `${isFolder ? 'folder' : 'request'} name contains invalid characters`) .required('name is required') }), onSubmit: (values) => { diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 49608c7b09..3f18c8e56b 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -7,7 +7,7 @@ import { createCollection } from 'providers/ReduxStore/slices/collections/action import toast from 'react-hot-toast'; import Tooltip from 'components/Tooltip'; import Modal from 'components/Modal'; -import { filenameRegex } from 'utils/common/regex'; +import { dirnameRegex } from 'utils/common/regex'; const CreateCollection = ({ onClose }) => { const inputRef = useRef(); @@ -27,8 +27,9 @@ const CreateCollection = ({ onClose }) => { .required('collection name is required'), collectionFolderName: Yup.string() .min(1, 'must be atleast 1 characters') - .max(50, 'must be 50 characters or less') - .matches(filenameRegex, 'Folder name contains invalid characters') + .max(250, 'must be 250 characters or less') + .trim() + .matches(dirnameRegex, 'Folder name contains invalid characters') .required('folder name is required'), collectionLocation: Yup.string().min(1, 'location is required').required('location is required') }), diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index 1513b811b6..8b0826cdd4 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -5,7 +5,7 @@ import * as Yup from 'yup'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; -import { filenameRegex } from 'utils/common/regex'; +import { dirnameRegex } from 'utils/common/regex'; const NewFolder = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -17,9 +17,11 @@ const NewFolder = ({ collection, item, onClose }) => { }, validationSchema: Yup.object({ folderName: Yup.string() - .min(1, 'must be atleast 1 characters') .required('name is required') - .matches(filenameRegex, 'Folder name contains invalid characters') + .min(1, 'must be atleast 1 characters') + .max(250, 'must be 250 characters or less') + .trim() + .matches(dirnameRegex, 'Folder name contains invalid characters') .test({ name: 'folderName', message: 'The folder name "environments" at the root of the collection is reserved in bruno', diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 764ee1e29d..c8ff66dff7 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -28,7 +28,6 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: Yup.string() .min(1, 'must be atleast 1 characters') .required('name is required') - .matches(filenameRegex, 'request name contains invalid characters') .test({ name: 'requestName', message: 'The request name "index" is reserved in bruno', 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 7d5577c869..4ce535d290 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -250,7 +250,7 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta if (!collection) { return reject(new Error('Collection not found')); } - + console.log(collection); const collectionCopy = cloneDeep(collection); const item = findItemInCollection(collectionCopy, itemUid); if (!item) { @@ -258,18 +258,10 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta } const dirname = getDirectoryName(item.pathname); - - let newPathname = ''; - if (item.type === 'folder') { - newPathname = path.join(dirname, trim(newName)); - } else { - const filename = resolveRequestFilename(newName); - newPathname = path.join(dirname, filename); - } const { ipcRenderer } = window; ipcRenderer - .invoke('renderer:rename-item', item.pathname, newPathname, newName) + .invoke('renderer:rename-item', item.pathname, dirname, newName) .then(() => { // In case of Mac and Linux, we get the unlinkDir and addDir IPC events from electron which takes care of updating the state // But in windows we don't get those events, so we need to update the state manually @@ -312,14 +304,13 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) ); if (!reqWithSameNameExists) { - const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`; const { ipcRenderer } = window; const requestItems = filter(collection.items, (i) => i.type !== 'folder'); itemToSave.seq = requestItems ? requestItems.length + 1 : 1; itemSchema .validate(itemToSave) - .then(() => ipcRenderer.invoke('renderer:new-request', fullName, itemToSave)) + .then(() => ipcRenderer.invoke('renderer:new-request', collection.pathname, itemToSave)) .then(resolve) .catch(reject); } else { @@ -331,15 +322,14 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) ); if (!reqWithSameNameExists) { - const dirname = getDirectoryName(item.pathname); - const fullName = path.join(dirname, filename); + const pathname = getDirectoryName(item.pathname); const { ipcRenderer } = window; const requestItems = filter(parentItem.items, (i) => i.type !== 'folder'); itemToSave.seq = requestItems ? requestItems.length + 1 : 1; itemSchema .validate(itemToSave) - .then(() => ipcRenderer.invoke('renderer:new-request', fullName, itemToSave)) + .then(() => ipcRenderer.invoke('renderer:new-request', pathname, itemToSave)) .then(resolve) .catch(reject); } else { @@ -592,10 +582,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => { item.seq = requestItems.length + 1; if (!reqWithSameNameExists) { - const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`; const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); + ipcRenderer.invoke('renderer:new-request', collection.pathname, item).then(resolve).catch(reject); // Add the new request name here so it can be opened in a new tab in useCollectionTreeSync.js dispatch(updateLastAction({ lastAction: { type: 'ADD_REQUEST', payload: item.name }, collectionUid })); } else { @@ -611,10 +600,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => { const requestItems = filter(currentItem.items, (i) => i.type !== 'folder'); item.seq = requestItems.length + 1; if (!reqWithSameNameExists) { - const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${filename}`; const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); + ipcRenderer.invoke('renderer:new-request', currentItem.pathname, item).then(resolve).catch(reject); // Add the new request name here so it can be opened in a new tab in useCollectionTreeSync.js dispatch(updateLastAction({ lastAction: { type: 'ADD_REQUEST', payload: item.name }, collectionUid })); } else { 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 ec89bb85da..5c103d05f0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -161,6 +161,9 @@ export const collectionsSlice = createSlice({ if (item) { item.name = action.payload.newName; + if (item.type === 'folder') { + item.pathname = path.join(path.dirname(item.pathname), action.payload.newName); + } } } }, @@ -962,6 +965,7 @@ export const collectionsSlice = createSlice({ } }, collectionAddDirectoryEvent: (state, action) => { + console.log('ADD DIR', action.payload); const { dir } = action.payload; const collection = findCollectionByUid(state.collections, dir.meta.collectionUid); @@ -1049,6 +1053,7 @@ export const collectionsSlice = createSlice({ if (existingEnv) { existingEnv.variables = environment.variables; + existingEnv.name = environment.name; } else { collection.environments.push(environment); diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js index f9c109580d..6c348a8907 100644 --- a/packages/bruno-app/src/utils/common/regex.js +++ b/packages/bruno-app/src/utils/common/regex.js @@ -1,3 +1,3 @@ -// Regex for validating filenames that covers most cases // See https://github.com/usebruno/bruno/pull/349 for more info -export const filenameRegex = /^(?!CON|PRN|AUX|NUL|COM\d|LPT\d|^ |^\-)[\w\-\. \(\)\[\]]+[^\. ]$/ +// Scrict regex for validating directories. Covers most edge cases like windows device names +export const dirnameRegex = /^(?!CON|PRN|AUX|NUL|COM\d|LPT\d|^ |^\-)[\w\-\. \(\)\[\]\!]+[^\. ]$/; diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index c5973e79cb..5fadb9f265 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -93,7 +93,10 @@ const addEnvironmentFile = async (win, pathname, collectionUid, collectionPath) } file.data = bruToEnvJson(bruContent); - file.data.name = basename.substring(0, basename.length - 4); + // Older env files do not have a meta block + if (!file.data.name) { + file.data.name = basename.substring(0, basename.length - 4); + } file.data.uid = getRequestUid(pathname); _.each(_.get(file, 'data.variables', []), (variable) => (variable.uid = uuid())); @@ -128,7 +131,10 @@ const changeEnvironmentFile = async (win, pathname, collectionUid, collectionPat const bruContent = fs.readFileSync(pathname, 'utf8'); file.data = bruToEnvJson(bruContent); - file.data.name = basename.substring(0, basename.length - 4); + // Older env files do not have a meta block + if (!file.data.name) { + file.data.name = basename.substring(0, basename.length - 4); + } file.data.uid = getRequestUid(pathname); _.each(_.get(file, 'data.variables', []), (variable) => (variable.uid = uuid())); @@ -143,6 +149,8 @@ const changeEnvironmentFile = async (win, pathname, collectionUid, collectionPat }); } + console.log(file); + // we are reusing the addEnvironmentFile event itself // this is because the uid of the pathname remains the same // and the collection tree will be able to update the existing environment diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 98ff0cd41f..135c7049f6 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const { ipcMain, shell } = require('electron'); -const { envJsonToBru, bruToJson, jsonToBru } = require('../bru'); +const { envJsonToBru, bruToJson, jsonToBru, bruToEnvJson } = require('../bru'); const { writeFile, @@ -11,7 +11,8 @@ const { browseDirectory, createDirectory, searchForBruFiles, - sanitizeDirectoryName + sanitizeDirectoryName, + sanitizeFilenme } = require('../utils/filesystem'); const { stringifyJson } = require('../utils/common'); const { openCollectionDialog, openCollection } = require('../app/collections'); @@ -99,12 +100,14 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection // new request ipcMain.handle('renderer:new-request', async (event, pathname, request) => { try { - if (fs.existsSync(pathname)) { - throw new Error(`path: ${pathname} already exists`); + const sanitizedPathname = path.join(pathname, sanitizeFilenme(request.name) + '.bru'); + + if (fs.existsSync(sanitizedPathname)) { + throw new Error(`path: ${sanitizedPathname} already exists`); } const content = jsonToBru(request); - await writeFile(pathname, content); + await writeFile(sanitizedPathname, content); } catch (error) { return Promise.reject(error); } @@ -132,13 +135,15 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(envDirPath); } - const envFilePath = path.join(envDirPath, `${name}.bru`); + const filenameSanatized = `${sanitizeFilenme(name)}.bru`; + const envFilePath = path.join(envDirPath, filenameSanatized); if (fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} already exists`); } const content = envJsonToBru({ - variables: [] + variables: [], + name: name }); await writeFile(envFilePath, content); } catch (error) { @@ -154,13 +159,15 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(envDirPath); } - const envFilePath = path.join(envDirPath, `${name}.bru`); + const filenameSanatized = sanitizeFilenme(`${name}.bru`); + const envFilePath = path.join(envDirPath, filenameSanatized); if (fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} already exists`); } const content = envJsonToBru({ - variables: baseVariables + variables: baseVariables, + name: name }); await writeFile(envFilePath, content); } catch (error) { @@ -176,9 +183,13 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(envDirPath); } - const envFilePath = path.join(envDirPath, `${environment.name}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environment.name)}.bru`); if (!fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} does not exist`); + // Fallback to unsatized filename for old envs + envFilePath = path.join(envDirPath, `${environment.name}.bru`); + if (!fs.existsSync(envFilePath)) { + throw new Error(`environment: ${envFilePath} does not exist`); + } } if (envHasSecrets(environment)) { @@ -196,16 +207,26 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:rename-environment', async (event, collectionPathname, environmentName, newName) => { try { const envDirPath = path.join(collectionPathname, 'environments'); - const envFilePath = path.join(envDirPath, `${environmentName}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environmentName)}.bru`); if (!fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} does not exist`); + // Fallback to unsatized env name + envFilePath = path.join(envDirPath, `${environmentName}.bru`); + if (!fs.existsSync(envFilePath)) { + throw new Error(`environment: ${envFilePath} does not exist`); + } } - const newEnvFilePath = path.join(envDirPath, `${newName}.bru`); - if (fs.existsSync(newEnvFilePath)) { + const newEnvFilePath = path.join(envDirPath, `${sanitizeFilenme(newName)}.bru`); + if (fs.existsSync(newEnvFilePath) && envFilePath !== newEnvFilePath) { throw new Error(`environment: ${newEnvFilePath} already exists`); } + // Update the name in the environment meta + const bruContent = fs.readFileSync(envFilePath, 'utf8'); + const content = bruToEnvJson(bruContent); + content.name = newName; + await writeFile(envFilePath, envJsonToBru(content)); + fs.renameSync(envFilePath, newEnvFilePath); environmentSecretsStore.renameEnvironment(collectionPathname, environmentName, newName); @@ -218,9 +239,13 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:delete-environment', async (event, collectionPathname, environmentName) => { try { const envDirPath = path.join(collectionPathname, 'environments'); - const envFilePath = path.join(envDirPath, `${environmentName}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environmentName)}.bru`); if (!fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} does not exist`); + // Fallback to unsatized env name + envFilePath = path.join(envDirPath, `${environmentName}.bru`); + if (!fs.existsSync(envFilePath)) { + throw new Error(`environment: ${envFilePath} does not exist`); + } } fs.unlinkSync(envFilePath); @@ -232,42 +257,50 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // rename item - ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => { + ipcMain.handle('renderer:rename-item', async (event, oldPathFull, newPath, newName) => { try { - if (!fs.existsSync(oldPath)) { - throw new Error(`path: ${oldPath} does not exist`); - } - if (fs.existsSync(newPath)) { - throw new Error(`path: ${oldPath} already exists`); + if (!fs.existsSync(oldPathFull)) { + throw new Error(`path: ${oldPathFull} does not exist`); } // if its directory, rename and return - if (isDirectory(oldPath)) { - const bruFilesAtSource = await searchForBruFiles(oldPath); + if (isDirectory(oldPathFull)) { + const bruFilesAtSource = await searchForBruFiles(oldPathFull); + + const newPathFull = path.join(newPath, newName); + if (fs.existsSync(newPathFull)) { + throw new Error(`path: ${newPathFull} already exists`); + } for (let bruFile of bruFilesAtSource) { - const newBruFilePath = bruFile.replace(oldPath, newPath); + const newBruFilePath = bruFile.replace(oldPathFull, newPathFull); moveRequestUid(bruFile, newBruFilePath); } - return fs.renameSync(oldPath, newPath); + return fs.renameSync(oldPathFull, newPathFull); } - const isBru = hasBruExtension(oldPath); + const isBru = hasBruExtension(oldPathFull); if (!isBru) { - throw new Error(`path: ${oldPath} is not a bru file`); + throw new Error(`path: ${oldPathFull} is not a bru file`); } + const newSantitizedPath = path.join(newPath, sanitizeFilenme(newName) + '.bru'); + // update name in file and save new copy, then delete old copy - const data = fs.readFileSync(oldPath, 'utf8'); + const data = fs.readFileSync(oldPathFull, 'utf8'); const jsonData = bruToJson(data); jsonData.name = newName; - moveRequestUid(oldPath, newPath); + moveRequestUid(oldPathFull, newSantitizedPath); const content = jsonToBru(jsonData); - await writeFile(newPath, content); - await fs.unlinkSync(oldPath); + await writeFile(newSantitizedPath, content); + + // Because of santization the name can change but the path stays the same + if (newSantitizedPath !== oldPathFull) { + fs.unlinkSync(oldPathFull); + } } catch (error) { return Promise.reject(error); } @@ -290,6 +323,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:delete-item', async (event, pathname, type) => { try { if (type === 'folder') { + console.log(pathname); if (!fs.existsSync(pathname)) { return Promise.reject(new Error('The directory does not exist')); } diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index b55dfd7258..00834cfecd 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -118,6 +118,10 @@ const sanitizeDirectoryName = (name) => { return name.replace(/[<>:"/\\|?*\x00-\x1F]+/g, '-'); }; +const sanitizeFilenme = (name) => { + return name.replace(/[^\w-_.]/g, '_'); +}; + module.exports = { isValidPathname, exists, @@ -132,5 +136,6 @@ module.exports = { browseDirectory, searchForFiles, searchForBruFiles, - sanitizeDirectoryName + sanitizeDirectoryName, + sanitizeFilenme }; diff --git a/packages/bruno-lang/v2/src/envToJson.js b/packages/bruno-lang/v2/src/envToJson.js index eef4de375d..5eb69e9293 100644 --- a/packages/bruno-lang/v2/src/envToJson.js +++ b/packages/bruno-lang/v2/src/envToJson.js @@ -2,7 +2,7 @@ const ohm = require('ohm-js'); const _ = require('lodash'); const grammar = ohm.grammar(`Bru { - BruEnvFile = (vars | secretvars)* + BruEnvFile = (meta | vars | secretvars)* nl = "\\r"? "\\n" st = " " | "\\t" @@ -25,6 +25,8 @@ const grammar = ohm.grammar(`Bru { arrayvalue = arrayvaluechar* arrayvaluechar = ~(nl | st | "[" | "]" | ",") any + meta = "meta" dictionary + secretvars = "vars:secret" array vars = "vars" dictionary }`); @@ -74,6 +76,14 @@ const mapArrayListToKeyValPairs = (arrayList = []) => { }); }; +const mapPairListToKeyValPair = (pairList = []) => { + if (!pairList || !pairList.length) { + return {}; + } + + return _.merge({}, ...pairList[0]); +}; + const concatArrays = (objValue, srcValue) => { if (_.isArray(objValue) && _.isArray(srcValue)) { return objValue.concat(srcValue); @@ -134,6 +144,13 @@ const sem = grammar.createSemantics().addAttribute('ast', { _iter(...elements) { return elements.map((e) => e.ast); }, + meta(_1, dictionary) { + let meta = mapPairListToKeyValPair(dictionary.ast); + + return { + name: meta.name + }; + }, vars(_1, dictionary) { const vars = mapPairListToKeyValPairs(dictionary.ast); _.each(vars, (v) => { diff --git a/packages/bruno-lang/v2/src/jsonToEnv.js b/packages/bruno-lang/v2/src/jsonToEnv.js index 42d0a4281d..d339fa71e0 100644 --- a/packages/bruno-lang/v2/src/jsonToEnv.js +++ b/packages/bruno-lang/v2/src/jsonToEnv.js @@ -1,6 +1,10 @@ const _ = require('lodash'); const envToJson = (json) => { + const meta = `meta { + name: ${json.name} +}\n\n`; + const variables = _.get(json, 'variables', []); const vars = variables .filter((variable) => !variable.secret) @@ -19,12 +23,12 @@ const envToJson = (json) => { }); if (!variables || !variables.length) { - return `vars { + return `${meta}vars { } `; } - let output = ''; + let output = meta; if (vars.length) { output += `vars { ${vars.join('\n')} From c7d0135fa04aee7573f005cafa04c293c4a5560b Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sun, 8 Oct 2023 15:09:56 +0200 Subject: [PATCH 3/8] fix: Fix some bugs with the new name validation --- .../ResponsePane/QueryResult/ImagePreview.js | 27 +++++++++++++++++++ .../providers/App/useCollectionNextAction.js | 1 + .../ReduxStore/slices/collections/actions.js | 12 ++++----- .../ReduxStore/slices/collections/index.js | 1 - .../bruno-app/src/utils/collections/index.js | 2 ++ packages/bruno-app/src/utils/common/index.js | 4 +++ packages/bruno-electron/src/app/watcher.js | 2 -- packages/bruno-electron/src/ipc/collection.js | 5 +++- 8 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js new file mode 100644 index 0000000000..6a1819082c --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js @@ -0,0 +1,27 @@ +import { useRef } from 'react'; +import { useEffect } from 'react'; + +const ImagePreview = ({ data, contentType }) => { + const imgRef = useRef(null); + + useEffect(() => { + imgRef.current.src = 'data:image/png;base64,' + Buffer.from(data).toString('base64'); + // const blob = new Blob([encodeURIComponent(data)], { + // type: contentType, + // }) + // var reader = new FileReader(); + // reader.onloadend = function () { + // console.log(reader.result, reader.result.length) + // imgRef.current.src = reader.result; + // }; + // reader.readAsDataURL(blob); + }, [data]); + + return ( +
+ +
+ ); +}; + +export default ImagePreview; diff --git a/packages/bruno-app/src/providers/App/useCollectionNextAction.js b/packages/bruno-app/src/providers/App/useCollectionNextAction.js index 94c58f604d..5399ae365c 100644 --- a/packages/bruno-app/src/providers/App/useCollectionNextAction.js +++ b/packages/bruno-app/src/providers/App/useCollectionNextAction.js @@ -14,6 +14,7 @@ const useCollectionNextAction = () => { useEffect(() => { each(collections, (collection) => { if (collection.nextAction && collection.nextAction.type === 'OPEN_REQUEST') { + console.log(collection.nextAction.payload.pathname, JSON.stringify(collection)); const item = findItemInCollectionByPathname(collection, get(collection, 'nextAction.payload.pathname')); if (item) { 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 ed9b81e0de..f4efe51b3f 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -1,4 +1,3 @@ -import path from 'path'; import toast from 'react-hot-toast'; import trim from 'lodash/trim'; import find from 'lodash/find'; @@ -45,8 +44,10 @@ import { import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs'; import { resolveRequestFilename } from 'utils/common/platform'; +import { sanitizeFilenme } from 'utils/common/index'; +import os from 'os'; -const PATH_SEPARATOR = path.sep; +const PATH_SEPARATOR = /Windows/i.test(os.release()) ? '\\' : '/'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { const state = getState(); @@ -250,7 +251,6 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta if (!collection) { return reject(new Error('Collection not found')); } - console.log(collection); const collectionCopy = cloneDeep(collection); const item = findItemInCollection(collectionCopy, itemUid); if (!item) { @@ -584,7 +584,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { if (!reqWithSameNameExists) { const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); + ipcRenderer.invoke('renderer:new-request', collection.pathname, item).then(resolve).catch(reject); // the useCollectionNextAction() will track this and open the new request in a new tab // once the request is created dispatch( @@ -592,7 +592,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { nextAction: { type: 'OPEN_REQUEST', payload: { - pathname: fullName + pathname: collection.pathname + PATH_SEPARATOR + sanitizeFilenme(item.name) + '.bru' } }, collectionUid @@ -622,7 +622,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { nextAction: { type: 'OPEN_REQUEST', payload: { - pathname: fullName + pathname: collection.pathname + PATH_SEPARATOR + sanitizeFilenme(item.name) + '.bru' } }, collectionUid 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 402236846e..21647aea2e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -979,7 +979,6 @@ export const collectionsSlice = createSlice({ } }, collectionAddDirectoryEvent: (state, action) => { - console.log('ADD DIR', action.payload); const { dir } = action.payload; const collection = findCollectionByUid(state.collections, dir.meta.collectionUid); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 9eb6c0d1f7..f1b452782e 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -98,6 +98,8 @@ export const findItemByPathname = (items = [], pathname) => { export const findItemInCollectionByPathname = (collection, pathname) => { let flattenedItems = flattenItems(collection.items); + console.log(flattenItems); + each(flattenItems, (item) => console.log(item.pathname, pathname, item.pathname === pathname)); return findItemByPathname(flattenedItems, pathname); }; diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 992ec233e6..d49689e105 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -94,3 +94,7 @@ export const getContentType = (headers) => { return ''; }; + +export const sanitizeFilenme = (name) => { + return name.replace(/[^\w-_.]/g, '_'); +}; diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index c6c377958e..64e30fba23 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -142,8 +142,6 @@ const changeEnvironmentFile = async (win, pathname, collectionUid, collectionPat }); } - console.log(file); - // we are reusing the addEnvironmentFile event itself // this is because the uid of the pathname remains the same // and the collection tree will be able to update the existing environment diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 135c7049f6..485e9f5594 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -285,6 +285,10 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } const newSantitizedPath = path.join(newPath, sanitizeFilenme(newName) + '.bru'); + console.log(oldPathFull, newSantitizedPath); + if (fs.existsSync(newSantitizedPath) && newSantitizedPath !== oldPathFull) { + throw new Error(`path: ${newSantitizedPath} already exists`); + } // update name in file and save new copy, then delete old copy const data = fs.readFileSync(oldPathFull, 'utf8'); @@ -323,7 +327,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:delete-item', async (event, pathname, type) => { try { if (type === 'folder') { - console.log(pathname); if (!fs.existsSync(pathname)) { return Promise.reject(new Error('The directory does not exist')); } From ab05ddcb04190f2dad51bad72241e31d74a9b4e5 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sun, 8 Oct 2023 15:32:07 +0200 Subject: [PATCH 4/8] test: Update bruno-lang v2 Env file tests --- .../bruno-lang/v2/tests/envToJson.spec.js | 36 ++++++++++++- .../bruno-lang/v2/tests/jsonToEnv.spec.js | 54 ++++++++++++++----- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/bruno-lang/v2/tests/envToJson.spec.js b/packages/bruno-lang/v2/tests/envToJson.spec.js index fbb74f2b95..e3dcebf0dc 100644 --- a/packages/bruno-lang/v2/tests/envToJson.spec.js +++ b/packages/bruno-lang/v2/tests/envToJson.spec.js @@ -14,8 +14,30 @@ vars { expect(output).toEqual(expected); }); + it('should parse empty vars with meta', () => { + const input = ` +meta { + name: This is a test +} + +vars { +}`; + + const output = parser(input); + const expected = { + variables: [], + name: 'This is a test' + }; + + expect(output).toEqual(expected); + }); + it('should parse single var line', () => { const input = ` +meta { + name: This is a test +} + vars { url: http://localhost:3000 }`; @@ -29,7 +51,8 @@ vars { enabled: true, secret: false } - ] + ], + name: 'This is a test' }; expect(output).toEqual(expected); @@ -37,6 +60,10 @@ vars { it('should parse multiple var lines', () => { const input = ` +meta { + name: This is a test +} + vars { url: http://localhost:3000 port: 3000 @@ -45,6 +72,7 @@ vars { const output = parser(input); const expected = { + name: 'This is a test', variables: [ { name: 'url', @@ -73,6 +101,11 @@ vars { it('should gracefully handle empty lines and spaces', () => { const input = ` +meta { + name: Yet another test + other-thing: ignore me +} + vars { url: http://localhost:3000 port: 3000 @@ -82,6 +115,7 @@ vars { const output = parser(input); const expected = { + name: 'Yet another test', variables: [ { name: 'url', diff --git a/packages/bruno-lang/v2/tests/jsonToEnv.spec.js b/packages/bruno-lang/v2/tests/jsonToEnv.spec.js index 62b7aa2697..9c32cdc112 100644 --- a/packages/bruno-lang/v2/tests/jsonToEnv.spec.js +++ b/packages/bruno-lang/v2/tests/jsonToEnv.spec.js @@ -3,11 +3,16 @@ const parser = require('../src/jsonToEnv'); describe('env parser', () => { it('should parse empty vars', () => { const input = { - variables: [] + variables: [], + name: 'test' }; const output = parser(input); - const expected = `vars { + const expected = `meta { + name: test +} + +vars { } `; @@ -22,11 +27,16 @@ describe('env parser', () => { value: 'http://localhost:3000', enabled: true } - ] + ], + name: 'test' }; const output = parser(input); - const expected = `vars { + const expected = `meta { + name: test +} + +vars { url: http://localhost:3000 } `; @@ -46,10 +56,15 @@ describe('env parser', () => { value: '3000', enabled: false } - ] + ], + name: 'test' }; - const expected = `vars { + const expected = `meta { + name: test +} + +vars { url: http://localhost:3000 ~port: 3000 } @@ -72,11 +87,16 @@ describe('env parser', () => { enabled: true, secret: true } - ] + ], + name: 'test' }; const output = parser(input); - const expected = `vars { + const expected = `meta { + name: test +} + +vars { url: http://localhost:3000 } vars:secret [ @@ -106,11 +126,16 @@ vars:secret [ enabled: false, secret: true } - ] + ], + name: 'test' }; const output = parser(input); - const expected = `vars { + const expected = `meta { + name: test +} + +vars { url: http://localhost:3000 } vars:secret [ @@ -130,11 +155,16 @@ vars:secret [ enabled: true, secret: true } - ] + ], + name: 'test' }; const output = parser(input); - const expected = `vars:secret [ + const expected = `meta { + name: test +} + +vars:secret [ token ] `; From 931259825d348c745223547769424dd707f4b248 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sun, 8 Oct 2023 17:42:51 +0200 Subject: [PATCH 5/8] fix: Cleanup pr #349 --- .../ResponsePane/QueryResult/ImagePreview.js | 27 ------------------- .../components/Sidebar/NewRequest/index.js | 1 - .../providers/App/useCollectionNextAction.js | 1 - .../bruno-app/src/utils/collections/index.js | 2 -- packages/bruno-electron/src/ipc/collection.js | 1 - 5 files changed, 32 deletions(-) delete mode 100644 packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js deleted file mode 100644 index 6a1819082c..0000000000 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/ImagePreview.js +++ /dev/null @@ -1,27 +0,0 @@ -import { useRef } from 'react'; -import { useEffect } from 'react'; - -const ImagePreview = ({ data, contentType }) => { - const imgRef = useRef(null); - - useEffect(() => { - imgRef.current.src = 'data:image/png;base64,' + Buffer.from(data).toString('base64'); - // const blob = new Blob([encodeURIComponent(data)], { - // type: contentType, - // }) - // var reader = new FileReader(); - // reader.onloadend = function () { - // console.log(reader.result, reader.result.length) - // imgRef.current.src = reader.result; - // }; - // reader.readAsDataURL(blob); - }, [data]); - - return ( -
- -
- ); -}; - -export default ImagePreview; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 241a9d3676..6a753fd97b 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -11,7 +11,6 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; -import { filenameRegex } from 'utils/common/regex'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); diff --git a/packages/bruno-app/src/providers/App/useCollectionNextAction.js b/packages/bruno-app/src/providers/App/useCollectionNextAction.js index 5399ae365c..94c58f604d 100644 --- a/packages/bruno-app/src/providers/App/useCollectionNextAction.js +++ b/packages/bruno-app/src/providers/App/useCollectionNextAction.js @@ -14,7 +14,6 @@ const useCollectionNextAction = () => { useEffect(() => { each(collections, (collection) => { if (collection.nextAction && collection.nextAction.type === 'OPEN_REQUEST') { - console.log(collection.nextAction.payload.pathname, JSON.stringify(collection)); const item = findItemInCollectionByPathname(collection, get(collection, 'nextAction.payload.pathname')); if (item) { diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index f1b452782e..9eb6c0d1f7 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -98,8 +98,6 @@ export const findItemByPathname = (items = [], pathname) => { export const findItemInCollectionByPathname = (collection, pathname) => { let flattenedItems = flattenItems(collection.items); - console.log(flattenItems); - each(flattenItems, (item) => console.log(item.pathname, pathname, item.pathname === pathname)); return findItemByPathname(flattenedItems, pathname); }; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index c1c0edd5e5..84ce2eea86 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -285,7 +285,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } const newSantitizedPath = path.join(newPath, sanitizeFilenme(newName) + '.bru'); - console.log(oldPathFull, newSantitizedPath); if (fs.existsSync(newSantitizedPath) && newSantitizedPath !== oldPathFull) { throw new Error(`path: ${newSantitizedPath} already exists`); } From 0a11a29b75fe01b99f13b03395d4536ed1e23c90 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Fri, 13 Oct 2023 23:48:20 +0200 Subject: [PATCH 6/8] Revert "fix(#251, #265): phantoms folders fix on rename/delete needs to be run only on windows" This reverts commit fcc12fb089472716328054665d767d7b0ae48e04. --- .../ReduxStore/slices/collections/actions.js | 18 +++--------------- .../bruno-app/src/utils/common/platform.js | 1 - 2 files changed, 3 insertions(+), 16 deletions(-) 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 80c823454b..adbe776e91 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -21,7 +21,7 @@ import { } from 'utils/collections'; import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema'; import { waitForNextTick } from 'utils/common'; -import { getDirectoryName, isWindowsOS } from 'utils/common/platform'; +import { getDirectoryName } from 'utils/common/platform'; import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network'; import { @@ -311,13 +311,7 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta ipcRenderer .invoke('renderer:rename-item', item.pathname, newPathname, newName) .then(() => { - // In case of Mac and Linux, we get the unlinkDir and addDir IPC events from electron which takes care of updating the state - // But in windows we don't get those events, so we need to update the state manually - // This looks like an issue in our watcher library chokidar - // GH: https://github.com/usebruno/bruno/issues/251 - if (isWindowsOS()) { - dispatch(_renameItem({ newName, itemUid, collectionUid })); - } + dispatch(_renameItem({ newName, itemUid, collectionUid })); resolve(); }) .catch(reject); @@ -405,13 +399,7 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { ipcRenderer .invoke('renderer:delete-item', item.pathname, item.type) .then(() => { - // In case of Mac and Linux, we get the unlinkDir IPC event from electron which takes care of updating the state - // But in windows we don't get those events, so we need to update the state manually - // This looks like an issue in our watcher library chokidar - // GH: https://github.com/usebruno/bruno/issues/265 - if (isWindowsOS()) { - dispatch(_deleteItem({ itemUid, collectionUid })); - } + dispatch(_deleteItem({ itemUid, collectionUid })); resolve(); }) .catch((error) => reject(error)); diff --git a/packages/bruno-app/src/utils/common/platform.js b/packages/bruno-app/src/utils/common/platform.js index 771daaf141..ceb84ad4b7 100644 --- a/packages/bruno-app/src/utils/common/platform.js +++ b/packages/bruno-app/src/utils/common/platform.js @@ -1,7 +1,6 @@ import trim from 'lodash/trim'; import path from 'path'; import slash from './slash'; -import platform from 'platform'; export const isElectron = () => { if (!window) { From 307c5d0177133877fed22551d266be002442a128 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:07:53 +0200 Subject: [PATCH 7/8] refactor: return created request file path and update sanitzation regex --- .../ReduxStore/slices/collections/actions.js | 14 +++++++++----- packages/bruno-electron/src/ipc/collection.js | 2 ++ packages/bruno-electron/src/utils/filesystem.js | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) 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 63cae70c7d..95eea255f5 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -572,7 +572,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di export const newHttpRequest = (params) => (dispatch, getState) => { const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid } = params; - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); if (!collection) { @@ -620,7 +620,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { if (!reqWithSameNameExists) { const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:new-request', collection.pathname, item).then(resolve).catch(reject); + const newPath = await ipcRenderer.invoke('renderer:new-request', collection.pathname, item); // the useCollectionNextAction() will track this and open the new request in a new tab // once the request is created dispatch( @@ -628,12 +628,14 @@ export const newHttpRequest = (params) => (dispatch, getState) => { nextAction: { type: 'OPEN_REQUEST', payload: { - pathname: collection.pathname + PATH_SEPARATOR + sanitizeFilenme(item.name) + '.bru' + pathname: newPath } }, collectionUid }) ); + + resolve(); } else { return reject(new Error('Duplicate request names are not allowed under the same folder')); } @@ -649,7 +651,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { if (!reqWithSameNameExists) { const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); + const newPath = await ipcRenderer.invoke('renderer:new-request', fullName, item); // the useCollectionNextAction() will track this and open the new request in a new tab // once the request is created @@ -658,12 +660,14 @@ export const newHttpRequest = (params) => (dispatch, getState) => { nextAction: { type: 'OPEN_REQUEST', payload: { - pathname: collection.pathname + PATH_SEPARATOR + sanitizeFilenme(item.name) + '.bru' + pathname: newPath } }, collectionUid }) ); + + resolve(); } else { return reject(new Error('Duplicate request names are not allowed under the same folder')); } diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index fe76db6da9..1bb4bf0607 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -119,6 +119,8 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection const content = jsonToBru(request); await writeFile(sanitizedPathname, content); + + return sanitizedPathname; } catch (error) { return Promise.reject(error); } diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index 00834cfecd..c523ffea09 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -119,7 +119,7 @@ const sanitizeDirectoryName = (name) => { }; const sanitizeFilenme = (name) => { - return name.replace(/[^\w-_.]/g, '_'); + return name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_'); }; module.exports = { From 8e68b8ff22e422a615fe113e5a6b07750044fa10 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:00:42 +0200 Subject: [PATCH 8/8] chore: Fix typos --- .../CreateEnvironment/index.js | 2 +- .../Sidebar/CreateCollection/index.js | 2 +- .../src/components/Sidebar/NewFolder/index.js | 2 +- .../ReduxStore/slices/collections/actions.js | 1 + packages/bruno-electron/src/ipc/collection.js | 36 +++++++++---------- .../bruno-electron/src/utils/filesystem.js | 4 +-- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js index d86a0641e0..a2fe4ffbee 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js @@ -17,7 +17,7 @@ const CreateEnvironment = ({ collection, onClose }) => { }, validationSchema: Yup.object({ name: Yup.string() - .min(1, 'must be atleast 1 characters') + .min(1, 'must be at least 1 character') .max(250, 'must be 250 characters or less') .required('name is required') }), diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 89620eb236..d4ac77a4ba 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -26,9 +26,9 @@ const CreateCollection = ({ onClose }) => { .max(50, 'must be 50 characters or less') .required('collection name is required'), collectionFolderName: Yup.string() - .max(250, 'must be 250 characters or less') .trim() .matches(dirnameRegex, 'Folder name contains invalid characters') + .max(250, 'must be 250 characters or less') .min(1, 'must be at least 1 character') .required('folder name is required'), collectionLocation: Yup.string().min(1, 'location is required').required('location is required') diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index cad779b63c..e1a8464aee 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -21,12 +21,12 @@ const NewFolder = ({ collection, item, onClose }) => { .min(1, 'must be at least 1 character') .required('name is required') .max(250, 'must be 250 characters or less') - .trim() .matches(dirnameRegex, 'Folder name contains invalid characters') .test({ name: 'folderName', message: 'The folder name "environments" at the root of the collection is reserved in bruno', test: (value) => { + // If the the item has a uid, it is inside a sub folder if (item && item.uid) { return true; } 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 52203a0e9e..0225b8a377 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -280,6 +280,7 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta if (!collection) { return reject(new Error('Collection not found')); } + const collectionCopy = cloneDeep(collection); const item = findItemInCollection(collectionCopy, itemUid); if (!item) { diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index a104e34c13..d670af7c23 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -12,7 +12,7 @@ const { createDirectory, searchForBruFiles, sanitizeDirectoryName, - sanitizeFilenme + sanitizeFilename } = require('../utils/filesystem'); const { stringifyJson } = require('../utils/common'); const { openCollectionDialog } = require('../app/collections'); @@ -104,7 +104,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection // new request ipcMain.handle('renderer:new-request', async (event, pathname, request) => { try { - const sanitizedPathname = path.join(pathname, sanitizeFilenme(request.name) + '.bru'); + const sanitizedPathname = path.join(pathname, sanitizeFilename(request.name) + '.bru'); if (fs.existsSync(sanitizedPathname)) { throw new Error(`path: ${sanitizedPathname} already exists`); @@ -141,8 +141,8 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(envDirPath); } - const filenameSanatized = sanitizeFilenme(`${name}.bru`); - const envFilePath = path.join(envDirPath, filenameSanatized); + const filenameSanitized = sanitizeFilename(`${name}.bru`); + const envFilePath = path.join(envDirPath, filenameSanitized); if (fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} already exists`); } @@ -172,9 +172,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(envDirPath); } - let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environment.name)}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilename(environment.name)}.bru`); if (!fs.existsSync(envFilePath)) { - // Fallback to unsatized filename for old envs + // Fallback to unsanitized filename for old envs envFilePath = path.join(envDirPath, `${environment.name}.bru`); if (!fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} does not exist`); @@ -196,16 +196,16 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:rename-environment', async (event, collectionPathname, environmentName, newName) => { try { const envDirPath = path.join(collectionPathname, 'environments'); - let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environmentName)}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilename(environmentName)}.bru`); if (!fs.existsSync(envFilePath)) { - // Fallback to unsatized env name + // Fallback to unsanitized env name envFilePath = path.join(envDirPath, `${environmentName}.bru`); if (!fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} does not exist`); } } - const newEnvFilePath = path.join(envDirPath, `${sanitizeFilenme(newName)}.bru`); + const newEnvFilePath = path.join(envDirPath, `${sanitizeFilename(newName)}.bru`); if (fs.existsSync(newEnvFilePath) && envFilePath !== newEnvFilePath) { throw new Error(`environment: ${newEnvFilePath} already exists`); } @@ -228,9 +228,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:delete-environment', async (event, collectionPathname, environmentName) => { try { const envDirPath = path.join(collectionPathname, 'environments'); - let envFilePath = path.join(envDirPath, `${sanitizeFilenme(environmentName)}.bru`); + let envFilePath = path.join(envDirPath, `${sanitizeFilename(environmentName)}.bru`); if (!fs.existsSync(envFilePath)) { - // Fallback to unsatized env name + // Fallback to unsanitized env name envFilePath = path.join(envDirPath, `${environmentName}.bru`); if (!fs.existsSync(envFilePath)) { throw new Error(`environment: ${envFilePath} does not exist`); @@ -273,9 +273,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection throw new Error(`path: ${oldPathFull} is not a bru file`); } - const newSantitizedPath = path.join(newPath, sanitizeFilenme(newName) + '.bru'); - if (fs.existsSync(newSantitizedPath) && newSantitizedPath !== oldPathFull) { - throw new Error(`path: ${newSantitizedPath} already exists`); + const newSanitizedPath = path.join(newPath, sanitizeFilename(newName) + '.bru'); + if (fs.existsSync(newSanitizedPath) && newSanitizedPath !== oldPathFull) { + throw new Error(`path: ${newSanitizedPath} already exists`); } // update name in file and save new copy, then delete old copy @@ -284,13 +284,13 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection jsonData.name = newName; - moveRequestUid(oldPathFull, newSantitizedPath); + moveRequestUid(oldPathFull, newSanitizedPath); const content = jsonToBru(jsonData); - await writeFile(newSantitizedPath, content); + await writeFile(newSanitizedPath, content); - // Because of santization the name can change but the path stays the same - if (newSantitizedPath !== oldPathFull) { + // Because of sanitization the name can change but the path stays the same + if (newSanitizedPath !== oldPathFull) { fs.unlinkSync(oldPathFull); } } catch (error) { diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index c523ffea09..6c2bba5cc6 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -118,7 +118,7 @@ const sanitizeDirectoryName = (name) => { return name.replace(/[<>:"/\\|?*\x00-\x1F]+/g, '-'); }; -const sanitizeFilenme = (name) => { +const sanitizeFilename = (name) => { return name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_'); }; @@ -137,5 +137,5 @@ module.exports = { searchForFiles, searchForBruFiles, sanitizeDirectoryName, - sanitizeFilenme + sanitizeFilename };