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"
|
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
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 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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(_)) => {
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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
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::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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue