diff --git a/packages/payload/src/utilities/sanitizeFilename.ts b/packages/payload/src/utilities/sanitizeFilename.ts index a1dea683a98..1d99087014e 100644 --- a/packages/payload/src/utilities/sanitizeFilename.ts +++ b/packages/payload/src/utilities/sanitizeFilename.ts @@ -1,3 +1,5 @@ +import sanitize from 'sanitize-filename' + import { APIError } from '../errors/APIError.js' /** @@ -19,6 +21,11 @@ export function sanitizeFilename(filename: string): string { // eslint-disable-next-line no-control-regex sanitized = sanitized.replace(/[\x00-\x1f\x80-\x9f]/g, '') + const ext = sanitized.includes('.') ? sanitized.split('.').pop()?.split('?')[0] : '' + const baseFilename = sanitize(sanitized.substring(0, sanitized.lastIndexOf('.')) || sanitized) + + sanitized = `${baseFilename}${ext ? `.${ext}` : ''}` + if (!sanitized) { throw new APIError('Invalid filename', 400) } diff --git a/packages/plugin-cloud-storage/src/utilities/getFileKey.spec.ts b/packages/plugin-cloud-storage/src/utilities/getFileKey.spec.ts index 7ee6e8d2ea1..d6419c548e8 100644 --- a/packages/plugin-cloud-storage/src/utilities/getFileKey.spec.ts +++ b/packages/plugin-cloud-storage/src/utilities/getFileKey.spec.ts @@ -180,5 +180,20 @@ describe('getFileKey', () => { }) expect(result.fileKey).not.toContain('..') }) + + it('should sanitize filenames with trailing dots consistently with upload filename generation', () => { + const result = getFileKey({ + collectionPrefix: 'collection', + filename: 'My Photo...png', + useCompositePrefixes: false, + }) + + expect(result).toEqual({ + fileKey: 'collection/My Photo.png', + sanitizedCollectionPrefix: 'collection', + sanitizedDocPrefix: '', + sanitizedFilename: 'My Photo.png', + }) + }) }) })