client-show.ts
Raw
1import { fileTypeFromBuffer } from "file-type";
2import hljs from "highlight.js";
3import { humanFileSize } from "./shared";
4
5async function decrypt(
6 encryptedContent: ArrayBuffer,
7 password: string,
8): Promise<ArrayBuffer> {
9 const encryptedArray = new Uint8Array(encryptedContent);
10 const salt = encryptedArray.slice(0, 16);
11 const iv = encryptedArray.slice(16, 32);
12 const keyMaterial = await crypto.subtle.importKey(
13 "raw",
14 new TextEncoder().encode(password),
15 { name: "PBKDF2" },
16 false,
17 ["deriveBits", "deriveKey"],
18 );
19 const key = await crypto.subtle.deriveKey(
20 {
21 name: "PBKDF2",
22 salt: salt,
23 iterations: 100000,
24 hash: "SHA-512",
25 },
26 keyMaterial,
27 { name: "AES-CBC", length: 256 },
28 false,
29 ["decrypt"],
30 );
31 const decryptedContent = await crypto.subtle.decrypt(
32 { name: "AES-CBC", iv: iv },
33 key,
34 encryptedArray.slice(32),
35 );
36 return decryptedContent;
37}
38
39function isValidUTF8(buf: ArrayBuffer) {
40 try {
41 new TextDecoder("utf-8", { fatal: true }).decode(buf);
42 return true;
43 } catch (_e) {
44 return false;
45 }
46}
47
48async function showContent(content: ArrayBuffer, filetype: string) {
49 const filesize = document.getElementById("filesize") as HTMLDivElement | null;
50 if (filesize) {
51 filesize.textContent = `size: ${humanFileSize(content.byteLength)}`;
52 }
53
54 const mediaBox = document.getElementById("mediabox") as HTMLDivElement | null;
55 if (!mediaBox) {
56 console.error("Missing mediaBox element.");
57 return;
58 }
59 let preview: HTMLElement = document.createElement("div");
60 preview.textContent = "This file can't be previewed";
61 if (isValidUTF8(content) && filetype !== "blob") {
62 preview = document.createElement("pre");
63 if (filetype === "none") {
64 preview.textContent = new TextDecoder().decode(content);
65 } else {
66 preview.innerHTML = hljs.highlight(
67 new TextDecoder("utf-8").decode(content),
68 { language: filetype },
69 ).value;
70 }
71 } else {
72 const filetype = await fileTypeFromBuffer(content);
73 if (filetype) {
74 const blob = new Blob([content], { type: filetype.mime });
75 const url = URL.createObjectURL(blob);
76 if (filetype.mime.startsWith("audio/")) {
77 preview = document.createElement("audio");
78 preview.setAttribute("controls", "");
79 preview.setAttribute("src", url);
80 } else if (filetype.mime.startsWith("video/")) {
81 preview = document.createElement("video");
82 preview.setAttribute("controls", "");
83 preview.setAttribute("src", url);
84 } else if (filetype.mime.startsWith("image/")) {
85 preview = document.createElement("img");
86 preview.setAttribute("src", url);
87 }
88 }
89 }
90 mediaBox.innerHTML = "";
91 mediaBox.appendChild(preview);
92}
93
94let content: ArrayBuffer;
95let filetype: string;
96let filename: string;
97
98async function onPasswordSubmit() {
99 const passwordInput = document.getElementById(
100 "password",
101 ) as HTMLInputElement | null;
102 const passwordLabel = document.getElementById(
103 "password-label",
104 ) as HTMLLabelElement | null;
105 if (!passwordInput || !passwordLabel) {
106 console.error("Missing passwordInput or passwordLabel element.");
107 return;
108 }
109 try {
110 content = await decrypt(content, passwordInput.value);
111 } catch (_e) {
112 passwordLabel.textContent = "Incorrect password";
113 return;
114 }
115 const dialog = document.getElementById(
116 "password-dialog",
117 ) as HTMLDialogElement | null;
118 dialog?.close();
119 const downloadForm = document.getElementById(
120 "download-form",
121 ) as HTMLFormElement | null;
122 downloadForm?.setAttribute("action", "");
123 downloadForm?.addEventListener("submit", (e) => {
124 e.preventDefault();
125 const blob = new Blob([content]);
126 const url = URL.createObjectURL(blob);
127 const link = document.createElement("a");
128 link.download = filename;
129 link.href = url;
130 link.click();
131 });
132 showContent(content, filetype);
133}
134
135const uuid = window.location.pathname
136 .split("/")
137 .filter((x) => x !== "")
138 .reverse()[0];
139
140fetch(`/raw/${uuid}?ignore_password=true`).then(async (response) => {
141 content = await response.arrayBuffer();
142 const mediaBox = document.getElementById("mediabox");
143 if (mediaBox) {
144 mediaBox.innerHTML = "";
145 }
146 filetype = response.headers.get("filetype") || "none";
147 filename = response.headers.get("filename") || "";
148 if (response.headers.get("encrypted") === "true") {
149 const dialog = document.getElementById(
150 "password-dialog",
151 ) as HTMLDialogElement | null;
152 dialog?.showModal();
153 } else {
154 showContent(content, filetype);
155 }
156});
157
158Object.assign(window, { onPasswordSubmit });
159