diff --git a/imap-codec/examples/client.rs b/imap-codec/examples/client.rs new file mode 100644 index 00000000..32b9b3c1 --- /dev/null +++ b/imap-codec/examples/client.rs @@ -0,0 +1,81 @@ +use imap_codec::{fragmentizer::Fragmentizer, GreetingCodec, ResponseCodec}; + +#[path = "common/common.rs"] +mod common; + +use common::read_more; + +use crate::common::Role; + +enum State { + Greeting, + Response, +} + +const WELCOME: &str = r#"# Parsing of IMAP greeting and responses + +"S:" denotes the server. +".." denotes the continuation of an (incomplete) response, e.g., due to the use of an IMAP literal. + +Note: "\n" will be automatically replaced by "\r\n". + +-------------------------------------------------------------------------------------------------- + +Enter intial IMAP greeting followed by IMAP responses (or "exit"). +"#; + +fn main() { + println!("{}", WELCOME); + + let mut fragmentizer = Fragmentizer::new(10 * 1024); + let mut state = State::Greeting; + + loop { + // Progress next fragment. + let Some(_fragment_info) = fragmentizer.progress() else { + // Read more bytes ... + let bytes = read_more(Role::Server, fragmentizer.message_bytes().is_empty()); + + // ... and pass the bytes to the Fragmentizer ... + fragmentizer.enqueue_bytes(&bytes); + + // ... and try again. + continue; + }; + + // Check whether the Fragmentizer detected a complete message. + if !fragmentizer.is_message_complete() { + // Read next fragment. + continue; + } + + // The Fragmentizer detected a complete message. + match state { + State::Greeting => { + match fragmentizer.decode_message(&GreetingCodec::default()) { + Ok(greeting) => { + // Do something with the greeting ... + println!("{:#?}", greeting); + + // ... and proceed with reponses. + state = State::Response; + } + Err(err) => { + println!("Error parsing greeting: {err:?}"); + } + }; + } + State::Response => { + match fragmentizer.decode_message(&ResponseCodec::default()) { + Ok(response) => { + // Do something with the response. + println!("{:#?}", response); + } + Err(err) => { + println!("Error parsing response: {err:?}"); + } + }; + } + }; + } +} diff --git a/imap-codec/examples/common/common.rs b/imap-codec/examples/common/common.rs index 6a601229..be69cd11 100644 --- a/imap-codec/examples/common/common.rs +++ b/imap-codec/examples/common/common.rs @@ -12,8 +12,8 @@ pub enum Role { Server, } -pub fn read_more(buffer: &mut Vec, role: Role) { - let prompt = if buffer.is_empty() { +pub fn read_more(role: Role, message_begin: bool) -> Vec { + let prompt = if message_begin { match role { Role::Client => "C: ", Role::Server => "S: ", @@ -30,7 +30,7 @@ pub fn read_more(buffer: &mut Vec, role: Role) { std::process::exit(0); } - buffer.extend_from_slice(line.as_bytes()); + line.into_bytes() } fn read_line(prompt: &str, role: Role) -> String { diff --git a/imap-codec/examples/fragmentizer_client.rs b/imap-codec/examples/fragmentizer_client.rs deleted file mode 100644 index 73a0b6f3..00000000 --- a/imap-codec/examples/fragmentizer_client.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::{io::Read, net::TcpStream}; - -use imap_codec::{fragmentizer::Fragmentizer, GreetingCodec, ResponseCodec}; - -enum State { - Greeting, - Response, -} - -fn main() { - let mut stream = TcpStream::connect("127.0.0.1:12345").unwrap(); - let mut fragmentizer = Fragmentizer::new(1024); - - let mut state = State::Greeting; - - loop { - match fragmentizer.progress() { - Some(fragment_info) => { - dbg!(fragment_info); - dbg!(fragmentizer.fragment_bytes(fragment_info)); - - if fragmentizer.is_message_complete() { - match state { - State::Greeting => { - match fragmentizer.decode_message(&GreetingCodec::new()) { - Ok(greeting) => { - dbg!(greeting); - state = State::Response; - } - Err(error) => { - dbg!(error); - } - } - } - State::Response => { - match fragmentizer.decode_message(&ResponseCodec::new()) { - Ok(response) => { - dbg!(response); - } - Err(error) => { - dbg!(error); - } - } - } - } - } - } - None => { - println!("Reading bytes..."); - let mut buffer = [0; 64]; - let count = dbg!(stream.read(&mut buffer).unwrap()); - if count == 0 { - println!(""); - break; - } - - fragmentizer.enqueue_bytes(&buffer[..count]); - } - } - } -} diff --git a/imap-codec/examples/fragmentizer_server.rs b/imap-codec/examples/fragmentizer_server.rs deleted file mode 100644 index 24e0aee8..00000000 --- a/imap-codec/examples/fragmentizer_server.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::{io::Read, net::TcpListener}; - -use imap_codec::{fragmentizer::Fragmentizer, AuthenticateDataCodec, CommandCodec, IdleDoneCodec}; -use imap_types::command::CommandBody; - -enum State { - Command, - AuthenticateData, - Idle, -} - -fn main() { - let mut stream = { - let listener = TcpListener::bind("127.0.0.1:12345").unwrap(); - listener.accept().unwrap().0 - }; - - let mut fragmentizer = Fragmentizer::new(1024); - - let mut state = State::Command; - - loop { - match fragmentizer.progress() { - Some(fragment_info) => { - dbg!(fragment_info); - dbg!(fragmentizer.fragment_bytes(fragment_info)); - - if fragmentizer.is_message_complete() { - match state { - State::Command => match fragmentizer.decode_message(&CommandCodec::new()) { - Ok(command) => { - dbg!(&command); - state = match command.body { - CommandBody::Authenticate { .. } => State::AuthenticateData, - CommandBody::Idle => State::Idle, - _ => State::Command, - }; - } - Err(error) => { - dbg!(error); - } - }, - State::AuthenticateData => { - match fragmentizer.decode_message(&AuthenticateDataCodec::new()) { - Ok(authenticate_data) => { - dbg!(authenticate_data); - // Pretend we are done after one SASL round. - state = State::Command; - } - Err(error) => { - dbg!(error); - } - } - } - State::Idle => match fragmentizer.decode_message(&IdleDoneCodec::new()) { - Ok(idle_done) => { - dbg!(idle_done); - state = State::Command; - } - Err(error) => { - dbg!(error); - } - }, - } - } - } - None => { - println!("Reading bytes..."); - let mut buffer = [0; 64]; - let count = dbg!(stream.read(&mut buffer).unwrap()); - if count == 0 { - println!(""); - break; - } - - fragmentizer.enqueue_bytes(&buffer[..count]); - } - } - } -} diff --git a/imap-codec/examples/parse_command.rs b/imap-codec/examples/parse_command.rs deleted file mode 100644 index 2c7ff481..00000000 --- a/imap-codec/examples/parse_command.rs +++ /dev/null @@ -1,65 +0,0 @@ -use imap_codec::{ - decode::{CommandDecodeError, Decoder}, - CommandCodec, -}; - -#[path = "common/common.rs"] -mod common; - -use common::{read_more, COLOR_SERVER, RESET}; - -use crate::common::Role; - -const WELCOME: &str = r#"# Parsing of IMAP commands - -"C:" denotes the client, -"S:" denotes the server, and -".." denotes the continuation of an (incomplete) command, e.g., due to the use of an IMAP literal. - -Note: "\n" will be automatically replaced by "\r\n". - --------------------------------------------------------------------------------------------------- - -Enter IMAP command (or "exit"). -"#; - -fn main() { - println!("{}", WELCOME); - - let mut buffer = Vec::new(); - - loop { - // Try to parse the first command in `buffer`. - match CommandCodec::default().decode(&buffer) { - // Parser succeeded. - Ok((remaining, command)) => { - // Do something with the command ... - println!("{:#?}", command); - - // ... and proceed with the remaining data. - buffer = remaining.to_vec(); - } - // Parser needs more data. - Err(CommandDecodeError::Incomplete) => { - // Read more data. - read_more(&mut buffer, Role::Client); - } - // Parser needs more data, and a command continuation request is expected. - Err(CommandDecodeError::LiteralFound { .. }) => { - // Simulate literal acknowledgement ... - println!("S: {COLOR_SERVER}+ {RESET}"); - - // ... and read more data. - read_more(&mut buffer, Role::Client); - } - // Parser failed. - Err(CommandDecodeError::Failed) => { - println!("Error parsing command."); - println!("Clearing buffer."); - - // Clear the buffer and proceed with loop. - buffer.clear(); - } - } - } -} diff --git a/imap-codec/examples/parse_greeting.rs b/imap-codec/examples/parse_greeting.rs deleted file mode 100644 index fee68c8d..00000000 --- a/imap-codec/examples/parse_greeting.rs +++ /dev/null @@ -1,55 +0,0 @@ -use imap_codec::{ - decode::{Decoder, GreetingDecodeError}, - GreetingCodec, -}; - -#[path = "common/common.rs"] -mod common; - -use common::read_more; - -use crate::common::Role; - -const WELCOME: &str = r#"# Parsing of IMAP greetings - -"S:" denotes the server. - -Note: "\n" will be automatically replaced by "\r\n". - --------------------------------------------------------------------------------------------------- - -Enter IMAP greeting (or "exit"). -"#; - -fn main() { - println!("{}", WELCOME); - - let mut buffer = Vec::new(); - - loop { - // Try to parse the first greeting in `buffer`. - match GreetingCodec::default().decode(&buffer) { - // Parser succeeded. - Ok((remaining, greeting)) => { - // Do something with the greeting ... - println!("{:#?}", greeting); - - // ... and proceed with the remaining data. - buffer = remaining.to_vec(); - } - // Parser needs more data. - Err(GreetingDecodeError::Incomplete) => { - // Read more data. - read_more(&mut buffer, Role::Server); - } - // Parser failed. - Err(GreetingDecodeError::Failed) => { - println!("Error parsing greeting."); - println!("Clearing buffer."); - - // Clear the buffer and proceed with loop. - buffer.clear(); - } - } - } -} diff --git a/imap-codec/examples/parse_response.rs b/imap-codec/examples/parse_response.rs deleted file mode 100644 index 27719c71..00000000 --- a/imap-codec/examples/parse_response.rs +++ /dev/null @@ -1,66 +0,0 @@ -use imap_codec::{ - decode::{Decoder, ResponseDecodeError}, - ResponseCodec, -}; - -#[path = "common/common.rs"] -mod common; - -use common::read_more; - -use crate::common::Role; - -const WELCOME: &str = r#"# Parsing of IMAP responses - -"S:" denotes the server, and -".." denotes the continuation of an (incomplete) response, e.g., due to the use of an IMAP literal. - -Note: "\n" will be automatically replaced by "\r\n". - --------------------------------------------------------------------------------------------------- - -Enter IMAP response (or "exit"). -"#; - -fn main() { - println!("{}", WELCOME); - - let mut buffer = Vec::new(); - - loop { - // Try to parse the first response in `buffer`. - match ResponseCodec::default().decode(&buffer) { - // Parser succeeded. - Ok((remaining, response)) => { - // Do something with the response ... - println!("{:#?}", response); - - // ... and proceed with the remaining data. - buffer = remaining.to_vec(); - } - // Parser needs more data. - Err(ResponseDecodeError::Incomplete) => { - // Read more data. - read_more(&mut buffer, Role::Server); - } - // Parser needs more data. - // - // A client MUST receive any literal and can't reject it. However, if the literal is too - // large, the client would have the (semi-optimal) option to still *read it* but discard - // the data chunk by chunk. It could also close the connection. This is why we have this - // option. - Err(ResponseDecodeError::LiteralFound { .. }) => { - // Read more data. - read_more(&mut buffer, Role::Server); - } - // Parser failed. - Err(ResponseDecodeError::Failed) => { - println!("Error parsing response."); - println!("Clearing buffer."); - - // Clear the buffer and proceed with loop. - buffer.clear(); - } - } - } -} diff --git a/imap-codec/examples/server.rs b/imap-codec/examples/server.rs new file mode 100644 index 00000000..a6537124 --- /dev/null +++ b/imap-codec/examples/server.rs @@ -0,0 +1,154 @@ +use imap_codec::{ + fragmentizer::{FragmentInfo, Fragmentizer, LiteralAnnouncement}, + AuthenticateDataCodec, CommandCodec, IdleDoneCodec, +}; + +#[path = "common/common.rs"] +mod common; + +use common::{read_more, COLOR_SERVER, RESET}; +use imap_types::{ + command::{Command, CommandBody}, + core::{LiteralMode, Tag}, + IntoStatic, +}; + +use crate::common::Role; + +enum State { + Command, + Authenticate(Tag<'static>), + Idle, +} + +const WELCOME: &str = r#"# Parsing of IMAP commands + +"C:" denotes the client, +"S:" denotes the server, and +".." denotes the continuation of an (incomplete) command, e.g., due to the use of an IMAP literal. + +Note: "\n" will be automatically replaced by "\r\n". + +-------------------------------------------------------------------------------------------------- + +Enter IMAP commands (or "exit"). +"#; + +fn main() { + println!("{}", WELCOME); + + let mut fragmentizer = Fragmentizer::new(10 * 1024); + let mut state = State::Command; + + // Send a greeting. + println!("S: {COLOR_SERVER}* OK ...{RESET}"); + + loop { + // Progress next fragment. + let Some(fragment_info) = fragmentizer.progress() else { + // Read more bytes ... + let bytes = read_more(Role::Client, fragmentizer.message_bytes().is_empty()); + + // ... and pass the bytes to the Fragmentizer ... + fragmentizer.enqueue_bytes(&bytes); + + // ... and try again. + continue; + }; + + // The Fragmentizer detected a line that announces a sync literal. + if let FragmentInfo::Line { + announcement: + Some(LiteralAnnouncement { + mode: LiteralMode::Sync, + length, + }), + .. + } = fragment_info + { + // Check the length of the literal. + if length <= 1024 { + // Accept the literal ... + println!("S: {COLOR_SERVER}+ {RESET}"); + + // ... and continue with the remaining message. + continue; + } else if let Some(tag) = fragmentizer.decode_tag() { + // Reject the literal ... + println!("S: {COLOR_SERVER}{} BAD ...{RESET}", tag.as_ref()); + + // ... and skip the current message ... + fragmentizer.skip_message(); + + // ... and continue with the next message. + continue; + } + } + + // Check whether the Fragmentizer detected a complete message. + if !fragmentizer.is_message_complete() { + // Read next fragment. + continue; + } + + // The Fragmentizer detected a complete message. + match state { + State::Command => { + match fragmentizer.decode_message(&CommandCodec::default()) { + Ok(Command { + tag, + body: CommandBody::Authenticate { .. }, + }) => { + // Request another SASL round ... + println!("S: {COLOR_SERVER}+ {RESET}"); + + // ... and proceed with authenticate data. + state = State::Authenticate(tag.into_static()); + } + Ok(Command { + body: CommandBody::Idle, + .. + }) => { + // Accept the idle ... + println!("S: {COLOR_SERVER}+ ...{RESET}"); + + // ... and proceed with idle done. + state = State::Idle; + } + Ok(command) => { + // Do something with the command. + println!("{:#?}", command); + } + Err(err) => { + println!("Error parsing command: {err:?}"); + } + }; + } + State::Authenticate(ref tag) => { + match fragmentizer.decode_message(&AuthenticateDataCodec::default()) { + Ok(_authenticate_data) => { + // Accept the authentication after one SASL round. + println!("S: {COLOR_SERVER}{} OK ...{RESET}", tag.as_ref()); + + // ... and proceed with commands. + state = State::Command; + } + Err(err) => { + println!("Error parsing authenticate data: {err:?}"); + } + }; + } + State::Idle => { + match fragmentizer.decode_message(&IdleDoneCodec::default()) { + Ok(_idle_done) => { + // End idle and proceed with commands. + state = State::Command; + } + Err(err) => { + println!("Error parsing idle done: {err:?}"); + } + }; + } + } + } +} diff --git a/imap-types/src/arbitrary.rs b/imap-types/src/arbitrary.rs index 68d752c1..006a223e 100644 --- a/imap-types/src/arbitrary.rs +++ b/imap-types/src/arbitrary.rs @@ -30,6 +30,8 @@ macro_rules! impl_arbitrary_try_from { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { match <$target>::try_from(<$from>::arbitrary(u)?) { Ok(passed) => Ok(passed), + // TODO(590) + #[allow(unreachable_patterns)] Err(_) => Err(arbitrary::Error::IncorrectFormat), } } diff --git a/imap-types/src/sequence.rs b/imap-types/src/sequence.rs index d7d36dd2..15d043fa 100644 --- a/imap-types/src/sequence.rs +++ b/imap-types/src/sequence.rs @@ -224,6 +224,8 @@ macro_rules! impl_try_from_num { type Error = ValidationError; fn try_from(value: $num) -> Result { + // TODO(590) + #[allow(irrefutable_let_patterns)] if let Ok(value) = u32::try_from(value) { if let Ok(value) = NonZeroU32::try_from(value) { return Ok(Self::Value(value));