Partial work
This commit is contained in:
parent
17dd44c8cc
commit
89aeb6ba2a
10 changed files with 386 additions and 136 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
@ -188,6 +188,15 @@ version = "3.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
|
@ -469,6 +478,21 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.17"
|
||||
|
@ -476,6 +500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -484,6 +509,17 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures-io"
|
||||
version = "0.3.17"
|
||||
|
@ -522,9 +558,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
|
@ -1135,6 +1173,7 @@ name = "omegaupload-web"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byte-unit",
|
||||
"bytes",
|
||||
"downcast-rs",
|
||||
"getrandom",
|
||||
|
@ -1143,7 +1182,7 @@ dependencies = [
|
|||
"image",
|
||||
"js-sys",
|
||||
"omegaupload-common",
|
||||
"reqwest",
|
||||
"reqwasm",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
|
@ -1394,6 +1433,23 @@ version = "0.6.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "reqwest"
|
||||
version = "0.11.5"
|
||||
|
@ -1995,6 +2051,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-width"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
|
|
8
Trunk.toml
Normal file
8
Trunk.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
[build]
|
||||
target = "web/index.html"
|
||||
release = true
|
||||
|
||||
[[proxy]]
|
||||
backend = "http://localhost:8081"
|
||||
rewrite = "/api/"
|
|
@ -6,7 +6,7 @@ use std::io::{Read, Write};
|
|||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use atty::Stream;
|
||||
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 reqwest::blocking::Client;
|
||||
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 mut container = Vec::new();
|
||||
std::io::stdin().read_to_end(&mut container)?;
|
||||
let mut enc =
|
||||
seal(&container, &nonce, &enc_key).map_err(|_| anyhow!("Failed to encrypt data"))?;
|
||||
seal_in_place(&mut container, &nonce, &enc_key)
|
||||
.map_err(|_| anyhow!("Failed to encrypt data"))?;
|
||||
|
||||
let pw_used = if let Some(password) = password {
|
||||
let pw_hash = hash(password.expose_secret().as_bytes());
|
||||
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"))?;
|
||||
true
|
||||
} else {
|
||||
|
@ -69,7 +69,7 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
|
|||
let key = base64::encode(&enc_key);
|
||||
let nonce = base64::encode(&nonce);
|
||||
|
||||
(enc, nonce, key, pw_used)
|
||||
(container, nonce, key, pw_used)
|
||||
};
|
||||
|
||||
let res = Client::new()
|
||||
|
@ -131,11 +131,11 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
|
|||
let pw_hash = hash(input.as_bytes());
|
||||
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?"))?;
|
||||
}
|
||||
|
||||
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?"))?;
|
||||
|
||||
if atty::is(Stream::Stdout) {
|
||||
|
|
|
@ -39,7 +39,7 @@ pub mod crypto {
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
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::XNonce;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
@ -62,11 +62,21 @@ pub mod crypto {
|
|||
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> {
|
||||
let cipher = XChaCha20Poly1305::new(key);
|
||||
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)]
|
||||
pub struct Nonce(XNonce);
|
||||
|
||||
|
@ -291,7 +301,16 @@ impl TryFrom<&HeaderValue> for Expiration {
|
|||
fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
|
||||
value
|
||||
.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>>()
|
||||
.map_err(|_| ParseHeaderValueError)
|
||||
.map(Self::UnixTime)
|
||||
|
|
|
@ -17,7 +17,7 @@ use rand::Rng;
|
|||
use rocksdb::IteratorMode;
|
||||
use rocksdb::{Options, DB};
|
||||
use tokio::task;
|
||||
use tracing::{error, instrument};
|
||||
use tracing::{error, instrument, trace};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::paste::Paste;
|
||||
|
@ -109,7 +109,7 @@ fn set_up_expirations(db: Arc<DB>) {
|
|||
info!("Cleanup timers have been initialized.");
|
||||
}
|
||||
|
||||
#[instrument(skip(db), err)]
|
||||
#[instrument(skip(db, body), err)]
|
||||
async fn upload<const N: usize>(
|
||||
Extension(db): Extension<Arc<DB>>,
|
||||
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 mut new_key = None;
|
||||
|
||||
trace!("Generating short code...");
|
||||
|
||||
// Try finding a code; give up after 1000 attempts
|
||||
// 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 db = Arc::clone(&db);
|
||||
let key = code.as_bytes();
|
||||
let query = task::spawn_blocking(move || db.key_may_exist(key)).await;
|
||||
if matches!(query, Ok(false)) {
|
||||
new_key = Some(key);
|
||||
trace!("Found new key after {} attempts.", i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let key = if let Some(key) = new_key {
|
||||
key
|
||||
} else {
|
||||
error!("Failed to generate a valid shortcode");
|
||||
error!("Failed to generate a valid short code!");
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
trace!("Serializing paste...");
|
||||
let value = if let Ok(v) = bincode::serialize(&paste) {
|
||||
v
|
||||
} else {
|
||||
|
@ -153,6 +158,8 @@ async fn upload<const N: usize>(
|
|||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
trace!("Finished serializing paste.");
|
||||
|
||||
let db_ref = Arc::clone(&db);
|
||||
match task::spawn_blocking(move || db_ref.put(key, value)).await {
|
||||
Ok(Ok(_)) => {
|
||||
|
|
|
@ -12,12 +12,14 @@ getrandom = { version = "*", features = ["js"] }
|
|||
|
||||
anyhow = "1"
|
||||
bytes = "1"
|
||||
byte-unit = "4"
|
||||
downcast-rs = "1"
|
||||
gloo-console = "0.1"
|
||||
http = "0.2"
|
||||
image = "0.23"
|
||||
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-futures = "0.4"
|
||||
web-sys = { version = "0.3", features = ["TextDecoder"] }
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
<meta charset="utf-8" />
|
||||
<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/highlight.min.js" dest="/" />
|
||||
<link data-trunk rel="copy-file" href="vendor/highlightjs-line-numbers.js/dist/highlightjs-line-numbers.min.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" />
|
||||
|
||||
<script src="reload_on_hash_change.js" async></script>
|
||||
|
|
138
web/src/decrypt.rs
Normal file
138
web/src/decrypt.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
208
web/src/main.rs
208
web/src/main.rs
|
@ -2,28 +2,39 @@
|
|||
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use byte_unit::Byte;
|
||||
use bytes::Bytes;
|
||||
use decrypt::DecryptionAgent;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use gloo_console::log;
|
||||
use http::header::EXPIRES;
|
||||
use http::uri::PathAndQuery;
|
||||
use http::{StatusCode, Uri};
|
||||
use image::GenericImageView;
|
||||
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 reqwasm::http::Request;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::TextDecoder;
|
||||
use web_sys::{Blob, Url};
|
||||
use yew::agent::Dispatcher;
|
||||
use yew::utils::window;
|
||||
use yew::Properties;
|
||||
use yew::{html, Component, ComponentLink, Html, ShouldRender};
|
||||
use yew::worker::Agent;
|
||||
use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, ShouldRender};
|
||||
use yew::{Dispatched, Properties};
|
||||
use yew_router::router::Router;
|
||||
use yew_router::Switch;
|
||||
use yewtil::future::LinkFuture;
|
||||
|
||||
use crate::decrypt::{DecryptionAgentMessage, DecryptionParams, PasteContext};
|
||||
|
||||
mod decrypt;
|
||||
|
||||
fn main() {
|
||||
yew::start_app::<App>();
|
||||
}
|
||||
|
@ -76,8 +87,9 @@ fn render_route(route: Route) -> Html {
|
|||
}
|
||||
}
|
||||
|
||||
struct Paste {
|
||||
pub struct Paste {
|
||||
state: Box<dyn PasteState>,
|
||||
_listener: Box<dyn Bridge<DecryptionAgent>>,
|
||||
}
|
||||
|
||||
impl Component for Paste {
|
||||
|
@ -95,20 +107,38 @@ impl Component for Paste {
|
|||
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();
|
||||
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 => {
|
||||
let expires = resp
|
||||
.headers()
|
||||
.get(EXPIRES)
|
||||
.get(EXPIRES.as_str())
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
.and_then(|v| Expiration::try_from(v).ok());
|
||||
let bytes = match resp.bytes().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
return Box::new(PasteError(anyhow!("Got {}.", e)))
|
||||
as Box<dyn PasteState>
|
||||
}
|
||||
|
||||
let data = {
|
||||
Uint8Array::new(
|
||||
&JsFuture::from(resp.as_raw().array_buffer().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
.to_vec()
|
||||
};
|
||||
|
||||
let info = url
|
||||
|
@ -118,13 +148,12 @@ impl Component for Paste {
|
|||
let key = info.decryption_key.unwrap();
|
||||
let nonce = info.nonce.unwrap();
|
||||
|
||||
if let Ok(completed) = decrypt(bytes, key, nonce, None) {
|
||||
Box::new(PasteComplete::new(link_clone, completed, expires))
|
||||
as Box<dyn PasteState>
|
||||
} else {
|
||||
todo!()
|
||||
// Box::new(partial) as Box<dyn PasteState>
|
||||
}
|
||||
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>
|
||||
}
|
||||
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
|
||||
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>,
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
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>() {
|
||||
return html! {
|
||||
<section class={"hljs centered"}>
|
||||
|
@ -197,9 +234,10 @@ impl Component for Paste {
|
|||
|
||||
struct PasteError(anyhow::Error);
|
||||
|
||||
#[derive(Properties, Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
struct PastePartial {
|
||||
parent: ComponentLink<Paste>,
|
||||
dispatcher: Dispatcher<DecryptionAgent>,
|
||||
data: Bytes,
|
||||
expires: Option<Expiration>,
|
||||
key: Option<Key>,
|
||||
|
@ -216,13 +254,13 @@ struct PasteComplete {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum DecryptedData {
|
||||
String(String),
|
||||
Blob(Blob),
|
||||
Image(Blob),
|
||||
pub enum DecryptedData {
|
||||
String(Arc<String>),
|
||||
Blob(Arc<Blob>),
|
||||
Image(Arc<Blob>, (u32, u32), usize),
|
||||
}
|
||||
|
||||
trait PasteState: Downcast {}
|
||||
pub trait PasteState: Downcast {}
|
||||
impl_downcast!(PasteState);
|
||||
|
||||
impl PasteState for PasteError {}
|
||||
|
@ -242,6 +280,10 @@ macro_rules! impl_paste_type_state {
|
|||
|
||||
impl_paste_type_state!(PasteLoading, PasteNotFound, PasteBadRequest);
|
||||
|
||||
struct PasteDecrypting(Dispatcher<DecryptionAgent>);
|
||||
|
||||
impl PasteState for PasteDecrypting {}
|
||||
|
||||
impl PastePartial {
|
||||
fn new(
|
||||
data: Bytes,
|
||||
|
@ -251,6 +293,7 @@ impl PastePartial {
|
|||
) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
dispatcher: DecryptionAgent::dispatcher(),
|
||||
data,
|
||||
expires,
|
||||
key: partial_parsed_url.decryption_key,
|
||||
|
@ -270,10 +313,10 @@ enum PartialPasteMessage {
|
|||
impl Component for PastePartial {
|
||||
type Message = PartialPasteMessage;
|
||||
|
||||
type Properties = Self;
|
||||
type Properties = ();
|
||||
|
||||
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
props
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
|
@ -289,18 +332,11 @@ impl Component for PastePartial {
|
|||
|| (!self.needs_pw && maybe_password.is_none()) =>
|
||||
{
|
||||
let parent = self.parent.clone();
|
||||
let data = self.data.clone();
|
||||
let mut data = self.data.to_vec();
|
||||
let expires = self.expires;
|
||||
|
||||
self.parent.send_future(async move {
|
||||
match decrypt(data, key, nonce, maybe_password) {
|
||||
Ok(decrypted) => Box::new(PasteComplete::new(parent, decrypted, expires))
|
||||
as Box<dyn PasteState>,
|
||||
Err(e) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
// self.dispatcher.send((data, key, nonce, maybe_password));
|
||||
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)]
|
||||
enum PasteCompleteConstructionError {
|
||||
pub enum PasteCompleteConstructionError {
|
||||
StageOneFailure,
|
||||
StageTwoFailure,
|
||||
}
|
||||
|
@ -395,22 +396,24 @@ impl PasteComplete {
|
|||
DecryptedData::String(decrypted) => html! {
|
||||
html! {
|
||||
<>
|
||||
<pre class={"paste"}>
|
||||
<header class={"hljs"}>
|
||||
{
|
||||
self.expires.as_ref().map(ToString::to_string).unwrap_or_else(||
|
||||
"This paste will not expire.".to_string()
|
||||
)
|
||||
}
|
||||
</header>
|
||||
<hr class={"hljs"} />
|
||||
<code>{decrypted}</code>
|
||||
</pre>
|
||||
<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>
|
||||
<script>
|
||||
{"
|
||||
hljs.highlightAll();
|
||||
hljs.initLineNumbersOnLoad();
|
||||
"}
|
||||
</script>
|
||||
</>
|
||||
}
|
||||
},
|
||||
|
@ -419,11 +422,11 @@ impl PasteComplete {
|
|||
if let Ok(object_url) = object_url {
|
||||
let file_name = window().location().pathname().unwrap_or("file".to_string());
|
||||
let mut cloned = self.clone();
|
||||
let decrypted_cloned = decrypted.clone();
|
||||
let decrypted_ref = Arc::clone(&decrypted);
|
||||
let display_anyways_callback =
|
||||
self.parent.callback_future_once(|_| async move {
|
||||
let array_buffer: ArrayBuffer =
|
||||
JsFuture::from(decrypted_cloned.array_buffer())
|
||||
JsFuture::from(decrypted_ref.array_buffer())
|
||||
.await
|
||||
.unwrap()
|
||||
.dyn_into()
|
||||
|
@ -431,15 +434,16 @@ impl PasteComplete {
|
|||
let decoder = TextDecoder::new().unwrap();
|
||||
cloned.decrypted = decoder
|
||||
.decode_with_buffer_source(&array_buffer)
|
||||
.map(Arc::new)
|
||||
.map(DecryptedData::String)
|
||||
.unwrap();
|
||||
Box::new(cloned) as Box<dyn PasteState>
|
||||
});
|
||||
html! {
|
||||
<section class="hljs centered">
|
||||
<section class="hljs fullscreen centered">
|
||||
<div class="centered">
|
||||
<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>
|
||||
<p onclick=display_anyways_callback class="display-anyways hljs-meta">{ "Display anyways?" }</p>
|
||||
</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);
|
||||
if let Ok(object_url) = object_url {
|
||||
let file_name = window().location().pathname().unwrap_or("file".to_string());
|
||||
html! {
|
||||
<section class="centered">
|
||||
<img src={object_url.clone()} />
|
||||
<a href={object_url} download=file_name class="hljs-meta">{"Download"}</a>
|
||||
<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>
|
||||
</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">
|
||||
<section class="hljs fullscreen centered">
|
||||
<p>{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }</p>
|
||||
</section>
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
@use '../vendor/highlight.js/src/styles/github-dark.css';
|
||||
|
||||
$padding: 1em;
|
||||
|
||||
@font-face {
|
||||
font-family: "Mplus Code";
|
||||
src: url("./MplusCodeLatin[wdth,wght].ttf") format("truetype");
|
||||
|
@ -5,17 +9,16 @@
|
|||
|
||||
body {
|
||||
background-color: #404040;
|
||||
font-family: 'Mplus Code', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
pre header {
|
||||
user-select: none;
|
||||
margin: 1em;
|
||||
.unselectable {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1em;
|
||||
@extend .hljs;
|
||||
margin: $padding 0;
|
||||
}
|
||||
|
||||
main {
|
||||
|
@ -25,11 +28,11 @@ main {
|
|||
}
|
||||
|
||||
.paste {
|
||||
border-radius: 1em;
|
||||
margin: 1em;
|
||||
padding: 1em;
|
||||
background-color: #0d1117;
|
||||
box-shadow: 0 0 1em black;
|
||||
@extend .hljs;
|
||||
border-radius: $padding;
|
||||
margin: $padding;
|
||||
padding: 2 * $padding;
|
||||
box-shadow: 0 0 $padding black;
|
||||
min-width: 120ch;
|
||||
}
|
||||
|
||||
|
@ -39,19 +42,22 @@ main {
|
|||
|
||||
.hljs-ln td.hljs-ln-numbers {
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
padding-right: $padding;
|
||||
}
|
||||
|
||||
.centered {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
min-height: 100vh;
|
||||
min-width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.display-anyways {
|
||||
margin-bottom: 4em;
|
||||
text-decoration: underline;
|
||||
|
@ -59,15 +65,11 @@ main {
|
|||
|
||||
img {
|
||||
margin-bottom: 4em;
|
||||
max-height: 75vh;
|
||||
max-width: 75vw;
|
||||
border-radius: $padding;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: #0d1117;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
}
|
||||
@extend .hljs;
|
||||
}
|
Loading…
Reference in a new issue