From cc8880065cf60ae02259f1c5703bf3243604661a Mon Sep 17 00:00:00 2001 From: SwanandBhuskute Date: Thu, 14 Nov 2024 12:30:52 +0000 Subject: [PATCH] enhanced with suggestions --- src/Utils/stringUtils.ts | 11 +++++-- .../Facility/Investigations/Reports/utils.tsx | 33 ++++++++++++------- .../Investigations/ShowInvestigation.tsx | 22 +++++++++++-- .../Facility/Investigations/Table.tsx | 11 +++++++ src/components/Patient/PatientRegister.tsx | 33 +++++++++++-------- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/Utils/stringUtils.ts b/src/Utils/stringUtils.ts index 0cc2a246487..89ab3dce851 100644 --- a/src/Utils/stringUtils.ts +++ b/src/Utils/stringUtils.ts @@ -8,11 +8,16 @@ export const camelCase = (str: string) => { .replace(/([A-Z])/g, (match) => match.toLowerCase()); }; -// Capitalize the first letter of each word in a string -export const startCase = (str: string) => { +// Capitalize the first letter of each word in a string, handling edge cases +export const startCase = (str: string): string => { + if (!str) return ""; + return str + .toLowerCase() + .replace(/\s+/g, " ") + .trim() .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .map((word) => (word ? word[0].toUpperCase() + word.slice(1) : "")) .join(" "); }; diff --git a/src/components/Facility/Investigations/Reports/utils.tsx b/src/components/Facility/Investigations/Reports/utils.tsx index 5a84e6fbe60..12897e4cb3a 100644 --- a/src/components/Facility/Investigations/Reports/utils.tsx +++ b/src/components/Facility/Investigations/Reports/utils.tsx @@ -1,13 +1,24 @@ import { InvestigationResponse } from "@/components/Facility/Investigations/Reports/types"; const memoize = any>(fn: T): T => { - const cache: Record> = {}; + const cache = new Map>(); + const MAX_CACHE_SIZE = 1000; return ((...args: Parameters): ReturnType => { - const key = JSON.stringify(args); - if (!cache[key]) { - cache[key] = fn(...args); + const key = args + .map((arg) => + typeof arg === "object" + ? JSON.stringify(Object.entries(arg).sort()) + : String(arg), + ) + .join("|"); + if (!cache.has(key)) { + if (cache.size >= MAX_CACHE_SIZE) { + const firstKey: any = cache.keys().next().value; + cache.delete(firstKey); + } + cache.set(key, fn(...args)); } - return cache[key]; + return cache.get(key)!; }) as T; }; @@ -41,15 +52,14 @@ export const transformData = memoize((data: InvestigationResponse) => { ), ); + const sessionMap = new Map( + sessions.map((session, index) => [session.session_external_id, index]), + ); const reqData = groupByInvestigation.map((value: any) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val: any) => { - const sessionIndex = sessions.findIndex( - (session) => - session.session_external_id === - val.session_object.session_external_id, - ); - + const sessionIndex = + sessionMap.get(val.session_object.session_external_id) ?? -1; if (sessionIndex > -1) { sessionValues[sessionIndex] = { min: val.investigation_object.min_value, @@ -82,6 +92,7 @@ export const transformData = memoize((data: InvestigationResponse) => { sessionValues, }; }); + return { sessions, data: reqData }; }); diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index daaf25f97d9..0e298377002 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -95,10 +95,28 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { let current = changedFields; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; - if (!current[key]) current[key] = {}; + + // Protect against prototype pollution by skipping unsafe keys - crai + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; + } + + // Use Object.create(null) to prevent accidental inheritance from Object prototype - coderabbit + current[key] = current[key] || Object.create(null); current = current[key]; } - current[keys[keys.length - 1]] = value; + + const lastKey = keys[keys.length - 1]; + + // Final key assignment, ensuring no prototype pollution vulnerability - coderabbit + if ( + lastKey !== "__proto__" && + lastKey !== "constructor" && + lastKey !== "prototype" + ) { + current[lastKey] = value; + } + dispatch({ type: "set_changed_fields", changedFields }); }; diff --git a/src/components/Facility/Investigations/Table.tsx b/src/components/Facility/Investigations/Table.tsx index 0eccf004bde..0d2b5f73971 100644 --- a/src/components/Facility/Investigations/Table.tsx +++ b/src/components/Facility/Investigations/Table.tsx @@ -60,6 +60,17 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { const handleValueChange = (value: any, name: string) => { const form = { ...state }; const keys = name.split("."); + + // Validate keys to prevent prototype pollution - coderabbit suggested + if ( + keys.some((key) => + ["__proto__", "constructor", "prototype"].includes(key), + ) + ) { + console.error("Invalid object key detected"); + return; + } + let current = form; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 66f19b0b1b3..0265afd8c34 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -1,6 +1,6 @@ import careConfig from "@careConfig"; import { navigate } from "raviger"; -import { useCallback, useReducer, useRef, useState } from "react"; +import { useCallback, useEffect, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -185,6 +185,24 @@ export const parseOccupationFromExt = (occupation: Occupation) => { return occupationObject?.id; }; +const useDebounce = (callback: (...args: any[]) => void, delay: number) => { + const callbackRef = useRef(callback); + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: any[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }; + return debouncedCallback; +}; + export const PatientRegister = (props: PatientRegisterProps) => { const submitController = useRef(); const authUser = useAuthUser(); @@ -746,19 +764,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { }); }; - const useDebounce = (callback: (...args: any[]) => void, delay: number) => { - const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: any[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - callback(...args); - }, delay); - }; - return debouncedCallback; - }; - const duplicateCheck = useDebounce(async (phoneNo: string) => { if ( phoneNo &&