components.tsx
Raw
1import { Html } from '@elysiajs/html'
2import { PropsWithChildren } from '@kitajs/html'
3import { escapeHTML } from 'bun'
4import { fileTypeFromBuffer } from 'file-type'
5import hljs from 'highlight.js'
6import isValidUTF8 from 'utf-8-validate'
7import { humanFileSize } from './shared'
8
9const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort())
10
11function Template(props: PropsWithChildren<{ css: string }>) {
12 return (
13 <>
14 {'<!doctype html>'}
15 <html lang="en">
16 <head>
17 <title>⚡Zigbin⚡</title>
18 <meta charset="UTF-8" />
19 <meta name="viewport" content="width=device-width, initial-scale=1" />
20 <link rel="stylesheet" href={props.css} />
21 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>" />
22 </head>
23 <body>{props.children}</body>
24 </html>
25 </>
26 )
27}
28
29export function NotFound() {
30 return (
31 <Template css="/default.css">
32 <h1>404</h1>
33 <p>File not found</p>
34 </Template>
35 )
36}
37
38export function Index(hostname: string, withJs: boolean) {
39 let body = (
40 <>
41 {withJs ?
42 <div style="text-align: center">JS version with client-side encryption, for the version without js an with server-side encryption, go <a href="/">here</a>.</div>
43 : <div style="text-align: center">JS-less version with server-side encryption, for the version with client-side encryption, go to <a href="/js">/js</a>.</div>
44 }
45 <hr />
46 <form action={withJs ? "/upload?withJs=true" : "/upload"} id="uploadForm" method="post" enctype="multipart/form-data" onsubmit={withJs ? "uploadFile(); return false" : undefined}>
47 <input type="reset" value="Reset form" />
48 <input required={true} type="file" id="file" name="file" />
49 <label for="filename">File name override:</label>
50 <small>Optional. If empty, will use the uploaded file name.</small>
51 <input type="text" id="filename" name="filename" placeholder="File name" />
52 <label for="filetype">File type:</label>
53 <small>Used for syntax highlighting, if the file is only containing valid UTF-8 characters. Otherwise it will always be stored as a binary blob. Can select <code>blob</code> to force this detection.</small>
54 <select size="10" required={true} id="filetype" name="filetype">
55 {
56 filetypes.map((filetype, i) => (
57 <option selected={i === 0} value={filetype}>{filetype}</option>
58 ))
59 }
60 </select>
61 <label for="password">Password:</label>
62 <small>Optional. If set, file will be encrypted and requires the given password to open.</small>
63 <input type="password" id="password" name="password" placeholder="Password" />
64 <div>
65 <label for="encrypted">Already encrypted:</label>
66 <input type="checkbox" id="encrypted" name="encrypted" />
67 </div>
68 <small>If set, file is already encrypted with an algorithm like in the python file available below. Password is ignored in this case.</small>
69 <input type="submit" value="Upload File" />
70 <hr />
71 <h3>curl guide</h3>
72 You can use curl to upload and download files<br />
73 Upload:
74 <pre>curl {hostname}upload \{"\n"}
75 -F file=@/path/to/file \{"\n"}
76 -F filetype="plaintext" \{"\n"}
77 # optional{"\n"}
78 -F filename="file name" \{"\n"}
79 # optional{"\n"}
80 -F password="mypassword" \{"\n"}
81 # optional{"\n"}
82 -F encrypted="on"</pre>
83 <p>
84 If you upload an already encrypted file, you should set the <code>encrypted="on"</code> parameter and encrypt it in a compatible way, so the frontend/backend can also decrypt it.
85 You can use this python script to encrypt a file locally: <a href="/encrypt.py">encrypt.py</a>
86 </p>
87 <br />
88 Download:
89 <pre>curl {hostname}raw/$uuid?ignore_password=true/false \{"\n"}
90 # optional, if encrypted with password{"\n"}
91 --cookie "password=mypassword"</pre>
92 <p>
93 If ignore_password is set to true, then encrypted files can be downloaded directly (in encrypted form) without supplying the password.
94 </p>
95 </form>
96 </>
97 )
98 if (withJs) {
99 body = (
100 <>
101 <div class="jsguard">JS seems to be disabled, enable it and refesh the page, or go back to<a href="/">here</a> for the JS-less version.</div>
102 <div class="jsguard">
103 {body}
104 </div>
105 <script src="/dist/client-index.js" />
106 </>
107 )
108 }
109 return (
110 <Template css="/default.css">
111 <h1>⚡Zigbin⚡</h1>
112 {body}
113 </Template>
114 )
115}
116
117
118export function DecryptFile(filename: string, uuid: string) {
119 return (
120 <Template css="/default.css">
121 <h1>Encrypted file: {filename}</h1>
122 <form action={`/set-cookie/${uuid}`} method="post">
123 <label for="password">Enter a Password to decrypt the file</label>
124 <input type="password" name="password" />
125 <input type="submit" value="Submit" />
126 </form>
127 </Template>
128 )
129}
130
131export function WrongPassword() {
132 return (
133 <Template css="/default.css">
134 <h1>Incorrect password</h1>
135 <p>Reload page to try again</p>
136 </Template>
137 )
138}
139
140export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string) {
141 //if content is null, this is for the js frontend
142 let preview;
143 if (!content) {
144 preview = <>Please wait for the file to load</>
145 } else {
146 preview = <>This file can't be previewed</>
147 if (isValidUTF8(Buffer.from(content)) && filetype !== "blob") {
148 if (filetype === "none") {
149 preview = <pre>{escapeHTML(new TextDecoder("utf-8").decode(content))}</pre>
150 } else {
151 preview = <pre>{hljs.highlight(new TextDecoder("utf-8").decode(content), { language: filetype }).value}</pre>
152 }
153 } else {
154 const filetype = await fileTypeFromBuffer(content)
155 if (filetype) {
156 const base64Content = `data:${filetype.mime};base64,${Buffer.from(content).toString("base64")}`;
157 if (filetype.mime.startsWith("audio/")) {
158 preview = <audio controls="" src={base64Content} />
159 } else if (filetype.mime.startsWith("video/")) {
160 preview = <video controls src={base64Content} />
161 } else if (filetype.mime.startsWith("image/")) {
162 preview = <img src={base64Content} />
163 }
164 }
165 }
166 }
167
168 return (
169 <Template css="/show.css">
170 <dialog id="password-dialog">
171 <form onsubmit="onPasswordSubmit(); return false">
172 <label id="password-label" for="password">File is encrypted, enter password to decrypt:</label>
173 <input required type="password" id="password" />
174 <input type="submit" value="Submit" />
175 </form>
176 </dialog>
177 <div id="content">
178 <div id="filename">{filename}</div>
179 <div id="mediabox">{preview}</div>
180 </div>
181 <div id="sidebar">
182 <h3>File info</h3>
183 <hr />
184 <p id="filesize">size: {content ? humanFileSize(content.byteLength) : "unknown"}</p>
185 <p>declared type: {filetype}</p>
186 <form id="download-form" action={`/raw/${uuid}`} method="get">
187 <button>Download</button>
188 </form>
189 </div>
190 {content ? "" : <script src="/dist/client-show.js" />}
191 </Template>
192 )
193}
194
195export function SetCookie(uuid: string) {
196 return (
197 <Template css="/default.css">
198 Redirecting you back to <a href={`/show/${uuid}`}>/show/{uuid}</a>
199 </Template>
200 )
201}