diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 8f2e289680..69ad20e398 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -347,6 +347,7 @@ const _CompileController = { } }, + // Keep in sync with the logic for zip files in ProjectDownloadsController _getSafeProjectName(project) { return project.name.replace(/[^\p{L}\p{Nd}]/gu, '_') }, diff --git a/services/web/app/src/Features/Downloads/ProjectDownloadsController.mjs b/services/web/app/src/Features/Downloads/ProjectDownloadsController.mjs index 6bd239b273..36aafda8c9 100644 --- a/services/web/app/src/Features/Downloads/ProjectDownloadsController.mjs +++ b/services/web/app/src/Features/Downloads/ProjectDownloadsController.mjs @@ -18,6 +18,11 @@ import { prepareZipAttachment } from '../../infrastructure/Response.js' let ProjectDownloadsController +// Keep in sync with the logic for PDF files in CompileController +function getSafeProjectName(project) { + return project.name.replace(/[^\p{L}\p{Nd}]/gu, '_') +} + export default ProjectDownloadsController = { downloadProject(req, res, next) { const projectId = req.params.Project_id @@ -41,7 +46,7 @@ export default ProjectDownloadsController = { if (error != null) { return next(error) } - prepareZipAttachment(res, `${project.name}.zip`) + prepareZipAttachment(res, `${getSafeProjectName(project)}.zip`) return stream.pipe(res) } ) diff --git a/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs b/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs index 1e339097fa..f3737df0ab 100644 --- a/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs +++ b/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs @@ -52,7 +52,7 @@ describe('ProjectDownloadsController', function () { .stub() .callsArgWith(1, null, ctx.stream) ctx.req.params = { Project_id: ctx.project_id } - ctx.project_name = 'project name with accĂȘnts' + ctx.project_name = 'project name with accĂȘnts and % special characters' ctx.ProjectGetter.getProject = sinon .stub() .callsArgWith(2, null, { name: ctx.project_name }) @@ -95,9 +95,9 @@ describe('ProjectDownloadsController', function () { .should.equal(true) }) - it('should name the downloaded file after the project', function (ctx) { + it('should name the downloaded file after the project but sanitise special characters', function (ctx) { ctx.res.headers.should.deep.equal({ - 'Content-Disposition': `attachment; filename="${ctx.project_name}.zip"`, + 'Content-Disposition': `attachment; filename="project_name_with_accĂȘnts_and___special_characters.zip"`, 'Content-Type': 'application/zip', 'X-Content-Type-Options': 'nosniff', })