diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
| commit | 0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch) | |
| tree | 40874799472fa07416f17b50a398ac33b7771905 /packages/backend/src/server/file | |
| parent | update deps (diff) | |
| download | misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2 misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip | |
refactoring
Resolve #7779
Diffstat (limited to 'packages/backend/src/server/file')
| -rw-r--r-- | packages/backend/src/server/file/assets/bad-egg.png | bin | 0 -> 1676 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/assets/cache-expired.png | bin | 0 -> 6048 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/assets/dummy.png | bin | 0 -> 6285 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/assets/not-an-image.png | bin | 0 -> 2780 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/assets/thumbnail-not-available.png | bin | 0 -> 5705 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/assets/tombstone.png | bin | 0 -> 5028 bytes | |||
| -rw-r--r-- | packages/backend/src/server/file/index.ts | 41 | ||||
| -rw-r--r-- | packages/backend/src/server/file/send-drive-file.ts | 126 |
8 files changed, 167 insertions, 0 deletions
diff --git a/packages/backend/src/server/file/assets/bad-egg.png b/packages/backend/src/server/file/assets/bad-egg.png Binary files differnew file mode 100644 index 0000000000..e96ba0dcc1 --- /dev/null +++ b/packages/backend/src/server/file/assets/bad-egg.png diff --git a/packages/backend/src/server/file/assets/cache-expired.png b/packages/backend/src/server/file/assets/cache-expired.png Binary files differnew file mode 100644 index 0000000000..5d988c502b --- /dev/null +++ b/packages/backend/src/server/file/assets/cache-expired.png diff --git a/packages/backend/src/server/file/assets/dummy.png b/packages/backend/src/server/file/assets/dummy.png Binary files differnew file mode 100644 index 0000000000..39332b0c1b --- /dev/null +++ b/packages/backend/src/server/file/assets/dummy.png diff --git a/packages/backend/src/server/file/assets/not-an-image.png b/packages/backend/src/server/file/assets/not-an-image.png Binary files differnew file mode 100644 index 0000000000..39e4aa0892 --- /dev/null +++ b/packages/backend/src/server/file/assets/not-an-image.png diff --git a/packages/backend/src/server/file/assets/thumbnail-not-available.png b/packages/backend/src/server/file/assets/thumbnail-not-available.png Binary files differnew file mode 100644 index 0000000000..07cad9919c --- /dev/null +++ b/packages/backend/src/server/file/assets/thumbnail-not-available.png diff --git a/packages/backend/src/server/file/assets/tombstone.png b/packages/backend/src/server/file/assets/tombstone.png Binary files differnew file mode 100644 index 0000000000..83159d6b3c --- /dev/null +++ b/packages/backend/src/server/file/assets/tombstone.png diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts new file mode 100644 index 0000000000..a455acd1cf --- /dev/null +++ b/packages/backend/src/server/file/index.ts @@ -0,0 +1,41 @@ +/** + * File Server + */ + +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import * as Koa from 'koa'; +import * as cors from '@koa/cors'; +import * as Router from '@koa/router'; +import sendDriveFile from './send-drive-file'; + +//const _filename = fileURLToPath(import.meta.url); +const _filename = __filename; +const _dirname = dirname(_filename); + +// Init app +const app = new Koa(); +app.use(cors()); +app.use(async (ctx, next) => { + ctx.set('Content-Security-Policy', `default-src 'none'; style-src 'unsafe-inline'`); + await next(); +}); + +// Init router +const router = new Router(); + +router.get('/app-default.jpg', ctx => { + const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); + ctx.body = file; + ctx.set('Content-Type', 'image/jpeg'); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); +}); + +router.get('/:key', sendDriveFile); +router.get('/:key/(.*)', sendDriveFile); + +// Register router +app.use(router.routes()); + +module.exports = app; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts new file mode 100644 index 0000000000..1908c969a5 --- /dev/null +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -0,0 +1,126 @@ +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import * as Koa from 'koa'; +import * as send from 'koa-send'; +import * as rename from 'rename'; +import * as tmp from 'tmp'; +import { serverLogger } from '../index'; +import { contentDisposition } from '@/misc/content-disposition'; +import { DriveFiles } from '@/models/index'; +import { InternalStorage } from '@/services/drive/internal-storage'; +import { downloadUrl } from '@/misc/download-url'; +import { detectType } from '@/misc/get-file-info'; +import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor'; +import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail'; +import { StatusError } from '@/misc/fetch'; + +//const _filename = fileURLToPath(import.meta.url); +const _filename = __filename; +const _dirname = dirname(_filename); + +const assets = `${_dirname}/../../server/file/assets/`; + +const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { + serverLogger.error(e); + ctx.status = 500; + ctx.set('Cache-Control', 'max-age=300'); +}; + +export default async function(ctx: Koa.Context) { + const key = ctx.params.key; + + // Fetch drive file + const file = await DriveFiles.createQueryBuilder('file') + .where('file.accessKey = :accessKey', { accessKey: key }) + .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) + .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) + .getOne(); + + if (file == null) { + ctx.status = 404; + ctx.set('Cache-Control', 'max-age=86400'); + await send(ctx as any, '/dummy.png', { root: assets }); + return; + } + + const isThumbnail = file.thumbnailAccessKey === key; + const isWebpublic = file.webpublicAccessKey === key; + + if (!file.storedInternal) { + if (file.isLink && file.uri) { // 期限切れリモートファイル + const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { + tmp.file((e, path, fd, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + + try { + await downloadUrl(file.uri, path); + + const { mime, ext } = await detectType(path); + + const convertFile = async () => { + if (isThumbnail) { + if (['image/jpeg', 'image/webp'].includes(mime)) { + return await convertToJpeg(path, 498, 280); + } else if (['image/png'].includes(mime)) { + return await convertToPngOrJpeg(path, 498, 280); + } else if (mime.startsWith('video/')) { + return await GenerateVideoThumbnail(path); + } + } + + return { + data: fs.readFileSync(path), + ext, + type: mime, + }; + }; + + const image = await convertFile(); + ctx.body = image.data; + ctx.set('Content-Type', image.type); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + } catch (e) { + serverLogger.error(`${e}`); + + if (e instanceof StatusError && e.isClientError) { + ctx.status = e.statusCode; + ctx.set('Cache-Control', 'max-age=86400'); + } else { + ctx.status = 500; + ctx.set('Cache-Control', 'max-age=300'); + } + } finally { + cleanup(); + } + return; + } + + ctx.status = 204; + ctx.set('Cache-Control', 'max-age=86400'); + return; + } + + if (isThumbnail || isWebpublic) { + const { mime, ext } = await detectType(InternalStorage.resolvePath(key)); + const filename = rename(file.name, { + suffix: isThumbnail ? '-thumb' : '-web', + 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)); + } else { + const readable = InternalStorage.read(file.accessKey!); + readable.on('error', commonReadableHandlerGenerator(ctx)); + ctx.body = readable; + ctx.set('Content-Type', file.type); + ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.set('Content-Disposition', contentDisposition('inline', file.name)); + } +} |