136 lines
4.7 KiB
TypeScript
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;
|
|
}
|
|
}
|