Compare commits
16 commits
c73af50857
...
253fccaf78
Author | SHA1 | Date | |
---|---|---|---|
|
253fccaf78 | ||
|
b57298ffb2 | ||
|
4e7b3dfd3b | ||
|
a9e9a93493 | ||
|
37bdbae640 | ||
|
3ab01300b2 | ||
|
3c9c46da18 | ||
|
37727bfd3d | ||
|
774b13e46c | ||
|
b793139a99 | ||
|
57bcd6371c | ||
|
064fd749a4 | ||
|
4694683b9a | ||
|
c934b36b35 | ||
|
711f79a255 | ||
|
9a7f13f8b9 |
19 changed files with 905 additions and 657 deletions
637
Cargo.lock
generated
637
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -113,5 +113,4 @@ There are a few reasons to not use OmegaUpload:
|
||||||
- Cannot download files larger than 512 MiB through the web frontend—this
|
- Cannot download files larger than 512 MiB through the web frontend—this
|
||||||
is a technical limitation of the current web frontend not using a web worker
|
is a technical limitation of the current web frontend not using a web worker
|
||||||
in addition to the fact that browsers are not optimized for XChaCha20.
|
in addition to the fact that browsers are not optimized for XChaCha20.
|
||||||
- Right now, you must upload via the CLI tool.
|
|
||||||
- The frontend uses WASM, which is a novel attack surface.
|
- The frontend uses WASM, which is a novel attack surface.
|
||||||
|
|
|
@ -22,6 +22,6 @@ CUR_DIR=$(pwd)
|
||||||
PROJECT_TOP_LEVEL=$(git rev-parse --show-toplevel)
|
PROJECT_TOP_LEVEL=$(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
cd "$PROJECT_TOP_LEVEL" || exit 1
|
cd "$PROJECT_TOP_LEVEL" || exit 1
|
||||||
git submodule foreach git pull
|
git submodule update --remote
|
||||||
|
|
||||||
cd "$CUR_DIR"
|
cd "$CUR_DIR"
|
|
@ -9,10 +9,9 @@ license = "GPL-3.0-or-later"
|
||||||
# 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]
|
||||||
omegaupload-common = "0.2"
|
omegaupload-common = { path = "../common" }
|
||||||
|
anyhow = "1.0.58"
|
||||||
anyhow = "1"
|
atty = "0.2.14"
|
||||||
atty = "0.2"
|
clap = { version = "3.2.15", features = ["derive"] }
|
||||||
clap = { version = "3", features = ["derive"] }
|
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "blocking"] }
|
||||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] }
|
rpassword = "7.0.0"
|
||||||
rpassword = "5"
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use omegaupload_common::crypto::{open_in_place, seal_in_place};
|
use omegaupload_common::crypto::{open_in_place, seal_in_place};
|
||||||
|
use omegaupload_common::fragment::Builder;
|
||||||
use omegaupload_common::secrecy::{ExposeSecret, SecretString, SecretVec};
|
use omegaupload_common::secrecy::{ExposeSecret, SecretString, SecretVec};
|
||||||
use omegaupload_common::{
|
use omegaupload_common::{
|
||||||
base64, Expiration, ParsedUrl, Url, API_ENDPOINT, EXPIRATION_HEADER_NAME,
|
base64, Expiration, ParsedUrl, Url, API_ENDPOINT, EXPIRATION_HEADER_NAME,
|
||||||
|
@ -31,11 +32,7 @@ use omegaupload_common::{
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use reqwest::header::EXPIRES;
|
use reqwest::header::EXPIRES;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use rpassword::prompt_password_stderr;
|
use rpassword::prompt_password;
|
||||||
|
|
||||||
use crate::fragment::Builder;
|
|
||||||
|
|
||||||
mod fragment;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
|
@ -120,8 +117,7 @@ fn handle_upload(
|
||||||
}
|
}
|
||||||
|
|
||||||
let password = if password {
|
let password = if password {
|
||||||
let maybe_password =
|
let maybe_password = prompt_password("Please set the password for this paste: ")?;
|
||||||
prompt_password_stderr("Please set the password for this paste: ")?;
|
|
||||||
Some(SecretVec::new(maybe_password.into_bytes()))
|
Some(SecretVec::new(maybe_password.into_bytes()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -200,8 +196,7 @@ fn handle_download(mut url: ParsedUrl) -> Result<()> {
|
||||||
|
|
||||||
let password = if url.needs_password {
|
let password = if url.needs_password {
|
||||||
// Only print prompt on interactive, else it messes with output
|
// Only print prompt on interactive, else it messes with output
|
||||||
let maybe_password =
|
let maybe_password = prompt_password("Please enter the password to access this paste: ")?;
|
||||||
prompt_password_stderr("Please enter the password to access this paste: ")?;
|
|
||||||
Some(SecretVec::new(maybe_password.into_bytes()))
|
Some(SecretVec::new(maybe_password.into_bytes()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -9,24 +9,24 @@ license = "MIT"
|
||||||
# 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]
|
||||||
base64 = "0.13"
|
base64 = "0.13.0"
|
||||||
bytes = { version = "1", features = ["serde"] }
|
bytes = { version = "1.2.0", features = ["serde"] }
|
||||||
chacha20poly1305 = { version = "0.9", features = ["stream", "std"] }
|
chacha20poly1305 = { version = "0.9.1", features = ["stream", "std"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
headers = "0.3"
|
headers = "0.3.7"
|
||||||
lazy_static = "1"
|
lazy_static = "1.4.0"
|
||||||
rand = "0.8"
|
rand = "0.8.5"
|
||||||
secrecy = "0.8"
|
secrecy = "0.8.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1.0.140", features = ["derive"] }
|
||||||
thiserror = "1"
|
thiserror = "1.0.31"
|
||||||
typenum = "1"
|
typenum = "1.15.0"
|
||||||
url = "2"
|
url = "2.2.2"
|
||||||
argon2 = "0.3.1"
|
argon2 = "0.4.1"
|
||||||
|
|
||||||
# Wasm features
|
# Wasm features
|
||||||
gloo-console = { version = "0.2", optional = true }
|
gloo-console = { version = "0.2.1", optional = true }
|
||||||
http = { version = "0.2", optional = true }
|
reqwasm = { version = "0.5.0", optional = true }
|
||||||
web-sys = { version = "0.3", features = ["Headers"], optional = true }
|
http = { version = "0.2.8", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
wasm = ["gloo-console", "http", "web-sys"]
|
wasm = ["gloo-console", "reqwasm", "http"]
|
||||||
|
|
|
@ -294,3 +294,43 @@ fn get_argon2() -> Argon2<'static> {
|
||||||
pub fn get_csrng() -> impl CryptoRng + Rng {
|
pub fn get_csrng() -> impl CryptoRng + Rng {
|
||||||
rand::thread_rng()
|
rand::thread_rng()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::open_in_place;
|
||||||
|
use super::seal_in_place;
|
||||||
|
use crate::crypto::SecretVec;
|
||||||
|
|
||||||
|
macro_rules! test_encryption {
|
||||||
|
($($name:ident, $content:expr, $password:expr),*) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let mut m = $content;
|
||||||
|
let n: Vec<u8> = $content;
|
||||||
|
let key = seal_in_place(&mut m, $password).unwrap();
|
||||||
|
assert_ne!(m, n);
|
||||||
|
assert!(open_in_place(&mut m, &key, $password).is_ok());
|
||||||
|
assert_eq!(m, n);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test_encryption!(empty, vec![], None);
|
||||||
|
test_encryption!(
|
||||||
|
empty_password,
|
||||||
|
vec![],
|
||||||
|
Some(SecretVec::from(b"password".to_vec()))
|
||||||
|
);
|
||||||
|
test_encryption!(
|
||||||
|
normal,
|
||||||
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
None
|
||||||
|
);
|
||||||
|
test_encryption!(
|
||||||
|
normal_password,
|
||||||
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
Some(SecretVec::from(b"password".to_vec()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use omegaupload_common::secrecy::{ExposeSecret, SecretString};
|
use crate::secrecy::{ExposeSecret, SecretString};
|
||||||
|
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
decryption_key: SecretString,
|
decryption_key: SecretString,
|
||||||
|
@ -8,6 +8,7 @@ pub struct Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
|
#[must_use]
|
||||||
pub fn new(decryption_key: SecretString) -> Self {
|
pub fn new(decryption_key: SecretString) -> Self {
|
||||||
Self {
|
Self {
|
||||||
decryption_key,
|
decryption_key,
|
||||||
|
@ -17,6 +18,7 @@ impl Builder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub const fn needs_password(mut self) -> Self {
|
pub const fn needs_password(mut self) -> Self {
|
||||||
self.needs_password = true;
|
self.needs_password = true;
|
||||||
self
|
self
|
||||||
|
@ -24,6 +26,7 @@ impl Builder {
|
||||||
|
|
||||||
// False positive
|
// False positive
|
||||||
#[allow(clippy::missing_const_for_fn)]
|
#[allow(clippy::missing_const_for_fn)]
|
||||||
|
#[must_use]
|
||||||
pub fn file_name(mut self, name: String) -> Self {
|
pub fn file_name(mut self, name: String) -> Self {
|
||||||
self.file_name = Some(name);
|
self.file_name = Some(name);
|
||||||
self
|
self
|
||||||
|
@ -31,11 +34,13 @@ impl Builder {
|
||||||
|
|
||||||
// False positive
|
// False positive
|
||||||
#[allow(clippy::missing_const_for_fn)]
|
#[allow(clippy::missing_const_for_fn)]
|
||||||
|
#[must_use]
|
||||||
pub fn language(mut self, language: String) -> Self {
|
pub fn language(mut self, language: String) -> Self {
|
||||||
self.language = Some(language);
|
self.language = Some(language);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn build(self) -> SecretString {
|
pub fn build(self) -> SecretString {
|
||||||
if !self.needs_password && self.file_name.is_none() && self.language.is_none() {
|
if !self.needs_password && self.file_name.is_none() && self.language.is_none() {
|
||||||
return self.decryption_key;
|
return self.decryption_key;
|
|
@ -1,4 +1,6 @@
|
||||||
#![warn(clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::nursery, clippy::pedantic)]
|
||||||
|
// False positive: https://github.com/rust-lang/rust-clippy/issues/6902
|
||||||
|
#![allow(clippy::use_self)]
|
||||||
|
|
||||||
//! Contains common functions and structures used by multiple projects
|
//! Contains common functions and structures used by multiple projects
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ use crate::crypto::Key;
|
||||||
|
|
||||||
pub mod base64;
|
pub mod base64;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
pub mod fragment;
|
||||||
|
|
||||||
pub const API_ENDPOINT: &str = "/api";
|
pub const API_ENDPOINT: &str = "/api";
|
||||||
|
|
||||||
|
@ -230,10 +233,10 @@ expiration_from_str! {
|
||||||
impl Display for Expiration {
|
impl Display for Expiration {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Expiration::BurnAfterReading | Expiration::BurnAfterReadingWithDeadline(_) => {
|
Self::BurnAfterReading | Self::BurnAfterReadingWithDeadline(_) => {
|
||||||
write!(f, "This item has been burned. You now have the only copy.")
|
write!(f, "This item has been burned. You now have the only copy.")
|
||||||
}
|
}
|
||||||
Expiration::UnixTime(time) => write!(
|
Self::UnixTime(time) => write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
time.format("This item will expire on %A, %B %-d, %Y at %T %Z.")
|
time.format("This item will expire on %A, %B %-d, %Y at %T %Z.")
|
||||||
|
@ -248,7 +251,7 @@ lazy_static! {
|
||||||
|
|
||||||
impl Header for Expiration {
|
impl Header for Expiration {
|
||||||
fn name() -> &'static HeaderName {
|
fn name() -> &'static HeaderName {
|
||||||
&*EXPIRATION_HEADER_NAME
|
&EXPIRATION_HEADER_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
|
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
|
||||||
|
@ -282,6 +285,8 @@ impl From<&Expiration> for HeaderValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Expiration> for HeaderValue {
|
impl From<Expiration> for HeaderValue {
|
||||||
|
// False positive: https://github.com/rust-lang/rust-clippy/issues/9095
|
||||||
|
#[allow(clippy::needless_borrow)]
|
||||||
fn from(expiration: Expiration) -> Self {
|
fn from(expiration: Expiration) -> Self {
|
||||||
(&expiration).into()
|
(&expiration).into()
|
||||||
}
|
}
|
||||||
|
@ -290,14 +295,12 @@ impl From<Expiration> for HeaderValue {
|
||||||
pub struct ParseHeaderValueError;
|
pub struct ParseHeaderValueError;
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
impl TryFrom<web_sys::Headers> for Expiration {
|
impl TryFrom<reqwasm::http::Headers> for Expiration {
|
||||||
type Error = ParseHeaderValueError;
|
type Error = ParseHeaderValueError;
|
||||||
|
|
||||||
fn try_from(headers: web_sys::Headers) -> Result<Self, Self::Error> {
|
fn try_from(headers: reqwasm::http::Headers) -> Result<Self, Self::Error> {
|
||||||
headers
|
headers
|
||||||
.get(http::header::EXPIRES.as_str())
|
.get(http::header::EXPIRES.as_str())
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(|v| Self::try_from(v).ok())
|
.and_then(|v| Self::try_from(v).ok())
|
||||||
.ok_or(ParseHeaderValueError)
|
.ok_or(ParseHeaderValueError)
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"highlight.js": "^11.4.0",
|
"highlight.js": "^11.4.0",
|
||||||
"highlightjs-line-numbers.js": "^2.8.0",
|
"highlightjs-line-numbers.js": "^2.8.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2",
|
||||||
|
"source-map-loader": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
|
|
|
@ -7,24 +7,24 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
omegaupload-common = { path = "../common" }
|
omegaupload-common = { path = "../common" }
|
||||||
anyhow = "1"
|
anyhow = "1.0.58"
|
||||||
axum = { version = "0.4", features = ["http2", "headers"] }
|
axum = { version = "0.5.14", features = ["http2", "headers"] }
|
||||||
bincode = "1"
|
bincode = "1.3.3"
|
||||||
# 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 = "1.2.0", features = ["serde"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
futures = "0.3"
|
futures = "0.3.21"
|
||||||
# We just need to pull in whatever axum is pulling in
|
# We just need to pull in whatever axum is pulling in
|
||||||
headers = "*"
|
headers = "0.3.7"
|
||||||
lazy_static = "1"
|
lazy_static = "1.4.0"
|
||||||
# Disable `random()` and `thread_rng()`
|
# Disable `random()` and `thread_rng()`
|
||||||
rand = { version = "0.8", default_features = false }
|
rand = { version = "0.8.5", default-features = false }
|
||||||
rocksdb = { version = "0.18", default_features = false, features = ["zstd"] }
|
rocksdb = { version = "0.18.0", default-features = false, features = ["zstd"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1.0.140", features = ["derive"] }
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3.14"
|
||||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] }
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread"] }
|
||||||
tower-http = { version = "0.2", features = ["fs"] }
|
tower-http = { version = "0.3.4", features = ["fs"] }
|
||||||
tracing = { version = "0.1" }
|
tracing = "0.1.35"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3.15"
|
||||||
|
|
|
@ -27,7 +27,7 @@ use axum::extract::{Extension, Path, TypedHeader};
|
||||||
use axum::http::header::EXPIRES;
|
use axum::http::header::EXPIRES;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::routing::{get, get_service, post};
|
use axum::routing::{get, get_service, post};
|
||||||
use axum::{AddExtensionLayer, Router};
|
use axum::Router;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use headers::HeaderMap;
|
use headers::HeaderMap;
|
||||||
|
@ -103,7 +103,7 @@ async fn main() -> Result<()> {
|
||||||
&format!("{API_ENDPOINT}/:code"),
|
&format!("{API_ENDPOINT}/:code"),
|
||||||
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(axum::Extension(db))
|
||||||
.into_make_service()
|
.into_make_service()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -9,27 +9,27 @@ crate-type = ["cdylib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
omegaupload-common = { path = "../common", features = ["wasm"] }
|
omegaupload-common = { path = "../common", features = ["wasm"] }
|
||||||
# Enables wasm support
|
# Enables wasm support
|
||||||
getrandom = { version = "*", features = ["js"] }
|
getrandom = { version = "0.2.7", features = ["js"] }
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1.0.58"
|
||||||
bytes = "1"
|
bytes = "1.2.0"
|
||||||
byte-unit = "4"
|
byte-unit = "4.0.14"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1.7"
|
||||||
gloo-console = "0.2"
|
gloo-console = "0.2.1"
|
||||||
http = "0.2"
|
http = "0.2.8"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3.59"
|
||||||
mime_guess = "2"
|
mime_guess = "2.0.4"
|
||||||
reqwasm = "0.4"
|
reqwasm = "0.5.0"
|
||||||
tree_magic_mini = { version = "3", features = ["with-gpl-data"] }
|
tree_magic_mini = { version = "3.0.3", features = ["with-gpl-data"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0.140", features = ["derive"] }
|
||||||
wasm-bindgen = { version = "0.2", features = ["serde-serialize"]}
|
wasm-bindgen = { version = "0.2.82", features = ["serde-serialize"] }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4.32"
|
||||||
zip = { version = "0.5", default-features = false, features = ["deflate"] }
|
zip = { version = "0.6.2", default-features = false, features = ["deflate"] }
|
||||||
flate2 = "1.0.22"
|
flate2 = "1.0.24"
|
||||||
tar = "0.4.38"
|
tar = "0.4.38"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3.59"
|
||||||
features = [
|
features = [
|
||||||
"BlobPropertyBag",
|
"BlobPropertyBag",
|
||||||
"TextDecoder",
|
"TextDecoder",
|
||||||
|
|
|
@ -25,9 +25,12 @@ use gloo_console::{error, log};
|
||||||
use http::uri::PathAndQuery;
|
use http::uri::PathAndQuery;
|
||||||
use http::{StatusCode, Uri};
|
use http::{StatusCode, Uri};
|
||||||
use js_sys::{Array, JsString, Object, Uint8Array};
|
use js_sys::{Array, JsString, Object, Uint8Array};
|
||||||
|
use omegaupload_common::base64;
|
||||||
|
use omegaupload_common::crypto::seal_in_place;
|
||||||
use omegaupload_common::crypto::{Error as CryptoError, Key};
|
use omegaupload_common::crypto::{Error as CryptoError, Key};
|
||||||
use omegaupload_common::secrecy::{Secret, SecretVec};
|
use omegaupload_common::fragment::Builder;
|
||||||
use omegaupload_common::{Expiration, PartialParsedUrl};
|
use omegaupload_common::secrecy::{ExposeSecret, Secret, SecretString, SecretVec};
|
||||||
|
use omegaupload_common::{Expiration, PartialParsedUrl, Url};
|
||||||
use reqwasm::http::Request;
|
use reqwasm::http::Request;
|
||||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
|
@ -50,6 +53,8 @@ extern "C" {
|
||||||
pub fn load_from_db(mime_type: JsString, name: Option<JsString>, language: Option<JsString>);
|
pub fn load_from_db(mime_type: JsString, name: Option<JsString>, language: Option<JsString>);
|
||||||
#[wasm_bindgen(js_name = renderMessage)]
|
#[wasm_bindgen(js_name = renderMessage)]
|
||||||
pub fn render_message(message: JsString);
|
pub fn render_message(message: JsString);
|
||||||
|
#[wasm_bindgen(js_name = createUploadUi)]
|
||||||
|
pub fn create_upload_ui();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window() -> Window {
|
fn window() -> Window {
|
||||||
|
@ -75,7 +80,7 @@ pub fn start() {
|
||||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
|
||||||
if location().pathname().unwrap() == "/" {
|
if location().pathname().unwrap() == "/" {
|
||||||
render_message("Go away".into());
|
create_upload_ui();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +171,55 @@ pub fn start() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[allow(clippy::future_not_send)]
|
||||||
|
pub fn encrypt_string(data: String) {
|
||||||
|
spawn_local(async move {
|
||||||
|
if let Err(e) = do_encrypt(data.into_bytes()).await {
|
||||||
|
log!(format!("[rs] Error encrypting string: {}", e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[allow(clippy::future_not_send)]
|
||||||
|
pub fn encrypt_array_buffer(data: Vec<u8>) {
|
||||||
|
spawn_local(async move {
|
||||||
|
if let Err(e) = do_encrypt(data).await {
|
||||||
|
log!(format!("[rs] Error encrypting array buffer: {}", e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::future_not_send)]
|
||||||
|
async fn do_encrypt(mut data: Vec<u8>) -> Result<()> {
|
||||||
|
let (data, key) = {
|
||||||
|
let enc_key = seal_in_place(&mut data, None)?;
|
||||||
|
let key = SecretString::new(base64::encode(&enc_key.expose_secret().as_ref()));
|
||||||
|
(data, key)
|
||||||
|
};
|
||||||
|
|
||||||
|
let s: String = location().to_string().into();
|
||||||
|
let mut url = Url::from_str(&s)?;
|
||||||
|
let fragment = Builder::new(key);
|
||||||
|
|
||||||
|
let js_data = Uint8Array::new_with_length(u32::try_from(data.len()).expect("Data too large"));
|
||||||
|
js_data.copy_from(&data);
|
||||||
|
|
||||||
|
let short_code = Request::post(url.as_ref())
|
||||||
|
.body(js_data)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
url.set_path(&short_code);
|
||||||
|
url.set_fragment(Some(fragment.build().expose_secret()));
|
||||||
|
location()
|
||||||
|
.set_href(url.as_ref())
|
||||||
|
.expect("Unable to navigate to encrypted upload");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::future_not_send)]
|
#[allow(clippy::future_not_send)]
|
||||||
async fn fetch_resources(
|
async fn fetch_resources(
|
||||||
request_uri: Uri,
|
request_uri: Uri,
|
||||||
|
|
|
@ -95,6 +95,14 @@ img, audio, video {
|
||||||
max-width: 75vw;
|
max-width: 75vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 75vw;
|
||||||
|
min-height: 75vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.primary {
|
.primary {
|
||||||
@extend .hljs;
|
@extend .hljs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,64 @@
|
||||||
|
|
||||||
import './main.scss';
|
import './main.scss';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { encrypt_string, encrypt_array_buffer } from '../pkg';
|
||||||
|
|
||||||
const hljs = require('highlight.js');
|
import hljs from 'highlight.js'
|
||||||
(window as any).hljs = hljs;
|
(window as any).hljs = hljs;
|
||||||
require('highlightjs-line-numbers.js');
|
require('highlightjs-line-numbers.js');
|
||||||
|
|
||||||
|
const FileForm = () => {
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const fr = new FileReader();
|
||||||
|
fr.onload = (e) => {
|
||||||
|
const { result } = e.target;
|
||||||
|
if (result instanceof ArrayBuffer) {
|
||||||
|
let data = new Uint8Array(result);
|
||||||
|
encrypt_array_buffer(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fr.readAsArrayBuffer((event.target as HTMLInputElement).files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input type="file" onChange={handleChange} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasteForm = () => {
|
||||||
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
encrypt_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<pre className='paste'>
|
||||||
|
<form className='hljs centered' onSubmit={handleSubmit}>
|
||||||
|
<textarea
|
||||||
|
placeholder="Sample text"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input type="submit" value="submit" />
|
||||||
|
</form>
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUploadUi() {
|
||||||
|
const html = <main className='hljs centered fullscreen'>
|
||||||
|
<FileForm />
|
||||||
|
<PasteForm />
|
||||||
|
</main>;
|
||||||
|
|
||||||
|
ReactDom.render(html, document.body);
|
||||||
|
}
|
||||||
|
|
||||||
function loadFromDb(mimeType: string, name?: string, language?: string) {
|
function loadFromDb(mimeType: string, name?: string, language?: string) {
|
||||||
let resolvedName;
|
let resolvedName: string;
|
||||||
if (name) {
|
if (name) {
|
||||||
resolvedName = name;
|
resolvedName = name;
|
||||||
} else {
|
} else {
|
||||||
|
@ -287,4 +337,4 @@ function getObjectUrl(data, mimeType?: string) {
|
||||||
|
|
||||||
window.addEventListener("hashchange", () => location.reload());
|
window.addEventListener("hashchange", () => location.reload());
|
||||||
|
|
||||||
export { renderMessage, loadFromDb };
|
export { renderMessage, createUploadUi, loadFromDb };
|
||||||
|
|
2
web/vendor/MPLUS_FONTS
vendored
2
web/vendor/MPLUS_FONTS
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit a1268635894c5ee23dfdece570418ca07b66c3fc
|
Subproject commit 8690be3625964d9992e7be4bc3e1a61a80161cc6
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
||||||
|
const { SourceMapDevToolPlugin } = require('webpack');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './web/src/index.js',
|
entry: './web/src/index.js',
|
||||||
|
@ -21,6 +22,8 @@ module.exports = {
|
||||||
"css-loader",
|
"css-loader",
|
||||||
// Compiles Sass to CSS
|
// Compiles Sass to CSS
|
||||||
"sass-loader",
|
"sass-loader",
|
||||||
|
// source map for debugging
|
||||||
|
"source-map-loader"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -41,6 +44,7 @@ module.exports = {
|
||||||
crateDirectory: path.resolve(__dirname, "web"),
|
crateDirectory: path.resolve(__dirname, "web"),
|
||||||
outDir: path.resolve(__dirname, "web/pkg"),
|
outDir: path.resolve(__dirname, "web/pkg"),
|
||||||
}),
|
}),
|
||||||
|
new SourceMapDevToolPlugin({}),
|
||||||
],
|
],
|
||||||
experiments: {
|
experiments: {
|
||||||
asyncWebAssembly: true,
|
asyncWebAssembly: true,
|
||||||
|
|
Loading…
Reference in a new issue