ily.li/src/controllers/LinkController.ts

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();
}
}