Skip to content

Commit

Permalink
Merge pull request #136 from waifuvault/add-processupload-exception
Browse files Browse the repository at this point in the history
Add processupload exception
  • Loading branch information
nakedmcse authored Mar 24, 2024
2 parents a5f56ab + d4f9602 commit 81b064d
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 48 deletions.
16 changes: 16 additions & 0 deletions src/engine/IErrorProcessorEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { HttpErrorRenderObj } from "../utils/typeings.js";
import { Exception } from "@tsed/exceptions";

export interface IErrorProcessorEngine<E extends Exception> {
/**
* Process error
* @param obj
*/
process(obj: HttpErrorRenderObj<E>): Promise<boolean>;

/**
* Returns true if this render engine supports the exception thrown by the system
* @param exception
*/
supportsError(exception: Exception): boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ERROR_PROCESSOR_ENGINE } from "../../../model/di/tokens.js";
import { Injectable, ProviderScope } from "@tsed/di";
import { Exception } from "@tsed/exceptions";
import { ProcessUploadException } from "../../../model/exceptions/ProcessUploadException.js";
import type { IErrorProcessorEngine } from "../../IErrorProcessorEngine.js";
import { HttpErrorRenderObj } from "../../../utils/typeings.js";
import { FileUtils } from "../../../utils/Utils.js";
import path from "node:path";

@Injectable({
scope: ProviderScope.SINGLETON,
type: ERROR_PROCESSOR_ENGINE,
})
export class ProcessUploadErrorProcessorEngine implements IErrorProcessorEngine<ProcessUploadException> {
public supportsError(exception: Exception): boolean {
return exception instanceof ProcessUploadException;
}

public async process(obj: HttpErrorRenderObj<ProcessUploadException>): Promise<boolean> {
if (obj.internalError.filePath) {
await FileUtils.deleteFile(path.basename(obj.internalError.filePath), true);
}
return true;
}
}
9 changes: 5 additions & 4 deletions src/engine/impl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
* @file Automatically generated by barrelsby.
*/

export * from "./ErrorProcessors/ProcessUploadErrorProcessorEngine.js";
export * from "./HttpErrorRenderers/AuthenticationErrorRenderEngine.js";
export * from "./HttpErrorRenderers/DefaultHttpRenderEngine.js";
export * from "./HttpErrorRenderers/FileProtectedRenderEngine.js";
export * from "./HttpErrorRenderers/ReCAPTCHALoginExceptionRenderEngine.js";
export * from "./av/ClamAvEngine.js";
export * from "./av/MsDefenderEngine.js";
export * from "./captcha/AbstractCaptchaEngine.js";
export * from "./captcha/HcaptchaEngine.js";
export * from "./captcha/ReCAPTCHAEngine.js";
export * from "./captcha/TurnstileCaptchaEngine.js";
export * from "./HttpErrorRenderers/AuthenticationErrorRenderEngine.js";
export * from "./HttpErrorRenderers/DefaultHttpRenderEngine.js";
export * from "./HttpErrorRenderers/FileProtectedRenderEngine.js";
export * from "./HttpErrorRenderers/ReCAPTCHALoginExceptionRenderEngine.js";
8 changes: 7 additions & 1 deletion src/factory/HttpErrorFactory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Inject, Injectable, ProviderScope } from "@tsed/di";
import { Exception } from "@tsed/exceptions";
import type { IHttpErrorRenderEngine } from "../engine/IHttpErrorRenderEngine.js";
import { HTTP_RENDER_ENGINE } from "../model/di/tokens.js";
import type { IErrorProcessorEngine } from "../engine/IErrorProcessorEngine.js";
import { ERROR_PROCESSOR_ENGINE, HTTP_RENDER_ENGINE } from "../model/di/tokens.js";
import { DefaultHttpRenderEngine } from "../engine/impl/index.js";

@Injectable({
Expand All @@ -12,11 +13,16 @@ export class HttpErrorFactory {

public constructor(
@Inject(HTTP_RENDER_ENGINE) private readonly engines: IHttpErrorRenderEngine<unknown, Exception>[],
@Inject(ERROR_PROCESSOR_ENGINE) private readonly processors: IErrorProcessorEngine<Exception>[],
) {
this.defaultRenderEngine = engines.find(engine => engine instanceof DefaultHttpRenderEngine)!;
}

public getRenderEngine(exception: Exception): IHttpErrorRenderEngine<unknown, Exception> {
return this.engines.find(engine => engine.supportsError(exception)) ?? this.defaultRenderEngine;
}

public getErrorProcessor(exception: Exception): IErrorProcessorEngine<Exception> | null {
return this.processors.find(processor => processor.supportsError(exception)) ?? null;
}
}
4 changes: 4 additions & 0 deletions src/filters/HttpExceptionFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ export class HttpExceptionFilter implements ExceptionFilterMethods<Exception> {

public async catch(exception: Exception, ctx: PlatformContext): Promise<void> {
const renderEngine = this.httpErrorFactory.getRenderEngine(exception);
const processorEngine = this.httpErrorFactory.getErrorProcessor(exception);
const obj: HttpErrorRenderObj<Exception> = {
status: exception.status,
message: exception.message,
internalError: exception,
};
const response = ctx.response;
if (processorEngine) {
await processorEngine.process(obj);
}
const template = await renderEngine.render(obj, response);
response.status(exception.status).body(template);
}
Expand Down
1 change: 1 addition & 0 deletions src/model/di/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const SQLITE_DATA_SOURCE = Symbol.for("SqliteDataSource");
export const HTTP_RENDER_ENGINE = Symbol.for("IHttpErrorRenderEngine");
export const ERROR_PROCESSOR_ENGINE = Symbol.for("IErrorProcessorEngine");
export const AV_ENGINE = Symbol.for("IAvEngine");
export const CAPTCHA_ENGINE = Symbol.for("ICaptchaEngine");
12 changes: 12 additions & 0 deletions src/model/exceptions/ProcessUploadException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { HTTPException } from "@tsed/exceptions";

export class ProcessUploadException extends HTTPException {
public constructor(
status: number,
message?: string,
public filePath?: string,
origin?: Error | string,
) {
super(status, message, origin);
}
}
96 changes: 53 additions & 43 deletions src/services/FileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { FileUploadResponseDto } from "../model/dto/FileUploadResponseDto.js";
import GlobalEnv from "../model/constants/GlobalEnv.js";
import { Logger } from "@tsed/logger";
import type { EntrySettings } from "../utils/typeings.js";
import { BadRequest, InternalServerError, NotFound, UnsupportedMediaType } from "@tsed/exceptions";
import { BadRequest, Exception, InternalServerError, NotFound, UnsupportedMediaType } from "@tsed/exceptions";
import { FileUtils, ObjectUtils } from "../utils/Utils.js";
import TimeUnit from "../model/constants/TimeUnit.js";
import argon2 from "argon2";
Expand All @@ -21,6 +21,7 @@ import { EncryptionService } from "./EncryptionService.js";
import { RecordInfoSocket } from "./socket/RecordInfoSocket.js";
import { EntryModificationDto } from "../model/dto/EntryModificationDto.js";
import { FileUploadParameters } from "../model/rest/FileUploadParameters.js";
import { ProcessUploadException } from "../model/exceptions/ProcessUploadException.js";

@Service()
export class FileService {
Expand All @@ -46,56 +47,65 @@ export class FileService {
{ password, hideFilename, expires }: FileUploadParameters,
secretToken?: string,
): Promise<[FileUploadResponseDto, boolean]> {
const token = crypto.randomUUID();
const uploadEntry = Builder(FileUploadModel).ip(ip).token(token);
const [resourcePath, originalFileName] = await this.determineResourcePathAndFileName(source);
uploadEntry.fileName(path.parse(resourcePath).name);
await this.scanFile(resourcePath);
await this.checkMime(resourcePath);
const mediaType = await this.mimeService.findMimeType(resourcePath);
uploadEntry.mediaType(mediaType);
const fileSize = await FileUtils.getFileSize(path.basename(resourcePath));
uploadEntry.fileSize(fileSize);
const checksum = await this.getFileHash(resourcePath);
let resourcePath: string | undefined;
let originalFileName: string | undefined;
try {
const token = crypto.randomUUID();
const uploadEntry = Builder(FileUploadModel).ip(ip).token(token);
[resourcePath, originalFileName] = await this.determineResourcePathAndFileName(source);
uploadEntry.fileName(path.parse(resourcePath).name);
await this.scanFile(resourcePath);
await this.checkMime(resourcePath);
const mediaType = await this.mimeService.findMimeType(resourcePath);
uploadEntry.mediaType(mediaType);
const fileSize = await FileUtils.getFileSize(path.basename(resourcePath));
uploadEntry.fileSize(fileSize);
const checksum = await this.getFileHash(resourcePath);

const existingFileModel = await this.handleExistingFileModel(resourcePath, checksum, ip);
if (existingFileModel) {
if (existingFileModel.hasExpired) {
await this.processDelete([existingFileModel.token], true);
} else {
return [FileUploadResponseDto.fromModel(existingFileModel, this.baseUrl, true), true];
const existingFileModel = await this.handleExistingFileModel(resourcePath, checksum, ip);
if (existingFileModel) {
if (existingFileModel.hasExpired) {
await this.processDelete([existingFileModel.token], true);
} else {
return [FileUploadResponseDto.fromModel(existingFileModel, this.baseUrl, true), true];
}
}
}

uploadEntry.settings(await this.buildEntrySettings(hideFilename, password));
uploadEntry.settings(await this.buildEntrySettings(hideFilename, password));

const ext = FileUtils.getExtension(originalFileName);
if (ext) {
uploadEntry.fileExtension(ext);
}
uploadEntry.originalFileName(originalFileName);
uploadEntry.checksum(checksum);
if (expires) {
this.calculateCustomExpires(uploadEntry, expires, secretToken);
} else if (secretToken !== this.secret) {
uploadEntry.expires(FileUtils.getExpiresBySize(fileSize));
}
const ext = FileUtils.getExtension(originalFileName);
if (ext) {
uploadEntry.fileExtension(ext);
}
uploadEntry.originalFileName(originalFileName);
uploadEntry.checksum(checksum);
if (expires) {
this.calculateCustomExpires(uploadEntry, expires, secretToken);
} else if (secretToken !== this.secret) {
uploadEntry.expires(FileUtils.getExpiresBySize(fileSize));
}

if (password) {
try {
const didEncrypt = await this.encryptionService.encrypt(resourcePath, password);
uploadEntry.encrypted(didEncrypt !== null);
} catch (e) {
await this.deleteUploadedFile(resourcePath);
this.logger.error(e.message);
throw new InternalServerError(e.message);
if (password) {
try {
const didEncrypt = await this.encryptionService.encrypt(resourcePath, password);
uploadEntry.encrypted(didEncrypt !== null);
} catch (e) {
await this.deleteUploadedFile(resourcePath);
this.logger.error(e.message);
throw new InternalServerError(e.message);
}
}
}
const savedEntry = await this.repo.saveEntry(uploadEntry.build());
const savedEntry = await this.repo.saveEntry(uploadEntry.build());

await this.recordInfoSocket.emit();
await this.recordInfoSocket.emit();

return [FileUploadResponseDto.fromModel(savedEntry, this.baseUrl, true), false];
return [FileUploadResponseDto.fromModel(savedEntry, this.baseUrl, true), false];
} catch (e) {
if (e instanceof Exception) {
throw new ProcessUploadException(e.status, e.message, resourcePath, e);
}
throw e;
}
}

private hashPassword(password: string): Promise<string> {
Expand Down

0 comments on commit 81b064d

Please sign in to comment.