Add UTF-8 filename decoding for multipart uploads in EquipmentService
- Introduced a new utility function to decode filenames from multipart uploads that may be misencoded as ISO-8859-1. - Updated EquipmentService to utilize this function when processing file attachments, ensuring correct original file names are stored and returned.
This commit is contained in:
32
server/src/common/multipart-filename.ts
Normal file
32
server/src/common/multipart-filename.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Busboy/Multer often pass multipart `filename` where UTF-8 bytes were read as ISO-8859-1,
|
||||
* which looks like mojibake in the UI (e.g. Cyrillic → "Ðкт...").
|
||||
* Re-encode the string as Latin-1 bytes and decode as UTF-8.
|
||||
*
|
||||
* Only applied when every character is in U+0000–U+00FF (typical for this bug).
|
||||
*/
|
||||
export function decodeUtf8FilenameFromMultipart(name: string): string {
|
||||
if (!name || name.length < 2) {
|
||||
return name;
|
||||
}
|
||||
for (let i = 0; i < name.length; i += 1) {
|
||||
if (name.charCodeAt(i) > 0xff) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
if (!/[\u00a1-\u00ff]{2}/.test(name)) {
|
||||
return name;
|
||||
}
|
||||
try {
|
||||
const decoded = Buffer.from(name, 'latin1').toString('utf8');
|
||||
if (decoded.includes('\uFFFD')) {
|
||||
return name;
|
||||
}
|
||||
if (decoded === name) {
|
||||
return name;
|
||||
}
|
||||
return decoded;
|
||||
} catch {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { EquipmentStatus, Prisma } from '@prisma/client';
|
||||
import type { Equipment } from '@prisma/client';
|
||||
import { Response } from 'express';
|
||||
import type { Express } from 'express';
|
||||
import { decodeUtf8FilenameFromMultipart } from '../../common/multipart-filename';
|
||||
import { setListHeaders } from '../../common/http';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import type { StoredAttachmentMeta } from '../../storage/storage.service';
|
||||
@@ -133,10 +134,11 @@ export class EquipmentService {
|
||||
|
||||
await this.deleteStoredAttachmentIfAny(item);
|
||||
|
||||
const objectKey = this.storage.buildEquipmentObjectKey(id, file.originalname);
|
||||
const decodedOriginalName = decodeUtf8FilenameFromMultipart(file.originalname);
|
||||
const objectKey = this.storage.buildEquipmentObjectKey(id, decodedOriginalName);
|
||||
const meta: StoredAttachmentMeta = {
|
||||
objectKey,
|
||||
originalFileName: file.originalname,
|
||||
originalFileName: decodedOriginalName,
|
||||
contentType: file.mimetype || 'application/octet-stream',
|
||||
sizeBytes: file.size,
|
||||
};
|
||||
@@ -217,7 +219,7 @@ export class EquipmentService {
|
||||
}
|
||||
return {
|
||||
objectKey: raw.objectKey,
|
||||
originalFileName: raw.originalFileName,
|
||||
originalFileName: decodeUtf8FilenameFromMultipart(raw.originalFileName),
|
||||
contentType: raw.contentType,
|
||||
sizeBytes: raw.sizeBytes,
|
||||
downloadUrl,
|
||||
|
||||
Reference in New Issue
Block a user