Skip to content

Commit b378104

Browse files
committed
fix: preserver utf8 charset in filenames
1 parent f8cde5d commit b378104

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

apps/client/src/common/api/db.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ export async function downloadProject(fileName: string) {
3232

3333
/**
3434
* HTTP request to upload project file
35+
*
36+
* Note: The browser's FormData API automatically encodes filenames according to
37+
* RFC 2231/RFC 5987 standards with UTF-8 charset. The issue is server-side where
38+
* Busboy (used by Multer) defaults to 'latin1' charset. The server-side fix in
39+
* upload.ts handles the encoding conversion.
3540
*/
3641
export async function uploadProjectFile(file: File): Promise<MessageResponse> {
3742
const formData = new FormData();
3843
formData.append('project', file);
39-
const response = await axios.post(`${dbPath}/upload`, formData, {
40-
headers: {
41-
'Content-Type': 'multipart/form-data',
42-
},
43-
});
44+
const response = await axios.post(`${dbPath}/upload`, formData);
4445
return response.data;
4546
}
4647

apps/server/src/utils/upload.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,33 @@ function generateNewFileName(filePath: string, callback: (newName: string) => vo
3030
checkExistence(newPath);
3131
}
3232

33+
/**
34+
* Fixes encoding issues where UTF-8 bytes are incorrectly interpreted as Latin-1.
35+
*
36+
* Multer uses Busboy internally, which defaults to 'latin1' charset for parsing
37+
* Content-Disposition header parameters (like filenames). This causes UTF-8
38+
* characters to be misinterpreted.
39+
*
40+
* Example: 'ø' (UTF-8: 0xC3 0xB8) gets misinterpreted as 'ø' (Latin-1: 0xC3 0xB8)
41+
*
42+
* Solution: Convert the string back to bytes using Latin-1 (which preserves
43+
* byte values), then re-interpret those bytes as UTF-8.
44+
*
45+
* Note: This is the standard workaround since Multer doesn't expose Busboy's
46+
* defParamCharset option directly.
47+
* @link https://github.com/expressjs/multer/issues/1104
48+
*/
49+
function fixFilenameEncoding(filename: string): string {
50+
try {
51+
// Convert the string back to bytes using Latin-1 (which preserves byte values),
52+
// then re-interpret those bytes as UTF-8
53+
return Buffer.from(filename, 'latin1').toString('utf8');
54+
} catch (error) {
55+
// If conversion fails, return the original filename
56+
return filename;
57+
}
58+
}
59+
3360
// Define multer storage object
3461
export const storage = multer.diskStorage({
3562
destination: function (_req, file, cb) {
@@ -40,7 +67,8 @@ export const storage = multer.diskStorage({
4067

4168
ensureDirectory(publicDir.uploadsDir);
4269

43-
const sanitisedName = sanitize(file.originalname);
70+
const fixedFilename = fixFilenameEncoding(file.originalname);
71+
const sanitisedName = sanitize(fixedFilename);
4472
const filePath = path.join(publicDir.uploadsDir, sanitisedName);
4573

4674
// Check if file already exists

0 commit comments

Comments
 (0)