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); } }); Object.assign(window, { onPasswordSubmit });