mod decode;
mod encode;
#[cfg(test)]
mod tests;
use crate::base64::alphabet::{self, Tables};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Utf7 {
imap: bool,
lenient: bool,
}
pub const IMAP: Utf7 = Utf7 {
imap: true,
lenient: false,
};
pub const MAIL: Utf7 = Utf7 {
imap: false,
lenient: false,
};
const SHIFT_END: u8 = b'-';
const REPLACEMENT: char = char::REPLACEMENT_CHARACTER;
static MAIL_DIRECT: ByteSet = ByteSet::new(
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 \t\r\n'(),-./:?!\"#$%&*;<=>@[]^_`{|}",
);
impl Utf7 {
pub const fn lenient(mut self) -> Self {
self.lenient = true;
self
}
#[inline(always)]
const fn shift(&self) -> u8 {
if self.imap { b'&' } else { b'+' }
}
#[inline(always)]
fn tables(&self) -> &'static Tables {
if self.imap {
&alphabet::IMAP
} else {
&alphabet::STANDARD
}
}
#[inline(always)]
fn is_direct(&self, byte: u8) -> bool {
if self.imap {
matches!(byte, b' '..=b'~') && byte != b'&'
} else {
MAIL_DIRECT.contains(byte)
}
}
#[inline(always)]
fn direct_len(&self, bytes: &[u8]) -> usize {
bytes
.iter()
.position(|&byte| !self.is_direct(byte))
.unwrap_or(bytes.len())
}
fn is_all_direct(&self, bytes: &[u8]) -> bool {
bytes
.iter()
.fold(true, |direct, &byte| direct & self.is_direct(byte))
}
}
struct ByteSet([bool; 256]);
impl ByteSet {
const fn new(members: &[u8]) -> Self {
let mut set = [false; 256];
let mut index = 0;
while index < members.len() {
set[members[index] as usize] = true;
index += 1;
}
ByteSet(set)
}
#[inline(always)]
fn contains(&self, byte: u8) -> bool {
self.0[byte as usize]
}
}