From c89998668564cbdd06d776a2b00c47e45004f167 Mon Sep 17 00:00:00 2001 From: rhn Date: Wed, 1 Nov 2017 18:15:34 +0100 Subject: [PATCH 1/2] Make Bad Response error print the actual response --- src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/error.rs b/src/error.rs index cc029a4..7b71edf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -62,6 +62,9 @@ impl fmt::Display for Error { Error::Io(ref e) => fmt::Display::fmt(e, f), Error::Tls(ref e) => fmt::Display::fmt(e, f), Error::TlsHandshake(ref e) => fmt::Display::fmt(e, f), + Error::BadResponse(ref data) => { + write!(f, "{}: {}", &String::from(self.description()), &data.join("\n")) + } ref e => f.write_str(e.description()), } } From 5acf34c3b40d937de73870f61de3eecd3c5fb7d8 Mon Sep 17 00:00:00 2001 From: rhn Date: Thu, 2 Nov 2017 16:58:56 +0100 Subject: [PATCH 2/2] Ensures that some operations don't accept invalid data. Invalid characters in input strings are \r and \n, according to RFC3501 section 4.3. --- src/client.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++------ src/error.rs | 25 +++++++++++++++++++++ 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index 701d8c2..676b08a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,7 +8,7 @@ use super::mailbox::Mailbox; use super::authenticator::Authenticator; use super::parse::{parse_authenticate_response, parse_capability, parse_response, parse_response_ok, parse_select_or_examine}; -use super::error::{Error, Result, ParseError}; +use super::error::{Error, Result, ParseError, ValidateError}; static TAG_PREFIX: &'static str = "a"; const INITIAL_TAG: u32 = 0; @@ -21,6 +21,17 @@ macro_rules! quote { ) } +fn validate_str(value: &str) -> Result { + let quoted = quote!(value); + if let Some(_) = quoted.find("\n") { + return Err(Error::Validate(ValidateError('\n'))); + } + if let Some(_) = quoted.find("\r") { + return Err(Error::Validate(ValidateError('\r'))); + } + Ok(quoted) +} + /// Stream to interface with the IMAP server. This interface is only for the command stream. #[derive(Debug)] pub struct Client { @@ -284,18 +295,22 @@ impl Client { /// Log in to the IMAP server. pub fn login(&mut self, username: &str, password: &str) -> Result<()> { - self.run_command_and_check_ok(&format!("LOGIN {} {}", quote!(username), quote!(password))) + self.run_command_and_check_ok(&format!( + "LOGIN {} {}", + validate_str(username)?, + validate_str(password)? + )) } /// Selects a mailbox pub fn select(&mut self, mailbox_name: &str) -> Result { - let lines = try!(self.run_command_and_read_response(&format!("SELECT {}", quote!(mailbox_name)))); + let lines = try!(self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?))); parse_select_or_examine(lines) } /// Examine is identical to Select, but the selected mailbox is identified as read-only pub fn examine(&mut self, mailbox_name: &str) -> Result { - let lines = try!(self.run_command_and_read_response(&format!("EXAMINE {}", quote!(mailbox_name)))); + let lines = try!(self.run_command_and_read_response(&format!("EXAMINE {}", validate_str(mailbox_name)?))); parse_select_or_examine(lines) } @@ -320,12 +335,12 @@ impl Client { /// Create creates a mailbox with the given name. pub fn create(&mut self, mailbox_name: &str) -> Result<()> { - self.run_command_and_check_ok(&format!("CREATE {}", quote!(mailbox_name))) + self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name)?)) } /// Delete permanently removes the mailbox with the given name. pub fn delete(&mut self, mailbox_name: &str) -> Result<()> { - self.run_command_and_check_ok(&format!("DELETE {}", quote!(mailbox_name))) + self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name)?)) } /// Rename changes the name of a mailbox. @@ -926,4 +941,37 @@ mod tests { fn quote_dquote() { assert_eq!("\"test\\\"text\"", quote!("test\"text")); } + + #[test] + fn validate_random() { + assert_eq!("\"~iCQ_k;>[&\\\"sVCvUW`e<[&\"sVCvUW`e< fmt::Display::fmt(e, f), Error::Tls(ref e) => fmt::Display::fmt(e, f), Error::TlsHandshake(ref e) => fmt::Display::fmt(e, f), + Error::Validate(ref e) => fmt::Display::fmt(e, f), Error::BadResponse(ref data) => { write!(f, "{}: {}", &String::from(self.description()), &data.join("\n")) } @@ -77,6 +80,7 @@ impl StdError for Error { Error::Tls(ref e) => e.description(), Error::TlsHandshake(ref e) => e.description(), Error::Parse(ref e) => e.description(), + Error::Validate(ref e) => e.description(), Error::BadResponse(_) => "Bad Response", Error::NoResponse(_) => "No Response", Error::ConnectionLost => "Connection lost", @@ -130,3 +134,24 @@ impl StdError for ParseError { } } } + +// Invalid character found. Expand as needed +#[derive(Debug)] +pub struct ValidateError(pub char); + +impl fmt::Display for ValidateError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // print character in debug form because invalid ones are often whitespaces + write!(f, "{}: {:?}", self.description(), self.0) + } +} + +impl StdError for ValidateError { + fn description(&self) -> &str { + "Invalid character in input" + } + + fn cause(&self) -> Option<&StdError> { + None + } +}