diff --git a/Cargo.lock b/Cargo.lock index 57f40d6..434980f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,12 +73,36 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -209,6 +233,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctrlc" version = "3.4.7" @@ -239,6 +273,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -340,6 +385,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -846,6 +901,8 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" name = "shrupl" version = "0.1.0-alpha" dependencies = [ + "base64ct", + "blake2", "clap", "console", "ctrlc", @@ -988,6 +1045,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index 623bc0c..d20c0ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" description = "ShrUpl is a tool to upload files to a Sharry Instance through a public Alias, leveraging the tus protocol" [dependencies] +base64ct = { version = "1.8.0", default-features = false, features = ["alloc"] } +blake2 = { version = "0.10.6", default-features = false } clap = { version = "4.5.38", features = ["derive"] } console = { version = "0.15.11", default-features = false } ctrlc = { version = "3.4.7", features = ["termination"] } diff --git a/src/cli.rs b/src/cli.rs index 546427b..2e2a947 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,11 +1,7 @@ -use std::{ - convert::Infallible, - fmt, - hash::{DefaultHasher, Hash, Hasher}, - io, - time::Duration, -}; +use std::{convert::Infallible, fmt, io, time::Duration}; +use base64ct::{Base64UrlUnpadded, Encoding}; +use blake2::{Blake2b, Digest, digest::consts::U16}; use clap::{ Parser, builder::{PossibleValuesParser, TypedValueParser}, @@ -18,7 +14,7 @@ use crate::{ sharry::{NewShareRequest, Uri}, }; -#[derive(Parser, Hash)] +#[derive(Parser)] #[command(version, about, long_about = None)] pub struct Cli { /// Timeout in seconds for HTTP actions (set 0 or invalid to disable) @@ -100,6 +96,18 @@ fn parse_sharry_file(data: &str) -> io::Result { Checked::new(data) } +type Blake2b128 = Blake2b; + +fn sorted(values: &[T]) -> Vec<&T> +where + T: Ord, +{ + let mut refs: Vec<_> = values.iter().collect(); + refs.sort_unstable(); + + refs +} + impl Cli { pub fn get_timeout(&self) -> Option { (!self.timeout.is_zero()).then_some(self.timeout) @@ -135,16 +143,14 @@ impl Cli { } pub fn get_hash(&self) -> String { - let file_refs = { - let mut refs: Vec<_> = self.files.iter().collect(); - refs.sort_unstable(); + let mut hasher = Blake2b128::new(); + hasher.update(self.get_uri()); + hasher.update(&self.alias); - refs - }; + for chk in sorted(&self.files) { + hasher.update(chk); + } - let mut hasher = DefaultHasher::new(); - (self.get_uri(), &self.alias, file_refs).hash(&mut hasher); - - format!("{:x}", hasher.finish()) + Base64UrlUnpadded::encode_string(&hasher.finalize()) } } diff --git a/src/file/checked.rs b/src/file/checked.rs index 58bdf79..45c1d90 100644 --- a/src/file/checked.rs +++ b/src/file/checked.rs @@ -11,10 +11,10 @@ use super::{FileTrait, Uploading}; /// Description of an existing, regular file /// -/// - impl Debug, Clone, Hash for `clap` compatibility +/// - impl Clone for `clap` compatibility /// - impl serde for appstate caching -/// - impl Ord to handle multiple files given -#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +/// - impl PartialEq..Ord to handle multiple files given +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct Checked { /// canonical path to a regular file pub(super) path: PathBuf, @@ -22,6 +22,12 @@ pub struct Checked { pub(super) size: u64, } +impl AsRef<[u8]> for Checked { + fn as_ref(&self) -> &[u8] { + self.path.as_os_str().as_encoded_bytes() + } +} + impl Checked { /// create a new checked file from some path reference /// diff --git a/src/sharry/api.rs b/src/sharry/api.rs index b762e4e..3f11560 100644 --- a/src/sharry/api.rs +++ b/src/sharry/api.rs @@ -3,28 +3,28 @@ use std::fmt; use log::trace; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Hash)] -pub struct Uri { - protocol: String, - base_url: String, -} +#[derive(Serialize, Deserialize, Debug)] +pub struct Uri(String); impl fmt::Display for Uri { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}://{}", self.protocol, self.base_url) + f.write_str(&self.0) + } +} + +impl AsRef<[u8]> for Uri { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() } } impl Uri { - pub fn new(protocol: impl Into, base_url: impl Into) -> Self { - Self { - protocol: protocol.into(), - base_url: base_url.into(), - } + pub fn new(protocol: impl fmt::Display, base_url: impl fmt::Display) -> Self { + Self(format!("{}://{}", protocol, base_url)) } fn endpoint(&self, path: fmt::Arguments) -> String { - let uri = format!("{}://{}/api/v2/{path}", self.protocol, self.base_url); + let uri = format!("{}/api/v2/{path}", self.0); trace!("endpoint: {uri:?}"); uri }