You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
5.2 KiB

//! Conversion formats
use super::*;
use std::{
ops::Deref,
borrow::Borrow,
fmt,
error,
convert::TryFrom,
};
use regex::{
Regex,
};
//TODO: Mayb use base65536 for URL stuffs? idk..
const BASE64_VALIDATE_RE_STR: &'static str = r#"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"#;
const MOD_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<char, char> = smallmap![
{'/' => '_'},
{'+' => '-'},
{'=' => '~'},
];
static ref BASE64_CONV_TABLE_REV: smallmap::Map<char, char> = BASE64_CONV_TABLE.clone().reverse();
static ref BASE64_VALIDATE_REGEX: Regex = Regex::new(BASE64_VALIDATE_RE_STR).expect("Failed to compile base64 validation regex");
static ref MOD_BASE64_VALIDATE_REGEX: Regex = Regex::new(MOD_BASE64_VALIDATE_RE_STR).expect("Failed to compile modified base64 validation regex");
}
/// An error in base64 or modified base64 encoding/decoding.
#[derive(Debug)]
pub struct Base64Error<T>(T);
impl<T> Base64Error<T>
{
/// The invalid value of this error
pub fn value(&self) -> &T
{
&self.0
}
/// Consume into the invalid value from this instance
#[inline] pub fn into_inner(self) -> T
{
self.0
}
}
impl<T> error::Error for Base64Error<T>
where T: AsRef<str> + fmt::Debug{}
impl<T> fmt::Display for Base64Error<T>
where T: AsRef<str>
{
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
{
#[inline(always)] 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()
}
/// Consume into the inner modified base64 string
pub fn into_string(self) -> String
{
self.0
}
/// Create an instance from a modified base64 string
pub fn new(from: String) -> Result<Self, Base64Error<String>>
{
if MOD_BASE64_VALIDATE_REGEX.is_match(from.as_str()) {
Ok(Self(from))
} else {
Err(Base64Error(from))
}
}
/// Try to convert a base64 string into a modified base64 string
pub fn try_from_base64<T: AsRef<str>>(base64: T) -> Result<Self, Base64Error<T>>
{
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 [u8]) -> usize
{
base64::decode_config_slice(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<u8>`
pub fn decode_new(self) -> Vec<u8>
{
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<str> for ModifiedBase64String
{
#[inline] fn as_ref(&self) -> &str
{
self.as_str()
}
}
impl TryFrom<String> for ModifiedBase64String
{
type Error = Base64Error<String>;
#[inline] fn try_from(from: String) -> Result<Self, Self::Error>
{
Self::try_from_base64(from)
}
}
impl From<ModifiedBase64String> for String
{
#[inline] fn from(from: ModifiedBase64String) -> Self
{
from.into_base64()
}
}
/// Convert this string with a specified char map. Returns a `char` yielding iterator.
#[inline] pub fn conv_str<'a, 'b>(table: &'b smallmap::Map<char, char>, string: &'a (impl AsRef<str> + ?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<T, I>(table: &smallmap::Map<char, char>, iter: I) -> CharSubstituteIter<I::IntoIter, T>
where I: IntoIterator<Item=T>,
T: From<char> + smallmap::Collapse,
char: Borrow<T>
{
iter.replace_chars(table)
}
#[cfg(test)]
mod tests
{
#[test]
fn mod_base64_enc_dec()
{
let mut value = [0u8; 512];
getrandom::getrandom(&mut value[..]).expect("setup failed");
let md = super::ModifiedBase64String::encode(&value[..]);
println!("e-md: {:?}", md);
let ou = md.decode_new();
assert_eq!(&ou[..], &value[..]);
}
}