Ensures that some operations don't accept invalid data.

Invalid characters in input strings are \r and \n, according to RFC3501 section 4.3.
This commit is contained in:
rhn 2017-11-02 16:58:56 +01:00
parent c899986685
commit 5acf34c3b4
2 changed files with 79 additions and 6 deletions

View file

@ -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<String> {
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<T: Read + Write> {
@ -284,18 +295,22 @@ impl<T: Read + Write> Client<T> {
/// 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<Mailbox> {
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<Mailbox> {
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<T: Read + Write> Client<T> {
/// 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<<P!wJ\"",
&validate_str("~iCQ_k;>[&\"sVCvUW`e<<P!wJ").unwrap());
}
#[test]
fn validate_newline() {
if let Err(ref e) = validate_str("test\nstring") {
if let &Error::Validate(ref ve) = e {
if ve.0 == '\n' {
return;
}
}
panic!("Wrong error: {:?}", e);
}
panic!("No error");
}
#[test]
#[allow(unreachable_patterns)]
fn validate_carriage_return() {
if let Err(ref e) = validate_str("test\rstring") {
if let &Error::Validate(ref ve) = e {
if ve.0 == '\r' {
return;
}
}
panic!("Wrong error: {:?}", e);
}
panic!("No error");
}
}

View file

@ -28,6 +28,8 @@ pub enum Error {
ConnectionLost,
// Error parsing a server response.
Parse(ParseError),
// Error validating input data
Validate(ValidateError),
// Error appending a mail
Append,
}
@ -62,6 +64,7 @@ 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::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
}
}