From c2eec272e6b5f77f5f2fff7c9c11bc29ff176407 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Mon, 18 Mar 2019 15:23:45 +0900 Subject: [PATCH] Content-Disposition in ObjectStrage (#4524) * Content-Disposition in ObjectStrage * encode filename --- package.json | 1 + src/misc/content-disposition.ts | 6 ++++++ src/server/file/send-drive-file.ts | 11 ++++++----- src/services/drive/add-file.ts | 15 ++++++++++----- 4 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 src/misc/content-disposition.ts diff --git a/package.json b/package.json index bb48d060c..e5eb6b81f 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "chai-http": "4.2.1", "chalk": "2.4.2", "commander": "2.19.0", + "content-disposition": "0.5.3", "crc-32": "1.2.0", "css-loader": "2.1.1", "cssnano": "4.1.10", diff --git a/src/misc/content-disposition.ts b/src/misc/content-disposition.ts new file mode 100644 index 000000000..9df7ed468 --- /dev/null +++ b/src/misc/content-disposition.ts @@ -0,0 +1,6 @@ +const cd = require('content-disposition'); + +export function contentDisposition(type: 'inline' | 'attachment', filename: string): string { + const fallback = filename.replace(/[^\w.-]/g, '_'); + return cd(filename, { type, fallback }); +} diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index c57648bb7..3f7533b82 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -6,6 +6,7 @@ import DriveFile, { getDriveFileBucket } from '../../models/drive-file'; import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic'; import { serverLogger } from '..'; +import { contentDisposition } from '../../misc/content-disposition'; const assets = `${__dirname}/../../server/file/assets/`; @@ -63,12 +64,12 @@ export default async function(ctx: Koa.BaseContext) { if (thumb != null) { ctx.set('Content-Type', 'image/jpeg'); - ctx.set('Content-Disposition', `filename="${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}"`); + ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}"`)); const bucket = await getDriveFileThumbnailBucket(); ctx.body = bucket.openDownloadStream(thumb._id); } else { if (file.contentType.startsWith('image/')) { - ctx.set('Content-Disposition', `filename="${file.filename}"`); + ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}"`)); await sendRaw(); } else { ctx.status = 404; @@ -82,17 +83,17 @@ export default async function(ctx: Koa.BaseContext) { if (web != null) { ctx.set('Content-Type', file.contentType); - ctx.set('Content-Disposition', `filename="${rename(file.filename, { suffix: '-web' })}"`); + ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}"`)); const bucket = await getDriveFileWebpublicBucket(); ctx.body = bucket.openDownloadStream(web._id); } else { - ctx.set('Content-Disposition', `filename="${file.filename}"`); + ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}"`)); await sendRaw(); } } else { if ('download' in ctx.query) { - ctx.set('Content-Disposition', `attachment; filename="${file.filename}`); + ctx.set('Content-Disposition', contentDisposition('attachment', `${file.filename}`)); } await sendRaw(); diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 0938b1841..5be71bc0a 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -26,6 +26,7 @@ import { driveLogger } from './logger'; import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor'; import Instance from '../../models/instance'; import checkSvg from '../../misc/check-svg'; +import { contentDisposition } from '../../misc/content-disposition'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -69,7 +70,7 @@ async function save(path: string, name: string, type: string, hash: string, size //#region Uploads logger.info(`uploading original: ${key}`); const uploads = [ - upload(key, fs.createReadStream(path), type) + upload(key, fs.createReadStream(path), type, name) ]; if (alts.webpublic) { @@ -77,7 +78,7 @@ async function save(path: string, name: string, type: string, hash: string, size webpublicUrl = `${ baseUrl }/${ webpublicKey }`; logger.info(`uploading webpublic: ${webpublicKey}`); - uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type)); + uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); } if (alts.thumbnail) { @@ -198,13 +199,17 @@ export async function generateAlts(path: string, type: string, generateWeb: bool /** * Upload to ObjectStorage */ -async function upload(key: string, stream: fs.ReadStream | Buffer, type: string) { +async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { const minio = new Minio.Client(config.drive.config); - await minio.putObject(config.drive.bucket, key, stream, null, { + const metadata = { 'Content-Type': type, 'Cache-Control': 'max-age=31536000, immutable' - }); + } as Minio.ItemBucketMetadata; + + if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename); + + await minio.putObject(config.drive.bucket, key, stream, null, metadata); } /**