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 ( +