Partial work

This commit is contained in:
Edward Shen 2021-10-23 10:10:55 -07:00
parent 17dd44c8cc
commit 89aeb6ba2a
Signed by: edward
GPG key ID: 19182661E818369F
10 changed files with 386 additions and 136 deletions

64
Cargo.lock generated
View file

@ -188,6 +188,15 @@ version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
[[package]]
name = "byte-unit"
version = "4.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f"
dependencies = [
"utf8-width",
]
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.7.2" version = "1.7.2"
@ -469,6 +478,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.17" version = "0.3.17"
@ -476,6 +500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@ -484,6 +509,17 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
[[package]]
name = "futures-executor"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.17" version = "0.3.17"
@ -522,9 +558,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro", "futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
"pin-project-lite", "pin-project-lite",
@ -1135,6 +1173,7 @@ name = "omegaupload-web"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"byte-unit",
"bytes", "bytes",
"downcast-rs", "downcast-rs",
"getrandom", "getrandom",
@ -1143,7 +1182,7 @@ dependencies = [
"image", "image",
"js-sys", "js-sys",
"omegaupload-common", "omegaupload-common",
"reqwest", "reqwasm",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -1394,6 +1433,23 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "reqwasm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34bf31941fb867ae9386a4b443b388e6713574944e6517136ee21a6a93cf996"
dependencies = [
"anyhow",
"futures",
"js-sys",
"serde",
"serde_json",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.5" version = "0.11.5"
@ -1995,6 +2051,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"

8
Trunk.toml Normal file
View file

@ -0,0 +1,8 @@
[build]
target = "web/index.html"
release = true
[[proxy]]
backend = "http://localhost:8081"
rewrite = "/api/"

View file

@ -6,7 +6,7 @@ use std::io::{Read, Write};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use atty::Stream; use atty::Stream;
use clap::Clap; use clap::Clap;
use omegaupload_common::crypto::{gen_key_nonce, open, seal, Key}; use omegaupload_common::crypto::{gen_key_nonce, open_in_place, seal_in_place, Key};
use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url}; use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url};
use reqwest::blocking::Client; use reqwest::blocking::Client;
use reqwest::header::EXPIRES; use reqwest::header::EXPIRES;
@ -53,13 +53,13 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
let (enc_key, nonce) = gen_key_nonce(); let (enc_key, nonce) = gen_key_nonce();
let mut container = Vec::new(); let mut container = Vec::new();
std::io::stdin().read_to_end(&mut container)?; std::io::stdin().read_to_end(&mut container)?;
let mut enc = seal_in_place(&mut container, &nonce, &enc_key)
seal(&container, &nonce, &enc_key).map_err(|_| anyhow!("Failed to encrypt data"))?; .map_err(|_| anyhow!("Failed to encrypt data"))?;
let pw_used = if let Some(password) = password { let pw_used = if let Some(password) = password {
let pw_hash = hash(password.expose_secret().as_bytes()); let pw_hash = hash(password.expose_secret().as_bytes());
let pw_key = Key::from_slice(pw_hash.as_ref()); let pw_key = Key::from_slice(pw_hash.as_ref());
enc = seal(&enc, &nonce.increment(), pw_key) seal_in_place(&mut container, &nonce.increment(), pw_key)
.map_err(|_| anyhow!("Failed to encrypt data"))?; .map_err(|_| anyhow!("Failed to encrypt data"))?;
true true
} else { } else {
@ -69,7 +69,7 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
let key = base64::encode(&enc_key); let key = base64::encode(&enc_key);
let nonce = base64::encode(&nonce); let nonce = base64::encode(&nonce);
(enc, nonce, key, pw_used) (container, nonce, key, pw_used)
}; };
let res = Client::new() let res = Client::new()
@ -131,11 +131,11 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
let pw_hash = hash(input.as_bytes()); let pw_hash = hash(input.as_bytes());
let pw_key = Key::from_slice(pw_hash.as_ref()); let pw_key = Key::from_slice(pw_hash.as_ref());
data = open(&data, &url.nonce.increment(), pw_key) open_in_place(&mut data, &url.nonce.increment(), pw_key)
.map_err(|_| anyhow!("Failed to decrypt data. Incorrect password?"))?; .map_err(|_| anyhow!("Failed to decrypt data. Incorrect password?"))?;
} }
data = open(&data, &url.nonce, &url.decryption_key) open_in_place(&mut data, &url.nonce, &url.decryption_key)
.map_err(|_| anyhow!("Failed to decrypt data. Incorrect decryption key?"))?; .map_err(|_| anyhow!("Failed to decrypt data. Incorrect decryption key?"))?;
if atty::is(Stream::Stdout) { if atty::is(Stream::Stdout) {

View file

@ -39,7 +39,7 @@ pub mod crypto {
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use chacha20poly1305::aead::generic_array::GenericArray; use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::aead::{Aead, Error, NewAead}; use chacha20poly1305::aead::{Aead, AeadInPlace, Buffer, Error, NewAead};
use chacha20poly1305::XChaCha20Poly1305; use chacha20poly1305::XChaCha20Poly1305;
use chacha20poly1305::XNonce; use chacha20poly1305::XNonce;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -62,11 +62,21 @@ pub mod crypto {
cipher.encrypt(nonce, plaintext) cipher.encrypt(nonce, plaintext)
} }
pub fn seal_in_place(buffer: &mut impl Buffer, nonce: &Nonce, key: &Key) -> Result<(), Error> {
let cipher = XChaCha20Poly1305::new(key);
cipher.encrypt_in_place(nonce, &[], buffer)
}
pub fn open(encrypted: &[u8], nonce: &Nonce, key: &Key) -> Result<Vec<u8>, Error> { pub fn open(encrypted: &[u8], nonce: &Nonce, key: &Key) -> Result<Vec<u8>, Error> {
let cipher = XChaCha20Poly1305::new(key); let cipher = XChaCha20Poly1305::new(key);
cipher.decrypt(nonce, encrypted) cipher.decrypt(nonce, encrypted)
} }
pub fn open_in_place(buffer: &mut impl Buffer, nonce: &Nonce, key: &Key) -> Result<(), Error> {
let cipher = XChaCha20Poly1305::new(key);
cipher.decrypt_in_place(nonce, &[], buffer)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Nonce(XNonce); pub struct Nonce(XNonce);
@ -291,7 +301,16 @@ impl TryFrom<&HeaderValue> for Expiration {
fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> { fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
value value
.to_str() .to_str()
.map_err(|_| ParseHeaderValueError)? .map_err(|_| ParseHeaderValueError)
.and_then(Self::try_from)
}
}
impl TryFrom<&str> for Expiration {
type Error = ParseHeaderValueError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value
.parse::<DateTime<Utc>>() .parse::<DateTime<Utc>>()
.map_err(|_| ParseHeaderValueError) .map_err(|_| ParseHeaderValueError)
.map(Self::UnixTime) .map(Self::UnixTime)

View file

@ -17,7 +17,7 @@ use rand::Rng;
use rocksdb::IteratorMode; use rocksdb::IteratorMode;
use rocksdb::{Options, DB}; use rocksdb::{Options, DB};
use tokio::task; use tokio::task;
use tracing::{error, instrument}; use tracing::{error, instrument, trace};
use tracing::{info, warn}; use tracing::{info, warn};
use crate::paste::Paste; use crate::paste::Paste;
@ -109,7 +109,7 @@ fn set_up_expirations(db: Arc<DB>) {
info!("Cleanup timers have been initialized."); info!("Cleanup timers have been initialized.");
} }
#[instrument(skip(db), err)] #[instrument(skip(db, body), err)]
async fn upload<const N: usize>( async fn upload<const N: usize>(
Extension(db): Extension<Arc<DB>>, Extension(db): Extension<Arc<DB>>,
maybe_expires: Option<TypedHeader<Expiration>>, maybe_expires: Option<TypedHeader<Expiration>>,
@ -127,25 +127,30 @@ async fn upload<const N: usize>(
let paste = Paste::new(maybe_expires.map(|v| v.0).unwrap_or_default(), body); let paste = Paste::new(maybe_expires.map(|v| v.0).unwrap_or_default(), body);
let mut new_key = None; let mut new_key = None;
trace!("Generating short code...");
// Try finding a code; give up after 1000 attempts // Try finding a code; give up after 1000 attempts
// Statistics show that this is very unlikely to happen // Statistics show that this is very unlikely to happen
for _ in 0..1000 { for i in 0..1000 {
let code: ShortCode<N> = thread_rng().sample(short_code::Generator); let code: ShortCode<N> = thread_rng().sample(short_code::Generator);
let db = Arc::clone(&db); let db = Arc::clone(&db);
let key = code.as_bytes(); let key = code.as_bytes();
let query = task::spawn_blocking(move || db.key_may_exist(key)).await; let query = task::spawn_blocking(move || db.key_may_exist(key)).await;
if matches!(query, Ok(false)) { if matches!(query, Ok(false)) {
new_key = Some(key); new_key = Some(key);
trace!("Found new key after {} attempts.", i);
break;
} }
} }
let key = if let Some(key) = new_key { let key = if let Some(key) = new_key {
key key
} else { } else {
error!("Failed to generate a valid shortcode"); error!("Failed to generate a valid short code!");
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);
}; };
trace!("Serializing paste...");
let value = if let Ok(v) = bincode::serialize(&paste) { let value = if let Ok(v) = bincode::serialize(&paste) {
v v
} else { } else {
@ -153,6 +158,8 @@ async fn upload<const N: usize>(
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);
}; };
trace!("Finished serializing paste.");
let db_ref = Arc::clone(&db); let db_ref = Arc::clone(&db);
match task::spawn_blocking(move || db_ref.put(key, value)).await { match task::spawn_blocking(move || db_ref.put(key, value)).await {
Ok(Ok(_)) => { Ok(Ok(_)) => {

View file

@ -12,12 +12,14 @@ getrandom = { version = "*", features = ["js"] }
anyhow = "1" anyhow = "1"
bytes = "1" bytes = "1"
byte-unit = "4"
downcast-rs = "1" downcast-rs = "1"
gloo-console = "0.1" gloo-console = "0.1"
http = "0.2" http = "0.2"
image = "0.23" image = "0.23"
js-sys = "0.3" js-sys = "0.3"
reqwest = { version = "0.11", default_features = false, features = ["tokio-rustls"] } # reqwest = { version = "0.11", default_features = false, features = ["tokio-rustls"] }
reqwasm = "0.2"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["TextDecoder"] } web-sys = { version = "0.3", features = ["TextDecoder"] }

View file

@ -5,12 +5,12 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Omegaupload</title> <title>Omegaupload</title>
<link data-trunk rel="rust" data-wasm-opt="0" data-keep-debug data-no-mangle />
<link data-trunk rel="copy-file" href="vendor/MPLUS_FONTS/fonts/ttf/MplusCodeLatin[wdth,wght].ttf" dest="/" /> <link data-trunk rel="copy-file" href="vendor/MPLUS_FONTS/fonts/ttf/MplusCodeLatin[wdth,wght].ttf" dest="/" />
<link data-trunk rel="copy-file" href="vendor/highlight.min.js" dest="/" /> <link data-trunk rel="copy-file" href="vendor/highlight.min.js" dest="/" />
<link data-trunk rel="copy-file" href="vendor/highlightjs-line-numbers.js/dist/highlightjs-line-numbers.min.js" <link data-trunk rel="copy-file" href="vendor/highlightjs-line-numbers.js/dist/highlightjs-line-numbers.min.js"
dest="/" /> dest="/" />
<link data-trunk rel="copy-file" href="src/reload_on_hash_change.js" dest="/" /> <link data-trunk rel="copy-file" href="src/reload_on_hash_change.js" dest="/" />
<link data-trunk rel="css" href="vendor/highlight.js/src/styles/github-dark.css" />
<link data-trunk rel="scss" href="src/main.scss" /> <link data-trunk rel="scss" href="src/main.scss" />
<script src="reload_on_hash_change.js" async></script> <script src="reload_on_hash_change.js" async></script>

138
web/src/decrypt.rs Normal file
View file

@ -0,0 +1,138 @@
use std::{collections::HashSet, sync::Arc};
use gloo_console::log;
use image::GenericImageView;
use js_sys::{Array, Uint8Array};
use omegaupload_common::{
crypto::{open_in_place, Key, Nonce},
Expiration,
};
use wasm_bindgen::JsCast;
use web_sys::Blob;
use yew::worker::{Agent, AgentLink, Context, HandlerId};
use yew::{html::Scope, worker::Public};
use crate::{DecryptedData, Paste, PasteCompleteConstructionError};
#[derive(Clone)]
pub struct DecryptionAgent {
link: AgentLink<Self>,
}
impl Agent for DecryptionAgent {
type Reach = Public<Self>;
type Message = ();
type Input = DecryptionAgentMessage;
type Output = Result<(DecryptedData, PasteContext), PasteCompleteConstructionError>;
fn create(link: AgentLink<Self>) -> Self {
Self { link }
}
fn update(&mut self, _: Self::Message) {}
fn handle_input(
&mut self,
DecryptionAgentMessage { context, params }: Self::Input,
id: HandlerId,
) {
let DecryptionParams {
data,
key,
nonce,
maybe_password,
} = params;
self.link.respond(
id,
decrypt(data, key, nonce, maybe_password).map(|res| (res, context)),
)
}
}
pub struct DecryptionAgentMessage {
context: PasteContext,
params: DecryptionParams,
}
impl DecryptionAgentMessage {
pub fn new(context: PasteContext, params: DecryptionParams) -> Self {
Self { context, params }
}
}
pub struct PasteContext {
pub link: Scope<Paste>,
pub expires: Option<Expiration>,
}
impl PasteContext {
pub fn new(link: Scope<Paste>, expires: Option<Expiration>) -> Self {
Self { link, expires }
}
}
pub struct DecryptionParams {
data: Vec<u8>,
key: Key,
nonce: Nonce,
maybe_password: Option<Key>,
}
impl DecryptionParams {
pub fn new(data: Vec<u8>, key: Key, nonce: Nonce, maybe_password: Option<Key>) -> Self {
Self {
data,
key,
nonce,
maybe_password,
}
}
}
fn decrypt(
mut container: Vec<u8>,
key: Key,
nonce: Nonce,
maybe_password: Option<Key>,
) -> Result<DecryptedData, PasteCompleteConstructionError> {
let container = &mut container;
log!("stage 1 decryption start");
if let Some(password) = maybe_password {
open_in_place(container, &nonce.increment(), &password)
.map_err(|_| PasteCompleteConstructionError::StageOneFailure)?;
}
log!("stage 2 decryption start");
open_in_place(container, &nonce, &key)
.map_err(|_| PasteCompleteConstructionError::StageTwoFailure)?;
log!("stage 2 decryption end");
if let Ok(decrypted) = std::str::from_utf8(&container) {
Ok(DecryptedData::String(Arc::new(decrypted.to_owned())))
} else {
log!("blob conversion start");
let blob_chunks = Array::new_with_length(container.chunks(65536).len().try_into().unwrap());
for (i, chunk) in container.chunks(65536).enumerate() {
let array = Uint8Array::new_with_length(chunk.len().try_into().unwrap());
array.copy_from(&chunk);
blob_chunks.set(i.try_into().unwrap(), array.dyn_into().unwrap());
}
let blob =
Arc::new(Blob::new_with_u8_array_sequence(blob_chunks.dyn_ref().unwrap()).unwrap());
log!("blob conversion end");
if let Ok(image) = image::load_from_memory(&container) {
Ok(DecryptedData::Image(
blob,
image.dimensions(),
container.len(),
))
} else {
Ok(DecryptedData::Blob(blob))
}
}
}

View file

@ -2,28 +2,39 @@
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use byte_unit::Byte;
use bytes::Bytes; use bytes::Bytes;
use decrypt::DecryptionAgent;
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use gloo_console::log; use gloo_console::log;
use http::header::EXPIRES; use http::header::EXPIRES;
use http::uri::PathAndQuery; use http::uri::PathAndQuery;
use http::{StatusCode, Uri}; use http::{StatusCode, Uri};
use image::GenericImageView;
use js_sys::{Array, ArrayBuffer, Uint8Array}; use js_sys::{Array, ArrayBuffer, Uint8Array};
use omegaupload_common::crypto::{open, Key, Nonce}; use omegaupload_common::crypto::{open, open_in_place, Key, Nonce};
use omegaupload_common::{Expiration, PartialParsedUrl}; use omegaupload_common::{Expiration, PartialParsedUrl};
use reqwasm::http::Request;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::TextDecoder; use web_sys::TextDecoder;
use web_sys::{Blob, Url}; use web_sys::{Blob, Url};
use yew::agent::Dispatcher;
use yew::utils::window; use yew::utils::window;
use yew::Properties; use yew::worker::Agent;
use yew::{html, Component, ComponentLink, Html, ShouldRender}; use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, ShouldRender};
use yew::{Dispatched, Properties};
use yew_router::router::Router; use yew_router::router::Router;
use yew_router::Switch; use yew_router::Switch;
use yewtil::future::LinkFuture; use yewtil::future::LinkFuture;
use crate::decrypt::{DecryptionAgentMessage, DecryptionParams, PasteContext};
mod decrypt;
fn main() { fn main() {
yew::start_app::<App>(); yew::start_app::<App>();
} }
@ -76,8 +87,9 @@ fn render_route(route: Route) -> Html {
} }
} }
struct Paste { pub struct Paste {
state: Box<dyn PasteState>, state: Box<dyn PasteState>,
_listener: Box<dyn Bridge<DecryptionAgent>>,
} }
impl Component for Paste { impl Component for Paste {
@ -95,20 +107,38 @@ impl Component for Paste {
Uri::from_parts(uri_parts).unwrap() Uri::from_parts(uri_parts).unwrap()
}; };
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));
let link_clone = link.clone(); let link_clone = link.clone();
link.send_future(async move { link.send_future(async move {
match reqwest::get(&request_uri.to_string()).await { match Request::get(&request_uri.to_string()).send().await {
Ok(resp) if resp.status() == StatusCode::OK => { Ok(resp) if resp.status() == StatusCode::OK => {
let expires = resp let expires = resp
.headers() .headers()
.get(EXPIRES) .get(EXPIRES.as_str())
.ok()
.flatten()
.as_deref()
.and_then(|v| Expiration::try_from(v).ok()); .and_then(|v| Expiration::try_from(v).ok());
let bytes = match resp.bytes().await {
Ok(bytes) => bytes, let data = {
Err(e) => { Uint8Array::new(
return Box::new(PasteError(anyhow!("Got {}.", e))) &JsFuture::from(resp.as_raw().array_buffer().unwrap())
as Box<dyn PasteState> .await
} .unwrap(),
)
.to_vec()
}; };
let info = url let info = url
@ -118,13 +148,12 @@ impl Component for Paste {
let key = info.decryption_key.unwrap(); let key = info.decryption_key.unwrap();
let nonce = info.nonce.unwrap(); let nonce = info.nonce.unwrap();
if let Ok(completed) = decrypt(bytes, key, nonce, None) { let mut decryption_agent = DecryptionAgent::dispatcher();
Box::new(PasteComplete::new(link_clone, completed, expires))
as Box<dyn PasteState> let params = DecryptionParams::new(data, key, nonce, None);
} else { let ctx = PasteContext::new(link_clone, expires);
todo!() decryption_agent.send(DecryptionAgentMessage::new(ctx, params));
// Box::new(partial) as Box<dyn PasteState> Box::new(PasteDecrypting(decryption_agent)) as Box<dyn PasteState>
}
} }
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
Box::new(PasteNotFound) as Box<dyn PasteState> Box::new(PasteNotFound) as Box<dyn PasteState>
@ -138,8 +167,10 @@ impl Component for Paste {
Err(err) => Box::new(PasteError(anyhow!("Got {}.", err))) as Box<dyn PasteState>, Err(err) => Box::new(PasteError(anyhow!("Got {}.", err))) as Box<dyn PasteState>,
} }
}); });
Self { Self {
state: Box::new(PasteLoading), state: Box::new(PasteLoading),
_listener: listener,
} }
} }
@ -159,6 +190,12 @@ impl Component for Paste {
}; };
} }
if self.state.is::<PasteDecrypting>() {
return html! {
"decrypting"
};
}
if self.state.is::<PasteNotFound>() { if self.state.is::<PasteNotFound>() {
return html! { return html! {
<section class={"hljs centered"}> <section class={"hljs centered"}>
@ -197,9 +234,10 @@ impl Component for Paste {
struct PasteError(anyhow::Error); struct PasteError(anyhow::Error);
#[derive(Properties, Clone, Debug)] #[derive(Debug)]
struct PastePartial { struct PastePartial {
parent: ComponentLink<Paste>, parent: ComponentLink<Paste>,
dispatcher: Dispatcher<DecryptionAgent>,
data: Bytes, data: Bytes,
expires: Option<Expiration>, expires: Option<Expiration>,
key: Option<Key>, key: Option<Key>,
@ -216,13 +254,13 @@ struct PasteComplete {
} }
#[derive(Clone)] #[derive(Clone)]
enum DecryptedData { pub enum DecryptedData {
String(String), String(Arc<String>),
Blob(Blob), Blob(Arc<Blob>),
Image(Blob), Image(Arc<Blob>, (u32, u32), usize),
} }
trait PasteState: Downcast {} pub trait PasteState: Downcast {}
impl_downcast!(PasteState); impl_downcast!(PasteState);
impl PasteState for PasteError {} impl PasteState for PasteError {}
@ -242,6 +280,10 @@ macro_rules! impl_paste_type_state {
impl_paste_type_state!(PasteLoading, PasteNotFound, PasteBadRequest); impl_paste_type_state!(PasteLoading, PasteNotFound, PasteBadRequest);
struct PasteDecrypting(Dispatcher<DecryptionAgent>);
impl PasteState for PasteDecrypting {}
impl PastePartial { impl PastePartial {
fn new( fn new(
data: Bytes, data: Bytes,
@ -251,6 +293,7 @@ impl PastePartial {
) -> Self { ) -> Self {
Self { Self {
parent, parent,
dispatcher: DecryptionAgent::dispatcher(),
data, data,
expires, expires,
key: partial_parsed_url.decryption_key, key: partial_parsed_url.decryption_key,
@ -270,10 +313,10 @@ enum PartialPasteMessage {
impl Component for PastePartial { impl Component for PastePartial {
type Message = PartialPasteMessage; type Message = PartialPasteMessage;
type Properties = Self; type Properties = ();
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self { fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
props unimplemented!()
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, msg: Self::Message) -> ShouldRender {
@ -289,18 +332,11 @@ impl Component for PastePartial {
|| (!self.needs_pw && maybe_password.is_none()) => || (!self.needs_pw && maybe_password.is_none()) =>
{ {
let parent = self.parent.clone(); let parent = self.parent.clone();
let data = self.data.clone(); let mut data = self.data.to_vec();
let expires = self.expires; let expires = self.expires;
self.parent.send_future(async move { // self.dispatcher.send((data, key, nonce, maybe_password));
match decrypt(data, key, nonce, maybe_password) { todo!()
Ok(decrypted) => Box::new(PasteComplete::new(parent, decrypted, expires))
as Box<dyn PasteState>,
Err(e) => {
todo!()
}
}
});
} }
_ => (), _ => (),
} }
@ -321,43 +357,8 @@ impl Component for PastePartial {
} }
} }
fn decrypt(
encrypted: Bytes,
key: Key,
nonce: Nonce,
maybe_password: Option<Key>,
) -> Result<DecryptedData, PasteCompleteConstructionError> {
let stage_one = maybe_password.map_or_else(
|| Ok(encrypted.to_vec()),
|password| open(&encrypted, &nonce.increment(), &password),
);
let stage_one = stage_one.map_err(|_| PasteCompleteConstructionError::StageOneFailure)?;
let stage_two = open(&stage_one, &nonce, &key)
.map_err(|_| PasteCompleteConstructionError::StageTwoFailure)?;
if let Ok(decrypted) = std::str::from_utf8(&stage_two) {
Ok(DecryptedData::String(decrypted.to_owned()))
} else {
let blob_chunks = Array::new_with_length(stage_two.chunks(65536).len().try_into().unwrap());
for (i, chunk) in stage_two.chunks(65536).enumerate() {
let array = Uint8Array::new_with_length(chunk.len().try_into().unwrap());
array.copy_from(&chunk);
blob_chunks.set(i.try_into().unwrap(), array.dyn_into().unwrap());
}
let blob = Blob::new_with_u8_array_sequence(blob_chunks.dyn_ref().unwrap()).unwrap();
if image::guess_format(&stage_two).is_ok() {
Ok(DecryptedData::Image(blob))
} else {
Ok(DecryptedData::Blob(blob))
}
}
}
#[derive(Debug)] #[derive(Debug)]
enum PasteCompleteConstructionError { pub enum PasteCompleteConstructionError {
StageOneFailure, StageOneFailure,
StageTwoFailure, StageTwoFailure,
} }
@ -395,22 +396,24 @@ impl PasteComplete {
DecryptedData::String(decrypted) => html! { DecryptedData::String(decrypted) => html! {
html! { html! {
<> <>
<pre class={"paste"}> <pre class="paste">
<header class={"hljs"}> <header class="unselectable">
{ {
self.expires.as_ref().map(ToString::to_string).unwrap_or_else(|| self.expires.as_ref().map(ToString::to_string).unwrap_or_else(||
"This paste will not expire.".to_string() "This paste will not expire.".to_string()
) )
} }
</header> </header>
<hr class={"hljs"} /> <hr />
<code>{decrypted}</code> <code>{decrypted}</code>
</pre> </pre>
<script>{" <script>
hljs.highlightAll(); {"
hljs.initLineNumbersOnLoad(); hljs.highlightAll();
"}</script> hljs.initLineNumbersOnLoad();
"}
</script>
</> </>
} }
}, },
@ -419,11 +422,11 @@ impl PasteComplete {
if let Ok(object_url) = object_url { if let Ok(object_url) = object_url {
let file_name = window().location().pathname().unwrap_or("file".to_string()); let file_name = window().location().pathname().unwrap_or("file".to_string());
let mut cloned = self.clone(); let mut cloned = self.clone();
let decrypted_cloned = decrypted.clone(); let decrypted_ref = Arc::clone(&decrypted);
let display_anyways_callback = let display_anyways_callback =
self.parent.callback_future_once(|_| async move { self.parent.callback_future_once(|_| async move {
let array_buffer: ArrayBuffer = let array_buffer: ArrayBuffer =
JsFuture::from(decrypted_cloned.array_buffer()) JsFuture::from(decrypted_ref.array_buffer())
.await .await
.unwrap() .unwrap()
.dyn_into() .dyn_into()
@ -431,15 +434,16 @@ impl PasteComplete {
let decoder = TextDecoder::new().unwrap(); let decoder = TextDecoder::new().unwrap();
cloned.decrypted = decoder cloned.decrypted = decoder
.decode_with_buffer_source(&array_buffer) .decode_with_buffer_source(&array_buffer)
.map(Arc::new)
.map(DecryptedData::String) .map(DecryptedData::String)
.unwrap(); .unwrap();
Box::new(cloned) as Box<dyn PasteState> Box::new(cloned) as Box<dyn PasteState>
}); });
html! { html! {
<section class="hljs centered"> <section class="hljs fullscreen centered">
<div class="centered"> <div class="centered">
<p>{ "Found a binary file." }</p> <p>{ "Found a binary file." }</p>
<a href={object_url} download=file_name class="hljs-meta">{"Download"}</a> <a href=object_url download=file_name class="hljs-meta">{"Download"}</a>
</div> </div>
<p onclick=display_anyways_callback class="display-anyways hljs-meta">{ "Display anyways?" }</p> <p onclick=display_anyways_callback class="display-anyways hljs-meta">{ "Display anyways?" }</p>
</section> </section>
@ -454,21 +458,29 @@ impl PasteComplete {
} }
} }
} }
DecryptedData::Image(decrypted) => { DecryptedData::Image(decrypted, (width, height), size) => {
let object_url = Url::create_object_url_with_blob(decrypted); let object_url = Url::create_object_url_with_blob(decrypted);
if let Ok(object_url) = object_url { if let Ok(object_url) = object_url {
let file_name = window().location().pathname().unwrap_or("file".to_string()); let file_name = window().location().pathname().unwrap_or("file".to_string());
html! { html! {
<section class="centered"> <section class="hljs fullscreen centered">
<img src={object_url.clone()} /> <img src=object_url.clone() />
<a href={object_url} download=file_name class="hljs-meta">{"Download"}</a> <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>
</section> </section>
} }
} else { } else {
// This branch really shouldn't happen, but might as well // This branch really shouldn't happen, but might as well
// try and give a user-friendly error message. // try and give a user-friendly error message.
html! { html! {
<section class="hljs centered"> <section class="hljs fullscreen centered">
<p>{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }</p> <p>{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }</p>
</section> </section>
} }

View file

@ -1,3 +1,7 @@
@use '../vendor/highlight.js/src/styles/github-dark.css';
$padding: 1em;
@font-face { @font-face {
font-family: "Mplus Code"; font-family: "Mplus Code";
src: url("./MplusCodeLatin[wdth,wght].ttf") format("truetype"); src: url("./MplusCodeLatin[wdth,wght].ttf") format("truetype");
@ -5,17 +9,16 @@
body { body {
background-color: #404040; background-color: #404040;
font-family: 'Mplus Code', sans-serif;
margin: 0; margin: 0;
} }
pre header { .unselectable {
user-select: none; user-select: none;
margin: 1em;
} }
hr { hr {
margin: 1em; @extend .hljs;
margin: $padding 0;
} }
main { main {
@ -25,11 +28,11 @@ main {
} }
.paste { .paste {
border-radius: 1em; @extend .hljs;
margin: 1em; border-radius: $padding;
padding: 1em; margin: $padding;
background-color: #0d1117; padding: 2 * $padding;
box-shadow: 0 0 1em black; box-shadow: 0 0 $padding black;
min-width: 120ch; min-width: 120ch;
} }
@ -39,19 +42,22 @@ main {
.hljs-ln td.hljs-ln-numbers { .hljs-ln td.hljs-ln-numbers {
text-align: right; text-align: right;
padding-right: 1em; padding-right: $padding;
} }
.centered { .centered {
height: 100vh;
width: 100vw;
margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.fullscreen {
min-height: 100vh;
min-width: 100vw;
margin: 0;
}
.display-anyways { .display-anyways {
margin-bottom: 4em; margin-bottom: 4em;
text-decoration: underline; text-decoration: underline;
@ -59,15 +65,11 @@ main {
img { img {
margin-bottom: 4em; margin-bottom: 4em;
max-height: 75vh;
max-width: 75vw;
border-radius: $padding;
} }
.primary { .primary {
background-color: #0d1117; @extend .hljs;
}
.image {
display: block;
a {
}
} }