diff --git a/Cargo.lock b/Cargo.lock index 3c54edd..edc97bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.2.1" @@ -225,6 +231,7 @@ dependencies = [ name = "datse" version = "0.1.0" dependencies = [ + "base64 0.13.0", "color-eyre", "cryptohelpers", "futures", @@ -233,7 +240,9 @@ dependencies = [ "log", "pretty_env_logger", "rand 0.7.3", + "regex", "serde", + "smallmap", "tokio", "uuid", "warp", @@ -519,7 +528,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" dependencies = [ - "base64", + "base64 0.12.3", "bitflags", "bytes", "headers-core", @@ -915,7 +924,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170d73bf11f39b4ce1809aabc95bf5c33564cdc16fc3200ddda17a5f6e5e48b" dependencies = [ - "base64", + "base64 0.12.3", "crypto-mac", "hmac", "rand 0.7.3", @@ -1235,6 +1244,15 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1253,6 +1271,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.117" @@ -1349,6 +1382,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smallmap" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735b09f6ab554c4165d045a7d67b3b7b5248acb39463dffba38ebced1b9110e2" +dependencies = [ + "rustc_version", +] + [[package]] name = "socket2" version = "0.3.16" @@ -1547,7 +1589,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" dependencies = [ - "base64", + "base64 0.12.3", "byteorder", "bytes", "http", diff --git a/Cargo.toml b/Cargo.toml index 77edefb..3ee82d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ server = [] client = [] [dependencies] +base64 = "0.13.0" color-eyre = {version = "0.5", default-features=false} cryptohelpers = {version = "1.5.1", features= ["sha256", "rsa", "serde"]} futures = "0.3.8" @@ -28,7 +29,9 @@ lazy_static = "1.4.0" log = "0.4.11" pretty_env_logger = "0.4.0" rand = "0.7.3" +regex = "1.4.2" serde = {version = "1.0", features = ["derive"]} +smallmap = "1.2" tokio = {version = "0.2", features = ["full"]} uuid = {version = "0.8.1", features = ["v4","serde"]} warp = "0.2.5" diff --git a/src/conv.rs b/src/conv.rs new file mode 100644 index 0000000..ad61758 --- /dev/null +++ b/src/conv.rs @@ -0,0 +1,133 @@ +//! Conversion formats +use super::*; +use std::{ + ops::Deref, + borrow::Borrow, + fmt, + error, +}; +use regex::{ + Regex, +}; + +const BASE64_VALIDATE_RE_STR: &'static str = r#"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"#; + +lazy_static!{ + static ref BASE64_CONV_TABLE: smallmap::Map = smallmap![ + {'/' => 'ł'}, + {'+' => 'þ'}, + {'=' => 'ø'}, + ]; + static ref BASE64_CONV_TABLE_REV: smallmap::Map = BASE64_CONV_TABLE.clone().reverse(); + + static ref BASE64_VALIDATE_REGEX: Regex = Regex::new(BASE64_VALIDATE_RE_STR).expect("Failed to compile base64 validation regex"); +} + +#[derive(Debug)] +pub struct Base64Error(T); + +impl error::Error for Base64Error +where T: AsRef + fmt::Debug{} +impl fmt::Display for Base64Error + where T: AsRef +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "invalid base64: {:?}", self.0.as_ref()) + } +} + + +/// A string of modified base64, appropriate for URL paths etc. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct ModifiedBase64String(String); + +impl fmt::Display for ModifiedBase64String +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.0) + } +} + +impl ModifiedBase64String +{ + fn from_base64_unchecked(string: &str) -> Self + { + Self(conv_str(&BASE64_CONV_TABLE, string).collect()) + } + + /// Consume into a normal base64 string + pub fn into_base64(self) -> String + { + conv_char_iter(&BASE64_CONV_TABLE_REV, self.0.chars()).collect() + } + + /// Try to convert a base64 string into a modified base64 string + pub fn try_from_base64>(base64: T) -> Result> + { + let string = base64.as_ref(); + if BASE64_VALIDATE_REGEX.is_match(string) { + Ok(Self::from_base64_unchecked(string)) + } else { + Err(Base64Error(base64)) + } + } + + /// As a string slice + #[inline(always)] pub fn as_str(&self) -> &str + { + self.0.as_str() + } + + /// Encode from a slice + pub fn encode(slice: impl AsRef<[u8]>) -> Self + { + Self::from_base64_unchecked(base64::encode(slice.as_ref()).as_str()) + } + + /// Consume into decoded bytes, write those bytes into the provided buffer + pub fn decode(self, output: &mut Vec) + { + base64::decode_config_buf(self.into_base64(), base64::STANDARD, output).expect("modified base64 string contained invalid formatted data") + } + + /// Consume into decoded bytes, return those bytes as a new `Vec` + pub fn decode_new(self) -> Vec + { + base64::decode(self.into_base64()).expect("modified base64 string contained invalid formatted data") + } +} + +impl Deref for ModifiedBase64String +{ + type Target = str; + #[inline] fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl AsRef for ModifiedBase64String +{ + #[inline] fn as_ref(&self) -> &str + { + self.as_str() + } +} + + +/// Convert this string with a specified char map. Returns a `char` yielding iterator. +#[inline] pub fn conv_str<'a, 'b>(table: &'b smallmap::Map, string: &'a (impl AsRef + ?Sized)) -> CharSubstituteIter<'b, std::str::Chars<'a>> +where 'b: 'a +{ + conv_char_iter(table, string.as_ref().chars()) +} + +/// Convert this iterator of chars with this char swapping map +#[inline] pub fn conv_char_iter(table: &smallmap::Map, iter: I) -> CharSubstituteIter +where I: IntoIterator, + T: From + smallmap::Collapse, + char: Borrow +{ + iter.replace_chars(table) +} diff --git a/src/ext.rs b/src/ext.rs index 83c2e3d..08ceb0a 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -1,5 +1,12 @@ //! Extensions use super::*; +use std::{ + borrow::{ + Borrow, ToOwned, + }, + iter, +}; + pub trait Tuple2MapExt { @@ -30,3 +37,92 @@ where T: rand::distributions::uniform::SampleUniform util::jitter(self.0, self.1) } } + +pub trait Unreference +{ + fn cloned(self) -> Option; +} + +impl<'a, T> Unreference for Option<&'a T> +where T: Clone +{ + fn cloned(self) -> Option { + self.map(Clone::clone) + } +} + + + +/// An iterator over `char` that maps certain characters to others +pub struct CharSubstituteIter<'map, I, T= char> +where I: Iterator, +{ + iter: I, + map: &'map smallmap::Map, +} + +impl<'a, I, T> Iterator for CharSubstituteIter<'a, I, T> +where I: Iterator, + T: From + smallmap::Collapse, + char: Borrow +{ + type Item = T; + fn next(&mut self) -> Option + { + self.iter.next() + .map(|item| self.map.get(&item) + .cloned() + .map(T::from) + .unwrap_or(item)) + } + + #[inline] fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + + +impl<'a, I, T> DoubleEndedIterator for CharSubstituteIter<'a, I, T> +where I: Iterator + DoubleEndedIterator, + T: From + smallmap::Collapse, + char: Borrow +{ + fn next_back(&mut self) -> Option + { + self.iter.next_back() + .map(|item| self.map.get(&item) + .cloned() + .map(T::from) + .unwrap_or(item)) + } +} + +impl<'a, I, T> iter::FusedIterator for CharSubstituteIter<'a, I, T> +where I: Iterator + iter::FusedIterator, + T: From + smallmap::Collapse, + char: Borrow{} + +impl<'a, I, T> iter::ExactSizeIterator for CharSubstituteIter<'a, I, T> +where I: Iterator + ExactSizeIterator, + T: From + smallmap::Collapse, + char: Borrow{} + +pub trait CharMapExt: Sized + IntoIterator +{ + /// Creates an iterator that maps chars over this one + fn replace_chars(self, map: &smallmap::Map) -> CharSubstituteIter<'_, Self::IntoIter, T>; +} + +impl CharMapExt for S +where S: IntoIterator, + T: From + smallmap::Collapse, + char: Borrow +{ + #[inline] fn replace_chars(self, map: &smallmap::Map) -> CharSubstituteIter<'_, Self::IntoIter, T> { + CharSubstituteIter { + iter: self.into_iter(), + map, + } + } +} + diff --git a/src/main.rs b/src/main.rs index 6e70aeb..76821f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use futures::{ }; use lazy_static::lazy_static; use getrandom::getrandom; +use smallmap::smallmap; use serde::{ Serialize, Deserialize, @@ -35,6 +36,8 @@ mod ext; use ext::*; mod util; +mod conv; + mod args; #[cfg(feature="server")] mod server; diff --git a/src/server/conv.rs b/src/server/conv.rs deleted file mode 100644 index 81c8f73..0000000 --- a/src/server/conv.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Conversion formats -use super::*; -use std::{ - ops::Deref, - -}; - -/// A string of modified base64, appropriate for URL paths etc. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -pub struct ModifiedBase64String(String); - -impl ModifiedBase64String -{ - /// Get as a reference - #[inline(always)] pub fn as_str(&self) -> &ModifiedBase64 - { - ModifiedBase64::new_unchecked(&self.0[..]) - } - - /// Get as a mutable reference - #[inline(always)] fn as_mut_str(&mut self) -> &mut ModifiedBase64 - { - ModifiedBase64::new_unchecked_mut(&mut self.0[..]) - } - - /// Consume into regular base64 string - pub fn into_base64(mut self) -> String - { - self.as_mut_str().unmodify(); - self.0 - } - - /// Create from a refular base64 string - pub fn from_base64(mut string: String) -> Self - { - ModifiedBase64::modify(&mut string[..]); - Self(string) - } -} - -impl AsRef for ModifiedBase64String -{ - #[inline] fn as_ref(&self) -> &ModifiedBase64 - { - self.as_str() - } -} - - -impl Deref for ModifiedBase64String -{ - type Target = ModifiedBase64; - - #[inline] fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -/// A string slice of modified base64, appropriate for URL paths etc. -#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)] -#[repr(transparent)] -pub struct ModifiedBase64(str); - -impl ModifiedBase64 -{ - #[inline(always)] fn new_unchecked<'a>(from: &'a str) -> &'a ModifiedBase64 - { - unsafe { - std::mem::transmute(from) - } - } - - #[inline(always)] fn new_unchecked_mut<'a>(from: &'a mut str) -> &'a mut ModifiedBase64 - { - unsafe { - std::mem::transmute(from) - } - } - - /// Get the underlying string slice - #[inline] pub fn as_str(&self) -> &str - { - unsafe { - std::mem::transmute::<&'_ Self, &'_ str>(self) - } - } - - /// Get the underlying string slice as a mutable reference. - /// - /// # This is not safe to expose in the API. - #[inline] fn as_mut_str(&mut self) -> &mut str - { - unsafe { - std::mem::transmute::<&'_ mut Self, &'_ mut str>(self) - } - } - - fn unmodify(&mut self) -> &mut str - { - todo!(); - - self.as_mut_str() - } - - /// Modify this base64 string into a mutable reference to self - pub fn modify(base64: &mut str) -> &mut Self - { - todo!(); - - Self::new_unchecked_mut(base64) - } -} - -impl AsRef for ModifiedBase64 -{ - #[inline(always)] fn as_ref(&self) -> &ModifiedBase64 - { - self - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 6e767e9..6674aa3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,7 +7,6 @@ pub struct Config } mod state; -mod conv; #[cfg(feature="server-http")] pub mod web; #[cfg(feature="server-tcp")] pub mod tcp;