omegaupload/web/src/main.rs

492 lines
16 KiB
Rust
Raw Normal View History

2021-10-21 18:35:54 -07:00
#![warn(clippy::nursery, clippy::pedantic)]
2021-10-22 19:15:23 -07:00
use std::fmt::{Debug, Display, Formatter};
2021-10-17 14:15:29 -07:00
use std::str::FromStr;
2021-10-23 10:10:55 -07:00
use std::sync::Arc;
2021-10-17 14:15:29 -07:00
2021-10-22 19:15:23 -07:00
use anyhow::{anyhow, bail, Context};
2021-10-23 10:10:55 -07:00
use byte_unit::Byte;
2021-10-19 18:48:32 -07:00
use bytes::Bytes;
2021-10-23 10:10:55 -07:00
use decrypt::DecryptionAgent;
2021-10-19 02:18:33 -07:00
use downcast_rs::{impl_downcast, Downcast};
2021-10-22 19:15:23 -07:00
use gloo_console::log;
2021-10-19 23:53:22 -07:00
use http::header::EXPIRES;
2021-10-19 18:48:32 -07:00
use http::uri::PathAndQuery;
use http::{StatusCode, Uri};
2021-10-23 10:10:55 -07:00
use image::GenericImageView;
2021-10-22 19:15:23 -07:00
use js_sys::{Array, ArrayBuffer, Uint8Array};
2021-10-23 10:10:55 -07:00
use omegaupload_common::crypto::{open, open_in_place, Key, Nonce};
2021-10-19 23:53:22 -07:00
use omegaupload_common::{Expiration, PartialParsedUrl};
2021-10-23 10:10:55 -07:00
use reqwasm::http::Request;
2021-10-22 19:15:23 -07:00
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::TextDecoder;
use web_sys::{Blob, Url};
2021-10-23 10:10:55 -07:00
use yew::agent::Dispatcher;
2021-10-17 14:15:29 -07:00
use yew::utils::window;
2021-10-23 10:10:55 -07:00
use yew::worker::Agent;
use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, ShouldRender};
use yew::{Dispatched, Properties};
2021-10-17 14:15:29 -07:00
use yew_router::router::Router;
use yew_router::Switch;
2021-10-19 18:48:32 -07:00
use yewtil::future::LinkFuture;
2021-10-17 14:15:29 -07:00
2021-10-23 10:10:55 -07:00
use crate::decrypt::{DecryptionAgentMessage, DecryptionParams, PasteContext};
mod decrypt;
2021-10-17 14:15:29 -07:00
fn main() {
yew::start_app::<App>();
}
struct App;
impl Component for App {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Self
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<Router<Route> render={Router::render(render_route)} />
}
}
}
#[derive(Clone, Debug, Switch)]
enum Route {
#[to = "/!"]
Index,
#[rest]
Path(String),
}
2021-10-21 18:35:54 -07:00
#[allow(clippy::needless_pass_by_value)]
2021-10-17 14:15:29 -07:00
fn render_route(route: Route) -> Html {
match route {
Route::Index => html! {
<main>
<p>{ "Hello world" }</p>
</main>
},
Route::Path(_) => html! {
<main>
<Paste/>
</main>
},
}
}
2021-10-23 10:10:55 -07:00
pub struct Paste {
2021-10-19 02:18:33 -07:00
state: Box<dyn PasteState>,
2021-10-23 10:10:55 -07:00
_listener: Box<dyn Bridge<DecryptionAgent>>,
2021-10-17 14:15:29 -07:00
}
impl Component for Paste {
2021-10-19 02:18:33 -07:00
type Message = Box<dyn PasteState>;
2021-10-17 14:15:29 -07:00
2021-10-16 09:50:11 -07:00
type Properties = ();
2021-10-17 14:15:29 -07:00
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
let url = String::from(window().location().to_string());
let request_uri = {
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
2021-10-21 18:35:54 -07:00
if let Some(parts) = uri_parts.path_and_query.as_mut() {
*parts = PathAndQuery::from_str(&format!("/api{}", parts.path())).unwrap();
}
2021-10-17 14:15:29 -07:00
Uri::from_parts(uri_parts).unwrap()
};
2021-10-23 10:10:55 -07:00
let handle_decryption_result = |res: <DecryptionAgent as Agent>::Output| {
log!("Got decryption result back!");
match res {
Ok((decrypted, context)) => {
Box::new(PasteComplete::new(context.link, decrypted, context.expires))
as Box<dyn PasteState>
}
Err(e) => Box::new(PasteError(anyhow!("wtf"))) as Box<dyn PasteState>,
}
};
let listener = DecryptionAgent::bridge(link.callback(handle_decryption_result));
2021-10-19 02:18:33 -07:00
let link_clone = link.clone();
2021-10-19 18:48:32 -07:00
link.send_future(async move {
2021-10-23 10:10:55 -07:00
match Request::get(&request_uri.to_string()).send().await {
2021-10-19 18:48:32 -07:00
Ok(resp) if resp.status() == StatusCode::OK => {
2021-10-19 23:53:22 -07:00
let expires = resp
.headers()
2021-10-23 10:10:55 -07:00
.get(EXPIRES.as_str())
.ok()
.flatten()
.as_deref()
2021-10-19 23:53:22 -07:00
.and_then(|v| Expiration::try_from(v).ok());
2021-10-23 10:10:55 -07:00
let data = {
Uint8Array::new(
&JsFuture::from(resp.as_raw().array_buffer().unwrap())
.await
.unwrap(),
)
.to_vec()
2021-10-19 18:48:32 -07:00
};
2021-10-19 02:18:33 -07:00
2021-10-22 19:15:23 -07:00
let info = url
.split_once('#')
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
.unwrap_or_default();
let key = info.decryption_key.unwrap();
let nonce = info.nonce.unwrap();
2021-10-23 10:10:55 -07:00
let mut decryption_agent = DecryptionAgent::dispatcher();
let params = DecryptionParams::new(data, key, nonce, None);
let ctx = PasteContext::new(link_clone, expires);
decryption_agent.send(DecryptionAgentMessage::new(ctx, params));
Box::new(PasteDecrypting(decryption_agent)) as Box<dyn PasteState>
2021-10-19 02:18:33 -07:00
}
2021-10-21 18:35:54 -07:00
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
Box::new(PasteNotFound) as Box<dyn PasteState>
}
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {
Box::new(PasteBadRequest) as Box<dyn PasteState>
2021-10-19 02:18:33 -07:00
}
2021-10-21 18:35:54 -07:00
Ok(err) => {
Box::new(PasteError(anyhow!("Got {}.", err.status()))) as Box<dyn PasteState>
}
Err(err) => Box::new(PasteError(anyhow!("Got {}.", err))) as Box<dyn PasteState>,
2021-10-19 18:48:32 -07:00
}
});
2021-10-23 10:10:55 -07:00
2021-10-19 18:48:32 -07:00
Self {
state: Box::new(PasteLoading),
2021-10-23 10:10:55 -07:00
_listener: listener,
2021-10-17 14:15:29 -07:00
}
2021-10-16 09:50:11 -07:00
}
2021-10-17 14:15:29 -07:00
2021-10-19 02:18:33 -07:00
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.state = msg;
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
if self.state.is::<PasteLoading>() {
return html! {
<p>{ "loading" }</p>
};
}
2021-10-23 10:10:55 -07:00
if self.state.is::<PasteDecrypting>() {
return html! {
"decrypting"
};
}
2021-10-19 02:18:33 -07:00
if self.state.is::<PasteNotFound>() {
return html! {
2021-10-22 19:15:23 -07:00
<section class={"hljs centered"}>
2021-10-21 18:35:54 -07:00
<p>{ "Either the paste has been burned or one never existed." }</p>
</section>
};
}
if self.state.is::<PasteBadRequest>() {
return html! {
2021-10-22 19:15:23 -07:00
<section class={"hljs centered"}>
2021-10-21 18:35:54 -07:00
<p>{ "Bad Request. Is this a valid paste URL?" }</p>
</section>
2021-10-19 02:18:33 -07:00
};
}
if let Some(error) = self.state.downcast_ref::<PasteError>() {
return html! {
2021-10-22 19:15:23 -07:00
<section class={"hljs centered"}><p>{ error.0.to_string() }</p></section>
2021-10-19 02:18:33 -07:00
};
}
if let Some(partial_paste) = self.state.downcast_ref::<PastePartial>() {
return partial_paste.view();
}
if let Some(paste) = self.state.downcast_ref::<PasteComplete>() {
return paste.view();
}
html! {
"An internal error occurred: client is in unknown state!"
}
}
}
struct PasteError(anyhow::Error);
2021-10-23 10:10:55 -07:00
#[derive(Debug)]
2021-10-19 02:18:33 -07:00
struct PastePartial {
parent: ComponentLink<Paste>,
2021-10-23 10:10:55 -07:00
dispatcher: Dispatcher<DecryptionAgent>,
2021-10-19 23:53:22 -07:00
data: Bytes,
expires: Option<Expiration>,
2021-10-19 02:18:33 -07:00
key: Option<Key>,
nonce: Option<Nonce>,
password: Option<Key>,
needs_pw: bool,
}
#[derive(Properties, Clone)]
struct PasteComplete {
2021-10-22 19:15:23 -07:00
parent: ComponentLink<Paste>,
decrypted: DecryptedData,
2021-10-19 23:53:22 -07:00
expires: Option<Expiration>,
2021-10-22 19:15:23 -07:00
}
#[derive(Clone)]
2021-10-23 10:10:55 -07:00
pub enum DecryptedData {
String(Arc<String>),
Blob(Arc<Blob>),
Image(Arc<Blob>, (u32, u32), usize),
2021-10-19 02:18:33 -07:00
}
2021-10-23 10:10:55 -07:00
pub trait PasteState: Downcast {}
2021-10-19 02:18:33 -07:00
impl_downcast!(PasteState);
2021-10-21 18:35:54 -07:00
2021-10-19 02:18:33 -07:00
impl PasteState for PasteError {}
impl PasteState for PastePartial {}
impl PasteState for PasteComplete {}
2021-10-21 18:35:54 -07:00
macro_rules! impl_paste_type_state {
(
$($state:ident),* $(,)?
) => {
$(
struct $state;
impl PasteState for $state {}
)*
};
}
impl_paste_type_state!(PasteLoading, PasteNotFound, PasteBadRequest);
2021-10-23 10:10:55 -07:00
struct PasteDecrypting(Dispatcher<DecryptionAgent>);
impl PasteState for PasteDecrypting {}
2021-10-19 02:18:33 -07:00
impl PastePartial {
fn new(
2021-10-19 23:53:22 -07:00
data: Bytes,
expires: Option<Expiration>,
2021-10-21 18:35:54 -07:00
partial_parsed_url: &PartialParsedUrl,
2021-10-19 02:18:33 -07:00
parent: ComponentLink<Paste>,
) -> Self {
Self {
parent,
2021-10-23 10:10:55 -07:00
dispatcher: DecryptionAgent::dispatcher(),
2021-10-19 23:53:22 -07:00
data,
expires,
2021-10-19 02:18:33 -07:00
key: partial_parsed_url.decryption_key,
nonce: partial_parsed_url.nonce,
password: None,
needs_pw: partial_parsed_url.needs_password,
}
}
}
enum PartialPasteMessage {
DecryptionKey(Key),
Nonce(Nonce),
Password(Key),
}
impl Component for PastePartial {
type Message = PartialPasteMessage;
2021-10-23 10:10:55 -07:00
type Properties = ();
2021-10-19 02:18:33 -07:00
2021-10-23 10:10:55 -07:00
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
unimplemented!()
2021-10-19 02:18:33 -07:00
}
2021-10-16 09:50:11 -07:00
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
2021-10-19 02:18:33 -07:00
PartialPasteMessage::DecryptionKey(key) => self.key = Some(key),
PartialPasteMessage::Nonce(nonce) => self.nonce = Some(nonce),
PartialPasteMessage::Password(password) => self.password = Some(password),
2021-10-16 09:50:11 -07:00
}
2021-10-19 02:18:33 -07:00
2021-10-19 23:53:22 -07:00
match (self.key, self.nonce, self.password) {
(Some(key), Some(nonce), maybe_password)
if (self.needs_pw && maybe_password.is_some())
|| (!self.needs_pw && maybe_password.is_none()) =>
{
2021-10-22 19:15:23 -07:00
let parent = self.parent.clone();
2021-10-23 10:10:55 -07:00
let mut data = self.data.to_vec();
2021-10-21 18:35:54 -07:00
let expires = self.expires;
2021-10-22 19:15:23 -07:00
2021-10-23 10:10:55 -07:00
// self.dispatcher.send((data, key, nonce, maybe_password));
todo!()
2021-10-19 02:18:33 -07:00
}
_ => (),
}
2021-10-21 18:35:54 -07:00
// parent should re-render so this element should be dropped; no point
// in saying this needs to be re-rendered.
2021-10-19 02:18:33 -07:00
false
2021-10-16 09:50:11 -07:00
}
2021-10-17 14:15:29 -07:00
2021-10-16 09:50:11 -07:00
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
2021-10-17 14:15:29 -07:00
2021-10-16 09:50:11 -07:00
fn view(&self) -> Html {
2021-10-19 02:18:33 -07:00
html! {
"got partial data"
}
}
}
2021-10-22 19:15:23 -07:00
#[derive(Debug)]
2021-10-23 10:10:55 -07:00
pub enum PasteCompleteConstructionError {
2021-10-22 19:15:23 -07:00
StageOneFailure,
StageTwoFailure,
}
impl std::error::Error for PasteCompleteConstructionError {}
impl Display for PasteCompleteConstructionError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PasteCompleteConstructionError::StageOneFailure => {
write!(f, "Failed to decrypt stage one.")
}
PasteCompleteConstructionError::StageTwoFailure => {
write!(f, "Failed to decrypt stage two.")
}
2021-10-19 02:18:33 -07:00
}
}
}
impl PasteComplete {
2021-10-19 23:53:22 -07:00
fn new(
2021-10-22 19:15:23 -07:00
parent: ComponentLink<Paste>,
decrypted: DecryptedData,
2021-10-19 23:53:22 -07:00
expires: Option<Expiration>,
) -> Self {
2021-10-19 02:18:33 -07:00
Self {
2021-10-22 19:15:23 -07:00
parent,
decrypted,
2021-10-19 23:53:22 -07:00
expires,
2021-10-19 02:18:33 -07:00
}
}
fn view(&self) -> Html {
2021-10-22 19:15:23 -07:00
match &self.decrypted {
DecryptedData::String(decrypted) => html! {
html! {
<>
2021-10-23 10:10:55 -07:00
<pre class="paste">
<header class="unselectable">
{
self.expires.as_ref().map(ToString::to_string).unwrap_or_else(||
"This paste will not expire.".to_string()
)
}
</header>
<hr />
<code>{decrypted}</code>
</pre>
<script>
{"
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
"}
</script>
2021-10-22 19:15:23 -07:00
</>
}
},
DecryptedData::Blob(decrypted) => {
let object_url = Url::create_object_url_with_blob(decrypted);
if let Ok(object_url) = object_url {
let file_name = window().location().pathname().unwrap_or("file".to_string());
let mut cloned = self.clone();
2021-10-23 10:10:55 -07:00
let decrypted_ref = Arc::clone(&decrypted);
2021-10-22 19:15:23 -07:00
let display_anyways_callback =
self.parent.callback_future_once(|_| async move {
let array_buffer: ArrayBuffer =
2021-10-23 10:10:55 -07:00
JsFuture::from(decrypted_ref.array_buffer())
2021-10-22 19:15:23 -07:00
.await
.unwrap()
.dyn_into()
.unwrap();
let decoder = TextDecoder::new().unwrap();
cloned.decrypted = decoder
.decode_with_buffer_source(&array_buffer)
2021-10-23 10:10:55 -07:00
.map(Arc::new)
2021-10-22 19:15:23 -07:00
.map(DecryptedData::String)
.unwrap();
Box::new(cloned) as Box<dyn PasteState>
});
html! {
2021-10-23 10:10:55 -07:00
<section class="hljs fullscreen centered">
2021-10-22 19:15:23 -07:00
<div class="centered">
<p>{ "Found a binary file." }</p>
2021-10-23 10:10:55 -07:00
<a href=object_url download=file_name class="hljs-meta">{"Download"}</a>
2021-10-22 19:15:23 -07:00
</div>
<p onclick=display_anyways_callback class="display-anyways hljs-meta">{ "Display anyways?" }</p>
</section>
}
} else {
// This branch really shouldn't happen, but might as well
// try and give a user-friendly error message.
html! {
<section class="hljs centered">
<p>{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }</p>
</section>
2021-10-19 23:53:22 -07:00
}
2021-10-22 19:15:23 -07:00
}
}
2021-10-23 10:10:55 -07:00
DecryptedData::Image(decrypted, (width, height), size) => {
2021-10-22 19:15:23 -07:00
let object_url = Url::create_object_url_with_blob(decrypted);
if let Ok(object_url) = object_url {
let file_name = window().location().pathname().unwrap_or("file".to_string());
html! {
2021-10-23 10:10:55 -07:00
<section class="hljs fullscreen centered">
<img src=object_url.clone() />
<a href=object_url download=file_name class="hljs-meta">
{
format!(
"Download {} \u{2014} {} by {}",
Byte::from_bytes(*size as u128).get_appropriate_unit(true),
width, height,
)
}
</a>
2021-10-22 19:15:23 -07:00
</section>
}
} else {
// This branch really shouldn't happen, but might as well
// try and give a user-friendly error message.
html! {
2021-10-23 10:10:55 -07:00
<section class="hljs fullscreen centered">
2021-10-22 19:15:23 -07:00
<p>{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }</p>
</section>
}
}
2021-10-17 14:15:29 -07:00
}
2021-10-16 09:50:11 -07:00
}
}
}