import { fileTypeFromBuffer } from 'file-type' import hljs from 'highlight.js' import { humanFileSize } from './shared' async function decrypt(encryptedContent: ArrayBuffer, password: string): Promise { const encryptedArray = new Uint8Array(encryptedContent) const salt = encryptedArray.slice(0, 16) const iv = encryptedArray.slice(16, 32) const keyMaterial = await crypto.subtle.importKey( "raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, ["deriveBits", "deriveKey"] ) const key = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: salt, iterations: 100000, hash: "SHA-512" }, keyMaterial, { name: "AES-CBC", length: 256 }, false, ["decrypt"] ) const decryptedContent = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv }, key, encryptedArray.slice(32) ) return decryptedContent } function isValidUTF8(buf: ArrayBuffer) { try { new TextDecoder("utf-8", { fatal: true }).decode(buf) return true } catch (e) { return false } } async function showContent(content: ArrayBuffer, filetype: string) { const filesize = document.getElementById("filesize") as HTMLDivElement | null if (filesize) { filesize.textContent = `size: ${humanFileSize(content.byteLength)}` } const mediaBox = document.getElementById("mediabox") as HTMLDivElement | null if (!mediaBox) { console.error("Missing mediaBox element.") return } let preview: HTMLElement = document.createElement("div") preview.textContent = "This file can't be previewed" if (isValidUTF8(content) && filetype !== "blob") { preview = document.createElement("pre") if (filetype === "none") { preview.textContent = new TextDecoder().decode(content) } else { preview.innerHTML = hljs.highlight(new TextDecoder("utf-8").decode(content), { language: filetype }).value } } else { const filetype = await fileTypeFromBuffer(content) if (filetype) { const blob = new Blob([content], { type: filetype.mime }) const url = URL.createObjectURL(blob) if (filetype.mime.startsWith("audio/")) { preview = document.createElement("audio") preview.setAttribute("controls", "") preview.setAttribute("src", url) } else if (filetype.mime.startsWith("video/")) { preview = document.createElement("video") preview.setAttribute("controls", "") preview.setAttribute("src", url) } else if (filetype.mime.startsWith("image/")) { preview = document.createElement("img") preview.setAttribute("src", url) } } } mediaBox.innerHTML = "" mediaBox.appendChild(preview) } let content: ArrayBuffer let filetype: string let filename: string async function onPasswordSubmit() { const passwordInput = document.getElementById("password") as HTMLInputElement | null const passwordLabel = document.getElementById("password-label") as HTMLLabelElement | null if (!passwordInput || !passwordLabel) { console.error("Missing passwordInput or passwordLabel element.") return } try { content = await decrypt(content, passwordInput.value) } catch (e) { passwordLabel.textContent = "Incorrect password" return } const dialog = document.getElementById("password-dialog") as HTMLDialogElement | null dialog?.close() const downloadForm = document.getElementById("download-form") as HTMLFormElement | null downloadForm?.setAttribute("action", "") downloadForm?.addEventListener("submit", (e) => { e.preventDefault() const blob = new Blob([content]) const url = URL.createObjectURL(blob) const link = document.createElement("a") link.download = filename link.href = url link.click(); }) showContent(content, filetype) } const uuid = window.location.pathname.split("/").filter((x) => x !== "").reverse()[0] fetch("/raw/" + uuid + "?ignore_password=true").then(async (response) => { content = await response.arrayBuffer() const mediaBox = document.getElementById("mediabox") if(mediaBox) { mediaBox.innerHTML = "" } filetype = response.headers.get("filetype") || "none" filename = response.headers.get("filename") || "" if (response.headers.get("encrypted") === "true") { const dialog = document.getElementById("password-dialog") as HTMLDialogElement | null dialog?.showModal() } else { showContent(content, filetype) } }); (window as any).onPasswordSubmit = onPasswordSubmit