From 0493911702d385378ddd69fc0e51de62716c1cd4 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sat, 4 Jan 2025 19:04:56 -0500 Subject: [PATCH] Add line wrapping to encoder --- src/decode.rs | 56 ++--------------------------- src/encode.rs | 74 ++++++++++++++++---------------------- src/error.rs | 57 +++++++++++++++++++++++++++++ src/lib.rs | 1 + src/testdata/lorem.txt | 1 + src/testdata/lorem_b64.txt | 11 ++++++ 6 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 src/error.rs create mode 100644 src/testdata/lorem.txt create mode 100644 src/testdata/lorem_b64.txt diff --git a/src/decode.rs b/src/decode.rs index 2e131dc..e10fe49 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -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 for Error { - fn from(value: io::Error) -> Self { - Self::Io(value) - } -} - -impl From for Error { - fn from(value: char) -> Self { - Self::IllegalChar(value) - } -} - -impl From 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 { reader: R, writer: W, diff --git a/src/encode.rs b/src/encode.rs index 0a5dd80..9fb5457 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -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 for Error { - fn from(value: io::Error) -> Self { - Self::Io(value) - } -} - -impl From 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 { reader: R, writer: W, alphabet: B64Alphabet, + wrap: Option, } impl Encoder { /// 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) -> Self { + pub fn new(reader: R, writer: W, alphabet: Option, wrap: Option) -> 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 Encoder { } 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 Encoder { #[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); + } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a0ca903 --- /dev/null +++ b/src/error.rs @@ -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 for Error { + fn from(value: fmt::Error) -> Self { + Self::Fmt(value) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + fn from(value: char) -> Self { + Self::IllegalChar(value) + } +} + +impl From 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, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0d73353..ca7eb01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #[warn(clippy::all, clippy::pedantic)] pub mod decode; pub mod encode; +pub mod error; #[derive(Clone, Copy)] pub struct B64Alphabet { diff --git a/src/testdata/lorem.txt b/src/testdata/lorem.txt new file mode 100644 index 0000000..7a9fae3 --- /dev/null +++ b/src/testdata/lorem.txt @@ -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. diff --git a/src/testdata/lorem_b64.txt b/src/testdata/lorem_b64.txt new file mode 100644 index 0000000..f9c1b46 --- /dev/null +++ b/src/testdata/lorem_b64.txt @@ -0,0 +1,11 @@ +TG9yZW0gaXBzdW0gb2RvciBhbWV0LCBjb25zZWN0ZXR1ZXIgYWRpcGlzY2luZyBlbGl0LiBNYXVy +aXMgcHJhZXNlbnQgc2VuZWN0dXMgdml2YW11cyB1cm5hIGRvbmVjIG1hZ25hIGJpYmVuZHVtIGFl +bmVhbi4gTmFtIG5lYyBzb2RhbGVzIGV4IGN1cnN1cyBpbmNlcHRvcyB2aXRhZSBub24gaGFiaXRh +bnQgY29uc2VjdGV0dXIuIEV4IG1vbnRlcyBxdWlzIHBlbGxlbnRlc3F1ZSB0ZW1wb3IgbmVxdWUu +IENsYXNzIGlwc3VtIGZyaW5naWxsYSBtYXVyaXMgcXVhbSB0ZW1wdXMuIE51bGxhbSBoYWJpdGFz +c2UgYXJjdSBhYyBuYXNjZXR1ciBjbGFzcyBhZCBpbi4gU2VuZWN0dXMgbm9zdHJhIGFyY3UgaGFi +aXRhbnQgYXJjdSBibGFuZGl0IHBvdGVudGkuIENvbmd1ZSBwZW5hdGlidXMgc29sbGljaXR1ZGlu +IHVsbGFtY29ycGVyIHJpZGljdWx1cyBzZW1wZXIuIEFjIHBvcnR0aXRvciBiaWJlbmR1bSBuYW07 +IHJob25jdXMgcGVsbGVudGVzcXVlIG5hdG9xdWUgcHJldGl1bSBzaXQgZmV1Z2lhdC4gTGl0b3Jh +IGNvbnZhbGxpcyBtYWVjZW5hcyBlbGl0IG5ldHVzIGJsYW5kaXQgbXVzIGR1aXMgZ3JhdmlkYSBz +b2xsaWNpdHVkaW4uCg== \ No newline at end of file