From 71de0afdeeaa2399c386f5a2278b40fcc0458120 Mon Sep 17 00:00:00 2001 From: mei23 Date: Thu, 13 Aug 2020 05:16:46 +0900 Subject: [PATCH] =?UTF-8?q?send-drive-file=20=E3=81=AE=E3=83=AA=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=AF=E3=82=BF=E3=81=AA=E3=81=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/file/send-drive-file.ts | 145 ++++++++++++----------------- 1 file changed, 58 insertions(+), 87 deletions(-) diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index 63031ee20457..1dda87a91796 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -1,10 +1,10 @@ import * as Router from '@koa/router'; -import * as send from 'koa-send'; import * as mongodb from 'mongodb'; import * as tmp from 'tmp'; import * as fs from 'fs'; +import * as stream from 'stream'; import * as rename from 'rename'; -import DriveFile, { getDriveFileBucket } from '../../models/drive-file'; +import DriveFile, { getDriveFileBucket, IDriveFile } from '../../models/drive-file'; import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic'; import { serverLogger } from '..'; @@ -16,8 +16,6 @@ import { detectType } from '../../misc/get-file-info'; import { downloadUrl } from '../../misc/download-url'; import { InternalStorage } from '../../services/drive/internal-storage'; -const assets = `${__dirname}/../../server/file/assets/`; - const commonReadableHandlerGenerator = (ctx: Router.RouterContext) => (e: Error): void => { serverLogger.error(e); ctx.status = 500; @@ -25,29 +23,29 @@ const commonReadableHandlerGenerator = (ctx: Router.RouterContext) => (e: Error) }; export default async function(ctx: Router.RouterContext) { - // Validate id + //#region Validate id if (!mongodb.ObjectID.isValid(ctx.params.id)) { - ctx.throw(400, 'incorrect id'); - return; + return await sendError(ctx, 404); } + //#endregion + //#region Fetch driveFile const fileId = new mongodb.ObjectID(ctx.params.id); - - // Fetch drive file const file = await DriveFile.findOne({ _id: fileId }); - if (file == null) { - ctx.status = 404; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx, '/dummy.png', { root: assets }); - return; + return await sendError(ctx, 404); } + //#endregion - // 未保存/期限切れリモートファイル - if (file.metadata.withoutChunks && (file.metadata.isRemote || file.metadata._user && file.metadata._user.host != null)) { + //#region 未保存/期限切れリモートファイル + if (file.metadata?.withoutChunks && (file.metadata.isRemote || file.metadata._user.host != null)) { // urlは過去のバグで張り替え忘れている可能性があるためuriを優先する const url = file.metadata.uri || file.metadata.url; + if (url == null) { + return await sendError(ctx, 404); + } + // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { tmp.file((e, path, fd, cleanup) => { @@ -80,46 +78,32 @@ export default async function(ctx: Router.RouterContext) { }; const file = await convertFile(); - ctx.body = file.data; - ctx.set('Content-Type', file.type); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + return await sendNormal(ctx, file.data, file.type); } catch (e) { serverLogger.error(e); - - if (typeof e == 'number' && e >= 400 && e < 500) { - ctx.status = e; - ctx.set('Cache-Control', 'max-age=86400'); - } else { - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); - } + return await sendError(ctx, typeof e === 'number' && e >= 400 && e < 500 ? e : 500); } finally { cleanup(); } - return; } + //#endregion 未保存/期限切れリモートファイル // 削除済み - if (file.metadata.deletedAt) { - ctx.status = 410; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx, '/tombstone.png', { root: assets }); - return; + if (file.metadata?.deletedAt) { + return await sendError(ctx, 410); } // ローカル保存じゃないのにここに来た - if (file.metadata.withoutChunks) { - ctx.status = 204; - ctx.set('Cache-Control', 'max-age=86400'); - return; + if (file.metadata?.withoutChunks) { + return await sendError(ctx, 404); } - // ファイルシステム格納 + //#region ファイルシステム格納 if (file.metadata?.fileSystem) { const isThumbnail = 'thumbnail' in ctx.query; const isWebpublic = 'web' in ctx.query; - if (isThumbnail || isWebpublic) { + if (isThumbnail || isWebpublic) { // オリジナル以外 const key = isThumbnail ? file.metadata.storageProps?.thumbnailKey : (file.metadata.storageProps?.webpublicKey || file.metadata.storageProps?.key); if (!key) throw 'fs but key not found'; @@ -129,17 +113,11 @@ export default async function(ctx: Router.RouterContext) { extname: ext ? `.${ext}` : undefined }).toString(); - ctx.body = InternalStorage.read(key); - ctx.set('Content-Type', mime); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', filename)); - return; + return await sendNormal(ctx, InternalStorage.read(key), mime, filename); } else { // オリジナル // オリジナルはキーチェック if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) { - ctx.status = 403; - ctx.set('Cache-Control', 'max-age=86400'); - return; + return await sendError(ctx, 403); } const key = file.metadata.storageProps?.key; @@ -147,69 +125,62 @@ export default async function(ctx: Router.RouterContext) { const readable = InternalStorage.read(key); readable.on('error', commonReadableHandlerGenerator(ctx)); - ctx.body = readable; - ctx.set('Content-Type', file.contentType); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', file.filename)); - return; + return await sendNormal(ctx, readable, file.contentType, file.filename); } } + //#endregion ファイルシステム格納 - const sendRaw = async () => { - if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) { - ctx.status = 403; - ctx.set('Cache-Control', 'max-age=86400'); - return; - } - - const bucket = await getDriveFileBucket(); - const readable = bucket.openDownloadStream(fileId); - readable.on('error', commonReadableHandlerGenerator(ctx)); - ctx.body = readable; - ctx.set('Content-Type', file.contentType); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - }; - + //#region DB格納 if ('thumbnail' in ctx.query) { const thumb = await DriveFileThumbnail.findOne({ - 'metadata.originalId': fileId + 'metadata.originalId': file._id }); if (thumb != null) { - ctx.set('Content-Type', thumb.contentType || 'image/jpeg'); - ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}`)); const bucket = await getDriveFileThumbnailBucket(); - ctx.body = bucket.openDownloadStream(thumb._id); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + return await sendNormal(ctx, bucket.openDownloadStream(thumb._id), thumb.contentType || 'image/jpeg', `${rename(file.filename, { suffix: '-thumb', extname: '.jpg' })}`); } else { if (file.contentType.startsWith('image/')) { - ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`)); - await sendRaw(); + return await sendRaw(ctx, file); } else { - ctx.status = 404; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx, '/thumbnail-not-available.png', { root: assets }); + return await sendError(ctx, 404); } } } else if ('web' in ctx.query) { const web = await DriveFileWebpublic.findOne({ - 'metadata.originalId': fileId + 'metadata.originalId': file._id }); if (web != null) { - ctx.set('Content-Type', web.contentType || file.contentType); - ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}`)); - const bucket = await getDriveFileWebpublicBucket(); - ctx.body = bucket.openDownloadStream(web._id); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + return await sendNormal(ctx, bucket.openDownloadStream(web._id), web.contentType || file.contentType, `${rename(file.filename, { suffix: '-web' })}`); } else { - ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`)); - await sendRaw(); + return await sendRaw(ctx, file); } } else { - ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`)); + return await sendRaw(ctx, file); + } +} - await sendRaw(); +async function sendRaw(ctx: Router.RouterContext, file: IDriveFile): Promise { + if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) { + return await sendError(ctx, 403); } + + const bucket = await getDriveFileBucket(); + const readable = bucket.openDownloadStream(file._id); + readable.on('error', commonReadableHandlerGenerator(ctx)); + return await sendNormal(ctx, readable, file.contentType, file.filename); +} + +async function sendNormal(ctx: Router.RouterContext, body: Buffer | stream.Stream, contentType: string, filename?: string): Promise { + ctx.body = body; + ctx.set('Content-Type', contentType); + ctx.set('Cache-Control', 'public, max-age=31536000, immutable'); + if (filename) ctx.set('Content-Disposition', contentDisposition('inline', filename)); +} + +async function sendError(ctx: Router.RouterContext, status: number): Promise { + ctx.status = status; + ctx.set('Cache-Control', 'public, max-age=3600'); }