diff --git a/common/src/lib.rs b/common/src/lib.rs index 258907c..576c875 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -52,6 +52,8 @@ pub struct ParsedUrl { pub struct PartialParsedUrl { pub decryption_key: Option>, pub needs_password: bool, + pub name: Option, + pub language: Option, } #[cfg(test)] @@ -91,7 +93,7 @@ impl TryFrom<&str> for PartialParsedUrl { return Ok(Self { decryption_key, - needs_password: false, + ..Self::default() }); } @@ -106,6 +108,8 @@ impl TryFrom<&str> for PartialParsedUrl { let mut decryption_key = None; let mut needs_password = false; + let mut name = None; + let mut language = None; for (key, value) in args { match (key, value) { @@ -117,6 +121,8 @@ impl TryFrom<&str> for PartialParsedUrl { ("pw", _) => { needs_password = true; } + ("name", Some(provided_name)) => name = Some(provided_name.to_owned()), + ("lang", Some(provided_lang)) => language = Some(provided_lang.to_owned()), _ => (), } } @@ -124,6 +130,8 @@ impl TryFrom<&str> for PartialParsedUrl { Ok(Self { decryption_key, needs_password, + name, + language, }) } } @@ -159,6 +167,7 @@ impl FromStr for ParsedUrl { let PartialParsedUrl { mut decryption_key, needs_password, + .. } = PartialParsedUrl::try_from(fragment)?; url.set_fragment(None); @@ -338,7 +347,7 @@ mod partial_parsed_url_parsing { DECRYPTION_KEY_STRING.parse(), Ok(PartialParsedUrl { decryption_key: decryption_key(), - needs_password: false + ..Default::default() }) ); } @@ -350,7 +359,7 @@ mod partial_parsed_url_parsing { input.parse(), Ok(PartialParsedUrl { decryption_key: decryption_key(), - needs_password: false + ..Default::default() }) ); } @@ -362,7 +371,34 @@ mod partial_parsed_url_parsing { input.parse(), Ok(PartialParsedUrl { decryption_key: decryption_key(), - needs_password: true + needs_password: true, + ..Default::default() + }) + ); + } + + #[test] + fn with_name() { + let input = "key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=!name:test_file.rs"; + assert_eq!( + input.parse(), + Ok(PartialParsedUrl { + decryption_key: decryption_key(), + name: Some("test_file.rs".to_owned()), + ..Default::default() + }) + ); + } + + #[test] + fn with_lang() { + let input = "key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=!lang:rust"; + assert_eq!( + input.parse(), + Ok(PartialParsedUrl { + decryption_key: decryption_key(), + language: Some("rust".to_owned()), + ..Default::default() }) ); } @@ -374,7 +410,8 @@ mod partial_parsed_url_parsing { input.parse(), Ok(PartialParsedUrl { decryption_key: decryption_key(), - needs_password: true + needs_password: true, + ..Default::default() }) ); } @@ -386,7 +423,7 @@ mod partial_parsed_url_parsing { input.parse(), Ok(PartialParsedUrl { decryption_key: decryption_key(), - needs_password: false + ..Default::default() }) ); } diff --git a/web/index.html b/web/index.html index 6d73838..61ab8d5 100644 --- a/web/index.html +++ b/web/index.html @@ -12,7 +12,7 @@ - + diff --git a/web/src/main.rs b/web/src/main.rs index 31649a0..642be00 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -47,7 +47,7 @@ const DOWNLOAD_SIZE_LIMIT: u128 = n_mib_bytes!(500); #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = loadFromDb)] - pub fn load_from_db(mimetype: JsString); + pub fn load_from_db(mime_type: JsString, name: Option, language: Option); #[wasm_bindgen(js_name = renderMessage)] pub fn render_message(message: JsString); } @@ -88,7 +88,15 @@ fn main() { Uri::from_parts(uri_parts).unwrap() }; - let (key, needs_pw) = { + let ( + key, + PartialParsedUrl { + needs_password, + name, + language, + .. + }, + ) = { let fragment = if let Some(fragment) = url.split_once('#').map(|(_, fragment)| fragment) { if fragment.is_empty() { error!("Key is missing in url; bailing."); @@ -102,7 +110,7 @@ fn main() { return; }; - let partial_parsed_url = match PartialParsedUrl::try_from(fragment) { + let mut partial_parsed_url = match PartialParsedUrl::try_from(fragment) { Ok(partial_parsed_url) => partial_parsed_url, Err(e) => { error!("Failed to parse text fragment; bailing."); @@ -111,7 +119,7 @@ fn main() { } }; - let key = if let Some(key) = partial_parsed_url.decryption_key { + let key = if let Some(key) = partial_parsed_url.decryption_key.take() { key } else { error!("Key is missing in url; bailing."); @@ -119,10 +127,10 @@ fn main() { return; }; - (key, partial_parsed_url.needs_password) + (key, partial_parsed_url) }; - let password = if needs_pw { + let password = if needs_password { loop { let pw = window().prompt_with_message("A password is required to decrypt this paste:"); @@ -150,7 +158,7 @@ fn main() { }; spawn_local(async move { - if let Err(e) = fetch_resources(request_uri, key, password).await { + if let Err(e) = fetch_resources(request_uri, key, password, name, language).await { log!(e.to_string()); } }); @@ -161,6 +169,8 @@ async fn fetch_resources( request_uri: Uri, key: Secret, password: Option>, + name: Option, + language: Option, ) -> Result<()> { match Request::get(&request_uri.to_string()).send().await { Ok(resp) if resp.status() == StatusCode::OK => { @@ -213,7 +223,7 @@ async fn fetch_resources( let db_open_req = open_idb()?; let on_success = Closure::once(Box::new(move |event| { - on_success(&event, &decrypted, mimetype, &expires); + on_success(&event, &decrypted, mimetype, &expires, name, language); })); db_open_req.set_onsuccess(Some(on_success.into_js_value().unchecked_ref())); @@ -245,7 +255,14 @@ async fn fetch_resources( Ok(()) } -fn on_success(event: &Event, decrypted: &DecryptedData, mimetype: MimeType, expires: &str) { +fn on_success( + event: &Event, + decrypted: &DecryptedData, + mimetype: MimeType, + expires: &str, + name: Option, + language: Option, +) { let transaction: IdbObjectStore = as_idb_db(event) .transaction_with_str_and_mode("decrypted data", IdbTransactionMode::Readwrite) .unwrap() @@ -294,7 +311,9 @@ fn on_success(event: &Event, decrypted: &DecryptedData, mimetype: MimeType, expi put_action.set_onsuccess(Some( Closure::once(Box::new(|| { log!("success"); - load_from_db(JsString::from(mimetype.0)); + let name = name.map(JsString::from); + let language = language.map(JsString::from); + load_from_db(JsString::from(mimetype.0), name, language); })) .into_js_value() .unchecked_ref(), diff --git a/web/src/main.scss b/web/src/main.scss index 0bfa35f..e93ae90 100644 --- a/web/src/main.scss +++ b/web/src/main.scss @@ -20,7 +20,7 @@ $padding: 1em; @font-face { font-family: "Mplus Code"; - src: url("./MplusCodeLatin[wdth,wght].ttf") format("truetype"); + src: url("./MPLUSCodeLatin[wdth,wght].ttf") format("truetype"); } body { diff --git a/web/src/main.ts b/web/src/main.ts index 9463e54..cf48e88 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -15,35 +15,38 @@ // along with this program. If not, see . // Exported to main.rs -function loadFromDb(mimetype: string) { - console.log("Got mime type:", mimetype); +function loadFromDb(mimeType: string, name?: string, language?: string) { + console.log("[js] Got name:", name); + console.log("[js] Got language:", language); + console.log("[js] Got mime type:", mimeType); + const dbReq = window.indexedDB.open("omegaupload", 1); dbReq.onsuccess = (evt) => { const db = (evt.target as IDBRequest).result; const obj_store = db .transaction("decrypted data") .objectStore("decrypted data"); - let fetchReq = obj_store.get(window.location.pathname); + const fetchReq = obj_store.get(window.location.pathname); fetchReq.onsuccess = (evt) => { const data = (evt.target as IDBRequest).result; switch (data.type) { case "string": - createStringPasteUi(data, mimetype); + createStringPasteUi(data, mimeType, name, language); break; case "blob": - createBlobPasteUi(data); + createBlobPasteUi(data, name); break; case "image": - createImagePasteUi(data); + createImagePasteUi(data, name); break; case "audio": - createAudioPasteUi(data); + createAudioPasteUi(data, name); break; case "video": - createVideoPasteUi(data); + createVideoPasteUi(data, name); break; case "archive": - createArchivePasteUi(data); + createArchivePasteUi(data, name); break; default: renderMessage("Something went wrong. Try clearing local data."); @@ -71,23 +74,24 @@ function loadFromDb(mimetype: string) { }; } -function createStringPasteUi(data, type: string) { - let bodyEle = document.getElementsByTagName("body")[0]; +function createStringPasteUi(data, mimeType: string, name?: string, lang?: string) { + const bodyEle = document.getElementsByTagName("body")[0]; bodyEle.textContent = ''; - let mainEle = document.createElement("main"); - let preEle = document.createElement("pre"); + const mainEle = document.createElement("main"); + const preEle = document.createElement("pre"); preEle.classList.add("paste"); - let headerEle = document.createElement("p"); + const headerEle = document.createElement("p"); headerEle.classList.add("unselectable"); headerEle.classList.add("centered"); headerEle.textContent = data.expiration; preEle.appendChild(headerEle); - let downloadEle = document.createElement("a"); - downloadEle.href = URL.createObjectURL(new Blob([data.data], { type })); - downloadEle.download = window.location.pathname; + const downloadEle = document.createElement("a"); + downloadEle.href = getObjectUrl([data.data], mimeType); + downloadEle.download = name; + downloadEle.classList.add("hljs-meta"); downloadEle.classList.add("centered"); downloadEle.textContent = "Download file."; @@ -95,43 +99,52 @@ function createStringPasteUi(data, type: string) { preEle.appendChild(document.createElement("hr")); - let codeEle = document.createElement("code"); + const codeEle = document.createElement("code"); codeEle.textContent = data.data; preEle.appendChild(codeEle); mainEle.appendChild(preEle); bodyEle.appendChild(mainEle); + if (!hljs.getLanguage(lang)) { + console.warn(`[js] User provided language (${lang}) is not known. Ignoring.`); + } else { + console.log(`[js] Selecting user provided language ${lang} for highlighting.`); + hljs.configure({ + languages: [lang], + }); + } + hljs.highlightAll(); hljs.initLineNumbersOnLoad(); } -function createBlobPasteUi(data) { - let bodyEle = document.getElementsByTagName("body")[0]; +function createBlobPasteUi(data, name?: string) { + const bodyEle = document.getElementsByTagName("body")[0]; bodyEle.textContent = ''; - let mainEle = document.createElement("main"); + const mainEle = document.createElement("main"); mainEle.classList.add("hljs"); mainEle.classList.add("centered"); mainEle.classList.add("fullscreen"); - let divEle = document.createElement("div"); + const divEle = document.createElement("div"); divEle.classList.add("centered"); - let expirationEle = document.createElement("p"); + const expirationEle = document.createElement("p"); expirationEle.textContent = data.expiration; divEle.appendChild(expirationEle); - let downloadEle = document.createElement("a"); - downloadEle.href = URL.createObjectURL(data.data); - downloadEle.download = window.location.pathname; + const downloadEle = document.createElement("a"); + downloadEle.href = getObjectUrl(data.data, name); + downloadEle.download = name; downloadEle.classList.add("hljs-meta"); downloadEle.textContent = "Download binary file."; divEle.appendChild(downloadEle); mainEle.appendChild(divEle); - let displayAnywayEle = document.createElement("p"); + const displayAnywayEle = document.createElement("p"); displayAnywayEle.classList.add("display-anyways"); displayAnywayEle.classList.add("hljs-comment"); displayAnywayEle.textContent = "Display anyways?"; @@ -145,41 +158,41 @@ function createBlobPasteUi(data) { bodyEle.appendChild(mainEle); } -function createImagePasteUi({ expiration, data, file_size }) { - createMultiMediaPasteUi("img", expiration, data, (downloadEle, imgEle) => { +function createImagePasteUi({ expiration, data, file_size }, name?: string) { + createMultiMediaPasteUi("img", expiration, data, name, (downloadEle, imgEle) => { imgEle.onload = () => { - let width = imgEle.naturalWidth || imgEle.width; - let height = imgEle.naturalHeight || imgEle.height; + const width = imgEle.naturalWidth || imgEle.width; + const height = imgEle.naturalHeight || imgEle.height; downloadEle.textContent = "Download " + file_size + " \u2014 " + width + " by " + height; } }); } -function createAudioPasteUi({ expiration, data }) { - createMultiMediaPasteUi("audio", expiration, data, "Download"); +function createAudioPasteUi({ expiration, data }, name?: string) { + createMultiMediaPasteUi("audio", expiration, data, name, "Download"); } -function createVideoPasteUi({ expiration, data }) { - createMultiMediaPasteUi("video", expiration, data, "Download"); +function createVideoPasteUi({ expiration, data }, name?: string) { + createMultiMediaPasteUi("video", expiration, data, name, "Download"); } -function createArchivePasteUi({ expiration, data, entries }) { - let bodyEle = document.getElementsByTagName("body")[0]; +function createArchivePasteUi({ expiration, data, entries }, name?: string) { + const bodyEle = document.getElementsByTagName("body")[0]; bodyEle.textContent = ''; - let mainEle = document.createElement("main"); + const mainEle = document.createElement("main"); - let sectionEle = document.createElement("section"); + const sectionEle = document.createElement("section"); sectionEle.classList.add("paste"); - let expirationEle = document.createElement("p"); + const expirationEle = document.createElement("p"); expirationEle.textContent = expiration; expirationEle.classList.add("centered"); sectionEle.appendChild(expirationEle); - let downloadEle = document.createElement("a"); - downloadEle.href = URL.createObjectURL(data); - downloadEle.download = window.location.pathname; + const downloadEle = document.createElement("a"); + downloadEle.href = getObjectUrl(data); + downloadEle.download = name; downloadEle.textContent = "Download"; downloadEle.classList.add("hljs-meta"); downloadEle.classList.add("centered"); @@ -187,7 +200,7 @@ function createArchivePasteUi({ expiration, data, entries }) { sectionEle.appendChild(document.createElement("hr")); - let mediaEle = document.createElement("table"); + const mediaEle = document.createElement("table"); mediaEle.classList.add("archive-table"); const tr = mediaEle.insertRow(); tr.classList.add("hljs-title"); @@ -224,30 +237,30 @@ function createArchivePasteUi({ expiration, data, entries }) { bodyEle.appendChild(mainEle); } -function createMultiMediaPasteUi(tag, expiration, data, on_create?) { - let bodyEle = document.getElementsByTagName("body")[0]; +function createMultiMediaPasteUi(tag, expiration, data, name?: string, on_create?) { + const bodyEle = document.getElementsByTagName("body")[0]; bodyEle.textContent = ''; - let mainEle = document.createElement("main"); + const mainEle = document.createElement("main"); mainEle.classList.add("hljs"); mainEle.classList.add("centered"); mainEle.classList.add("fullscreen"); - const downloadLink = URL.createObjectURL(data); + const downloadLink = getObjectUrl(data, name); - let expirationEle = document.createElement("p"); + const expirationEle = document.createElement("p"); expirationEle.textContent = expiration; mainEle.appendChild(expirationEle); - let mediaEle = document.createElement(tag); + const mediaEle = document.createElement(tag); mediaEle.src = downloadLink; mediaEle.controls = true; mainEle.appendChild(mediaEle); - let downloadEle = document.createElement("a"); + const downloadEle = document.createElement("a"); downloadEle.href = downloadLink; - downloadEle.download = window.location.pathname; + downloadEle.download = name; downloadEle.classList.add("hljs-meta"); mainEle.appendChild(downloadEle); @@ -261,9 +274,9 @@ function createMultiMediaPasteUi(tag, expiration, data, on_create?) { } function renderMessage(message) { - let body = document.getElementsByTagName("body")[0]; + const body = document.getElementsByTagName("body")[0]; body.textContent = ''; - let mainEle = document.createElement("main"); + const mainEle = document.createElement("main"); mainEle.classList.add("hljs"); mainEle.classList.add("centered"); mainEle.classList.add("fullscreen"); @@ -271,4 +284,10 @@ function renderMessage(message) { body.appendChild(mainEle); } +function getObjectUrl(data, mimeType?: string) { + return URL.createObjectURL(new Blob(data, { + type: mimeType, + })); +} + window.addEventListener("hashchange", () => location.reload());