implement better hashing

- use Blake2b128 for the CLI arguments hash
This commit is contained in:
Jörn-Michael Miehe 2025-06-24 16:29:38 +00:00
parent ece742a1e3
commit 6e553cc185
5 changed files with 109 additions and 32 deletions

63
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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> {
Checked::new(data)
}
type Blake2b128 = Blake2b<U16>;
fn sorted<T>(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<Duration> {
(!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())
}
}

View file

@ -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
///

View file

@ -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<String>, base_url: impl Into<String>) -> 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
}