use crate::{
grammar::{self, CHAR_CR, CHAR_LF},
Error, Result, BASE64_WRAP_WIDTH, ENCAPSULATION_BOUNDARY_DELIMITER,
POST_ENCAPSULATION_BOUNDARY, PRE_ENCAPSULATION_BOUNDARY,
};
use base64ct::{Base64, Encoding};
#[cfg(feature = "alloc")]
use alloc::string::String;
pub fn encode<'a>(
label: &str,
line_ending: LineEnding,
input: &[u8],
buf: &'a mut [u8],
) -> Result<&'a [u8]> {
grammar::validate_label(label.as_bytes())?;
let mut buf = Buffer::new(buf, line_ending);
buf.write(PRE_ENCAPSULATION_BOUNDARY)?;
buf.write(label.as_bytes())?;
buf.writeln(ENCAPSULATION_BOUNDARY_DELIMITER)?;
for chunk in input.chunks((BASE64_WRAP_WIDTH * 3) / 4) {
buf.write_base64ln(chunk)?;
}
buf.write(POST_ENCAPSULATION_BOUNDARY)?;
buf.write(label.as_bytes())?;
buf.writeln(ENCAPSULATION_BOUNDARY_DELIMITER)?;
buf.finish()
}
pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> usize {
PRE_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
+ input
.chunks((BASE64_WRAP_WIDTH * 3) / 4)
.fold(0, |acc, chunk| {
acc + Base64::encoded_len(chunk) + line_ending.len()
})
+ POST_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode_string(label: &str, line_ending: LineEnding, input: &[u8]) -> Result<String> {
let mut buf = vec![0u8; encoded_len(label, line_ending, input)];
encode(label, line_ending, input, &mut buf)?;
String::from_utf8(buf).map_err(|_| Error::CharacterEncoding)
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum LineEnding {
CR,
LF,
CRLF,
}
impl Default for LineEnding {
#[cfg(windows)]
fn default() -> LineEnding {
LineEnding::CRLF
}
#[cfg(not(windows))]
fn default() -> LineEnding {
LineEnding::LF
}
}
#[allow(clippy::len_without_is_empty)]
impl LineEnding {
pub fn as_bytes(self) -> &'static [u8] {
match self {
LineEnding::CR => &[CHAR_CR],
LineEnding::LF => &[CHAR_LF],
LineEnding::CRLF => &[CHAR_CR, CHAR_LF],
}
}
pub fn len(self) -> usize {
self.as_bytes().len()
}
}
struct Buffer<'a> {
bytes: &'a mut [u8],
position: usize,
line_ending: LineEnding,
}
impl<'a> Buffer<'a> {
pub fn new(bytes: &'a mut [u8], line_ending: LineEnding) -> Self {
Self {
bytes,
position: 0,
line_ending,
}
}
pub fn write(&mut self, slice: &[u8]) -> Result<()> {
let reserved = self.reserve(slice.len())?;
reserved.copy_from_slice(slice);
Ok(())
}
pub fn writeln(&mut self, slice: &[u8]) -> Result<()> {
self.write(slice)?;
self.write(self.line_ending.as_bytes())
}
pub fn write_base64ln(&mut self, bytes: &[u8]) -> Result<()> {
let reserved = self.reserve(Base64::encoded_len(bytes))?;
Base64::encode(bytes, reserved)?;
self.write(self.line_ending.as_bytes())
}
pub fn finish(self) -> Result<&'a [u8]> {
self.bytes.get(..self.position).ok_or(Error::Length)
}
fn reserve(&mut self, nbytes: usize) -> Result<&mut [u8]> {
let new_position = self.position.checked_add(nbytes).ok_or(Error::Length)?;
let reserved = self
.bytes
.get_mut(self.position..new_position)
.ok_or(Error::Length)?;
self.position = new_position;
Ok(reserved)
}
}