barebones web
This commit is contained in:
parent
006850e35d
commit
26ac52e74b
14 changed files with 666 additions and 340 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
/database
|
||||
**/database
|
||||
/web/dist/
|
||||
|
|
195
Cargo.lock
generated
195
Cargo.lock
generated
|
@ -41,6 +41,12 @@ version = "0.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.51"
|
||||
|
@ -194,7 +200,7 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"nom 6.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -243,6 +249,8 @@ dependencies = [
|
|||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -326,13 +334,10 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
|
||||
dependencies = [
|
||||
"signature",
|
||||
]
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
|
@ -447,8 +452,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -469,6 +476,18 @@ dependencies = [
|
|||
"gloo-timers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-console"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "770942b86a2ab86330201eeafc5fe526fb203e54dbc6ef82a36453cebcb90e4c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-console-timer"
|
||||
version = "0.1.0"
|
||||
|
@ -715,6 +734,19 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
|
@ -743,18 +775,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsodium-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
|
@ -813,6 +833,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.1.2"
|
||||
|
@ -873,13 +904,20 @@ dependencies = [
|
|||
"omegaupload-common",
|
||||
"reqwest",
|
||||
"secrecy",
|
||||
"sodiumoxide",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "omegaupload-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chacha20poly1305",
|
||||
"rand",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "omegaupload-server"
|
||||
|
@ -889,6 +927,7 @@ dependencies = [
|
|||
"axum",
|
||||
"bincode",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"headers",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
|
@ -903,8 +942,15 @@ dependencies = [
|
|||
name = "omegaupload-web"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chacha20poly1305",
|
||||
"anyhow",
|
||||
"downcast-rs",
|
||||
"getrandom",
|
||||
"gloo-console",
|
||||
"http",
|
||||
"omegaupload-common",
|
||||
"web-sys",
|
||||
"yew",
|
||||
"yew-router",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -969,12 +1015,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.7.2"
|
||||
|
@ -1204,15 +1244,6 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
|
@ -1289,6 +1320,19 @@ dependencies = [
|
|||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
|
@ -1304,12 +1348,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.5"
|
||||
|
@ -1332,24 +1370,18 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sodiumoxide"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028"
|
||||
dependencies = [
|
||||
"ed25519",
|
||||
"libc",
|
||||
"libsodium-sys",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -1716,17 +1748,6 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
|
@ -1750,6 +1771,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
|
@ -1925,6 +1948,48 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yew-router"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27666236d9597eac9be560e841e415e20ba67020bc8cd081076be178e159c8bc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-match",
|
||||
"gloo",
|
||||
"js-sys",
|
||||
"log",
|
||||
"nom 5.1.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"yew",
|
||||
"yew-router-macro",
|
||||
"yew-router-route-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yew-router-macro"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0ace2924b7a175e2d1c0e62ee7022a5ad840040dcd52414ce5f410ab322dba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"yew-router-route-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yew-router-route-parser"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de4a67208fb46b900af18a7397938b01f379dfc18da34799cfa8347eec715697"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.2"
|
||||
|
|
|
@ -12,6 +12,4 @@ anyhow = "1"
|
|||
atty = "0.2"
|
||||
clap = "3.0.0-beta.4"
|
||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] }
|
||||
secrecy = { version = "0.8", features = ["serde"] }
|
||||
sodiumoxide = "0.2"
|
||||
url = "2"
|
||||
secrecy = { version = "0.8", features = ["serde"] }
|
103
cli/src/main.rs
103
cli/src/main.rs
|
@ -1,17 +1,16 @@
|
|||
#![warn(clippy::nursery, clippy::pedantic)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
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::{base64, hash, ParsedUrl, Url};
|
||||
use reqwest::blocking::Client;
|
||||
use reqwest::StatusCode;
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use sodiumoxide::base64;
|
||||
use sodiumoxide::base64::Variant::UrlSafe;
|
||||
use sodiumoxide::crypto::hash::sha256;
|
||||
use sodiumoxide::crypto::secretbox::{gen_key, gen_nonce, open, seal, Key, Nonce, KEYBYTES};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clap)]
|
||||
struct Opts {
|
||||
|
@ -32,7 +31,6 @@ enum Action {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
sodiumoxide::init().map_err(|_| anyhow!("Failed to init sodiumoxide"))?;
|
||||
let opts = Opts::parse();
|
||||
|
||||
match opts.action {
|
||||
|
@ -51,24 +49,24 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
|
|||
}
|
||||
|
||||
let (data, nonce, key, pw_used) = {
|
||||
let enc_key = gen_key();
|
||||
let nonce = gen_nonce();
|
||||
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);
|
||||
let mut enc =
|
||||
seal(&container, &nonce, &enc_key).map_err(|_| anyhow!("Failed to encrypt data"))?;
|
||||
|
||||
let pw_used = if let Some(password) = password {
|
||||
assert_eq!(sha256::DIGESTBYTES, KEYBYTES);
|
||||
let pw_hash = sha256::hash(password.expose_secret().as_bytes());
|
||||
let pw_key = Key::from_slice(pw_hash.as_ref()).expect("to succeed");
|
||||
enc = seal(&enc, &nonce.increment_le(), &pw_key);
|
||||
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)
|
||||
.map_err(|_| anyhow!("Failed to encrypt data"))?;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let key = base64::encode(&enc_key, UrlSafe);
|
||||
let nonce = base64::encode(&nonce, UrlSafe);
|
||||
let key = base64::encode(&enc_key);
|
||||
let nonce = base64::encode(&nonce);
|
||||
|
||||
(enc, nonce, key, pw_used)
|
||||
};
|
||||
|
@ -122,11 +120,10 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
|
|||
std::io::stdin().read_line(&mut input)?;
|
||||
input.pop(); // last character is \n, we need to drop it.
|
||||
|
||||
assert_eq!(sha256::DIGESTBYTES, KEYBYTES);
|
||||
let pw_hash = sha256::hash(input.as_bytes());
|
||||
let pw_key = Key::from_slice(pw_hash.as_ref()).expect("to succeed");
|
||||
let pw_hash = hash(input.as_bytes());
|
||||
let pw_key = Key::from_slice(pw_hash.as_ref());
|
||||
|
||||
data = open(&data, &url.nonce.increment_le(), &pw_key)
|
||||
data = open(&data, &url.nonce.increment(), pw_key)
|
||||
.map_err(|_| anyhow!("Failed to decrypt data. Incorrect password?"))?;
|
||||
}
|
||||
|
||||
|
@ -145,69 +142,3 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ParsedUrl {
|
||||
sanitized_url: Url,
|
||||
decryption_key: Key,
|
||||
nonce: Nonce,
|
||||
needs_password: bool,
|
||||
}
|
||||
|
||||
impl FromStr for ParsedUrl {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut url = Url::from_str(s)?;
|
||||
let fragment = url
|
||||
.fragment()
|
||||
.context("Missing fragment. The decryption key is part of the fragment.")?;
|
||||
if fragment.is_empty() {
|
||||
bail!("Empty fragment. The decryption key is part of the fragment.");
|
||||
}
|
||||
|
||||
let args = fragment.split('!').filter_map(|kv| {
|
||||
let (k, v) = {
|
||||
let mut iter = kv.split(':');
|
||||
(iter.next(), iter.next())
|
||||
};
|
||||
|
||||
Some((k?, v))
|
||||
});
|
||||
|
||||
let mut decryption_key = None;
|
||||
let mut needs_password = false;
|
||||
let mut nonce = None;
|
||||
|
||||
for (key, value) in args {
|
||||
match (key, value) {
|
||||
("key", Some(value)) => {
|
||||
let key = base64::decode(value, UrlSafe)
|
||||
.map_err(|_| anyhow!("Failed to decode key"))?;
|
||||
let key = Key::from_slice(&key).context("Failed to parse key")?;
|
||||
decryption_key = Some(key);
|
||||
}
|
||||
("pw", _) => {
|
||||
needs_password = true;
|
||||
}
|
||||
("nonce", Some(value)) => {
|
||||
nonce = Some(
|
||||
Nonce::from_slice(
|
||||
&base64::decode(value, UrlSafe)
|
||||
.map_err(|_| anyhow!("Failed to decode nonce"))?,
|
||||
)
|
||||
.context("Invalid nonce provided")?,
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
url.set_fragment(None);
|
||||
Ok(Self {
|
||||
sanitized_url: url,
|
||||
decryption_key: decryption_key.context("Missing decryption key")?,
|
||||
needs_password,
|
||||
nonce: nonce.context("Missing nonce")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,3 +6,10 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.13"
|
||||
chacha20poly1305 = "0.9"
|
||||
rand = "0.8"
|
||||
sha2 = "0.9"
|
||||
thiserror = "1"
|
||||
url = "2"
|
||||
|
|
|
@ -1,7 +1,205 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
#![warn(clippy::nursery, clippy::pedantic)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
//! Contains common functions and structures used by multiple projects
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use thiserror::Error;
|
||||
pub use url::Url;
|
||||
|
||||
use crate::crypto::{Key, Nonce};
|
||||
|
||||
pub mod base64 {
|
||||
/// URL-safe Base64 encoding.
|
||||
pub fn encode(input: impl AsRef<[u8]>) -> String {
|
||||
base64::encode_config(input, base64::URL_SAFE)
|
||||
}
|
||||
|
||||
/// URL-safe Base64 decoding.
|
||||
pub fn decode(input: impl AsRef<[u8]>) -> Result<Vec<u8>, base64::DecodeError> {
|
||||
base64::decode_config(input, base64::URL_SAFE)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hashes an input to output a usable key.
|
||||
pub fn hash(data: impl AsRef<[u8]>) -> crypto::Key {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
hasher.finalize()
|
||||
}
|
||||
|
||||
pub mod crypto {
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||
use chacha20poly1305::aead::{Aead, Error, NewAead};
|
||||
use chacha20poly1305::XChaCha20Poly1305;
|
||||
use chacha20poly1305::XNonce;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
pub use chacha20poly1305::Key;
|
||||
|
||||
/// Securely generates a random key and nonce.
|
||||
#[must_use]
|
||||
pub fn gen_key_nonce() -> (Key, Nonce) {
|
||||
let mut rng = thread_rng();
|
||||
let mut key: Key = GenericArray::default();
|
||||
rng.fill(key.as_mut_slice());
|
||||
let mut nonce = Nonce::default();
|
||||
rng.fill(nonce.as_mut_slice());
|
||||
(key, nonce)
|
||||
}
|
||||
|
||||
pub fn seal(plaintext: &[u8], nonce: &Nonce, key: &Key) -> Result<Vec<u8>, Error> {
|
||||
let cipher = XChaCha20Poly1305::new(key);
|
||||
cipher.encrypt(nonce, plaintext)
|
||||
}
|
||||
|
||||
pub fn open(encrypted: &[u8], nonce: &Nonce, key: &Key) -> Result<Vec<u8>, Error> {
|
||||
let cipher = XChaCha20Poly1305::new(key);
|
||||
cipher.decrypt(nonce, encrypted)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Nonce(XNonce);
|
||||
|
||||
impl Default for Nonce {
|
||||
fn default() -> Self {
|
||||
Self(GenericArray::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Nonce {
|
||||
type Target = XNonce;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Nonce {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Nonce {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Nonce {
|
||||
#[must_use]
|
||||
pub fn increment(&self) -> Self {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
Self(*XNonce::from_slice(slice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParsedUrl {
|
||||
pub sanitized_url: Url,
|
||||
pub decryption_key: Key,
|
||||
pub nonce: Nonce,
|
||||
pub needs_password: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PartialParsedUrl {
|
||||
pub decryption_key: Option<Key>,
|
||||
pub nonce: Option<Nonce>,
|
||||
pub needs_password: bool,
|
||||
}
|
||||
|
||||
impl From<&str> for PartialParsedUrl {
|
||||
fn from(fragment: &str) -> Self {
|
||||
let args = fragment.split('!').filter_map(|kv| {
|
||||
let (k, v) = {
|
||||
let mut iter = kv.split(':');
|
||||
(iter.next(), iter.next())
|
||||
};
|
||||
|
||||
Some((k?, v))
|
||||
});
|
||||
|
||||
let mut decryption_key = None;
|
||||
let mut needs_password = false;
|
||||
let mut nonce = None;
|
||||
|
||||
for (key, value) in args {
|
||||
match (key, value) {
|
||||
("key", Some(value)) => {
|
||||
decryption_key = base64::decode(value)
|
||||
.map(|k| Key::from_slice(&k).clone())
|
||||
.ok();
|
||||
}
|
||||
("pw", _) => {
|
||||
needs_password = true;
|
||||
}
|
||||
("nonce", Some(value)) => {
|
||||
nonce = base64::decode(value).as_deref().map(Nonce::from_slice).ok();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
decryption_key,
|
||||
nonce,
|
||||
needs_password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseUrlError {
|
||||
#[error("The provided url was bad")]
|
||||
BadUrl,
|
||||
#[error("Missing decryption key")]
|
||||
NeedKey,
|
||||
#[error("Missing nonce")]
|
||||
NeedNonce,
|
||||
#[error("Missing decryption key and nonce")]
|
||||
NeedKeyAndNonce,
|
||||
}
|
||||
|
||||
impl FromStr for ParsedUrl {
|
||||
type Err = ParseUrlError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut url = Url::from_str(s).map_err(|_| ParseUrlError::BadUrl)?;
|
||||
let fragment = url.fragment().ok_or(ParseUrlError::NeedKeyAndNonce)?;
|
||||
if fragment.is_empty() {
|
||||
return Err(ParseUrlError::NeedKeyAndNonce);
|
||||
}
|
||||
|
||||
let PartialParsedUrl {
|
||||
decryption_key,
|
||||
needs_password,
|
||||
nonce,
|
||||
} = PartialParsedUrl::from(fragment);
|
||||
|
||||
url.set_fragment(None);
|
||||
|
||||
let (decryption_key, nonce) = match (&decryption_key, nonce) {
|
||||
(None, None) => Err(ParseUrlError::NeedKeyAndNonce),
|
||||
(None, Some(_)) => Err(ParseUrlError::NeedKey),
|
||||
(Some(_), None) => Err(ParseUrlError::NeedNonce),
|
||||
(Some(k), Some(v)) => Ok((*k, v)),
|
||||
}?;
|
||||
|
||||
Ok(Self {
|
||||
sanitized_url: url,
|
||||
decryption_key,
|
||||
needs_password,
|
||||
nonce,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ axum = { version = "0.2", features = ["http2", "headers"] }
|
|||
bincode = "1"
|
||||
# We don't care about which version (We want to match with axum), we just need
|
||||
# to enable the feature
|
||||
bytes = { version = "*", features= ["serde"] }
|
||||
bytes = { version = "*", features = ["serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
# We just need to pull in whatever axum is pulling in
|
||||
headers = "*"
|
||||
lazy_static = "1"
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
#![warn(clippy::nursery, clippy::pedantic)]
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use paste::Expiration;
|
||||
use rand::prelude::StdRng;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rocksdb::IteratorMode;
|
||||
use rocksdb::WriteBatch;
|
||||
use rocksdb::{Options, DB};
|
||||
use short_code::ShortCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::body::Bytes;
|
||||
use axum::extract::{Extension, Path, TypedHeader};
|
||||
use axum::handler::{get, post};
|
||||
use axum::http::header::EXPIRES;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{AddExtensionLayer, Router};
|
||||
use chrono::Duration;
|
||||
use headers::HeaderMap;
|
||||
use rand::thread_rng;
|
||||
use rand::Rng;
|
||||
use rocksdb::IteratorMode;
|
||||
use rocksdb::WriteBatch;
|
||||
use rocksdb::{Options, DB};
|
||||
use tokio::task;
|
||||
use tracing::warn;
|
||||
use tracing::{error, instrument};
|
||||
|
||||
use crate::paste::Paste;
|
||||
use crate::time::FIVE_MINUTES;
|
||||
use crate::paste::{Expiration, Paste};
|
||||
use crate::short_code::ShortCode;
|
||||
|
||||
mod paste;
|
||||
mod short_code;
|
||||
mod time;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
@ -41,7 +39,7 @@ async fn main() -> Result<()> {
|
|||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
task::spawn(cleanup(Arc::clone(&stop_signal), Arc::clone(&db)));
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:8080".parse()?)
|
||||
axum::Server::bind(&"0.0.0.0:8081".parse()?)
|
||||
.serve(
|
||||
Router::new()
|
||||
.route("/", post(upload::<SHORT_CODE_SIZE>))
|
||||
|
@ -50,7 +48,6 @@ async fn main() -> Result<()> {
|
|||
get(paste::<SHORT_CODE_SIZE>).delete(delete::<SHORT_CODE_SIZE>),
|
||||
)
|
||||
.layer(AddExtensionLayer::new(db))
|
||||
.layer(AddExtensionLayer::new(StdRng::from_entropy()))
|
||||
.into_make_service(),
|
||||
)
|
||||
.await?;
|
||||
|
@ -64,7 +61,6 @@ async fn main() -> Result<()> {
|
|||
#[instrument(skip(db), err)]
|
||||
async fn upload<const N: usize>(
|
||||
Extension(db): Extension<Arc<DB>>,
|
||||
Extension(mut rng): Extension<StdRng>,
|
||||
maybe_expires: Option<TypedHeader<Expiration>>,
|
||||
body: Bytes,
|
||||
) -> Result<Vec<u8>, StatusCode> {
|
||||
|
@ -83,7 +79,7 @@ async fn upload<const N: usize>(
|
|||
// Try finding a code; give up after 1000 attempts
|
||||
// Statistics show that this is very unlikely to happen
|
||||
for _ in 0..1000 {
|
||||
let code: ShortCode<N> = rng.sample(short_code::Generator);
|
||||
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;
|
||||
|
@ -121,7 +117,7 @@ async fn upload<const N: usize>(
|
|||
async fn paste<const N: usize>(
|
||||
Extension(db): Extension<Arc<DB>>,
|
||||
Path(url): Path<ShortCode<N>>,
|
||||
) -> Result<Bytes, StatusCode> {
|
||||
) -> Result<(HeaderMap, Bytes), StatusCode> {
|
||||
let key = url.as_bytes();
|
||||
|
||||
let parsed: Paste = {
|
||||
|
@ -172,7 +168,11 @@ async fn paste<const N: usize>(
|
|||
})?;
|
||||
}
|
||||
|
||||
Ok(parsed.bytes)
|
||||
let mut map = HeaderMap::new();
|
||||
if let Some(expiration) = parsed.expiration {
|
||||
map.insert(EXPIRES, expiration.into());
|
||||
}
|
||||
Ok((map, parsed.bytes))
|
||||
}
|
||||
|
||||
#[instrument(skip(db))]
|
||||
|
@ -189,7 +189,7 @@ async fn delete<const N: usize>(
|
|||
/// Periodic clean-up task that deletes expired entries.
|
||||
async fn cleanup(stop_signal: Arc<AtomicBool>, db: Arc<DB>) {
|
||||
while !stop_signal.load(Ordering::Acquire) {
|
||||
tokio::time::sleep(*FIVE_MINUTES).await;
|
||||
tokio::time::sleep(Duration::minutes(5).to_std().expect("infallible")).await;
|
||||
let mut batch = WriteBatch::default();
|
||||
for (key, value) in db.snapshot().iterator(IteratorMode::Start) {
|
||||
// TODO: only partially decode struct for max perf
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use axum::body::Bytes;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use headers::{Header, HeaderName, HeaderValue};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::time::{FIVE_MINUTES, ONE_DAY, ONE_HOUR, TEN_MINUTES};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Paste {
|
||||
expiration: Option<Expiration>,
|
||||
pub expiration: Option<Expiration>,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
|
@ -25,10 +22,7 @@ impl Paste {
|
|||
self.expiration
|
||||
.map(|expires| match expires {
|
||||
Expiration::BurnAfterReading => false,
|
||||
Expiration::UnixTime(expiration) => {
|
||||
let now = time_since_unix();
|
||||
expiration < now
|
||||
}
|
||||
Expiration::UnixTime(expiration) => expiration < Utc::now(),
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
@ -41,7 +35,7 @@ impl Paste {
|
|||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Expiration {
|
||||
BurnAfterReading,
|
||||
UnixTime(Duration),
|
||||
UnixTime(DateTime<Utc>),
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
@ -58,34 +52,44 @@ impl Header for Expiration {
|
|||
Self: Sized,
|
||||
I: Iterator<Item = &'i HeaderValue>,
|
||||
{
|
||||
let now = time_since_unix();
|
||||
match values
|
||||
.next()
|
||||
.ok_or_else(headers::Error::invalid)?
|
||||
.as_bytes()
|
||||
{
|
||||
b"read" => Ok(Self::BurnAfterReading),
|
||||
b"5m" => Ok(Self::UnixTime(now + *FIVE_MINUTES)),
|
||||
b"10m" => Ok(Self::UnixTime(now + *TEN_MINUTES)),
|
||||
b"1h" => Ok(Self::UnixTime(now + *ONE_HOUR)),
|
||||
b"1d" => Ok(Self::UnixTime(now + *ONE_DAY)),
|
||||
b"5m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(5))),
|
||||
b"10m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(10))),
|
||||
b"1h" => Ok(Self::UnixTime(Utc::now() + Duration::hours(1))),
|
||||
b"1d" => Ok(Self::UnixTime(Utc::now() + Duration::days(1))),
|
||||
_ => Err(headers::Error::invalid()),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode<E: Extend<HeaderValue>>(&self, _: &mut E) {
|
||||
unimplemented!("This shouldn't need implementation")
|
||||
fn encode<E: Extend<HeaderValue>>(&self, container: &mut E) {
|
||||
container.extend(std::iter::once(self.into()));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Expiration> for HeaderValue {
|
||||
fn from(expiration: &Expiration) -> Self {
|
||||
unsafe {
|
||||
HeaderValue::from_maybe_shared_unchecked(match expiration {
|
||||
Expiration::BurnAfterReading => Bytes::from_static(b"0"),
|
||||
Expiration::UnixTime(duration) => Bytes::from(duration.to_rfc3339()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expiration> for HeaderValue {
|
||||
fn from(expiration: Expiration) -> Self {
|
||||
(&expiration).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Expiration {
|
||||
fn default() -> Self {
|
||||
Self::UnixTime(time_since_unix() + *ONE_DAY)
|
||||
Self::UnixTime(Utc::now() + Duration::days(1))
|
||||
}
|
||||
}
|
||||
|
||||
fn time_since_unix() -> Duration {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("time since epoch to always work")
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FIVE_MINUTES: Duration = Duration::from_secs(5 * 60);
|
||||
pub static ref TEN_MINUTES: Duration = Duration::from_secs(5 * 60);
|
||||
pub static ref ONE_HOUR: Duration = Duration::from_secs(5 * 60);
|
||||
pub static ref ONE_DAY: Duration = Duration::from_secs(5 * 60);
|
||||
}
|
|
@ -11,6 +11,8 @@ omegaupload-common = { path = "../common" }
|
|||
getrandom = { version = "*", features = ["js"] }
|
||||
|
||||
anyhow = "1"
|
||||
downcast-rs = "1"
|
||||
gloo-console = "0.1"
|
||||
http = "0.2"
|
||||
web-sys = { version = "0.3", features = ["Request", "Window"] }
|
||||
yew = { version = "0.18", features = ["wasm-bindgen-futures"] }
|
||||
|
|
8
web/dist/index.html
vendored
8
web/dist/index.html
vendored
|
@ -1,11 +1,13 @@
|
|||
<!DOCTYPE html><html><head>
|
||||
<meta charset="utf-8">
|
||||
<title>Omegaupload</title>
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
|
||||
<link rel="preload" href="/index-8214e6336313b7fe_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<link rel="modulepreload" href="/index-8214e6336313b7fe.js"></head>
|
||||
<link rel="preload" href="/index-2ccb59df4818eaff_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<link rel="modulepreload" href="/index-2ccb59df4818eaff.js"></head>
|
||||
|
||||
<body><script type="module">import init from '/index-8214e6336313b7fe.js';init('/index-8214e6336313b7fe_bg.wasm');</script><script>(function () {
|
||||
<body><script type="module">import init from '/index-2ccb59df4818eaff.js';init('/index-2ccb59df4818eaff_bg.wasm');</script><script>(function () {
|
||||
var url = 'ws://' + window.location.host + '/_trunk/ws';
|
||||
var poll_interval = 5000;
|
||||
var reload_upon_connect = () => {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Omegaupload</title>
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
</head>
|
||||
|
||||
</html>
|
367
web/src/main.rs
367
web/src/main.rs
|
@ -1,13 +1,19 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, bail};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use gloo_console::log;
|
||||
use http::uri::{Authority, PathAndQuery};
|
||||
use omegaupload_common::crypto::{Key, Nonce};
|
||||
use omegaupload_common::ParsedUrl;
|
||||
use omegaupload_common::crypto::{open, Key, Nonce};
|
||||
use omegaupload_common::{ParsedUrl, PartialParsedUrl};
|
||||
use yew::format::{Binary, Nothing};
|
||||
use yew::services::fetch::{FetchTask, Request, Response, StatusCode, Uri};
|
||||
use yew::services::{ConsoleService, FetchService};
|
||||
use yew::services::FetchService;
|
||||
use yew::utils::window;
|
||||
use yew::Properties;
|
||||
use yew::{html, Component, ComponentLink, Html, ShouldRender};
|
||||
use yew_router::router::Router;
|
||||
use yew_router::Switch;
|
||||
|
@ -64,87 +70,13 @@ fn render_route(route: Route) -> Html {
|
|||
}
|
||||
|
||||
struct Paste {
|
||||
state: PasteState,
|
||||
state: Box<dyn PasteState>,
|
||||
// Need to keep this alive so that the fetch request doesn't get dropped
|
||||
_fetch_handle: FetchTask,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
enum PasteState {
|
||||
NotFound,
|
||||
Error,
|
||||
NeedInformation {
|
||||
data: Option<Vec<u8>>,
|
||||
key: Option<Key>,
|
||||
nonce: Option<Nonce>,
|
||||
needs_pw: bool,
|
||||
},
|
||||
Done {
|
||||
data: Vec<u8>,
|
||||
key: Key,
|
||||
nonce: Nonce,
|
||||
password: Option<Key>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PasteState {
|
||||
fn set_data(&mut self, new_data: Vec<u8>) {
|
||||
match self {
|
||||
PasteState::NeedInformation { data, .. } => {
|
||||
assert!(data.is_none());
|
||||
*data = Some(new_data);
|
||||
}
|
||||
_ => panic!("Tried to set data in invalid state"),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_key(&mut self, new_key: Key) {
|
||||
match self {
|
||||
PasteState::NeedInformation { key, .. } => {
|
||||
assert!(key.is_none());
|
||||
*key = Some(new_key);
|
||||
}
|
||||
_ => panic!("Tried to set key in invalid state"),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_nonce(&mut self, new_nonce: Nonce) {
|
||||
match self {
|
||||
PasteState::NeedInformation { nonce, .. } => {
|
||||
assert!(nonce.is_none());
|
||||
*nonce = Some(new_nonce);
|
||||
}
|
||||
_ => panic!("Tried to set key in invalid state"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_completed(&self) -> bool {
|
||||
match self {
|
||||
PasteState::NeedInformation {
|
||||
data,
|
||||
key,
|
||||
nonce,
|
||||
needs_pw,
|
||||
} => todo!(),
|
||||
_ => panic!(),
|
||||
}
|
||||
assert!(matches!(self, PasteState::NeedInformation { .. }));
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
enum PasteMessage {
|
||||
Data(Vec<u8>),
|
||||
Error(anyhow::Error),
|
||||
DecryptionKey(Key),
|
||||
Nonce(Nonce),
|
||||
Password(Key),
|
||||
NotFound,
|
||||
_fetch_handle: Option<FetchTask>,
|
||||
}
|
||||
|
||||
impl Component for Paste {
|
||||
type Message = PasteMessage;
|
||||
type Message = Box<dyn PasteState>;
|
||||
|
||||
type Properties = ();
|
||||
|
||||
|
@ -152,46 +84,53 @@ impl Component for Paste {
|
|||
let url = String::from(window().location().to_string());
|
||||
let request_uri = {
|
||||
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
|
||||
uri_parts
|
||||
.authority
|
||||
.as_mut()
|
||||
.map(|auth| *auth = Authority::from_str(auth.host()).unwrap());
|
||||
uri_parts.path_and_query.as_mut().map(|parts| {
|
||||
*parts = PathAndQuery::from_str(&format!("/api{}", parts.path())).unwrap()
|
||||
});
|
||||
Uri::from_parts(uri_parts).unwrap()
|
||||
};
|
||||
|
||||
ConsoleService::log(&request_uri.to_string());
|
||||
let link_clone = link.clone();
|
||||
|
||||
let fetch = FetchService::fetch_binary(
|
||||
Request::get(request_uri).body(Nothing).unwrap(),
|
||||
link.callback(move |resp: Response<Binary>| match resp.status() {
|
||||
StatusCode::OK => PasteMessage::Data(resp.into_body().unwrap()),
|
||||
StatusCode::NOT_FOUND => PasteMessage::NotFound,
|
||||
code => PasteMessage::Error(anyhow!("Got resp error: {}", code)),
|
||||
Request::get(&request_uri).body(Nothing).unwrap(),
|
||||
link.callback_once(move |resp: Response<Binary>| match resp.status() {
|
||||
StatusCode::OK => {
|
||||
let partial = PastePartial::new(
|
||||
resp,
|
||||
url.split_once('#')
|
||||
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
|
||||
.unwrap_or_default(),
|
||||
link_clone,
|
||||
);
|
||||
|
||||
if let Ok(completed) = PasteComplete::try_from(partial.clone()) {
|
||||
Box::new(completed) as Box<dyn PasteState>
|
||||
} else {
|
||||
Box::new(partial) as Box<dyn PasteState>
|
||||
}
|
||||
}
|
||||
StatusCode::NOT_FOUND => Box::new(PasteNotFound) as Box<dyn PasteState>,
|
||||
code => {
|
||||
Box::new(PasteError(anyhow!("Got resp error: {}", code))) as Box<dyn PasteState>
|
||||
}
|
||||
}),
|
||||
);
|
||||
Self {
|
||||
state: PasteState::NeedInformation {
|
||||
data: None,
|
||||
key: None,
|
||||
nonce: None,
|
||||
needs_pw: false,
|
||||
|
||||
match fetch {
|
||||
Ok(task) => Self {
|
||||
state: Box::new(PasteLoading),
|
||||
_fetch_handle: Some(task),
|
||||
},
|
||||
Err(e) => Self {
|
||||
state: Box::new(PasteError(e)) as Box<dyn PasteState>,
|
||||
_fetch_handle: None,
|
||||
},
|
||||
_fetch_handle: fetch.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
PasteMessage::Data(data) => self.state.set_data(data),
|
||||
PasteMessage::Error(e) => self.state = PasteState::Error,
|
||||
PasteMessage::NotFound => self.state = PasteState::NotFound,
|
||||
PasteMessage::DecryptionKey(key) => self.state.set_key(key),
|
||||
PasteMessage::Nonce(nonce) => self.state.set_nonce(nonce),
|
||||
PasteMessage::Password(_) => todo!(),
|
||||
}
|
||||
self.state = msg;
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -200,21 +139,207 @@ impl Component for Paste {
|
|||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
match self.state {
|
||||
PasteState::NeedInformation { .. } => todo!(),
|
||||
PasteState::Done { .. } => {
|
||||
todo!()
|
||||
}
|
||||
PasteState::Error => html! {
|
||||
<main>
|
||||
{"An error occurred. Please try again later."}
|
||||
</main>
|
||||
},
|
||||
PasteState::NotFound => html! {
|
||||
<main>
|
||||
{"The paste you are looking for is not here."}
|
||||
</main>
|
||||
},
|
||||
if self.state.is::<PasteLoading>() {
|
||||
return html! {
|
||||
<p>{ "loading" }</p>
|
||||
};
|
||||
}
|
||||
|
||||
if self.state.is::<PasteNotFound>() {
|
||||
return html! {
|
||||
<p>{ "Either the paste has been burned or one never existed." }</p>
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(error) = self.state.downcast_ref::<PasteError>() {
|
||||
return html! {
|
||||
<p>{ error.0.to_string() }</p>
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(partial_paste) = self.state.downcast_ref::<PastePartial>() {
|
||||
return partial_paste.view();
|
||||
}
|
||||
|
||||
if let Some(paste) = self.state.downcast_ref::<PasteComplete>() {
|
||||
return paste.view();
|
||||
}
|
||||
|
||||
html! {
|
||||
"An internal error occurred: client is in unknown state!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PasteLoading;
|
||||
struct PasteNotFound;
|
||||
|
||||
struct PasteError(anyhow::Error);
|
||||
|
||||
#[derive(Properties, Clone, Debug)]
|
||||
struct PastePartial {
|
||||
parent: ComponentLink<Paste>,
|
||||
data: Option<Rc<Vec<u8>>>,
|
||||
key: Option<Key>,
|
||||
nonce: Option<Nonce>,
|
||||
password: Option<Key>,
|
||||
needs_pw: bool,
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone)]
|
||||
struct PasteComplete {
|
||||
data: Rc<Vec<u8>>,
|
||||
key: Key,
|
||||
nonce: Nonce,
|
||||
password: Option<Key>,
|
||||
}
|
||||
|
||||
trait PasteState: Downcast {}
|
||||
impl_downcast!(PasteState);
|
||||
impl PasteState for PasteLoading {}
|
||||
impl PasteState for PasteNotFound {}
|
||||
impl PasteState for PasteError {}
|
||||
impl PasteState for PastePartial {}
|
||||
impl PasteState for PasteComplete {}
|
||||
|
||||
impl PastePartial {
|
||||
fn new(
|
||||
resp: Response<Binary>,
|
||||
partial_parsed_url: PartialParsedUrl,
|
||||
parent: ComponentLink<Paste>,
|
||||
) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
data: Some(Rc::new(resp.into_body().unwrap())),
|
||||
key: partial_parsed_url.decryption_key,
|
||||
nonce: partial_parsed_url.nonce,
|
||||
password: None,
|
||||
needs_pw: partial_parsed_url.needs_password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PartialPasteMessage {
|
||||
DecryptionKey(Key),
|
||||
Nonce(Nonce),
|
||||
Password(Key),
|
||||
}
|
||||
|
||||
impl Component for PastePartial {
|
||||
type Message = PartialPasteMessage;
|
||||
|
||||
type Properties = Self;
|
||||
|
||||
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
props
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
PartialPasteMessage::DecryptionKey(key) => self.key = Some(key),
|
||||
PartialPasteMessage::Nonce(nonce) => self.nonce = Some(nonce),
|
||||
PartialPasteMessage::Password(password) => self.password = Some(password),
|
||||
}
|
||||
|
||||
match (self.data.clone(), self.key, self.nonce, self.password) {
|
||||
(Some(data), Some(key), Some(nonce), Some(password)) if self.needs_pw => {
|
||||
self.parent.callback(move |Nothing| {
|
||||
Box::new(PasteComplete::new(
|
||||
Rc::clone(&data),
|
||||
key,
|
||||
nonce,
|
||||
Some(password),
|
||||
)) as Box<dyn PasteState>
|
||||
});
|
||||
}
|
||||
(Some(data), Some(key), Some(nonce), None) if !self.needs_pw => {
|
||||
self.parent.callback(move |Nothing| {
|
||||
Box::new(PasteComplete::new(Rc::clone(&data), key, nonce, None))
|
||||
as Box<dyn PasteState>
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// parent should re-render so this element should be dropped.
|
||||
false
|
||||
}
|
||||
|
||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
"got partial data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PastePartial> for PasteComplete {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(partial: PastePartial) -> Result<Self, Self::Error> {
|
||||
match partial {
|
||||
PastePartial {
|
||||
data: Some(data),
|
||||
key: Some(key),
|
||||
nonce: Some(nonce),
|
||||
password: Some(password),
|
||||
needs_pw: true,
|
||||
..
|
||||
} => Ok(PasteComplete {
|
||||
data,
|
||||
key,
|
||||
nonce,
|
||||
password: Some(password),
|
||||
}),
|
||||
PastePartial {
|
||||
data: Some(data),
|
||||
key: Some(key),
|
||||
nonce: Some(nonce),
|
||||
needs_pw: false,
|
||||
..
|
||||
} => Ok(PasteComplete {
|
||||
data,
|
||||
key,
|
||||
nonce,
|
||||
password: None,
|
||||
}),
|
||||
_ => bail!("missing field"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PasteComplete {
|
||||
fn new(data: Rc<Vec<u8>>, key: Key, nonce: Nonce, password: Option<Key>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
key,
|
||||
nonce,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let stage_one = if let Some(password) = self.password {
|
||||
open(&self.data, &self.nonce.increment(), &password).unwrap()
|
||||
} else {
|
||||
self.data.to_vec()
|
||||
};
|
||||
|
||||
let decrypted = open(&stage_one, &self.nonce, &self.key).unwrap();
|
||||
|
||||
if let Ok(str) = String::from_utf8(decrypted) {
|
||||
html! {
|
||||
<>
|
||||
<pre><code>{str}</code></pre>
|
||||
|
||||
<script>{ "hljs.highlightAll();" }</script>
|
||||
</>
|
||||
}
|
||||
} else {
|
||||
html! { "binary" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue