Fix closure panics

This commit is contained in:
Edward Shen 2021-10-24 16:16:02 -07:00
parent a509ff08b4
commit beda106f6a
Signed by: edward
GPG key ID: 19182661E818369F
7 changed files with 219 additions and 152 deletions

2
Cargo.lock generated
View file

@ -1051,12 +1051,14 @@ dependencies = [
"chacha20poly1305",
"chrono",
"headers",
"http",
"lazy_static",
"rand",
"serde",
"sha2",
"thiserror",
"url",
"web-sys",
]
[[package]]

View file

@ -17,3 +17,9 @@ serde = { version = "1", features = ["derive"] }
sha2 = "0.9"
thiserror = "1"
url = "2"
web-sys = { version = "0.3", features = ["Headers"], optional = true }
http = { version = "0.2", optional = true }
[features]
wasm = ["web-sys", "http"]

View file

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

View file

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
omegaupload-common = { path = "../common" }
omegaupload-common = { path = "../common", features = ["wasm"] }
# Enables wasm support
getrandom = { version = "*", features = ["js"] }

89
web/src/idb_object.rs Normal file
View file

@ -0,0 +1,89 @@
use std::{hint::unreachable_unchecked, marker::PhantomData};
use js_sys::{Array, JsString, Object};
use wasm_bindgen::JsValue;
pub struct IdbObject<State>(Array, PhantomData<State>);
impl<State: IdbObjectState> IdbObject<State> {
fn add_tuple<NextState>(self, key: &str, value: &JsValue) -> IdbObject<NextState> {
let array = Array::new();
array.push(&JsString::from(key));
array.push(value);
self.0.push(&array);
IdbObject(self.0, PhantomData)
}
}
impl From<IdbObject<Ready>> for Object {
fn from(db_object: IdbObject<Ready>) -> Self {
match Object::from_entries(db_object.as_ref()) {
Ok(o) => o,
// SAFETY: IdbObject maintains the invariant that it can eventually
// be constructed into a JS object.
_ => unsafe { panic!() },
}
}
}
impl IdbObject<NeedsType> {
pub fn new() -> Self {
Self(Array::new(), PhantomData)
}
pub fn video(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("video"))
}
pub fn audio(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("audio"))
}
pub fn image(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("image"))
}
pub fn blob(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("blob"))
}
pub fn string(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("string"))
}
}
impl IdbObject<NeedsExpiration> {
pub fn expiration_text(self, expires: &str) -> IdbObject<NeedsData> {
self.add_tuple("expiration", &JsString::from(expires))
}
}
impl IdbObject<NeedsData> {
pub fn data(self, value: &JsValue) -> IdbObject<Ready> {
self.add_tuple("data", value)
}
}
impl IdbObject<Ready> {
pub fn extra(self, key: &str, value: impl Into<JsValue>) -> Self {
self.add_tuple(key, &value.into())
}
}
impl AsRef<JsValue> for IdbObject<Ready> {
fn as_ref(&self) -> &JsValue {
self.0.as_ref()
}
}
macro_rules! impl_idb_object_state {
($($ident:ident),*) => {
pub trait IdbObjectState {}
$(
pub enum $ident {}
impl IdbObjectState for $ident {}
)*
};
}
impl_idb_object_state!(NeedsType, NeedsExpiration, NeedsData, Ready);

View file

@ -1,36 +1,56 @@
#![warn(clippy::nursery, clippy::pedantic)]
use std::marker::PhantomData;
use std::str::FromStr;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use byte_unit::Byte;
use decrypt::DecryptedData;
use gloo_console::log;
use http::header::EXPIRES;
use http::uri::PathAndQuery;
use http::{StatusCode, Uri};
use js_sys::{Array, JsString, Object, Uint8Array};
use js_sys::{JsString, Object, Uint8Array};
use omegaupload_common::{Expiration, PartialParsedUrl};
use reqwasm::http::Request;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{window, Event, IdbDatabase, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode};
use web_sys::{Event, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode, Location, Window};
use crate::decrypt::decrypt;
use crate::idb_object::IdbObject;
use crate::util::as_idb_db;
mod decrypt;
mod idb_object;
mod util;
#[wasm_bindgen]
extern "C" {
fn loadFromDb();
fn createNotFoundUi();
#[wasm_bindgen(js_name = loadFromDb)]
fn load_from_db();
#[wasm_bindgen(js_name = createNotFoundUi)]
fn create_not_found_ui();
}
fn window() -> Window {
web_sys::window().expect("Failed to get a reference of the window")
}
fn location() -> Location {
window().location()
}
fn open_idb() -> Result<IdbOpenDbRequest> {
window()
.indexed_db()
.unwrap()
.context("Missing browser idb impl")?
.open("omegaupload")
.map_err(|_| anyhow!("Failed to open idb"))
}
fn main() {
let window = window().unwrap();
let url = String::from(window.location().to_string());
let url = String::from(location().to_string());
let request_uri = {
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
if let Some(parts) = uri_parts.path_and_query.as_mut() {
@ -39,30 +59,27 @@ fn main() {
Uri::from_parts(uri_parts).unwrap()
};
if window.location().pathname().unwrap() == "/" {
log!(&url);
log!(&request_uri.to_string());
log!(&location().pathname().unwrap());
if location().pathname().unwrap() == "/" {
} else {
spawn_local(async {
a(request_uri, url).await;
if let Err(e) = fetch_resources(request_uri, url).await {
log!(e.to_string());
}
});
}
}
#[allow(clippy::future_not_send)]
async fn a(request_uri: Uri, url: String) -> Result<()> {
async fn fetch_resources(request_uri: Uri, url: String) -> Result<()> {
match Request::get(&request_uri.to_string()).send().await {
Ok(resp) if resp.status() == StatusCode::OK => {
let expires = resp
.headers()
.get(EXPIRES.as_str())
.ok()
.flatten()
.as_deref()
.and_then(|v| Expiration::try_from(v).ok())
.as_ref()
.map_or_else(
|| "This item does not expire.".to_string(),
Expiration::to_string,
);
let expires = Expiration::try_from(resp.headers()).map_or_else(
|_| "This item does not expire.".to_string(),
|expires| expires.to_string(),
);
let data = {
let data_fut = resp
@ -75,44 +92,33 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
Uint8Array::new(&data).to_vec()
};
let info = url
.split_once('#')
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
.unwrap_or_default();
let key = info
.decryption_key
.expect("missing key should be handled in the future");
let nonce = info.nonce.expect("missing nonce be handled in the future");
let result = decrypt(data, key, nonce, None);
let decrypted = match result {
Ok(decrypted) => decrypted,
Err(err) => {
// log!("decryption error: {}", err);
// return Box::new(PasteError(err));
unimplemented!()
}
let (key, nonce) = {
let partial_parsed_url = url
.split_once('#')
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
.unwrap_or_default();
let key = partial_parsed_url
.decryption_key
.context("missing key should be handled in the future")?;
let nonce = partial_parsed_url
.nonce
.context("missing nonce be handled in the future")?;
(key, nonce)
};
let db_open_req = window()
.unwrap()
.indexed_db()
.unwrap()
.unwrap()
.open("omegaupload")
.unwrap();
let decrypted = decrypt(data, key, nonce, None)?;
let db_open_req = open_idb()?;
// On success callback
let on_success = Closure::once(Box::new(move |event: Event| {
let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap();
let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap();
let transaction: IdbObjectStore = db
let transaction: IdbObjectStore = as_idb_db(&event)
.transaction_with_str_and_mode("decrypted data", IdbTransactionMode::Readwrite)
.unwrap()
.object_store("decrypted data")
.unwrap();
log!(line!());
let decrypted_object = match &decrypted {
DecryptedData::String(s) => IdbObject::new()
.string()
@ -146,37 +152,49 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
.data(blob),
};
let db_entry = Object::from_entries(decrypted_object.as_ref()).unwrap();
transaction
log!(line!());
let put_action = transaction
.put_with_key(
&db_entry,
&JsString::from(window().unwrap().location().pathname().unwrap()),
&Object::from(decrypted_object),
&JsString::from(location().pathname().unwrap()),
)
.unwrap()
.set_onsuccess(Some(
Closure::once(Box::new(|| {
log!("success");
loadFromDb();
}) as Box<dyn FnOnce()>)
.into_js_value()
.dyn_ref()
.unwrap(),
));
.unwrap();
put_action.set_onsuccess(Some(
Closure::wrap(Box::new(|| {
log!("success");
load_from_db();
}) as Box<dyn Fn()>)
.into_js_value()
.unchecked_ref(),
));
put_action.set_onerror(Some(
Closure::wrap(Box::new(|e| {
log!(line!());
log!(e);
}) as Box<dyn Fn(Event)>)
.into_js_value()
.unchecked_ref(),
));
}) as Box<dyn FnOnce(Event)>);
db_open_req.set_onsuccess(Some(on_success.into_js_value().dyn_ref().unwrap()));
// On upgrade callback
db_open_req.set_onsuccess(Some(on_success.into_js_value().unchecked_ref()));
db_open_req.set_onerror(Some(
Closure::wrap(Box::new(|e| {
log!(line!());
log!(e);
}) as Box<dyn Fn(Event)>)
.into_js_value()
.unchecked_ref(),
));
let on_upgrade = Closure::wrap(Box::new(move |event: Event| {
let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap();
let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap();
let _obj_store = db.create_object_store("decrypted data").unwrap();
let db = as_idb_db(&event);
let _ = db.create_object_store("decrypted data").unwrap();
}) as Box<dyn FnMut(Event)>);
db_open_req.set_onupgradeneeded(Some(on_upgrade.into_js_value().dyn_ref().unwrap()));
db_open_req.set_onupgradeneeded(Some(on_upgrade.into_js_value().unchecked_ref()));
log!(&db_open_req);
}
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
createNotFoundUi();
create_not_found_ui();
}
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {}
Ok(err) => {}
@ -185,79 +203,3 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
Ok(())
}
struct IdbObject<State>(Array, PhantomData<State>);
impl<State: IdbObjectState> IdbObject<State> {
fn add_tuple<NextState>(self, key: &str, value: &JsValue) -> IdbObject<NextState> {
let array = Array::new();
array.push(&JsString::from(key));
array.push(value);
self.0.push(&array);
IdbObject(self.0, PhantomData)
}
}
impl IdbObject<NeedsType> {
fn new() -> Self {
Self(Array::new(), PhantomData)
}
fn video(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("video"))
}
fn audio(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("audio"))
}
fn image(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("image"))
}
fn blob(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("blob"))
}
fn string(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("string"))
}
}
impl IdbObject<NeedsExpiration> {
fn expiration_text(self, expires: &str) -> IdbObject<NeedsData> {
self.add_tuple("expiration", &JsString::from(expires))
}
}
impl IdbObject<NeedsData> {
fn data(self, value: &JsValue) -> IdbObject<Ready> {
self.add_tuple("data", value)
}
}
impl IdbObject<Ready> {
fn extra(self, key: &str, value: impl Into<JsValue>) -> Self {
self.add_tuple(key, &value.into())
}
}
impl AsRef<JsValue> for IdbObject<Ready> {
fn as_ref(&self) -> &JsValue {
self.0.as_ref()
}
}
trait IdbObjectState {}
enum NeedsType {}
impl IdbObjectState for NeedsType {}
enum NeedsExpiration {}
impl IdbObjectState for NeedsExpiration {}
enum NeedsData {}
impl IdbObjectState for NeedsData {}
enum Ready {}
impl IdbObjectState for Ready {}

13
web/src/util.rs Normal file
View file

@ -0,0 +1,13 @@
use js_sys::Function;
use wasm_bindgen::closure::WasmClosure;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::{Event, IdbDatabase, IdbOpenDbRequest};
/// # Panics
///
/// This will panic if event is not an event from the IDB API.
pub fn as_idb_db(event: &Event) -> IdbDatabase {
let target: IdbOpenDbRequest = event.target().map(JsCast::unchecked_into).unwrap();
target.result().map(JsCast::unchecked_into).unwrap()
}