Fix closure panics
This commit is contained in:
parent
a509ff08b4
commit
beda106f6a
7 changed files with 219 additions and 152 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1051,12 +1051,14 @@ dependencies = [
|
|||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"headers",
|
||||
"http",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"url",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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"]
|
|
@ -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 {
|
||||
|
|
|
@ -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
89
web/src/idb_object.rs
Normal 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);
|
244
web/src/main.rs
244
web/src/main.rs
|
@ -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
13
web/src/util.rs
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue