From 1f995085b7d186e28ca06af46d692495bd7e2004 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Thu, 21 May 2026 09:43:20 +0330 Subject: [PATCH] fix persian name bug --- src/modules/uploader/providers/s3.service.ts | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/modules/uploader/providers/s3.service.ts b/src/modules/uploader/providers/s3.service.ts index 661d0bf..9bedb3b 100644 --- a/src/modules/uploader/providers/s3.service.ts +++ b/src/modules/uploader/providers/s3.service.ts @@ -44,9 +44,9 @@ export class S3Service { try { const sanitizedMetadata = metadata ? Object.entries(metadata).reduce( - (acc, [key, value]) => { - // Encode all values to be ASCII-safe - acc[key] = encodeURIComponent(value); + (acc, [metaKey, value]) => { + // S3 user metadata must be US-ASCII; base64 keeps signing stable for Unicode names + acc[metaKey] = Buffer.from(value, 'utf8').toString('base64'); return acc; }, {} as Record, @@ -63,7 +63,8 @@ export class S3Service { await this.s3Client.send(command); - const url = `${this.endpointUrl}/${this.bucketName}/${key}`; + const encodedKey = key.split('/').map(segment => encodeURIComponent(segment)).join('/'); + const url = `${this.endpointUrl}/${this.bucketName}/${encodedKey}`; this.logger.log(`File uploaded to S3: ${key}`); @@ -144,7 +145,7 @@ export class S3Service { generateFileKey(originalName: string, fileType: FileType): string { const timestamp = Date.now(); const randomSuffix = randomInt(1000000, 9999999); - const extension = originalName.split('.').pop(); + const extension = this.extractSafeExtension(originalName); const basePathMap: Record = { image: 'images', @@ -158,4 +159,21 @@ export class S3Service { const basePath = basePathMap[fileType]; return `${basePath}/${timestamp}-${randomSuffix}.${extension}`; } + + /** + * Extract a safe ASCII extension so Persian/spaced names never end up in the S3 object key. + */ + private extractSafeExtension(originalName: string): string { + const basename = originalName.replace(/\\/g, '/').split('/').pop() ?? 'file'; + const lastDot = basename.lastIndexOf('.'); + + if (lastDot > 0 && lastDot < basename.length - 1) { + const ext = basename.slice(lastDot + 1).toLowerCase(); + if (/^[a-z0-9]{1,16}$/.test(ext)) { + return ext; + } + } + + return 'bin'; + } }