Skip to content

Commit be2fd9e

Browse files
committed
fix: fix crash when project or language name was __proto__ or other prototype pollution keyword
1 parent fe88a7a commit be2fd9e

9 files changed

Lines changed: 730 additions & 725 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
2+
"typescript.tsdk": "node_modules/typescript/lib",
3+
"editor.defaultFormatter": "esbenp.prettier-vscode",
34
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
45
}

package-lock.json

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
"@mantine/next": "^6.0.21",
1111
"@mantine/notifications": "^7.3.2",
1212
"@radix-ui/react-icons": "^1.3.0",
13-
"chart.js": "^4.4.1",
13+
"chart.js": "^4.5.1",
1414
"date-fns": "^4.1.0",
1515
"formik": "^2.4.5",
1616
"i18next": "^23.7.12",
1717
"i18next-resources-to-backend": "^1.2.0",
1818
"next": "^14.0.4",
1919
"next-i18n-router": "^5.1.0",
2020
"react": "^18.2.0",
21-
"react-chartjs-2": "^5.2.0",
21+
"react-chartjs-2": "^5.3.1",
2222
"react-cookie": "^8.0.1",
2323
"react-dom": "^18.2.0",
2424
"react-feather": "^2.0.10",
@@ -41,7 +41,7 @@
4141
"@storybook/nextjs": "^8.6.9",
4242
"@storybook/react": "^8.6.9",
4343
"@svgr/webpack": "^8.1.0",
44-
"@types/node": "^20.12.5",
44+
"@types/node": "^25.0.3",
4545
"@types/react": "^18.2.74",
4646
"@types/react-dom": "^18.2.24",
4747
"@types/react-router": "^5.1.20",
@@ -55,7 +55,7 @@
5555
"prettier": "^3.1.1",
5656
"prettier-eslint": "^16.2.0",
5757
"storybook": "^8.6.9",
58-
"typescript": "^5.3.3",
58+
"typescript": "^5.9.3",
5959
"vitest": "^3.0.9"
6060
}
6161
}

src/components/PerProjectChart.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Text } from "@mantine/core";
2-
import { groupBy, sumBy } from "../utils/arrayUtils";
2+
import { sumBy } from "../utils/arrayUtils";
33
import { calculateTickValues } from "../utils/chartUtils";
44
import { prettifyProgrammingLanguageName } from "../utils/programmingLanguagesUtils";
5-
import { colors, isColor } from "../utils/colors";
5+
import { colors } from "../utils/colors";
66
import { Bar } from "react-chartjs-2";
77
import {
88
Chart as ChartJS,
@@ -55,7 +55,7 @@ const defaultColors = [
5555
"#b15928",
5656
];
5757

58-
const chosenRandomColors: Record<string, string> = {};
58+
const chosenRandomColors = new Map<string, string>();
5959

6060
function getLanguageColor(str: string): string {
6161
let language = str.toLowerCase();
@@ -64,18 +64,24 @@ function getLanguageColor(str: string): string {
6464
if (language.split(" ").length > 1) {
6565
language = language
6666
.split(" ")
67-
.filter((possibleName) => isColor(possibleName))[0];
67+
.filter((possibleName) => colors.has(possibleName))[0];
6868
}
6969

70-
if (isColor(language)) {
71-
return colors[language];
70+
if (colors.has(language)) {
71+
// Should never be undefined
72+
return colors.get(language) ?? "#000000";
7273
}
7374

74-
if (language in chosenRandomColors) return chosenRandomColors[language];
75+
if (chosenRandomColors.has(language)) {
76+
// Should never be undefined
77+
78+
return chosenRandomColors.get(language) ?? "#000000";
79+
}
7580

7681
const randomColor =
7782
defaultColors[Math.floor(defaultColors.length * Math.random())];
78-
chosenRandomColors[language] = randomColor;
83+
chosenRandomColors.set(language, randomColor);
84+
7985
return randomColor;
8086
}
8187

@@ -87,21 +93,28 @@ export const PerProjectChart = ({
8793
}: PerProjectChartProps) => {
8894
if (entries.length === 0) return <Text>No data</Text>;
8995

90-
let languageNames = entries
96+
const languageNames = entries
9197
.sort((entry1, entry2) => entry2.duration - entry1.duration)
9298
.map((entry) => entry.language || "other")
9399
.filter((item, index, array) => array.indexOf(item) === index)
94-
.slice(0, 9);
95-
languageNames.push("other");
96-
languageNames = languageNames.reverse();
100+
.slice(0, 9)
101+
.concat("other") // TODO: Why is this here
102+
.toReversed();
97103

98104
// Get the total time spent on each project
99-
const projectGroups = groupBy(entries, (e) => e.project_name ?? "Unknown");
105+
const projectGroups = Object.groupBy(
106+
entries,
107+
(x) => x.project_name ?? "undefined",
108+
);
109+
100110
const totalTimeByProject = Object.keys(projectGroups).map((projectName) => {
101-
const projectEntries = projectGroups[projectName];
102-
const langGroups = groupBy(projectEntries, (e) => e.language);
111+
const projectEntries = projectGroups[projectName] ?? [];
112+
const langGroups = Object.groupBy(
113+
projectEntries,
114+
(e) => e.language ?? "undefined",
115+
);
103116
const totalTimeByLanguage = Object.keys(langGroups).map((lang) => {
104-
const langEntries = langGroups[lang];
117+
const langEntries = langGroups[lang] ?? [];
105118
return {
106119
language: lang,
107120
duration: sumBy(langEntries, (e) => e.duration),
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { groupBy, sumBy } from "../../utils/arrayUtils";
1+
import { sumBy } from "../../utils/arrayUtils";
22
import { isStringNull } from "../../utils/stringUtils";
33

44
interface TopLanguagesRequirement {
@@ -7,11 +7,14 @@ interface TopLanguagesRequirement {
77
}
88

99
export const getAllTimeTopLanguages = (entries: TopLanguagesRequirement[]) => {
10-
const byLanguage = groupBy(entries, (entry) => entry.language);
10+
const byLanguage = Object.groupBy(
11+
entries,
12+
(entry) => entry.language ?? "undefined",
13+
);
1114
return Object.keys(byLanguage)
1215
.map((language) => ({
1316
language: isStringNull(language) ? undefined : language,
14-
duration: sumBy(byLanguage[language], (entry) => entry.duration),
17+
duration: sumBy(byLanguage[language] ?? [], (entry) => entry.duration),
1518
}))
1619
.sort((a, b) => b.duration - a.duration);
1720
};
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { groupBy, sumBy } from "../../utils/arrayUtils";
1+
import { sumBy } from "../../utils/arrayUtils";
22
import { isStringNull } from "../../utils/stringUtils";
33

44
interface TopProjectsRequirements {
@@ -7,11 +7,14 @@ interface TopProjectsRequirements {
77
}
88

99
export const getAllTimeTopProjects = (entries: TopProjectsRequirements[]) => {
10-
const byProject = groupBy(entries, (entry) => entry.project_name);
10+
const byProject = Object.groupBy(
11+
entries,
12+
(entry) => entry.project_name ?? "undefined",
13+
);
1114
return Object.keys(byProject)
1215
.map((project) => ({
1316
project_name: isStringNull(project) ? undefined : project,
14-
duration: sumBy(byProject[project], (entry) => entry.duration),
17+
duration: sumBy(byProject[project] ?? [], (entry) => entry.duration),
1518
}))
1619
.sort((a, b) => b.duration - a.duration);
1720
};

src/utils/arrayUtils.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,3 @@
1-
export function groupBy<T>(
2-
array: T[],
3-
predicate: (elem: T) => string | number | symbol | undefined | null,
4-
) {
5-
return array.reduce<Record<string | number | symbol, T[]>>((b, e) => {
6-
const k = predicate(e);
7-
const key = k ?? "undefined";
8-
if (key in b) {
9-
b[key].push(e);
10-
} else {
11-
b[key] = [e];
12-
}
13-
return b;
14-
}, {});
15-
}
16-
171
export function sumBy<T>(array: T[], predicate: (elem: T) => number) {
182
return array.reduce((prev, curr) => prev + predicate(curr), 0);
193
}

0 commit comments

Comments
 (0)