Skip to content

Commit a3d25b5

Browse files
chore: Send md5 hex to server action on submit instead of base64 (#6294)
* send md5 hex to server action on submit instead of base64 * remove async digest create so copyobject can run in hook
1 parent 1bc0d29 commit a3d25b5

File tree

6 files changed

+91
-122
lines changed

6 files changed

+91
-122
lines changed

app/(gcforms)/[locale]/(form filler)/id/[...props]/lib/client/fileUploader.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import axios, { AxiosError, AxiosProgressEvent } from "axios";
44
import { Responses, FileInputResponse, FileInput } from "@gcforms/types";
55
import { FileUploadError } from "../client/exceptions";
66
import { isMimeTypeValid } from "@gcforms/core";
7-
import { generateFileChecksums } from "@lib/utils/fileChecksum";
87

98
const isFileInput = (response: unknown): response is FileInput => {
109
return (
@@ -65,10 +64,7 @@ export const copyObjectExcludingFileContent = (
6564
};
6665
filterFileContent(originalObject, formValuesWithoutFileContent);
6766

68-
// Calculate checksums for all files key / value pairs
69-
const fileChecksums = generateFileChecksums(fileObjsRef);
70-
71-
return { formValuesWithoutFileContent, fileObjsRef, fileChecksums };
67+
return { formValuesWithoutFileContent, fileObjsRef };
7268
};
7369

7470
export const uploadFile = async (

components/clientComponents/forms/Form/Form.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ga } from "@lib/client/clientHelpers";
3636
import { SubmitProgress } from "@clientComponents/forms/SubmitProgress/SubmitProgress";
3737
import { handleUploadError } from "@lib/fileInput/handleUploadError";
3838
import { hasFiles } from "@lib/fileExtractor";
39+
import { generateFileChecksums } from "@lib/utils/fileChecksum";
3940

4041
import {
4142
copyObjectExcludingFileContent,
@@ -300,9 +301,10 @@ export const Form = withFormik<FormProps, Responses>({
300301
);
301302

302303
// Extract file content from formValues so they are not part of the submission call to the submit action
303-
const { formValuesWithoutFileContent, fileObjsRef, fileChecksums } =
304+
const { formValuesWithoutFileContent, fileObjsRef } =
304305
copyObjectExcludingFileContent(formValues);
305306

307+
const fileChecksums = await generateFileChecksums(fileObjsRef);
306308
let submitProgress = 0;
307309
let progressInterval: NodeJS.Timeout | undefined = undefined;
308310

lib/utils/fileChecksum.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { createHash } from "crypto";
1+
import { md5 } from "hash-wasm";
22
import { FileInput } from "@gcforms/types";
33
import { logMessage } from "@lib/logger";
44

5-
export function calculateMD5Checksum(content: ArrayBuffer): string {
5+
// Creates Hexadecimal base MD5 hash
6+
export async function calculateMD5Checksum(content: ArrayBuffer): Promise<string> {
67
const buffer = Buffer.from(content);
7-
const hash = createHash("md5");
8-
hash.update(buffer);
9-
return hash.digest("base64");
8+
return md5(buffer);
109
}
1110

12-
export function calculateFileChecksum(file: FileInput): string {
11+
export async function calculateFileChecksum(file: FileInput): Promise<string> {
1312
// Ensure file content is available
1413
if (!file.content) {
1514
throw new Error(`Unable to calculate checksum for file ${file.name}: no content available`);
@@ -22,19 +21,21 @@ export function calculateFileChecksum(file: FileInput): string {
2221
* @param fileObjsRef - Map of file IDs to FileInput objects
2322
* @returns Map of file IDs to their MD5 checksums
2423
*/
25-
export function generateFileChecksums(
24+
export async function generateFileChecksums(
2625
fileObjsRef: Record<string, FileInput>
27-
): Record<string, string> {
26+
): Promise<Record<string, string>> {
2827
const checksums: Record<string, string> = {};
2928

30-
for (const [fileId, file] of Object.entries(fileObjsRef)) {
31-
try {
32-
checksums[fileId] = calculateFileChecksum(file);
33-
} catch (error) {
34-
logMessage.warn(`Failed to calculate checksum for file ${file.name}: ${error}`);
35-
// Continue with other files, don't fail the entire process
36-
}
37-
}
29+
await Promise.all(
30+
Object.entries(fileObjsRef).map(async ([fileId, file]) => {
31+
try {
32+
checksums[fileId] = await calculateFileChecksum(file);
33+
} catch (error) {
34+
logMessage.warn(`Failed to calculate checksum for file ${file.name}: ${error}`);
35+
// Continue with other files, don't fail the entire process
36+
}
37+
})
38+
);
3839

3940
return checksums;
4041
}
Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,130 @@
1-
import { describe, it, expect } from 'vitest';
2-
import { calculateMD5Checksum, calculateFileChecksum, generateFileChecksums } from '../fileChecksum';
3-
import { FileInput } from '@gcforms/types';
4-
import { readFileSync } from 'fs';
5-
import { join } from 'path';
6-
7-
describe('fileChecksum', () => {
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
calculateMD5Checksum,
4+
calculateFileChecksum,
5+
generateFileChecksums,
6+
} from "../fileChecksum";
7+
import { FileInput } from "@gcforms/types";
8+
import { readFileSync } from "fs";
9+
import { join } from "path";
10+
11+
describe("fileChecksum", () => {
812
// Helper function to load CSV fixtures
913
const loadCsvFixture = (filename: string): ArrayBuffer => {
10-
const csvPath = join(__dirname, 'fixtures', filename);
14+
const csvPath = join(__dirname, "fixtures", filename);
1115
return readFileSync(csvPath).buffer;
1216
};
1317

14-
describe('calculateMD5Checksum', () => {
15-
it('should calculate MD5 checksum for file content', () => {
16-
const content = loadCsvFixture('sample.csv');
17-
const checksum = calculateMD5Checksum(content);
18+
describe("calculateMD5Checksum", () => {
19+
it("should calculate MD5 checksum for file content", async () => {
20+
const content = loadCsvFixture("sample.csv");
21+
const checksum = await calculateMD5Checksum(content);
1822

1923
// This checksum should be consistent for the sample.csv file
2024
expect(checksum).toBeTruthy();
21-
expect(typeof checksum).toBe('string');
25+
expect(typeof checksum).toBe("string");
2226
});
2327

24-
it('should return different checksums for different content', () => {
25-
const content1 = loadCsvFixture('sample.csv');
26-
const content2 = new TextEncoder().encode('Different content').buffer;
28+
it("should return different checksums for different content", async () => {
29+
const content1 = loadCsvFixture("sample.csv");
30+
const content2 = new TextEncoder().encode("Different content").buffer;
2731

28-
const checksum1 = calculateMD5Checksum(content1);
29-
const checksum2 = calculateMD5Checksum(content2);
32+
const checksum1 = await calculateMD5Checksum(content1);
33+
const checksum2 = await calculateMD5Checksum(content2);
3034

3135
expect(checksum1).not.toBe(checksum2);
3236
});
3337

34-
it('should return same checksum for same content', () => {
35-
const content1 = loadCsvFixture('sample.csv');
36-
const content2 = loadCsvFixture('sample-copy.csv');
38+
it("should return same checksum for same content", async () => {
39+
const content1 = loadCsvFixture("sample.csv");
40+
const content2 = loadCsvFixture("sample-copy.csv");
3741

38-
const checksum1 = calculateMD5Checksum(content1);
39-
const checksum2 = calculateMD5Checksum(content2);
42+
const checksum1 = await calculateMD5Checksum(content1);
43+
const checksum2 = await calculateMD5Checksum(content2);
4044

4145
expect(checksum1).toBe(checksum2);
4246
});
4347
});
4448

45-
describe('calculateFileChecksum', () => {
46-
it('should calculate checksum for FileInput object', () => {
47-
const csvContent = loadCsvFixture('sample.csv');
49+
describe("calculateFileChecksum", () => {
50+
it("should calculate checksum for FileInput object", async () => {
51+
const csvContent = loadCsvFixture("sample.csv");
4852
const file: FileInput = {
49-
name: 'sample.csv',
53+
name: "sample.csv",
5054
size: csvContent.byteLength,
5155
content: csvContent,
5256
};
5357

54-
const checksum = calculateFileChecksum(file);
58+
const checksum = await calculateFileChecksum(file);
5559
expect(checksum).toBeTruthy();
56-
expect(typeof checksum).toBe('string');
60+
expect(typeof checksum).toBe("string");
5761
});
5862

59-
it('should throw error for file without content', () => {
63+
it("should throw error for file without content", async () => {
6064
const file = {
61-
name: 'test.txt',
65+
name: "test.txt",
6266
size: 0,
6367
content: null,
6468
} as unknown as FileInput;
6569

66-
expect(() => calculateFileChecksum(file)).toThrow(
67-
'Unable to calculate checksum for file test.txt: no content available'
70+
expect(calculateFileChecksum(file)).rejects.toThrow(
71+
"Unable to calculate checksum for file test.txt: no content available"
6872
);
6973
});
7074
});
7175

72-
describe('generateFileChecksums', () => {
73-
it('should generate checksums for multiple files', () => {
74-
const csvContent1 = loadCsvFixture('sample.csv');
75-
const csvContent2 = loadCsvFixture('sample-copy.csv');
76+
describe("generateFileChecksums", () => {
77+
it("should generate checksums for multiple files", async () => {
78+
const csvContent1 = loadCsvFixture("sample.csv");
79+
const csvContent2 = loadCsvFixture("sample-copy.csv");
7680

7781
const fileObjsRef = {
78-
'file1': {
79-
name: 'sample.csv',
82+
file1: {
83+
name: "sample.csv",
8084
size: csvContent1.byteLength,
8185
content: csvContent1,
8286
} as FileInput,
83-
'file2': {
84-
name: 'sample-copy.csv',
87+
file2: {
88+
name: "sample-copy.csv",
8589
size: csvContent2.byteLength,
8690
content: csvContent2,
8791
} as FileInput,
8892
};
8993

90-
const checksums = generateFileChecksums(fileObjsRef);
94+
const checksums = await generateFileChecksums(fileObjsRef);
9195

92-
expect(checksums).toHaveProperty('file1');
93-
expect(checksums).toHaveProperty('file2');
96+
expect(checksums).toHaveProperty("file1");
97+
expect(checksums).toHaveProperty("file2");
9498
// Both files have identical content, so checksums should be the same
9599
expect(checksums.file1).toBe(checksums.file2);
96100
});
97101

98-
it('should handle empty file objects', () => {
99-
const checksums = generateFileChecksums({});
102+
it("should handle empty file objects", async () => {
103+
const checksums = await generateFileChecksums({});
100104
expect(checksums).toEqual({});
101105
});
102106

103-
it('should continue processing when one file fails', () => {
104-
const csvContent = loadCsvFixture('sample.csv');
107+
it("should continue processing when one file fails", async () => {
108+
const csvContent = loadCsvFixture("sample.csv");
105109

106110
const fileObjsRef = {
107-
'file1': {
108-
name: 'sample.csv',
111+
file1: {
112+
name: "sample.csv",
109113
size: csvContent.byteLength,
110114
content: csvContent,
111115
} as FileInput,
112-
'file2': {
113-
name: 'broken.csv',
116+
file2: {
117+
name: "broken.csv",
114118
size: 0,
115119
content: null,
116120
} as unknown as FileInput,
117121
};
118122

119-
const checksums = generateFileChecksums(fileObjsRef);
123+
const checksums = await generateFileChecksums(fileObjsRef);
120124

121-
expect(checksums).toHaveProperty('file1');
122-
expect(checksums).not.toHaveProperty('file2');
123-
expect(typeof checksums.file1).toBe('string');
125+
expect(checksums).toHaveProperty("file1");
126+
expect(checksums).not.toHaveProperty("file2");
127+
expect(typeof checksums.file1).toBe("string");
124128
});
125129
});
126-
});
130+
});

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"formik": "2.4.6",
8989
"fuse.js": "^7.1.0",
9090
"got": "11",
91+
"hash-wasm": "^4.12.0",
9192
"htmlparser2": "8.0.2",
9293
"i18next": "^25.5.2",
9394
"i18next-browser-languagedetector": "^8.2.0",
@@ -103,7 +104,6 @@
103104
"lodash.set": "4.3.2",
104105
"lodash.unset": "4.5.2",
105106
"markdown-to-jsx": "7.7.13",
106-
"md5": "^2.3.0",
107107
"native-file-system-adapter": "^3.0.1",
108108
"next": "15.5.4",
109109
"next-auth": "5.0.0-beta.29",
@@ -155,7 +155,6 @@
155155
"@types/lodash.merge": "^4",
156156
"@types/lodash.set": "^4.3.9",
157157
"@types/lodash.unset": "^4.5.9",
158-
"@types/md5": "^2.3.5",
159158
"@types/node": "^24.7.2",
160159
"@types/node-fetch": "^2.6.9",
161160
"@types/pg": "^8.10.9",

0 commit comments

Comments
 (0)