misskey/packages/backend/src/server/FileServerService.ts

136 lines
4.7 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import type { Config } from '@/config.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { StatusError } from '@/misc/status-error.js';
import type Logger from '@/logger.js';
import { DownloadService } from '@/core/DownloadService.js';
import { InternalStorageService } from '@/core/InternalStorageService.js';
import { FileInfoService } from '@/core/FileInfoService.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import { FileServerDriveHandler } from './file/FileServerDriveHandler.js';
import { FileServerFileResolver } from './file/FileServerFileResolver.js';
import { FileServerProxyHandler } from './file/FileServerProxyHandler.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const assets = `${_dirname}/../../server/file/assets/`;
@Injectable()
export class FileServerService {
private logger: Logger;
private driveHandler: FileServerDriveHandler;
private proxyHandler: FileServerProxyHandler;
private fileResolver: FileServerFileResolver;
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private fileInfoService: FileInfoService,
private downloadService: DownloadService,
private imageProcessingService: ImageProcessingService,
private videoProcessingService: VideoProcessingService,
private internalStorageService: InternalStorageService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('server', 'gray');
this.fileResolver = new FileServerFileResolver(
this.driveFilesRepository,
this.fileInfoService,
this.downloadService,
this.internalStorageService,
);
this.driveHandler = new FileServerDriveHandler(
this.config,
this.fileResolver,
assets,
this.videoProcessingService,
);
this.proxyHandler = new FileServerProxyHandler(
this.config,
this.fileResolver,
assets,
this.imageProcessingService,
);
//this.createServer = this.createServer.bind(this);
}
@bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
if (process.env.NODE_ENV === 'development') {
reply.header('Access-Control-Allow-Origin', '*');
}
done();
});
fastify.register((fastify, options, done) => {
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
fastify.get('/files/app-default.jpg', (request, reply) => {
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
reply.header('Content-Type', 'image/jpeg');
reply.header('Cache-Control', 'max-age=31536000, immutable');
return reply.send(file);
});
fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
return await this.driveHandler.handle(request, reply)
.catch(err => this.errorHandler(request, reply, err));
});
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
return await reply.redirect(`${this.config.url}/files/${request.params.key}`, 301);
});
done();
});
fastify.get<{
Params: { url: string; };
Querystring: { url?: string; };
}>('/proxy/:url*', async (request, reply) => {
return await this.proxyHandler.handle(request, reply)
.catch(err => this.errorHandler(request, reply, err));
});
done();
}
@bindThis
private async errorHandler(request: FastifyRequest<{ Params?: { [x: string]: any }; Querystring?: { [x: string]: any }; }>, reply: FastifyReply, err?: any) {
this.logger.error(`${err}`);
reply.header('Cache-Control', 'max-age=300');
if (request.query && 'fallback' in request.query) {
return reply.sendFile('/dummy.png', assets);
}
if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) {
reply.code(err.statusCode);
return;
}
reply.code(500);
return;
}
}