Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
11 changes: 6 additions & 5 deletions apps/client/src/common/api/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ export async function downloadProject(fileName: string) {

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

Expand Down
30 changes: 29 additions & 1 deletion apps/server/src/utils/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ function generateNewFileName(filePath: string, callback: (newName: string) => vo
checkExistence(newPath);
}

/**
* Fixes encoding issues where UTF-8 bytes are incorrectly interpreted as Latin-1.
*
* Multer uses Busboy internally, which defaults to 'latin1' charset for parsing
* Content-Disposition header parameters (like filenames). This causes UTF-8
* characters to be misinterpreted.
*
* Example: 'ø' (UTF-8: 0xC3 0xB8) gets misinterpreted as 'ø' (Latin-1: 0xC3 0xB8)
*
* Solution: Convert the string back to bytes using Latin-1 (which preserves
* byte values), then re-interpret those bytes as UTF-8.
*
* Note: This is the standard workaround since Multer doesn't expose Busboy's
* defParamCharset option directly.
* @link https://github.com/expressjs/multer/issues/1104
*/
function fixFilenameEncoding(filename: string): string {
try {
// Convert the string back to bytes using Latin-1 (which preserves byte values),
// then re-interpret those bytes as UTF-8
return Buffer.from(filename, 'latin1').toString('utf8');
} catch (error) {
// If conversion fails, return the original filename
return filename;
}
}

// Define multer storage object
export const storage = multer.diskStorage({
destination: function (_req, file, cb) {
Expand All @@ -40,7 +67,8 @@ export const storage = multer.diskStorage({

ensureDirectory(publicDir.uploadsDir);

const sanitisedName = sanitize(file.originalname);
const fixedFilename = fixFilenameEncoding(file.originalname);
const sanitisedName = sanitize(fixedFilename);
const filePath = path.join(publicDir.uploadsDir, sanitisedName);

// Check if file already exists
Expand Down
Loading