Web workers

master
Edward Shen 2022-08-02 02:10:58 -07:00
parent 600d04eba9
commit 9004c4ed29
Signed by: edward
GPG Key ID: 19182661E818369F
11 changed files with 811 additions and 643 deletions

196
Cargo.lock generated
View File

@ -361,6 +361,22 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.2"
@ -419,6 +435,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]]
name = "filetime"
version = "0.2.17"
@ -453,6 +478,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@ -766,6 +806,19 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -787,6 +840,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.5.0"
@ -941,6 +1003,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "native-tls"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.1"
@ -1053,7 +1133,7 @@ dependencies = [
"js-sys",
"mime_guess",
"omegaupload-common",
"reqwasm",
"reqwest",
"serde",
"tar",
"tree_magic_mini",
@ -1075,6 +1155,51 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.2.0"
@ -1265,6 +1390,15 @@ version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "reqwasm"
version = "0.5.0"
@ -1290,11 +1424,13 @@ dependencies = [
"http-body",
"hyper",
"hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
@ -1303,6 +1439,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tower-service",
"url",
@ -1381,6 +1518,16 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "schannel"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
dependencies = [
"lazy_static",
"windows-sys",
]
[[package]]
name = "sct"
version = "0.7.0"
@ -1400,6 +1547,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "security-framework"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.140"
@ -1571,6 +1741,20 @@ dependencies = [
"xattr",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -1671,6 +1855,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"

View File

@ -294,18 +294,18 @@ impl From<Expiration> for HeaderValue {
pub struct ParseHeaderValueError;
#[cfg(feature = "wasm")]
impl TryFrom<reqwasm::http::Headers> for Expiration {
type Error = ParseHeaderValueError;
// #[cfg(feature = "wasm")]
// impl TryFrom<reqwest::header::HeaderMap<&str>> for Expiration {
// type Error = ParseHeaderValueError;
fn try_from(headers: reqwasm::http::Headers) -> Result<Self, Self::Error> {
headers
.get(http::header::EXPIRES.as_str())
.as_deref()
.and_then(|v| Self::try_from(v).ok())
.ok_or(ParseHeaderValueError)
}
}
// fn try_from(headers: reqwest::header::HeaderMap) -> Result<Self, Self::Error> {
// headers
// .get(http::header::EXPIRES.as_str())
// .as_deref()
// .and_then(|v| Self::try_from(v).ok())
// .ok_or(ParseHeaderValueError)
// }
// }
impl TryFrom<HeaderValue> for Expiration {
type Error = ParseHeaderValueError;

View File

@ -19,7 +19,6 @@ gloo-console = "0.2.1"
http = "0.2.8"
js-sys = "0.3.59"
mime_guess = "2.0.4"
reqwasm = "0.5.0"
tree_magic_mini = { version = "3.0.3", features = ["with-gpl-data"] }
serde = { version = "1.0.140", features = ["derive"] }
wasm-bindgen = { version = "0.2.82", features = ["serde-serialize"] }
@ -27,6 +26,7 @@ wasm-bindgen-futures = "0.4.32"
zip = { version = "0.6.2", default-features = false, features = ["deflate"] }
flate2 = "1.0.24"
tar = "0.4.38"
reqwest = "0.11"
[dependencies.web-sys]
version = "0.3.59"

33
web/src/bg_encrypt.ts Normal file
View File

@ -0,0 +1,33 @@
// OmegaUpload Web Frontend
// Copyright (C) 2021 Edward Shen
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { encrypt_array_buffer } from '../pkg';
interface BgData {
location: string,
data: any
}
addEventListener('message', (event: MessageEvent<BgData>) => {
let { location, data } = event.data;
console.log('[js-worker] Sending data to rust in a worker thread...');
encrypt_array_buffer(location, data).then(url => {
console.log("done buffer");
postMessage(url);
}).catch(e => console.error(e));
})
postMessage("init");

View File

@ -16,6 +16,7 @@
use std::{hint::unreachable_unchecked, marker::PhantomData};
use gloo_console::log;
use js_sys::{Array, JsString, Object};
use wasm_bindgen::JsValue;
@ -37,7 +38,10 @@ impl From<IdbObject<Ready>> for Object {
Ok(o) => o,
// SAFETY: IdbObject maintains the invariant that it can eventually
// be constructed into a JS object.
_ => unsafe { unreachable_unchecked() },
_ => {
log!("IdbObject invariant violated?!");
unsafe { unreachable_unchecked() }
}
}
}
}

View File

@ -1,3 +1,6 @@
import { start } from '../pkg';
import './main.scss';
start();
start();
window.addEventListener("hashchange", () => location.reload());

View File

@ -24,17 +24,16 @@ use decrypt::{DecryptedData, MimeType};
use gloo_console::{error, log};
use http::uri::PathAndQuery;
use http::{StatusCode, Uri};
use js_sys::{Array, JsString, Object, Uint8Array};
use js_sys::{Array, JsString, Object};
use omegaupload_common::base64;
use omegaupload_common::crypto::seal_in_place;
use omegaupload_common::crypto::{Error as CryptoError, Key};
use omegaupload_common::fragment::Builder;
use omegaupload_common::secrecy::{ExposeSecret, Secret, SecretString, SecretVec};
use omegaupload_common::{Expiration, PartialParsedUrl, Url};
use reqwasm::http::Request;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::{spawn_local, JsFuture};
use wasm_bindgen_futures::spawn_local;
use web_sys::{Event, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode, Location, Window};
use crate::decrypt::decrypt;
@ -173,51 +172,36 @@ pub fn start() {
#[wasm_bindgen]
#[allow(clippy::future_not_send)]
pub fn encrypt_string(data: String) {
spawn_local(async move {
if let Err(e) = do_encrypt(data.into_bytes()).await {
log!(format!("[rs] Error encrypting string: {}", e));
}
});
}
#[wasm_bindgen]
#[allow(clippy::future_not_send)]
pub fn encrypt_array_buffer(data: Vec<u8>) {
spawn_local(async move {
if let Err(e) = do_encrypt(data).await {
log!(format!("[rs] Error encrypting array buffer: {}", e));
}
});
pub async fn encrypt_array_buffer(location: String, data: Vec<u8>) -> Result<JsString, JsString> {
do_encrypt(location, data).await.map_err(|e| {
log!(format!("[rs] Error encrypting array buffer: {}", e));
JsString::from(e.to_string())
})
}
#[allow(clippy::future_not_send)]
async fn do_encrypt(mut data: Vec<u8>) -> Result<()> {
async fn do_encrypt(location: String, mut data: Vec<u8>) -> Result<JsString> {
let (data, key) = {
let enc_key = seal_in_place(&mut data, None)?;
let key = SecretString::new(base64::encode(&enc_key.expose_secret().as_ref()));
(data, key)
};
let s: String = location().to_string().into();
let mut url = Url::from_str(&s)?;
let mut url = Url::from_str(&location)?;
let fragment = Builder::new(key);
let js_data = Uint8Array::new_with_length(u32::try_from(data.len()).expect("Data too large"));
js_data.copy_from(&data);
let short_code = Request::post(url.as_ref())
.body(js_data)
let short_code = reqwest::Client::new()
.post(url.as_ref())
.body(data)
.send()
.await?
.text()
.await?;
url.set_path(&short_code);
url.set_fragment(Some(fragment.build().expose_secret()));
location()
.set_href(url.as_ref())
.expect("Unable to navigate to encrypted upload");
Ok(())
Ok(JsString::from(url.as_ref()))
}
#[allow(clippy::future_not_send)]
@ -228,31 +212,26 @@ async fn fetch_resources(
name: Option<String>,
language: Option<String>,
) -> Result<()> {
match Request::get(&request_uri.to_string()).send().await {
match reqwest::Client::new()
.get(&request_uri.to_string())
.send()
.await
{
Ok(resp) if resp.status() == StatusCode::OK => {
let expires = Expiration::try_from(resp.headers()).map_or_else(
|_| "This item does not expire.".to_string(),
|expires| expires.to_string(),
);
let expires = resp
.headers()
.get(http::header::EXPIRES)
.and_then(|header| Expiration::try_from(header).ok())
.map_or_else(
|| "This item does not expire.".to_string(),
|expires| expires.to_string(),
);
let data = {
let data_fut = resp
.as_raw()
.array_buffer()
.expect("to get raw bytes from a response");
let data = match JsFuture::from(data_fut).await {
Ok(data) => data,
Err(e) => {
render_message(
"Network failure: Failed to completely read encryption paste.".into(),
);
bail!(format!(
"JsFuture returned an error while fetching resp buffer: {e:?}",
));
}
};
Uint8Array::new(&data).to_vec()
};
let data = resp
.bytes()
.await
.expect("to get raw bytes from a response")
.to_vec();
if data.len() as u128 > DOWNLOAD_SIZE_LIMIT {
render_message("The paste is too large to decrypt from the web browser. You must use the CLI tool to download this paste.".into());
@ -300,7 +279,7 @@ async fn fetch_resources(
render_message("Invalid paste URL.".into());
}
Ok(err) => {
render_message(err.status_text().into());
render_message(err.status().as_str().into());
}
Err(err) => {
render_message(format!("{err}").into());

View File

@ -40,6 +40,7 @@ hr {
main {
display: inline-flex;
min-width: 100%;
max-width: 100%;
justify-content: center;
}
@ -121,11 +122,9 @@ textarea {
.button {
@extend .hljs;
border: 1px solid white;
border-radius: $padding;
padding: $padding;
margin: $padding;
font-size: 16px;
text-decoration: underline;
border: none;
&:hover {
cursor: pointer;

View File

@ -14,28 +14,29 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import './main.scss';
import ReactDom from 'react-dom';
import React, { useState } from 'react';
import { encrypt_string, encrypt_array_buffer } from '../pkg';
import hljs from 'highlight.js'
(window as any).hljs = hljs;
require('highlightjs-line-numbers.js');
let hljs;
if (typeof WorkerGlobalScope === 'undefined' || !(self instanceof WorkerGlobalScope)) {
hljs = require('highlight.js');
(window as any).hljs = hljs;
require('highlightjs-line-numbers.js');
}
const FileForm = () => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
let file = event.target.files![0];
const fr = new FileReader();
fr.onload = (_e) => {
let data = new Uint8Array(fr.result as ArrayBuffer);
encrypt_array_buffer(data);
encryptMessage(new Uint8Array(fr.result as ArrayBuffer));
}
fr.readAsArrayBuffer(file);
}
return <>
<label className="file-upload" >
<label className="file-upload hljs-meta" >
Select a file
<input type="file" onChange={handleChange} />
</label>
@ -43,12 +44,14 @@ const FileForm = () => {
}
const PasteForm = () => {
const [value, setValue] = useState("");
const [data, setValue] = useState("");
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (value.trim() !== "") {
encrypt_string(value);
if (data.trim() !== "") {
encryptMessage(new TextEncoder().encode(data));
} else {
console.log("[js] Not sending string because it was empty.");
}
}
@ -56,14 +59,28 @@ const PasteForm = () => {
<form className='hljs centered' onSubmit={handleSubmit}>
<textarea
placeholder="すいちゃんは~ 今日もかわい~!!"
value={value}
value={data}
onChange={(e) => setValue(e.target.value)}
/>
<input className="text-upload" type="submit" value="submit" />
<input className="text-upload hljs-meta" type="submit" value="Submit" />
</form>
)
}
function encryptMessage(data: Uint8Array) {
const worker = new Worker(new URL('./bg_encrypt.ts', import.meta.url));
worker.onmessage = (event: MessageEvent<string>) => {
console.log(event);
if (event.data === 'init') {
console.log("[js] Sending data to worker");
const message = { data, location: window.location.toString() };
worker.postMessage(message, [message.data.buffer]);
} else {
window.location.assign(event.data);
}
}
}
function createUploadUi() {
const html = <main className='hljs centered fullscreen'>
<FileForm />
@ -149,7 +166,7 @@ function loadFromDb(mimeType: string, name?: string, language?: string) {
};
}
function createStringPasteUi(data, mimeType: string, name: string, lang?: string) {
function createStringPasteUi(data, mimeType: string, name: string, lang?: string, skipSyntaxHighlight?: boolean) {
const html = <main>
<pre className='paste'>
<p className='unselectable centered'>{data.expiration}</p>
@ -165,6 +182,10 @@ function createStringPasteUi(data, mimeType: string, name: string, lang?: string
ReactDom.render(html, document.body);
if (skipSyntaxHighlight) {
return;
}
let languages = undefined;
if (!hljs.getLanguage(lang)) {
@ -203,6 +224,7 @@ function createStringPasteUi(data, mimeType: string, name: string, lang?: string
}
// If we still haven't set languages here, then we're leaving it up to the
// library
if (!languages) {
console.log("[js] Deferring to hljs inference for syntax highlighting.");
} else {
@ -226,7 +248,7 @@ function createBlobPasteUi(data, name: string) {
<p className='display-anyways hljs-comment' onClick={() => {
data.data.text().then(text => {
data.data = text;
createStringPasteUi(data, "application/octet-stream", name);
createStringPasteUi(data, "application/octet-stream", name, undefined, true);
})
}}>Display anyways?</p>
</main>;
@ -337,6 +359,5 @@ function getObjectUrl(data, mimeType?: string) {
return URL.createObjectURL(new Blob([data], { type: mimeType }));
}
window.addEventListener("hashchange", () => location.reload());
export { renderMessage, createUploadUi, loadFromDb };

@ -1 +1 @@
Subproject commit 8690be3625964d9992e7be4bc3e1a61a80161cc6
Subproject commit a1268635894c5ee23dfdece570418ca07b66c3fc

1039
yarn.lock

File diff suppressed because it is too large Load Diff