mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-27 08:34:40 +02:00
- Introduced FileServerDriveHandler to manage drive file requests. - Created FileServerFileResolver for resolving file access keys and downloading files. - Added FileServerProxyHandler to handle proxy requests and image processing. - Moved utility functions to FileServerUtils for better organization. - Removed redundant methods from FileServerService, improving readability and maintainability.
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import rename from 'rename';
|
|
import type { Config } from '@/config.js';
|
|
import type { IImageStreamable } from '@/core/ImageProcessingService.js';
|
|
import { contentDisposition } from '@/misc/content-disposition.js';
|
|
import { correctFilename } from '@/misc/correct-filename.js';
|
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
|
import { VideoProcessingService } from '@/core/VideoProcessingService.js';
|
|
import { attachStreamCleanup, handleRangeRequest, setFileResponseHeaders, getSafeContentType, needsCleanup } from './FileServerUtils.js';
|
|
import type { FileServerFileResolver } from './FileServerFileResolver.js';
|
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
|
|
export class FileServerDriveHandler {
|
|
constructor(
|
|
private config: Config,
|
|
private fileResolver: FileServerFileResolver,
|
|
private assetsPath: string,
|
|
private videoProcessingService: VideoProcessingService,
|
|
) {}
|
|
|
|
public async handle(request: FastifyRequest<{ Params: { key: string } }>, reply: FastifyReply) {
|
|
const key = request.params.key;
|
|
const file = await this.fileResolver.resolveFileByAccessKey(key);
|
|
|
|
if (file.kind === 'not-found') {
|
|
reply.code(404);
|
|
reply.header('Cache-Control', 'max-age=86400');
|
|
return reply.sendFile('/dummy.png', this.assetsPath);
|
|
}
|
|
|
|
if (file.kind === 'unavailable') {
|
|
reply.code(204);
|
|
reply.header('Cache-Control', 'max-age=86400');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (file.kind === 'remote') {
|
|
let image: IImageStreamable | null = null;
|
|
|
|
if (file.fileRole === 'thumbnail') {
|
|
if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) {
|
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
|
|
|
const url = new URL(`${this.config.mediaProxy}/static.webp`);
|
|
url.searchParams.set('url', file.url);
|
|
url.searchParams.set('static', '1');
|
|
|
|
file.cleanup();
|
|
return await reply.redirect(url.toString(), 301);
|
|
} else if (file.mime.startsWith('video/')) {
|
|
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
|
if (externalThumbnail) {
|
|
file.cleanup();
|
|
return await reply.redirect(externalThumbnail, 301);
|
|
}
|
|
|
|
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
|
|
}
|
|
}
|
|
|
|
if (file.fileRole === 'webpublic') {
|
|
if (['image/svg+xml'].includes(file.mime)) {
|
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
|
|
|
const url = new URL(`${this.config.mediaProxy}/svg.webp`);
|
|
url.searchParams.set('url', file.url);
|
|
|
|
file.cleanup();
|
|
return await reply.redirect(url.toString(), 301);
|
|
}
|
|
}
|
|
|
|
image ??= {
|
|
data: handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path),
|
|
ext: file.ext,
|
|
type: file.mime,
|
|
};
|
|
|
|
attachStreamCleanup(image.data, file.cleanup);
|
|
|
|
reply.header('Content-Type', getSafeContentType(image.type));
|
|
reply.header('Content-Length', file.file.size);
|
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
|
reply.header('Content-Disposition',
|
|
contentDisposition(
|
|
'inline',
|
|
correctFilename(file.filename, image.ext),
|
|
),
|
|
);
|
|
return image.data;
|
|
}
|
|
|
|
if (file.fileRole !== 'original') {
|
|
const filename = rename(file.filename, {
|
|
suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web',
|
|
extname: file.ext ? `.${file.ext}` : '.unknown',
|
|
}).toString();
|
|
|
|
setFileResponseHeaders(reply, { mime: file.mime, filename });
|
|
return handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path);
|
|
} else {
|
|
setFileResponseHeaders(reply, { mime: file.file.type, filename: file.filename, size: file.file.size });
|
|
return handleRangeRequest(reply, request.headers.range as string | undefined, file.file.size, file.path);
|
|
}
|
|
} catch (e) {
|
|
if (file.kind === 'remote') file.cleanup();
|
|
throw e;
|
|
}
|
|
}
|
|
}
|