124 lines
5.1 KiB
TypeScript
124 lines
5.1 KiB
TypeScript
import config from "config";
|
|
import {NextFunction, Request, Response} from "express";
|
|
import * as fs from "fs";
|
|
import {RequireRequestAuthMiddleware} from "swaf/auth/AuthComponent";
|
|
import Controller from "swaf/Controller";
|
|
import {BadRequestError, NotFoundHttpError, ServerError} from "swaf/HttpError";
|
|
import {logger} from "swaf/Logger";
|
|
import {promisify} from "util";
|
|
|
|
import FileModel from "../models/FileModel.js";
|
|
import URLRedirect from "../models/URLRedirect.js";
|
|
import generateSlug from "../SlugGenerator.js";
|
|
import {encodeRFC5987ValueChars} from "../Utils.js";
|
|
import FileController, {FileUploadFormMiddleware} from "./FileController.js";
|
|
import URLRedirectController from "./URLRedirectController.js";
|
|
|
|
export default class LinkController extends Controller {
|
|
public routes(): void {
|
|
this.post('/', this.postFile, 'post-file', RequireRequestAuthMiddleware, FileUploadFormMiddleware);
|
|
this.delete('/:slug', this.deleteFile, 'delete-file', RequireRequestAuthMiddleware);
|
|
this.get('/:slug', this.getFile, 'get-file');
|
|
this.put('/:slug', this.putFile, 'put-file', RequireRequestAuthMiddleware, FileUploadFormMiddleware);
|
|
|
|
this.post('/', this.addURL, 'post-url', RequireRequestAuthMiddleware);
|
|
this.delete('/:slug', this.deleteURL, 'delete-url', RequireRequestAuthMiddleware);
|
|
this.get('/:slug', this.getURLRedirect, 'get-url');
|
|
this.put('/:slug', this.addURL, 'put-url', RequireRequestAuthMiddleware);
|
|
|
|
this.get(/(.*)/, this.domainFilter);
|
|
}
|
|
|
|
protected async getFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
const file = await FileModel.getBySlug(req.params.slug);
|
|
if (!file) return next();
|
|
if (file.shouldBeDeleted()) {
|
|
await FileController.deleteFile(file);
|
|
return next();
|
|
}
|
|
|
|
const fileName = file.getOrFail('real_name');
|
|
|
|
switch (file.storage_type) {
|
|
case 'local': {
|
|
const stats = await promisify(fs.stat)(file.getOrFail('storage_path'));
|
|
|
|
// If file is bigger than max hotlink size, fallback to express download
|
|
if (stats.size > config.get<number>('max_hotlink_size') * 1024 * 1024) {
|
|
logger.info(`Fallback to express download for file of size ${stats.size}`);
|
|
return res.download(file.getOrFail('storage_path'), fileName);
|
|
}
|
|
|
|
// File type
|
|
const parts = fileName.split('.');
|
|
res.type(parts[parts.length - 1]);
|
|
|
|
// File name
|
|
res.header('Content-Disposition', `inline; filename*=UTF-8''${encodeRFC5987ValueChars(fileName)}`);
|
|
|
|
fs.readFile(file.getOrFail('storage_path'), (err, data) => {
|
|
if (err) return next(err);
|
|
res.send(data);
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
throw new ServerError(`This file cannot be served. Download protocol for ${file.storage_type} storage type not implemented.`);
|
|
}
|
|
}
|
|
|
|
protected async postFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
if (req.body.type !== 'file') return next();
|
|
|
|
await FileController.handleFileUpload(req.body.slug || await generateSlug(10), req, res, true);
|
|
}
|
|
|
|
protected async putFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
if (req.body.type !== 'file') return next();
|
|
const slug = req.params.slug;
|
|
if (!slug) {
|
|
throw new BadRequestError(
|
|
'Cannot put without a slug.',
|
|
'Either provide a slug or use POST method instead.',
|
|
req.url,
|
|
);
|
|
}
|
|
|
|
await FileController.handleFileUpload(slug, req, res, true);
|
|
}
|
|
|
|
protected async deleteFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
return await FileController.deleteFileRoute(req, res, next, true);
|
|
}
|
|
|
|
protected async addURL(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
return await URLRedirectController.addURL(req, res, next, undefined, true);
|
|
}
|
|
|
|
protected async getURLRedirect(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
const url = await URLRedirect.getBySlug(req.params.slug);
|
|
if (!url) return next();
|
|
|
|
res.redirect(url.getOrFail('target_url'), 301);
|
|
}
|
|
|
|
protected async deleteURL(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
const urlRedirect = await URLRedirect.getBySlug(req.params.slug);
|
|
if (!urlRedirect) return next();
|
|
|
|
throw new BadRequestError(
|
|
'Deleting url redirects is disabled for security reasons.',
|
|
'If you still want to disable the redirection, please contact us via email.',
|
|
req.url,
|
|
);
|
|
}
|
|
|
|
protected async domainFilter(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
if (req.hostname !== config.get('domain')) {
|
|
if (req.path === '/') return res.redirect(config.get<string>('public_url'));
|
|
throw new NotFoundHttpError('Page', req.url);
|
|
}
|
|
next();
|
|
}
|
|
}
|