Add line wrapping to encoder
This commit is contained in:
parent
13fda08e1b
commit
0493911702
6 changed files with 103 additions and 97 deletions
|
@ -1,60 +1,8 @@
|
|||
use {
|
||||
super::B64Alphabet,
|
||||
std::{
|
||||
fmt,
|
||||
io::{self, ErrorKind, Read, Write},
|
||||
num::TryFromIntError,
|
||||
},
|
||||
crate::{error::Error, B64Alphabet},
|
||||
std::io::{ErrorKind, Read, Write},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors which might possibly occur while decoding base64 encoded data
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
IllegalChar(char),
|
||||
MissingPadding,
|
||||
IntError(TryFromIntError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "{e}"),
|
||||
Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"),
|
||||
Self::MissingPadding => write!(f, "Missing Padding"),
|
||||
Self::IntError(e) => write!(f, "Int Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Error {
|
||||
fn from(value: char) -> Self {
|
||||
Self::IllegalChar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for Error {
|
||||
fn from(value: TryFromIntError) -> Self {
|
||||
Self::IntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Io(e) => Some(e),
|
||||
Self::IntError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Decoder<R: Read, W: Write> {
|
||||
reader: R,
|
||||
writer: W,
|
||||
|
|
|
@ -1,65 +1,32 @@
|
|||
use {
|
||||
super::B64Alphabet,
|
||||
crate::{error::Error, B64Alphabet},
|
||||
std::{
|
||||
fmt::{self, Write},
|
||||
io::{self, ErrorKind, Read},
|
||||
fmt::Write,
|
||||
io::{ErrorKind, Read},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
Fmt(fmt::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "{e}"),
|
||||
Self::Fmt(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
fn from(value: fmt::Error) -> Self {
|
||||
Self::Fmt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Fmt(e) => Some(e),
|
||||
Self::Io(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Encoder<R: Read, W: Write> {
|
||||
reader: R,
|
||||
writer: W,
|
||||
alphabet: B64Alphabet,
|
||||
wrap: Option<usize>,
|
||||
}
|
||||
|
||||
impl<R: Read, W: Write> Encoder<R, W> {
|
||||
/// Creates a new encoder with the given reader and writer. If alphabet is
|
||||
/// `None` the encoder will use the default rfc4648 alphabet.
|
||||
pub fn new(reader: R, writer: W, alphabet: Option<B64Alphabet>) -> Self {
|
||||
pub fn new(reader: R, writer: W, alphabet: Option<B64Alphabet>, wrap: Option<usize>) -> Self {
|
||||
Self {
|
||||
reader,
|
||||
writer,
|
||||
alphabet: alphabet.unwrap_or_default(),
|
||||
wrap,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(&mut self) -> Result<(), Error> {
|
||||
let mut total: usize = 0;
|
||||
loop {
|
||||
let mut ibuf = [0; 3];
|
||||
let mut obuf = [self.alphabet.pad(); 4];
|
||||
|
@ -93,7 +60,17 @@ impl<R: Read, W: Write> Encoder<R, W> {
|
|||
}
|
||||
num >>= 6;
|
||||
}
|
||||
write!(self.writer, "{}{}{}{}", obuf[0], obuf[1], obuf[2], obuf[3])?;
|
||||
if let Some(wrap) = self.wrap {
|
||||
for idx in 0..=3 {
|
||||
write!(self.writer, "{}", obuf[idx])?;
|
||||
total += 1;
|
||||
if total % wrap == 0 {
|
||||
writeln!(self.writer)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
write!(self.writer, "{}{}{}{}", obuf[0], obuf[1], obuf[2], obuf[3])?;
|
||||
}
|
||||
if outlen < 4 {
|
||||
break;
|
||||
}
|
||||
|
@ -111,21 +88,32 @@ impl<R: Read> Encoder<R, String> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::{self, File};
|
||||
|
||||
#[test]
|
||||
fn encode() {
|
||||
let mut encoder = Encoder::new("Hello, World".as_bytes(), String::new(), None);
|
||||
let mut encoder = Encoder::new("Hello, World".as_bytes(), String::new(), None, None);
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "SGVsbG8sIFdvcmxk");
|
||||
encoder = Encoder {
|
||||
reader: "Hello, World!".as_bytes(),
|
||||
writer: String::new(),
|
||||
alphabet: B64Alphabet::default(),
|
||||
wrap: None,
|
||||
};
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "SGVsbG8sIFdvcmxkIQ==");
|
||||
encoder = Encoder::new("Hello, World!\n".as_bytes(), String::new(), None);
|
||||
encoder = Encoder::new("Hello, World!\n".as_bytes(), String::new(), None, None);
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "SGVsbG8sIFdvcmxkIQo=");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapping() {
|
||||
let infile = File::open("src/testdata/lorem.txt").unwrap();
|
||||
let outfile = fs::read_to_string("src/testdata/lorem_b64.txt").unwrap();
|
||||
let mut encoder = Encoder::new(infile, String::new(), None, Some(76));
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), outfile);
|
||||
}
|
||||
}
|
||||
|
|
57
src/error.rs
Normal file
57
src/error.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::{error, fmt, io, num::TryFromIntError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Fmt(fmt::Error),
|
||||
Io(io::Error),
|
||||
IllegalChar(char),
|
||||
IntError(TryFromIntError),
|
||||
MissingPadding,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Fmt(e) => write!(f, "{e}"),
|
||||
Self::Io(e) => write!(f, "{e}"),
|
||||
Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"),
|
||||
Self::MissingPadding => write!(f, "Missing Padding"),
|
||||
Self::IntError(e) => write!(f, "Int Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
fn from(value: fmt::Error) -> Self {
|
||||
Self::Fmt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Error {
|
||||
fn from(value: char) -> Self {
|
||||
Self::IllegalChar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for Error {
|
||||
fn from(value: TryFromIntError) -> Self {
|
||||
Self::IntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Fmt(e) => Some(e),
|
||||
Self::Io(e) => Some(e),
|
||||
Self::IntError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#[warn(clippy::all, clippy::pedantic)]
|
||||
pub mod decode;
|
||||
pub mod encode;
|
||||
pub mod error;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct B64Alphabet {
|
||||
|
|
1
src/testdata/lorem.txt
vendored
Normal file
1
src/testdata/lorem.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Lorem ipsum odor amet, consectetuer adipiscing elit. Mauris praesent senectus vivamus urna donec magna bibendum aenean. Nam nec sodales ex cursus inceptos vitae non habitant consectetur. Ex montes quis pellentesque tempor neque. Class ipsum fringilla mauris quam tempus. Nullam habitasse arcu ac nascetur class ad in. Senectus nostra arcu habitant arcu blandit potenti. Congue penatibus sollicitudin ullamcorper ridiculus semper. Ac porttitor bibendum nam; rhoncus pellentesque natoque pretium sit feugiat. Litora convallis maecenas elit netus blandit mus duis gravida sollicitudin.
|
11
src/testdata/lorem_b64.txt
vendored
Normal file
11
src/testdata/lorem_b64.txt
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
TG9yZW0gaXBzdW0gb2RvciBhbWV0LCBjb25zZWN0ZXR1ZXIgYWRpcGlzY2luZyBlbGl0LiBNYXVy
|
||||
aXMgcHJhZXNlbnQgc2VuZWN0dXMgdml2YW11cyB1cm5hIGRvbmVjIG1hZ25hIGJpYmVuZHVtIGFl
|
||||
bmVhbi4gTmFtIG5lYyBzb2RhbGVzIGV4IGN1cnN1cyBpbmNlcHRvcyB2aXRhZSBub24gaGFiaXRh
|
||||
bnQgY29uc2VjdGV0dXIuIEV4IG1vbnRlcyBxdWlzIHBlbGxlbnRlc3F1ZSB0ZW1wb3IgbmVxdWUu
|
||||
IENsYXNzIGlwc3VtIGZyaW5naWxsYSBtYXVyaXMgcXVhbSB0ZW1wdXMuIE51bGxhbSBoYWJpdGFz
|
||||
c2UgYXJjdSBhYyBuYXNjZXR1ciBjbGFzcyBhZCBpbi4gU2VuZWN0dXMgbm9zdHJhIGFyY3UgaGFi
|
||||
aXRhbnQgYXJjdSBibGFuZGl0IHBvdGVudGkuIENvbmd1ZSBwZW5hdGlidXMgc29sbGljaXR1ZGlu
|
||||
IHVsbGFtY29ycGVyIHJpZGljdWx1cyBzZW1wZXIuIEFjIHBvcnR0aXRvciBiaWJlbmR1bSBuYW07
|
||||
IHJob25jdXMgcGVsbGVudGVzcXVlIG5hdG9xdWUgcHJldGl1bSBzaXQgZmV1Z2lhdC4gTGl0b3Jh
|
||||
IGNvbnZhbGxpcyBtYWVjZW5hcyBlbGl0IG5ldHVzIGJsYW5kaXQgbXVzIGR1aXMgZ3JhdmlkYSBz
|
||||
b2xsaWNpdHVkaW4uCg==
|
Loading…
Add table
Reference in a new issue