lb64/
lib.rs

1//! Focuses on making a Base64 number as a type, with extensive documentation, and never panicing
2//! code
3//!
4//! Base64 Library that has encoding and decoding of unsigned decimal values and bytes
5//! Along with a full fledged Base64 type in order to store the input of encoding an unsigned
6//! decimal value or bytes. Base64 type implements Clone, Eqs, Debug, along with multiple
7//! constructors for creation, all start with 'new', for example, from_string is from a Base64
8//! compliant &str. Has default Configurations for Base64 numbers see more
9//! [here](https://en.wikipedia.org/wiki/Base64) for `Standard`, `URL_SAFE` with and without padding,
10//! `IMAP` and `MIME` compliant configs along with full fledged support of creating your own Base64 type.
11//! Furthermore, supports random generation of a n long Base64 number.
12//! ```
13//! extern crate lb64;
14//!
15//! use lb64::Base64;
16//! use lb64::config::{Config, MIME};
17//!
18//! fn main() {
19//!    let s: &str = "Hello!";
20//!    let b64 = Base64::new_encode_bytes(s.as_bytes(), MIME);
21//!    println!("{}", b64);
22//!    let mut v: u128 = 0;
23//!    match b64.decode_to_unsigned() {
24//!         Ok(value) => v = value,
25//!         Err(e) => println!("{}", e),
26//!    }
27//!    let b64_other = Base64::new_encode_unsigned(&v, MIME);
28//!    if b64_other == b64 {
29//!         println!("They're equal!");
30//!    }
31//!    match String::from_utf8(b64.decode_to_bytes()) {
32//!         Ok(value) => println!("{}", value), // prints Hello
33//!         Err(e) => println!("{}", e),
34//!    }
35//! }
36//! ```
37//! See Examples for more.
38
39// Allow use of pow to prevent panics from overflowing
40#![feature(no_panic_pow)]
41// Prevent trivial casts, require implement debug, completely safe code, and require documentation
42#![deny(
43    trivial_numeric_casts,
44    trivial_casts,
45    missing_debug_implementations,
46    unsafe_code,
47    missing_docs
48)]
49// Requiring a is_empty function doesn't make sense in this context
50#![allow(clippy::len_without_is_empty)]
51extern crate rand;
52
53use rand::prelude::*;
54
55use std::cmp::Ordering;
56use std::cmp::PartialEq;
57use std::fmt::{Display, Formatter};
58
59/// Creation of custom configs for Base64 numbers containing different characters, with or without
60/// padding, with or without a maximum line length. In addition, 5 configs are already defined
61/// because of their popularity (`STANDARD`, `MIME`, `IMAP`, `URLSAFE` with and without padding).
62pub mod config;
63/// Decoding functions for Base64
64mod decode;
65/// Enconding functions for Base64
66mod encode;
67/// Enums for Errors that can occur when making a Config or when decoding
68pub mod error;
69
70/// Base64 number
71///
72/// value: a Vec<char> representing the value of the Base64 number
73///
74/// conf: the config specific for this Base64 number
75///
76/// Implements Clone, Debug, Eqs, and Compare
77#[derive(Eq, Debug, Clone)]
78pub struct Base64<'a> {
79    value: Vec<char>,
80    conf: &'a config::Config<'a>,
81}
82
83impl<'a> Base64<'a> {
84    /// Creates a default Base64 number equivalent to 0 ("A") with
85    /// [STANDARD](../base64/config/constant.STANDARD.html)
86    ///
87    /// # Returns:
88    /// The new Base64 number with the Standard configuration and value of "A"
89    ///
90    /// # Example:
91    /// ```
92    /// extern crate lb64; // Import/Include crate
93    /// use lb64::{Base64}; // Base64
94    ///
95    /// fn main() {
96    ///     let b64 = Base64::default(); // Creates new Base64 of value "A"
97    ///     println!("{}", b64);
98    /// }
99    /// ```
100    pub fn default() -> Self {
101        Base64 {
102            value: vec!['A'],
103            conf: config::STANDARD,
104        }
105    }
106
107    /// Creates a random base64 number of at least the provided length.
108    ///
109    /// # Parameters:
110    /// new length of base64 number and the configuration struct, Note if the configuration
111    /// specifies padding then the length may be higher if the length specififed isn't divisible by
112    /// 4
113    ///
114    /// #Returns:
115    /// the new random base64 number
116    ///
117    /// # Example:
118    /// ```
119    /// extern crate lb64; // Import/Include crate
120    /// use lb64::{Base64}; // Base64
121    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
122    ///
123    /// fn main() {
124    ///     let b64 = Base64::new_random(5, URL_SAFE_NO_PADDING); // Sets the length to 5 and randomly generates the values
125    ///     println!("{}", b64); // Since there's no padding then the length will be 5
126    ///     let b64 = Base64::new_random(5, URL_SAFE_PADDING); // Generates a random value of length 5, but with padding
127    ///     println!("{}", b64); // Since there's padding then the length will be divisible by 4 therefore length 8
128    /// }
129    /// ```
130    pub fn new_random(len: usize, conf: &'a config::Config<'a>) -> Self {
131        let mut val: Vec<char> = Vec::new();
132        for _i in 0..len {
133            val.push(generate_base64(conf.get_character_set()));
134        }
135        let mut b64 = Base64 { value: val, conf };
136        b64.add_padding(); // Add padding if necessary
137        b64
138    }
139
140    /// Sets the value of the Base64 number to a random value.  Param: len, length for base64 number
141    ///
142    /// # Parameters:
143    /// The minimum length for the base64 number
144    ///
145    /// # Example:
146    /// ```
147    /// extern crate lb64; // Import/Include crate
148    /// use lb64::{Base64}; // Base64
149    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
150    ///
151    /// fn main() {
152    ///     let mut b64 = Base64::new_random(5, URL_SAFE_NO_PADDING); // Sets the length to 5 and randomly generates the values
153    ///     println!("{}", b64); // Since there's no padding then the length will be 5
154    ///     b64.set_random(8);
155    ///     println!("{}", b64); // No padding length will now be 8
156    /// }
157    /// ```
158    pub fn set_random(&mut self, len: usize) {
159        let mut val: Vec<char> = Vec::new();
160        for _i in 0..len {
161            val.push(generate_base64(self.conf.get_character_set()));
162        }
163        self.value = val;
164        self.add_padding();
165    }
166
167    /// Get length of Base64 number
168    ///
169    /// # Return:
170    /// Return usize of Base64 number
171    ///
172    /// # Example:
173    /// ```
174    /// extern crate lb64; // Import/Include crate
175    /// use lb64::{Base64}; // Base64
176    /// use lb64::config::{URL_SAFE_NO_PADDING}; // Constant config
177    ///
178    /// fn main() {
179    ///     let mut b64 = Base64::new_random(5, URL_SAFE_NO_PADDING); // Sets the length to 5 and randomly generates the values
180    ///     println!("{}", b64); // Since there's no padding then the length will be 5
181    ///     println!("{}", b64.len()); // Length of 5
182    /// }
183    /// ```
184    pub fn len(&self) -> usize {
185        self.value.len()
186    }
187
188    /// Adds the padding character if the Base64 config has padding turned on until the number is
189    /// divisible by 4
190    fn add_padding(&mut self) {
191        if self.conf.get_padding().is_some() {
192            while self.len() % 4 != 0 {
193                self.value.push(self.conf.get_padding().unwrap());
194            }
195        }
196    }
197
198    /// Sets Base64 to that String if it's valid
199    ///
200    /// # Return:
201    /// If all characters are valid Base64 return Self otherwise a
202    /// [Base64Error::InvalidBase64CharacterError](error/enum.Base64Error.html#variant.InvalidBase64CharacterError)
203    ///
204    /// # Example:
205    /// ```
206    /// extern crate lb64; // Import/Include crate
207    /// use lb64::{Base64}; // Base64
208    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
209    ///
210    /// fn main() {
211    ///     let b64 = Base64::new_from_string("Hello", URL_SAFE_PADDING); // Sets b64 to the string if the string is valid Base64
212    ///     // It returns a Result<Base64, String>
213    ///     match b64 {
214    ///         Ok(value) => println!("{}", 64), // prints "Hello===" adds padding so it's divisible by 4
215    ///         Err(e) => println!("{}", e), // The error message stating the first incorrect character
216    ///     }
217    /// }
218    /// ```
219    pub fn new_from_string(
220        new: &str,
221        conf: &'a config::Config<'a>,
222    ) -> Result<Self, error::Base64Error> {
223        let mut val: Vec<char> = Vec::new();
224        for ch in new.chars() {
225            if !is_valid_base64('\0', conf.get_character_set(), ch)
226                || (conf.get_padding().is_some()
227                    && !is_valid_base64(conf.get_padding().unwrap(), conf.get_character_set(), ch))
228            {
229                return Err(error::Base64Error::InvalidBase64CharacterError);
230            } else {
231                val.push(ch);
232            }
233        }
234        let mut b64 = Base64 { value: val, conf };
235        b64.add_padding();
236        Ok(b64)
237    }
238
239    /// Takes a new configuration and converts the Base64 number to that representation
240    ///
241    /// # Example
242    /// ```
243    /// extern crate lb64; // Import/Include crate
244    /// use lb64::{Base64}; // Base64
245    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
246    ///
247    /// fn main() {
248    ///     let mut b64 = Base64::new_encode_unsigned(&63, URL_SAFE_NO_PADDING); // Sets b64 to _
249    ///     b64.set_config(URL_SAFE_PADDING); // Changes configs and adds padding to _
250    ///     println!("{}", b64); // Prints _===
251    /// }
252    /// ```
253    pub fn set_config(&mut self, conf: &'a config::Config<'a>) {
254        // If they aren't the same actually convert
255        self.value = self.convert_to_new_config(conf);
256        self.conf = conf;
257        self.add_padding();
258    }
259
260    fn convert_to_new_config(&self, conf: &'a config::Config<'a>) -> Vec<char> {
261        let mut v: Vec<char> = Vec::new();
262        for i in &self.value {
263            if self.conf.get_padding().is_some() && *i == self.conf.get_padding().unwrap() {
264                if conf.get_padding().is_some()
265                    && conf.get_padding().unwrap() == self.conf.get_padding().unwrap()
266                {
267                    // Both have padding and their padding is the same
268                    continue;
269                } else if conf.get_padding().is_some()
270                    && conf.get_padding().unwrap() != self.conf.get_padding().unwrap()
271                {
272                    // They both have padding and they're different
273                    v.push(conf.get_padding().unwrap());
274                } else {
275                    // The new configuration doesn't have padding and therefore skip it
276                    continue;
277                }
278            } else {
279                // Convert the current configuration value to it's new equivalent
280                v.push(decimal_to_base64_char(
281                    conf.get_character_set(),
282                    base64_char_to_decimal(self.conf.get_character_set(), *i),
283                ));
284            }
285        }
286        v
287    }
288
289    /// Sets the Base64 value to a given String
290    ///
291    /// # Return:
292    /// false if any value is invalid
293    ///
294    /// # Example:
295    /// ```
296    /// extern crate lb64; // Import/Include crate
297    /// use lb64::{Base64}; // Base64
298    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
299    ///
300    /// fn main() {
301    ///     let mut b64 = Base64::new_encode_unsigned(&63, URL_SAFE_NO_PADDING); // Sets b64 to _
302    ///     b64.set_config(URL_SAFE_PADDING); // Changes configs and adds padding to _
303    ///     println!("{}", b64); // Prints _===
304    /// }
305    /// ```
306    pub fn set_from_string(&mut self, new: &str) -> bool {
307        let mut val: Vec<char> = Vec::new();
308        for ch in new.chars() {
309            if (self.conf.get_padding().is_none()
310                && is_valid_base64('\0', self.conf.get_character_set(), ch))
311                || (self.conf.get_padding().is_some()
312                    && is_valid_base64(
313                        self.conf.get_padding().unwrap(),
314                        self.conf.get_character_set(),
315                        ch,
316                    ))
317            {
318                val.push(ch);
319            } else {
320                return false;
321            }
322        }
323        self.value = val;
324        self.add_padding();
325        true
326    }
327
328    /// Extends base 64 number by prepending As to it to fit a new size
329    ///
330    /// # Parameters:
331    /// len, the new size of the base64 value
332    ///
333    /// # Example:
334    /// ```
335    /// extern crate lb64; // Import/Include crate
336    /// use lb64::{Base64}; // Base64
337    /// use lb64::config::{URL_SAFE_NO_PADDING, URL_SAFE_PADDING}; // Constant configs
338    ///
339    /// fn main() {
340    ///     let mut b64 = Base64::new_encode_unsigned(&63, URL_SAFE_NO_PADDING); // Sets b64 to _
341    ///     b64.set_config(URL_SAFE_PADDING); // Changes configs and adds padding to _
342    ///     println!("{}", b64); // Prints _===
343    /// }
344    /// ```
345    pub fn expand_to(&mut self, len: usize) {
346        while self.value.len() < len {
347            self.value.push('A');
348        }
349        self.value.reverse();
350        self.add_padding();
351    }
352
353    /// Truncates base64 number be removing the most significant values until it fits the new size
354    ///
355    /// # Parameters:
356    /// len, the new size of the base64 value. Must be greater than 0
357    ///
358    /// # Example:
359    /// ```
360    /// extern crate lb64; // Import/Include crate
361    /// use lb64::{Base64}; // Base64
362    /// use lb64::config::{URL_SAFE_PADDING, URL_SAFE_NO_PADDING}; // Constant configs
363    ///
364    /// fn main() {
365    ///     let mut b64 = Base64::new_encode_unsigned(&63, URL_SAFE_PADDING); // Sets b64 to _===
366    ///     println!("{}", b64); // Prints _===
367    ///     b64.truncate_to(2); // This does essentially nothing because padding is required and therefore it must be divisible by 4
368    ///     println!("{}", b64); // Prints _=== stil
369    ///     let mut b64 = Base64::new_encode_unsigned(&63, URL_SAFE_NO_PADDING); // Sets b64 to _
370    ///     b64.truncate_to(1); // Length is already 1 so it remains _
371    /// }
372    /// ```
373    pub fn truncate_to(&mut self, len: usize) {
374        if len > 0 && self.value.len() > len {
375            self.value.reverse(); // flip to remove most significant values
376            while self.value.len() > len {
377                self.value.pop();
378            }
379            self.value.reverse(); // flip back
380            self.add_padding(); // Add padding
381        }
382    }
383}
384
385/// Generates values from 0 to 63 and returns the character corresponding to it
386fn generate_base64(a: &[char]) -> char {
387    decimal_to_base64_char(a, thread_rng().gen_range(0, 64) as u128)
388}
389
390/// Checks if a character is a valid value in Base64
391/// Param: val, the character to check as a u8
392/// Return: true if it's value false otherwise
393fn is_valid_base64(pad: char, a: &[char], val: char) -> bool {
394    if val == '\n' || val == ' ' || val == pad {
395        return true;
396    } else {
397        for i in a.iter() {
398            if val == *i {
399                return true;
400            }
401        }
402    }
403    false
404}
405
406/// Convert decimal value to base64 by mod 64 to get the base64 place and then dividing
407/// by 64 to get the value
408/// Param: value, the value to convert
409/// Return Vector of chars that is the Base64 value
410pub(crate) fn decimal_to_base64(conf: &config::Config, mut value: u128) -> Vec<char> {
411    let mut v: Vec<char> = Vec::new();
412    while value > 0 {
413        let base64_val = value % 64;
414        value /= 64;
415        v.push(decimal_to_base64_char(conf.get_character_set(), base64_val));
416    }
417    v.reverse(); // Reverse to get into proper order
418    v
419}
420
421/// Converts a decimal value to it's base 64 value
422/// Param: value, the value to convert
423/// Return: the character corresponding to the decimal in Base64
424pub(crate) fn decimal_to_base64_char(a: &[char], value: u128) -> char {
425    a[value as usize]
426}
427
428/// Converts a char to it's corresponding u128 value in base64
429/// Param: value, char to convert
430/// Return: u128, the value of the char in base64
431pub(crate) fn base64_char_to_decimal(a: &[char], c: char) -> u128 {
432    for (i, val) in a.iter().enumerate() {
433        if c == *val {
434            return i as u128;
435        }
436    }
437    0 // Not Possible
438}
439
440impl<'a> Display for Base64<'a> {
441    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
442        let mut print: String = String::new();
443        for i in &self.value {
444            print.push(*i);
445        }
446        write!(f, "{}", print)
447    }
448}
449
450impl<'a> PartialEq for Base64<'a> {
451    fn eq(&self, other: &Base64) -> bool {
452        if self.value.len() != other.value.len() {
453            return false;
454        } else {
455            for i in 0..self.value.len() {
456                if self.value[i] != other.value[i] {
457                    return false;
458                }
459            }
460        }
461        true
462    }
463}
464
465impl<'a> Ord for Base64<'a> {
466    fn cmp(&self, other: &Base64<'a>) -> Ordering {
467        if self.value.len() != other.value.len() {
468            // Different lengths
469            return self.value.len().cmp(&other.value.len());
470        } else {
471            for i in 0..self.value.len() {
472                if self.value[i] != '\n'
473                    && other.value[i] != '\n'
474                    && self.value[i] != other.value[i]
475                {
476                    // Convert each to their decimal value then cmp
477                    return base64_char_to_decimal(self.conf.get_character_set(), self.value[i])
478                        .cmp(&base64_char_to_decimal(
479                            other.conf.get_character_set(),
480                            other.value[i],
481                        ));
482                }
483            }
484        }
485        Ordering::Equal
486    }
487}
488
489impl<'a> PartialOrd for Base64<'a> {
490    fn partial_cmp(&self, other: &Base64<'a>) -> Option<Ordering> {
491        Some(self.cmp(other))
492    }
493}