🐛 Fix URL encoded filename on download (#675)

* fix: #665 url encoded filename on download
* ♻️ back: refactor content-disposition attachment filename encoding (#665)

---------

Co-authored-by: Eric Doughty-Papassideris <edoughtypapassideris@linagora.com>
This commit is contained in:
Montassar Ghanmy
2024-10-03 13:43:33 +01:00
committed by GitHub
parent 7daac6cb8b
commit 32276edb5d
3 changed files with 18 additions and 5 deletions
@@ -24,6 +24,7 @@ import {
import { DriveFileDTO } from "../dto/drive-file-dto";
import { DriveFileDTOBuilder } from "../../services/drive-file-dto-builder";
import config from "config";
import { formatAttachmentContentDispositionHeader } from "../../../files/utils";
export class DocumentsController {
private driveFileDTOBuilder = new DriveFileDTOBuilder();
@@ -510,9 +511,9 @@ export class DocumentsController {
return response;
} else if (archiveOrFile.file) {
const data = archiveOrFile.file;
const filename = encodeURIComponent(data.name.replace(/[^\p{L}0-9 _.-]/gu, ""));
response.header("Content-disposition", `attachment; filename="${filename}"`);
response.header("Content-Disposition", formatAttachmentContentDispositionHeader(data.name));
if (data.size) response.header("Content-Length", data.size);
response.type(data.mime);
return response.send(data.file);
@@ -565,7 +566,10 @@ export class DocumentsController {
try {
const archive = await globalResolver.services.documents.documents.createZip(ids, context);
reply.raw.setHeader("content-disposition", 'attachment; filename="twake_drive.zip"');
reply.raw.setHeader(
"content-disposition",
formatAttachmentContentDispositionHeader("twake_drive.zip"),
);
archive.on("finish", () => {
reply.status(200);
@@ -22,3 +22,12 @@ export const fileIsMedia = (file: Partial<File>): boolean => {
file.metadata?.mime?.startsWith("image/")
);
};
/**
* Generate RFC 5987 UTF-8 encoding compliant header value for `Content-Disposition`
* to make a browser download a reponse to a file with the provided name
*/
export const formatAttachmentContentDispositionHeader = (filename: string) => {
const encoded = encodeURIComponent(filename.replace(/[^\p{L}0-9 _.-]/gu, ""));
return `attachment; filename="${encoded}"; filename*=UTF-8''${encoded}`;
};
@@ -5,6 +5,7 @@ import { CompanyExecutionContext } from "../types";
import { UploadOptions } from "../../types";
import { PublicFile } from "../../entities/file";
import gr from "../../../global-resolver";
import { formatAttachmentContentDispositionHeader } from "../../utils";
export class FileController {
async save(
@@ -47,9 +48,8 @@ export class FileController {
const params = request.params;
try {
const data = await gr.services.files.download(params.id, context);
const filename = data.name.replace(/[^a-zA-Z0-9 -_.]/g, "");
response.header("Content-Disposition", formatAttachmentContentDispositionHeader(data.name));
response.header("Content-disposition", `attachment; filename="${filename}"`);
if (data.size) response.header("Content-Length", data.size);
response.type(data.mime);
return response.send(data.file);