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
|
/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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.51"
|
version = "0.1.51"
|
||||||
|
@ -194,7 +200,7 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89"
|
checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom 6.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -243,6 +249,8 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -326,13 +334,10 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "downcast-rs"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
|
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||||
dependencies = [
|
|
||||||
"signature",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
|
@ -447,8 +452,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -469,6 +476,18 @@ dependencies = [
|
||||||
"gloo-timers",
|
"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]]
|
[[package]]
|
||||||
name = "gloo-console-timer"
|
name = "gloo-console-timer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -715,6 +734,19 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.103"
|
version = "0.2.103"
|
||||||
|
@ -743,18 +775,6 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -813,6 +833,17 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "6.1.2"
|
version = "6.1.2"
|
||||||
|
@ -873,13 +904,20 @@ dependencies = [
|
||||||
"omegaupload-common",
|
"omegaupload-common",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"sodiumoxide",
|
|
||||||
"url",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "omegaupload-common"
|
name = "omegaupload-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"rand",
|
||||||
|
"sha2",
|
||||||
|
"thiserror",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "omegaupload-server"
|
name = "omegaupload-server"
|
||||||
|
@ -889,6 +927,7 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"headers",
|
"headers",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -903,8 +942,15 @@ dependencies = [
|
||||||
name = "omegaupload-web"
|
name = "omegaupload-web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chacha20poly1305",
|
"anyhow",
|
||||||
|
"downcast-rs",
|
||||||
|
"getrandom",
|
||||||
|
"gloo-console",
|
||||||
|
"http",
|
||||||
|
"omegaupload-common",
|
||||||
|
"web-sys",
|
||||||
"yew",
|
"yew",
|
||||||
|
"yew-router",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -969,12 +1015,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -1204,15 +1244,6 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
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]]
|
[[package]]
|
||||||
name = "sct"
|
name = "sct"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -1289,6 +1320,19 @@ dependencies = [
|
||||||
"opaque-debug",
|
"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]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -1304,12 +1348,6 @@ 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 = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signature"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -1332,24 +1370,18 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -1716,17 +1748,6 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
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]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1750,6 +1771,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1925,6 +1948,48 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
|
|
@ -13,5 +13,3 @@ atty = "0.2"
|
||||||
clap = "3.0.0-beta.4"
|
clap = "3.0.0-beta.4"
|
||||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] }
|
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] }
|
||||||
secrecy = { version = "0.8", features = ["serde"] }
|
secrecy = { version = "0.8", features = ["serde"] }
|
||||||
sodiumoxide = "0.2"
|
|
||||||
url = "2"
|
|
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::io::{Read, Write};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
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::{base64, hash, ParsedUrl, Url};
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
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)]
|
#[derive(Clap)]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
|
@ -32,7 +31,6 @@ enum Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
sodiumoxide::init().map_err(|_| anyhow!("Failed to init sodiumoxide"))?;
|
|
||||||
let opts = Opts::parse();
|
let opts = Opts::parse();
|
||||||
|
|
||||||
match opts.action {
|
match opts.action {
|
||||||
|
@ -51,24 +49,24 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (data, nonce, key, pw_used) = {
|
let (data, nonce, key, pw_used) = {
|
||||||
let enc_key = gen_key();
|
let (enc_key, nonce) = gen_key_nonce();
|
||||||
let nonce = gen_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(&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 {
|
let pw_used = if let Some(password) = password {
|
||||||
assert_eq!(sha256::DIGESTBYTES, KEYBYTES);
|
let pw_hash = hash(password.expose_secret().as_bytes());
|
||||||
let pw_hash = sha256::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()).expect("to succeed");
|
enc = seal(&enc, &nonce.increment(), pw_key)
|
||||||
enc = seal(&enc, &nonce.increment_le(), &pw_key);
|
.map_err(|_| anyhow!("Failed to encrypt data"))?;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = base64::encode(&enc_key, UrlSafe);
|
let key = base64::encode(&enc_key);
|
||||||
let nonce = base64::encode(&nonce, UrlSafe);
|
let nonce = base64::encode(&nonce);
|
||||||
|
|
||||||
(enc, nonce, key, pw_used)
|
(enc, nonce, key, pw_used)
|
||||||
};
|
};
|
||||||
|
@ -122,11 +120,10 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
|
||||||
std::io::stdin().read_line(&mut input)?;
|
std::io::stdin().read_line(&mut input)?;
|
||||||
input.pop(); // last character is \n, we need to drop it.
|
input.pop(); // last character is \n, we need to drop it.
|
||||||
|
|
||||||
assert_eq!(sha256::DIGESTBYTES, KEYBYTES);
|
let pw_hash = hash(input.as_bytes());
|
||||||
let pw_hash = sha256::hash(input.as_bytes());
|
let pw_key = Key::from_slice(pw_hash.as_ref());
|
||||||
let pw_key = Key::from_slice(pw_hash.as_ref()).expect("to succeed");
|
|
||||||
|
|
||||||
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?"))?;
|
.map_err(|_| anyhow!("Failed to decrypt data. Incorrect password?"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,69 +142,3 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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)]
|
#![warn(clippy::nursery, clippy::pedantic)]
|
||||||
mod tests {
|
#![deny(unsafe_code)]
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
//! Contains common functions and structures used by multiple projects
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
|
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"
|
bincode = "1"
|
||||||
# We don't care about which version (We want to match with axum), we just need
|
# We don't care about which version (We want to match with axum), we just need
|
||||||
# to enable the feature
|
# 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
|
# We just need to pull in whatever axum is pulling in
|
||||||
headers = "*"
|
headers = "*"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
|
@ -1,33 +1,31 @@
|
||||||
#![warn(clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::nursery, clippy::pedantic)]
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
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 anyhow::Result;
|
||||||
use axum::body::Bytes;
|
use axum::body::Bytes;
|
||||||
use axum::extract::{Extension, Path, TypedHeader};
|
use axum::extract::{Extension, Path, TypedHeader};
|
||||||
use axum::handler::{get, post};
|
use axum::handler::{get, post};
|
||||||
|
use axum::http::header::EXPIRES;
|
||||||
|
use axum::http::StatusCode;
|
||||||
use axum::{AddExtensionLayer, Router};
|
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 tokio::task;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
use crate::paste::Paste;
|
use crate::paste::{Expiration, Paste};
|
||||||
use crate::time::FIVE_MINUTES;
|
use crate::short_code::ShortCode;
|
||||||
|
|
||||||
mod paste;
|
mod paste;
|
||||||
mod short_code;
|
mod short_code;
|
||||||
mod time;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
@ -41,7 +39,7 @@ async fn main() -> Result<()> {
|
||||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||||
task::spawn(cleanup(Arc::clone(&stop_signal), Arc::clone(&db)));
|
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(
|
.serve(
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", post(upload::<SHORT_CODE_SIZE>))
|
.route("/", post(upload::<SHORT_CODE_SIZE>))
|
||||||
|
@ -50,7 +48,6 @@ async fn main() -> Result<()> {
|
||||||
get(paste::<SHORT_CODE_SIZE>).delete(delete::<SHORT_CODE_SIZE>),
|
get(paste::<SHORT_CODE_SIZE>).delete(delete::<SHORT_CODE_SIZE>),
|
||||||
)
|
)
|
||||||
.layer(AddExtensionLayer::new(db))
|
.layer(AddExtensionLayer::new(db))
|
||||||
.layer(AddExtensionLayer::new(StdRng::from_entropy()))
|
|
||||||
.into_make_service(),
|
.into_make_service(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -64,7 +61,6 @@ async fn main() -> Result<()> {
|
||||||
#[instrument(skip(db), err)]
|
#[instrument(skip(db), err)]
|
||||||
async fn upload<const N: usize>(
|
async fn upload<const N: usize>(
|
||||||
Extension(db): Extension<Arc<DB>>,
|
Extension(db): Extension<Arc<DB>>,
|
||||||
Extension(mut rng): Extension<StdRng>,
|
|
||||||
maybe_expires: Option<TypedHeader<Expiration>>,
|
maybe_expires: Option<TypedHeader<Expiration>>,
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
) -> Result<Vec<u8>, StatusCode> {
|
) -> Result<Vec<u8>, StatusCode> {
|
||||||
|
@ -83,7 +79,7 @@ async fn upload<const N: usize>(
|
||||||
// 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 _ 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 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;
|
||||||
|
@ -121,7 +117,7 @@ async fn upload<const N: usize>(
|
||||||
async fn paste<const N: usize>(
|
async fn paste<const N: usize>(
|
||||||
Extension(db): Extension<Arc<DB>>,
|
Extension(db): Extension<Arc<DB>>,
|
||||||
Path(url): Path<ShortCode<N>>,
|
Path(url): Path<ShortCode<N>>,
|
||||||
) -> Result<Bytes, StatusCode> {
|
) -> Result<(HeaderMap, Bytes), StatusCode> {
|
||||||
let key = url.as_bytes();
|
let key = url.as_bytes();
|
||||||
|
|
||||||
let parsed: Paste = {
|
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))]
|
#[instrument(skip(db))]
|
||||||
|
@ -189,7 +189,7 @@ async fn delete<const N: usize>(
|
||||||
/// Periodic clean-up task that deletes expired entries.
|
/// Periodic clean-up task that deletes expired entries.
|
||||||
async fn cleanup(stop_signal: Arc<AtomicBool>, db: Arc<DB>) {
|
async fn cleanup(stop_signal: Arc<AtomicBool>, db: Arc<DB>) {
|
||||||
while !stop_signal.load(Ordering::Acquire) {
|
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();
|
let mut batch = WriteBatch::default();
|
||||||
for (key, value) in db.snapshot().iterator(IteratorMode::Start) {
|
for (key, value) in db.snapshot().iterator(IteratorMode::Start) {
|
||||||
// TODO: only partially decode struct for max perf
|
// TODO: only partially decode struct for max perf
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
use axum::body::Bytes;
|
use axum::body::Bytes;
|
||||||
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use headers::{Header, HeaderName, HeaderValue};
|
use headers::{Header, HeaderName, HeaderValue};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::time::{FIVE_MINUTES, ONE_DAY, ONE_HOUR, TEN_MINUTES};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Paste {
|
pub struct Paste {
|
||||||
expiration: Option<Expiration>,
|
pub expiration: Option<Expiration>,
|
||||||
pub bytes: Bytes,
|
pub bytes: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,10 +22,7 @@ impl Paste {
|
||||||
self.expiration
|
self.expiration
|
||||||
.map(|expires| match expires {
|
.map(|expires| match expires {
|
||||||
Expiration::BurnAfterReading => false,
|
Expiration::BurnAfterReading => false,
|
||||||
Expiration::UnixTime(expiration) => {
|
Expiration::UnixTime(expiration) => expiration < Utc::now(),
|
||||||
let now = time_since_unix();
|
|
||||||
expiration < now
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
@ -41,7 +35,7 @@ impl Paste {
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||||
pub enum Expiration {
|
pub enum Expiration {
|
||||||
BurnAfterReading,
|
BurnAfterReading,
|
||||||
UnixTime(Duration),
|
UnixTime(DateTime<Utc>),
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -58,34 +52,44 @@ impl Header for Expiration {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
I: Iterator<Item = &'i HeaderValue>,
|
I: Iterator<Item = &'i HeaderValue>,
|
||||||
{
|
{
|
||||||
let now = time_since_unix();
|
|
||||||
match values
|
match values
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(headers::Error::invalid)?
|
.ok_or_else(headers::Error::invalid)?
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
{
|
{
|
||||||
b"read" => Ok(Self::BurnAfterReading),
|
b"read" => Ok(Self::BurnAfterReading),
|
||||||
b"5m" => Ok(Self::UnixTime(now + *FIVE_MINUTES)),
|
b"5m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(5))),
|
||||||
b"10m" => Ok(Self::UnixTime(now + *TEN_MINUTES)),
|
b"10m" => Ok(Self::UnixTime(Utc::now() + Duration::minutes(10))),
|
||||||
b"1h" => Ok(Self::UnixTime(now + *ONE_HOUR)),
|
b"1h" => Ok(Self::UnixTime(Utc::now() + Duration::hours(1))),
|
||||||
b"1d" => Ok(Self::UnixTime(now + *ONE_DAY)),
|
b"1d" => Ok(Self::UnixTime(Utc::now() + Duration::days(1))),
|
||||||
_ => Err(headers::Error::invalid()),
|
_ => Err(headers::Error::invalid()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode<E: Extend<HeaderValue>>(&self, _: &mut E) {
|
fn encode<E: Extend<HeaderValue>>(&self, container: &mut E) {
|
||||||
unimplemented!("This shouldn't need implementation")
|
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 {
|
impl Default for Expiration {
|
||||||
fn default() -> Self {
|
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"] }
|
getrandom = { version = "*", features = ["js"] }
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
downcast-rs = "1"
|
||||||
|
gloo-console = "0.1"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
web-sys = { version = "0.3", features = ["Request", "Window"] }
|
web-sys = { version = "0.3", features = ["Request", "Window"] }
|
||||||
yew = { version = "0.18", features = ["wasm-bindgen-futures"] }
|
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>
|
<!DOCTYPE html><html><head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Omegaupload</title>
|
<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="preload" href="/index-2ccb59df4818eaff_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||||
<link rel="modulepreload" href="/index-8214e6336313b7fe.js"></head>
|
<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 url = 'ws://' + window.location.host + '/_trunk/ws';
|
||||||
var poll_interval = 5000;
|
var poll_interval = 5000;
|
||||||
var reload_upon_connect = () => {
|
var reload_upon_connect = () => {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Omegaupload</title>
|
<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>
|
</head>
|
||||||
|
|
||||||
</html>
|
</html>
|
365
web/src/main.rs
365
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 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 http::uri::{Authority, PathAndQuery};
|
||||||
use omegaupload_common::crypto::{Key, Nonce};
|
use omegaupload_common::crypto::{open, Key, Nonce};
|
||||||
use omegaupload_common::ParsedUrl;
|
use omegaupload_common::{ParsedUrl, PartialParsedUrl};
|
||||||
use yew::format::{Binary, Nothing};
|
use yew::format::{Binary, Nothing};
|
||||||
use yew::services::fetch::{FetchTask, Request, Response, StatusCode, Uri};
|
use yew::services::fetch::{FetchTask, Request, Response, StatusCode, Uri};
|
||||||
use yew::services::{ConsoleService, FetchService};
|
use yew::services::FetchService;
|
||||||
use yew::utils::window;
|
use yew::utils::window;
|
||||||
|
use yew::Properties;
|
||||||
use yew::{html, Component, ComponentLink, Html, ShouldRender};
|
use yew::{html, Component, ComponentLink, Html, ShouldRender};
|
||||||
use yew_router::router::Router;
|
use yew_router::router::Router;
|
||||||
use yew_router::Switch;
|
use yew_router::Switch;
|
||||||
|
@ -64,87 +70,13 @@ fn render_route(route: Route) -> Html {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Paste {
|
struct Paste {
|
||||||
state: PasteState,
|
state: Box<dyn PasteState>,
|
||||||
// Need to keep this alive so that the fetch request doesn't get dropped
|
// Need to keep this alive so that the fetch request doesn't get dropped
|
||||||
_fetch_handle: FetchTask,
|
_fetch_handle: Option<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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Paste {
|
impl Component for Paste {
|
||||||
type Message = PasteMessage;
|
type Message = Box<dyn PasteState>;
|
||||||
|
|
||||||
type Properties = ();
|
type Properties = ();
|
||||||
|
|
||||||
|
@ -152,46 +84,53 @@ impl Component for Paste {
|
||||||
let url = String::from(window().location().to_string());
|
let url = String::from(window().location().to_string());
|
||||||
let request_uri = {
|
let request_uri = {
|
||||||
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
|
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| {
|
uri_parts.path_and_query.as_mut().map(|parts| {
|
||||||
*parts = PathAndQuery::from_str(&format!("/api{}", parts.path())).unwrap()
|
*parts = PathAndQuery::from_str(&format!("/api{}", parts.path())).unwrap()
|
||||||
});
|
});
|
||||||
Uri::from_parts(uri_parts).unwrap()
|
Uri::from_parts(uri_parts).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
ConsoleService::log(&request_uri.to_string());
|
let link_clone = link.clone();
|
||||||
|
|
||||||
let fetch = FetchService::fetch_binary(
|
let fetch = FetchService::fetch_binary(
|
||||||
Request::get(request_uri).body(Nothing).unwrap(),
|
Request::get(&request_uri).body(Nothing).unwrap(),
|
||||||
link.callback(move |resp: Response<Binary>| match resp.status() {
|
link.callback_once(move |resp: Response<Binary>| match resp.status() {
|
||||||
StatusCode::OK => PasteMessage::Data(resp.into_body().unwrap()),
|
StatusCode::OK => {
|
||||||
StatusCode::NOT_FOUND => PasteMessage::NotFound,
|
let partial = PastePartial::new(
|
||||||
code => PasteMessage::Error(anyhow!("Got resp error: {}", code)),
|
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 {
|
match fetch {
|
||||||
data: None,
|
Ok(task) => Self {
|
||||||
key: None,
|
state: Box::new(PasteLoading),
|
||||||
nonce: None,
|
_fetch_handle: Some(task),
|
||||||
needs_pw: false,
|
},
|
||||||
|
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 {
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
match msg {
|
self.state = 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!(),
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,21 +139,207 @@ impl Component for Paste {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
match self.state {
|
if self.state.is::<PasteLoading>() {
|
||||||
PasteState::NeedInformation { .. } => todo!(),
|
return html! {
|
||||||
PasteState::Done { .. } => {
|
<p>{ "loading" }</p>
|
||||||
todo!()
|
};
|
||||||
}
|
}
|
||||||
PasteState::Error => html! {
|
|
||||||
<main>
|
if self.state.is::<PasteNotFound>() {
|
||||||
{"An error occurred. Please try again later."}
|
return html! {
|
||||||
</main>
|
<p>{ "Either the paste has been burned or one never existed." }</p>
|
||||||
},
|
};
|
||||||
PasteState::NotFound => html! {
|
}
|
||||||
<main>
|
|
||||||
{"The paste you are looking for is not here."}
|
if let Some(error) = self.state.downcast_ref::<PasteError>() {
|
||||||
</main>
|
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