import { Elysia, StatusMap, t } from "elysia" import { Database } from "bun:sqlite" import { randomUUIDv7 } from "bun" import staticPlugin from "@elysiajs/static" import { html } from '@elysiajs/html' import crypto from 'crypto' import { DecryptFile, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components" const db = new Database("./db.sqlite") db.exec("PRAGMA foreign_keys = ON") db.exec("PRAGMA journal_mode = WAL2") db.exec("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) STRICT") db.exec("PRAGMA optimize") function encrypt(content: Uint8Array, password: string): Buffer { let iv = crypto.randomBytes(16) const salt = crypto.randomBytes(16) const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha512") let cipher = crypto.createCipheriv('aes-256-cbc', key, iv) let encrypted_content = Buffer.concat([salt, iv, cipher.update(content), cipher.final()]) return encrypted_content } 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") let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) let content = Buffer.concat([decipher.update(encrypted_content.slice(32)), decipher.final()]) return content } const app = new Elysia({ serve: { maxRequestBodySize: 1024 * 1024 * 1024 // 1GB } }) .use(staticPlugin({ assets: "./assets", prefix: "/" })) .use(html()) .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; 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) VALUES (?, ?, ?, ?, ?)", [uuid, body.filename || body.file.name, content, body.filetype, encrypted]) 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*/, password: t.Optional(t.String()), encrypted: t.Optional(t.String()) }), query: t.Object({ withJs: t.Optional(t.Boolean()) }) }) .get("/show/:uuid", async ({ set, params, cookie }) => { const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || 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) }, { params: t.Object({ uuid: t.String() }) }) .get("/show-js/:uuid", async ({ set, params, cookie }) => { const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null if (!result) { set.status = StatusMap["Not Found"] return NotFound() } return ShowFile(result.filename, params.uuid, null, result.filetype) }, { 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(`⚡ Zigbin is running at ${app.server?.hostname}:${app.server?.port} ⚡`)