import { Database } from "bun:sqlite"; import crypto from "node:crypto"; import cron from "@elysiajs/cron"; import { html } from "@elysiajs/html"; import staticPlugin from "@elysiajs/static"; import { randomUUIDv7 } from "bun"; import { Elysia, StatusMap, t } from "elysia"; import { DecryptFile, filetypes, Index, NotFound, SetCookie, ShowFile, WrongPassword, } from "./components"; const db = new Database("./db/db.sqlite"); db.run("PRAGMA foreign_keys = ON"); db.run("PRAGMA journal_mode = WAL2"); db.run( "CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL, delete_at INTEGER) STRICT", ); db.run("PRAGMA optimize"); function encrypt(content: Uint8Array, password: string): Buffer { const iv = crypto.randomBytes(16); const salt = crypto.randomBytes(16); const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha512"); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); return Buffer.concat([salt, iv, cipher.update(content), cipher.final()]); } function decrypt(encrypted_content: Uint8Array, password: string): Buffer { const salt = encrypted_content.slice(0, 16); const iv = encrypted_content.slice(16, 32); const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha512"); const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); return Buffer.concat([ decipher.update(encrypted_content.slice(32)), decipher.final(), ]); } function stringArrayToEnum( arr: readonly T[], ): { [K in T]: K } { return arr.reduce((acc, key) => { acc[key] = key; return acc; }, Object.create(null)); } const app = new Elysia({ serve: { maxRequestBodySize: 1024 * 1024 * 1024, // 1GB }, }) .use(staticPlugin({ assets: "./assets", prefix: "/" })) .use(html()) .use( cron({ name: "delete", pattern: "*/5 * * * * *", run() { db.exec("DELETE FROM files WHERE delete_at < strftime('%s', 'now')"); }, }), ) .get("/", ({ server }) => Index(server?.url.toString() ?? "", false)) .get("/js", ({ server }) => Index(server?.url.toString() ?? "", true)) .post( "/upload", async ({ set, body, query }) => { const uuid = randomUUIDv7(); let content = Buffer.from(await body.file.bytes()); let encrypted = false; let delete_at: number | null = null; if (body.delete_in_minutes) { delete_at = Math.floor(Date.now() / 1000) + Number(body.delete_in_minutes) * 60; } if (body.encrypted === "on") { encrypted = true; } else if (body.password) { content = encrypt(content, body.password); encrypted = true; } db.exec( "INSERT INTO files (uuid, filename, content, filetype, encrypted, delete_at) VALUES (?, ?, ?, ?, ?, ?)", [ uuid, body.filename || body.file.name, content, body.filetype, encrypted, delete_at, ], ); set.status = StatusMap["See Other"]; if (query.withJs) { set.headers.location = `/show-js/${uuid}`; } else { set.headers.location = `/show/${uuid}`; } return `Created with id: ${uuid}`; }, { body: t.Object({ file: t.File(), filename: t.Optional(t.String()), //filetype: t.String()/*todo:verifiy via t.Enum*/, filetype: t.Enum(stringArrayToEnum(filetypes)), password: t.Optional(t.String()), encrypted: t.Optional(t.String()), delete_in_minutes: t.String({ format: "regex", pattern: "(^$|^[0-9]+$)", }), }), query: t.Object({ withJs: t.Optional(t.Boolean()) }), }, ) .get( "/show/:uuid", async ({ set, params, cookie }) => { const result = (db .prepare( "SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?", ) .get(params.uuid) as { filename: string; content: Uint8Array; filetype: string; encrypted: boolean; delete_at: number | null; }) || null; if (!result) { set.status = StatusMap["Not Found"]; return NotFound(); } if (result.encrypted) { const password = cookie.password.value; if (!password) { return DecryptFile(result.filename, params.uuid); } else { try { result.content = decrypt(result.content, password); } catch (_e) { set.status = StatusMap.Forbidden; set.headers["set-cookie"] = [ `password=; Path=/show/${params.uuid}; SameSite=lax; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT`, `password=; Path=/raw/${params.uuid}; SameSite=lax; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT`, ]; return WrongPassword(); } } } return ShowFile( result.filename, params.uuid, result.content, result.filetype, result.delete_at, ); }, { params: t.Object({ uuid: t.String() }), cookie: t.Object({ password: t.Optional(t.String()) }), }, ) .get( "/show-js/:uuid", async ({ set, params }) => { const result = (db .prepare( "SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?", ) .get(params.uuid) as { filename: string; content: Uint8Array; filetype: string; encrypted: boolean; delete_at: number | null; }) || null; if (!result) { set.status = StatusMap["Not Found"]; return NotFound(); } return ShowFile( result.filename, params.uuid, null, result.filetype, result.delete_at, ); }, { params: t.Object({ uuid: t.String() }) }, ) .post( "/set-cookie/:uuid", ({ set, body, params }) => { set.headers["set-cookie"] = [ `password=${body.password}; Path=/show/${params.uuid}; SameSite=lax; HttpOnly`, `password=${body.password}; Path=/raw/${params.uuid}; SameSite=lax; HttpOnly`, ]; set.headers.location = `/show/${params.uuid}`; set.status = StatusMap["See Other"]; return SetCookie(params.uuid); }, { body: t.Object({ password: t.String() }), params: t.Object({ uuid: t.String() }), }, ) .get( "/raw/:uuid", ({ set, params, cookie, query }) => { const result = (db .prepare( "SELECT content, filename, encrypted, filetype FROM files WHERE uuid = ?", ) .get(params.uuid) as { content: Uint8Array; filename: string; encrypted: boolean; filetype: string; }) || null; if (!result) { set.status = StatusMap["Not Found"]; return "File not found"; } if (result.encrypted && query.ignore_password !== "true") { if (!cookie.password.value) { set.status = StatusMap.Unauthorized; return 'This file is encrypted, set the cookie "password" with the correct password to allow the server to decrypt it'; } try { result.content = decrypt(result.content, cookie.password.value); } catch (_e) { set.status = StatusMap.Forbidden; return "Incorrect password"; } } set.headers.encrypted = result.encrypted ? "true" : "false"; set.headers.filetype = result.filetype; set.headers.filename = result.filename; set.headers["content-disposition"] = `inline; filename=${result.filename}`; return result.content; }, { params: t.Object({ uuid: t.String() }), cookie: t.Object({ password: t.Optional(t.String()) }), query: t.Object({ ignore_password: t.Optional(t.String()) }), }, ) .listen(3000); console.log( `⚡ ZBin is running at ${app.server?.hostname}:${app.server?.port} ⚡`, );