Skip to content

Commit

Permalink
initial rework
Browse files Browse the repository at this point in the history
  • Loading branch information
inetol committed Feb 18, 2024
1 parent d14e3fe commit ea466ee
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 284 deletions.
6 changes: 3 additions & 3 deletions src/classes/AbstractEndpoint.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Elysia } from 'elysia';
import type { Server } from './Server.ts';

export abstract class AbstractEndpoint {
protected readonly server: Elysia;
protected readonly server: Server;

protected constructor(server: Elysia) {
protected constructor(server: Server) {
this.server = server;
}

Expand Down
223 changes: 95 additions & 128 deletions src/classes/DocumentHandler.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,138 @@
import { unlink } from 'node:fs/promises';
import type { Access, Edit, Exists, Publish, Remove } from '../types/DocumentHandler.ts';
import { ServerVersion } from '../types/Server.ts';
import { ValidatorUtils } from '../utils/ValidatorUtils.ts';
import { JSPError } from './JSPError.ts';
import { ErrorCode } from '../types/JSPError.ts';
import { Server } from './Server.ts';
import { DocumentManager } from './DocumentManager.ts';
import { unlink } from 'node:fs/promises';
import { StringUtils } from '../utils/StringUtils.ts';
import type { IDocumentDataStruct } from '../structures/Structures';
import type {
HandleAccess,
HandleEdit,
HandleExists,
HandleGetDocument,
HandlePublish,
HandleRemove
} from '../types/DocumentHandler.ts';
import { ServerVersion } from '../types/Server.ts';
import { JSPError } from './JSPError.ts';
import { Server } from './Server.ts';
import type { Range } from '../types/Range.ts';
import { ErrorCode, type ErrorType } from '../types/JSPError.ts';
import type { BunFile } from 'bun';

export class DocumentHandler {
public static async handleAccess(
set: any,
{ key, password, raw }: HandleAccess & { raw?: never },
version: ServerVersion
): Promise<
| ErrorType
| {
key: string;
data: string;
url?: string;
expirationTimestamp?: number;
}
>;
public static async handleAccess(
set: any,
{ key, password, raw }: HandleAccess & { raw: true },
version: ServerVersion
): Promise<ErrorType | Response>;
public static async handleAccess(
set: any,
{ key, password, raw = false }: HandleAccess,
version: ServerVersion
): Promise<
| ErrorType
| Response
| {
key: string;
data: string;
url?: string;
expirationTimestamp?: number;
}
> {
const res = await DocumentHandler.handleGetDocument(set, { key: key, password });
if (ValidatorUtils.isJSPError(res)) return res;

if (raw) return new Response(res.rawFileData);

const data = new TextDecoder().decode(res.rawFileData);
private readonly server: Server;
private context: any;

public constructor(server: Server) {
this.server = server;
}

public set setContext(value: any) {
this.context = value;
}

public async access(params: Access, version: ServerVersion) {
this.validateKeyValue(params.key);

const file = await this.validateKeyExistance(params.key);
const document = await DocumentManager.read(file);

if (
document.expirationTimestamp &&
ValidatorUtils.isLengthBetweenLimits(document.expirationTimestamp, 0, Date.now())
) {
await unlink(Server.config.documents.documentPath + params.key);

throw JSPError.send(this.context, 404, JSPError.message[ErrorCode.documentNotFound]);
}

if (document.password && document.password !== params.password) {
throw JSPError.send(this.context, 403, JSPError.message[ErrorCode.documentInvalidPassword]);
}

const data = new TextDecoder().decode(document.rawFileData);

switch (version) {
case ServerVersion.v1:
return { key, data };
return { key: params.key, data };

case ServerVersion.v2:
return {
key,
key: params.key,
data,
url: (Server.config.tls ? 'https://' : 'http://').concat(Server.config.domain + '/') + key,
expirationTimestamp: res.expirationTimestamp ? Number(res.expirationTimestamp) : undefined
url: (Server.config.tls ? 'https://' : 'http://').concat(Server.config.domain + '/') + params.key,
expirationTimestamp: document.expirationTimestamp
};
}
}

public static async handleEdit(set: any, { key, newBody, secret }: HandleEdit) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);
public async edit(params: Edit) {
this.validateKeyValue(params.key);

const file = Bun.file(Server.config.documents.documentPath + key);
const fileExists = await file.exists();

if (!fileExists) return JSPError.send(set, 404, JSPError.message[ErrorCode.documentNotFound]);

const buffer = Buffer.from(newBody as ArrayBuffer);
const buffer = Buffer.from(params.newBody as ArrayBuffer);

if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, Server.config.documents.maxLength))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidLength]);
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentInvalidLength]);

const doc = await DocumentManager.read(file);
const file = await this.validateKeyExistance(params.key);
const document = await DocumentManager.read(file);

if (doc.secret && doc.secret !== secret)
return JSPError.send(set, 403, JSPError.message[ErrorCode.documentInvalidSecret]);
if (document.secret && document.secret !== params.secret) {
throw JSPError.send(this.context, 403, JSPError.message[ErrorCode.documentInvalidSecret]);
}

doc.rawFileData = buffer;
document.rawFileData = buffer;

return {
edited: await DocumentManager.write(Server.config.documents.documentPath + key, doc)
edited: await DocumentManager.write(Server.config.documents.documentPath + params.key, document)
.then(() => true)
.catch(() => false)
};
}

public static async handleExists(set: any, { key }: HandleExists) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);
public async exists(params: Exists): Promise<boolean> {
this.validateKeyValue(params.key);

return await Bun.file(Server.config.documents.documentPath + key).exists();
return Bun.file(Server.config.documents.documentPath + params.key).exists();
}

public static async handlePublish(
set: any,
{ body, selectedSecret, lifetime, password, selectedKeyLength, selectedKey }: HandlePublish,
version: ServerVersion
) {
const buffer = Buffer.from(body as ArrayBuffer);
// TODO: Rework publish
public async publish(params: Publish, version: ServerVersion) {
const buffer = Buffer.from(params.body as ArrayBuffer);

if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, Server.config.documents.maxLength))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidLength]);
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentInvalidLength]);

const secret = selectedSecret || StringUtils.createSecret();
const secret = params.selectedSecret || StringUtils.createSecret();

if (!ValidatorUtils.isStringLengthBetweenLimits(secret || '', 1, 255))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidSecretLength]);
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentInvalidSecretLength]);

if (
selectedKey &&
(!ValidatorUtils.isStringLengthBetweenLimits(selectedKey, 2, 32) ||
!ValidatorUtils.isAlphanumeric(selectedKey))
params.selectedKey &&
(!ValidatorUtils.isStringLengthBetweenLimits(params.selectedKey, 2, 32) ||
!ValidatorUtils.isAlphanumeric(params.selectedKey))
)
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.inputInvalid]);

if (selectedKeyLength && (selectedKeyLength > 32 || selectedKeyLength < 2))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidKeyLength]);
if (params.selectedKeyLength && (params.selectedKeyLength > 32 || params.selectedKeyLength < 2))
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentInvalidKeyLength]);

if (password && !ValidatorUtils.isStringLengthBetweenLimits(password, 0, 255))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidPasswordLength]);
if (params.password && !ValidatorUtils.isStringLengthBetweenLimits(params.password, 0, 255))
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentInvalidPasswordLength]);

lifetime = lifetime ?? Server.config.documents.maxTime;
params.lifetime = params.lifetime ?? Server.config.documents.maxTime;

// Make the document permanent if the value exceeds 5 years
if (lifetime > 157_784_760) lifetime = 0;
if (params.lifetime > 157_784_760) params.lifetime = 0;

const msLifetime = lifetime * 1000;
const msLifetime = params.lifetime * 1000;
const expirationTimestamp = msLifetime > 0 ? BigInt(Date.now() + msLifetime) : undefined;

const newDoc: IDocumentDataStruct = {
rawFileData: buffer,
secret,
expirationTimestamp,
password
password: params.password
};

const key = selectedKey || (await StringUtils.createKey((selectedKeyLength as Range<2, 32>) || 8));
const key =
params.selectedKey || (await StringUtils.createKey((params.selectedKeyLength as Range<2, 32>) || 8));

if (selectedKey && (await StringUtils.keyExists(key)))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentKeyAlreadyExists]);
if (params.selectedKey && (await StringUtils.keyExists(key)))
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.documentKeyAlreadyExists]);

await DocumentManager.write(Server.config.documents.documentPath + key, newDoc);

Expand All @@ -170,49 +150,36 @@ export class DocumentHandler {
}
}

public static async handleRemove(set: any, { key, secret }: HandleRemove) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);
public async remove(params: Remove) {
this.validateKeyValue(params.key);

const file = Bun.file(Server.config.documents.documentPath + key);
const fileExists = await file.exists();

if (!fileExists) return JSPError.send(set, 404, JSPError.message[ErrorCode.documentNotFound]);

const doc = await DocumentManager.read(file);
const file = await this.validateKeyExistance(params.key);
const document = await DocumentManager.read(file);

if (doc.secret && doc.secret !== secret)
return JSPError.send(set, 403, JSPError.message[ErrorCode.documentInvalidSecret]);
if (document.secret && document.secret !== params.secret) {
throw JSPError.send(this.context, 403, JSPError.message[ErrorCode.documentInvalidSecret]);
}

return {
// TODO: Use optimized Bun.unlink when available -> https://bun.sh/docs/api/file-io#writing-files-bun-write
removed: await unlink(Server.config.documents.documentPath + key)
removed: await unlink(Server.config.documents.documentPath + params.key)
.then(() => true)
.catch(() => false)
};
}

private static async handleGetDocument(set: any, { key, password }: HandleGetDocument) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);
private validateKeyValue(key: string): void {
if (!ValidatorUtils.isAlphanumeric(key) || !ValidatorUtils.isStringLengthBetweenLimits(key, 2, 32)) {
throw JSPError.send(this.context, 400, JSPError.message[ErrorCode.inputInvalid]);
}
}

private async validateKeyExistance(key: string): Promise<BunFile> {
const file = Bun.file(Server.config.documents.documentPath + key);
const fileExists = await file.exists();
const doc = fileExists && (await DocumentManager.read(file));

if (!doc || (doc.expirationTimestamp && doc.expirationTimestamp > 0 && doc.expirationTimestamp < Date.now())) {
// TODO: Use optimized Bun.unlink when available -> https://bun.sh/docs/api/file-io#writing-files-bun-write
if (fileExists) await unlink(Server.config.documents.documentPath + key).catch(() => null);

return JSPError.send(set, 404, JSPError.message[ErrorCode.documentNotFound]);
if (!(await file.exists())) {
throw JSPError.send(this.context, 404, JSPError.message[ErrorCode.documentNotFound]);
}

if (doc.password && !password)
return JSPError.send(set, 401, JSPError.message[ErrorCode.documentPasswordNeeded]);

if (doc.password && doc.password !== password)
return JSPError.send(set, 403, JSPError.message[ErrorCode.documentInvalidPassword]);

return doc;
return file;
}
}
2 changes: 1 addition & 1 deletion src/classes/JSPError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class JSPError {
}
};

public static readonly errorSchema = t.Object(
public static readonly schema = t.Object(
{
type: t.String({ description: 'The error type' }),
message: t.String({ description: 'The error message' }),
Expand Down
Loading

0 comments on commit ea466ee

Please sign in to comment.