Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix name validation consistency #349

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contributing_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ Bruno построен с использованием NextJs и React. Мы т
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
- Пример: feature/dark-mode
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
- Пример bugfix/bug-1
- Пример bugfix/bug-1
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const CreateEnvironment = ({ collection, onClose }) => {
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.min(1, 'must be atleast 1 characters')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what was the intention of this change? The previous wording was grammatically correct.

.max(250, 'must be 250 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.max(250, 'must be 250 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { dirnameRegex } from 'utils/common/regex';

const RenameCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch();
Expand All @@ -18,7 +19,9 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.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) => {
Expand Down
20 changes: 10 additions & 10 deletions packages/bruno-app/src/components/Sidebar/CreateCollection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 { dirnameRegex } from 'utils/common/regex';

const CreateCollection = ({ onClose }) => {
const inputRef = useRef();
Expand All @@ -25,9 +26,10 @@ 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')
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.matches(/^[\w\-. ]+$/, 'Folder name contains invalid characters')
.required('folder name is required'),
collectionLocation: Yup.string().min(1, 'location is required').required('location is required')
}),
Expand Down Expand Up @@ -76,14 +78,12 @@ const CreateCollection = ({ onClose }) => {
name="collectionName"
ref={inputRef}
className="block textbox mt-2 w-full"
onChange = {
(e) => {
formik.handleChange(e);
if (formik.values.collectionName === formik.values.collectionFolderName) {
formik.setFieldValue("collectionFolderName", e.target.value);
}
}
}
onChange={(e) => {
formik.handleChange(e);
if (formik.values.collectionName === formik.values.collectionFolderName) {
formik.setFieldValue('collectionFolderName', e.target.value);
}
}}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
Expand Down
4 changes: 4 additions & 0 deletions packages/bruno-app/src/components/Sidebar/NewFolder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 { dirnameRegex } from 'utils/common/regex';

const NewFolder = ({ collection, item, onClose }) => {
const dispatch = useDispatch();
Expand All @@ -19,6 +20,9 @@ const NewFolder = ({ collection, item, onClose }) => {
.trim()
.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',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from 'path';
import toast from 'react-hot-toast';
import trim from 'lodash/trim';
import find from 'lodash/find';
Expand All @@ -19,9 +18,9 @@ import {
isItemAFolder,
refreshUidsInItem
} from 'utils/collections';
import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema';
import { collectionSchema, itemSchema, environmentSchema } from '@usebruno/schema';
import { waitForNextTick } from 'utils/common';
import { getDirectoryName, isWindowsOS, PATH_SEPARATOR } from 'utils/common/platform';
import { getDirectoryName, PATH_SEPARATOR } from 'utils/common/platform';
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';

import {
Expand Down Expand Up @@ -93,7 +92,6 @@ export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
console.log(collection.root);

return new Promise((resolve, reject) => {
if (!collection) {
Expand Down Expand Up @@ -282,25 +280,16 @@ 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) {
return reject(new Error('Unable to locate item'));
}

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).then(resolve).catch(reject);
ipcRenderer.invoke('renderer:rename-item', item.pathname, dirname, newName).then(resolve).catch(reject);
});
};

Expand Down Expand Up @@ -332,14 +321,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 {
Expand All @@ -351,15 +339,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 {
Expand All @@ -382,12 +369,7 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => {
if (item) {
const { ipcRenderer } = window;

ipcRenderer
.invoke('renderer:delete-item', item.pathname, item.type)
.then(() => {
resolve();
})
.catch((error) => reject(error));
ipcRenderer.invoke('renderer:delete-item', item.pathname, item.type).then(resolve).catch(reject);
}
return;
});
Expand Down Expand Up @@ -570,7 +552,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) {
Expand Down Expand Up @@ -616,23 +598,24 @@ 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);
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(
updateNextAction({
nextAction: {
type: 'OPEN_REQUEST',
payload: {
pathname: fullName
pathname: newPath
}
},
collectionUid
})
);

resolve();
} else {
return reject(new Error('Duplicate request names are not allowed under the same folder'));
}
Expand All @@ -646,10 +629,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);
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
Expand All @@ -658,12 +640,14 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
nextAction: {
type: 'OPEN_REQUEST',
payload: {
pathname: fullName
pathname: newPath
}
},
collectionUid
})
);

resolve();
} else {
return reject(new Error('Duplicate request names are not allowed under the same folder'));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,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);
}
}
}
},
Expand Down Expand Up @@ -1203,6 +1206,7 @@ export const collectionsSlice = createSlice({

if (existingEnv) {
existingEnv.variables = environment.variables;
existingEnv.name = environment.name;
} else {
collection.environments.push(environment);

Expand Down
4 changes: 4 additions & 0 deletions packages/bruno-app/src/utils/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@ export const getContentType = (headers) => {

return '';
};

export const sanitizeFilenme = (name) => {
return name.replace(/[^\w-_.]/g, '_');
};
3 changes: 3 additions & 0 deletions packages/bruno-app/src/utils/common/regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// See https://github.com/usebruno/bruno/pull/349 for more info
// Scrict regex for validating directories. Covers most edge cases like windows device names
export const dirnameRegex = /^(?!CON|PRN|AUX|NUL|COM\d|LPT\d|^ |^\-)[^<>:"/\\|?*\x00-\x1F]+[^\. ]$/;
10 changes: 8 additions & 2 deletions packages/bruno-electron/src/app/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ const addEnvironmentFile = async (win, pathname, collectionUid, collectionPath)
let 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()));
Expand Down Expand Up @@ -135,7 +138,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()));

Expand Down
Loading