Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ErrorSender among other things #58

Merged
merged 6 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
# (Don't expose the server to the internet, use a reverse proxy)
#PORT=4000

### Documents:
# Maximum document length in bytes [2000000]:number
#DOCUMENTS_MAXLENGTH=2000000

# Maximum document lifetime in seconds [86400]:number
#DOCUMENTS_MAXTIME=86400

### Documentation:
# Enable documentation? [true]:boolean
# (Disabling this will also disable the playground)
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ jobs:
- name: 'Setup dependencies'
run: bun install --production --frozen-lockfile --ignore-scripts

# FIXME: Lint failing
- name: 'Run lint'
continue-on-error: true
run: bun run lint

build:
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
**/node_modules/
documents/
src/structures/
!src/structures/proto/
.prettierrc.json
LICENSE
tsconfig.json
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "bun run build:structures && bun run build:structures:dts && bun run build:bundle",
"build:bundle": "bun build --target bun --format esm --minify --outdir ./dist/ ./src/index.ts",
"build:standalone": "bun build --compile --target bun --format esm --minify --outfile ./dist/jspaste ./src/index.ts",
"build:structures": "bunx pbjs -t static-module -w es6 --no-create --no-typeurl --no-service -o ./src/structures/Structures.js ./src/structures/**/*.proto",
"build:structures": "bunx pbjs -t static-module -w es6 --no-create --no-typeurl --no-service -o ./src/structures/Structures.js ./src/structures/proto/**/*.proto",
"build:structures:dts": "bunx pbts -o ./src/structures/Structures.d.ts ./src/structures/Structures.js",
"fix": "bun run fix:prettier",
"fix:prettier": "bunx prettier . --write",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Elysia } from 'elysia';

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

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

protected abstract load(): Elysia;
protected abstract register(prefix: string): void;
}
11 changes: 0 additions & 11 deletions src/classes/AbstractRoute.ts

This file was deleted.

174 changes: 82 additions & 92 deletions src/classes/DocumentHandler.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,57 @@
import { unlink } from 'node:fs/promises';
import { ValidatorUtils } from '../utils/ValidatorUtils.ts';
import { DocumentManager } from './DocumentManager.ts';
import {
basePath,
defaultDocumentLifetime,
JSPErrorCode,
JSPErrorMessage,
maxDocLength,
type Range,
serverConfig,
ServerVersion
} from '../utils/constants.ts';
import { ErrorSender } from './ErrorSender.ts';
import { StringUtils } from '../utils/StringUtils.ts';
import type { IDocumentDataStruct } from '../structures/Structures';

interface HandleAccess {
errorSender: ErrorSender;
key: string;
password?: string;
raw?: boolean;
}

interface HandleEdit {
errorSender: ErrorSender;
key: string;
newBody: any;
secret?: string;
}

interface HandleExists {
errorSender: ErrorSender;
key: string;
}

interface HandlePublish {
errorSender: ErrorSender;
body: any;
selectedSecret?: string;
lifetime?: number;
password?: string;
selectedKeyLength?: number;
selectedKey?: string;
}

interface HandleRemove {
errorSender: ErrorSender;
key: string;
secret: string;
}

interface HandleGetDocument {
errorSender: ErrorSender;
key: string;
password?: string;
}
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';

export class DocumentHandler {
public static async handleAccess(
{ errorSender, key, password, raw = false }: HandleAccess,
set: any,
{ key, password, raw }: HandleAccess & { raw?: never },
version: ServerVersion
) {
const res = await DocumentHandler.handleGetDocument({ errorSender, key: key, password });
if (ErrorSender.isJSPError(res)) return res;
): 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);

Expand All @@ -76,75 +65,76 @@ export class DocumentHandler {
return {
key,
data,
url: (serverConfig.tls ? 'https://' : 'http://').concat(serverConfig.domain + '/') + key,
url: (Server.config.tls ? 'https://' : 'http://').concat(Server.config.domain + '/') + key,
expirationTimestamp: res.expirationTimestamp ? Number(res.expirationTimestamp) : undefined
};
}
}

public static async handleEdit({ errorSender, key, newBody, secret }: HandleEdit) {
public static async handleEdit(set: any, { key, newBody, secret }: HandleEdit) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.inputInvalid]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);

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

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

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

if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, maxDocLength))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentInvalidLength]);
if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, Server.config.documents.maxLength))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidLength]);

const doc = await DocumentManager.read(file);

if (doc.secret && doc.secret !== secret)
return errorSender.sendError(403, JSPErrorMessage[JSPErrorCode.documentInvalidSecret]);
return JSPError.send(set, 403, JSPError.message[ErrorCode.documentInvalidSecret]);

doc.rawFileData = buffer;

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

public static async handleExists({ errorSender, key }: HandleExists) {
public static async handleExists(set: any, { key }: HandleExists) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.inputInvalid]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);

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

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

if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, maxDocLength))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentInvalidLength]);
if (!ValidatorUtils.isLengthBetweenLimits(buffer, 1, Server.config.documents.maxLength))
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidLength]);

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

if (!ValidatorUtils.isStringLengthBetweenLimits(secret || '', 1, 255))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentInvalidSecretLength]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidSecretLength]);

if (
selectedKey &&
(!ValidatorUtils.isStringLengthBetweenLimits(selectedKey, 2, 32) ||
!ValidatorUtils.isAlphanumeric(selectedKey))
)
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.inputInvalid]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);

if (selectedKeyLength && (selectedKeyLength > 32 || selectedKeyLength < 2))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentInvalidKeyLength]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidKeyLength]);

if (password && !ValidatorUtils.isStringLengthBetweenLimits(password, 0, 255))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentInvalidPasswordLength]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentInvalidPasswordLength]);

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

// Make the document permanent if the value exceeds 5 years
if (lifetime > 157_784_760) lifetime = 0;
Expand All @@ -162,9 +152,9 @@ export class DocumentHandler {
const key = selectedKey || (await StringUtils.createKey((selectedKeyLength as Range<2, 32>) || 8));

if (selectedKey && (await StringUtils.keyExists(key)))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.documentKeyAlreadyExists]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.documentKeyAlreadyExists]);

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

switch (version) {
case ServerVersion.v1:
Expand All @@ -174,54 +164,54 @@ export class DocumentHandler {
return {
key,
secret,
url: (serverConfig.tls ? 'https://' : 'http://').concat(serverConfig.domain + '/') + key,
url: (Server.config.tls ? 'https://' : 'http://').concat(Server.config.domain + '/') + key,
expirationTimestamp: Number(expirationTimestamp ?? 0)
};
}
}

public static async handleRemove({ errorSender, key, secret }: HandleRemove) {
public static async handleRemove(set: any, { key, secret }: HandleRemove) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.inputInvalid]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);

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

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

const doc = await DocumentManager.read(file);

if (doc.secret && doc.secret !== secret)
return errorSender.sendError(403, JSPErrorMessage[JSPErrorCode.documentInvalidSecret]);
return JSPError.send(set, 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(basePath + key)
removed: await unlink(Server.config.documents.documentPath + key)
.then(() => true)
.catch(() => false)
};
}

private static async handleGetDocument({ errorSender, key, password }: HandleGetDocument) {
private static async handleGetDocument(set: any, { key, password }: HandleGetDocument) {
if (!ValidatorUtils.isStringLengthBetweenLimits(key, 1, 255) || !ValidatorUtils.isAlphanumeric(key))
return errorSender.sendError(400, JSPErrorMessage[JSPErrorCode.inputInvalid]);
return JSPError.send(set, 400, JSPError.message[ErrorCode.inputInvalid]);

const file = Bun.file(basePath + key);
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(basePath + key).catch(() => null);
if (fileExists) await unlink(Server.config.documents.documentPath + key).catch(() => null);

return errorSender.sendError(404, JSPErrorMessage[JSPErrorCode.documentNotFound]);
return JSPError.send(set, 404, JSPError.message[ErrorCode.documentNotFound]);
}

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

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

return doc;
}
Expand Down
4 changes: 2 additions & 2 deletions src/classes/DocumentManager.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { BunFile } from 'bun';
import { zlibConfig } from '../utils/constants.ts';
import { DocumentDataStruct, type IDocumentDataStruct } from '../structures/Structures';
import { Server } from './Server.ts';

export class DocumentManager {
public static async read(file: BunFile): Promise<DocumentDataStruct> {
return DocumentDataStruct.decode(Bun.inflateSync(Buffer.from(await file.arrayBuffer())));
}

public static async write(filePath: string, document: IDocumentDataStruct): Promise<void> {
await Bun.write(filePath, Bun.deflateSync(DocumentDataStruct.encode(document).finish(), zlibConfig));
await Bun.write(filePath, Bun.deflateSync(DocumentDataStruct.encode(document).finish(), Server.config.zlib));
}
}
Loading