use constants::{MessageType, MAX_MESSAGE_LENGTH}; use std::io; #[deriving(PartialEq, Eq, Show)] pub struct Message { from: Option, message_type: MessageType, params: Vec, } impl Message { pub fn new (from: Option, message_type: MessageType, params: Vec) -> Message { Message { from: from, message_type: message_type, params: params } } pub fn parse (msg: &str) -> Result { if msg.len() > MAX_MESSAGE_LENGTH { return Err("message too long"); } let message_parser = regex!(r"^(?::([^ ]+) )?([A-Z]+|[0-9]{3}) ([^\r\n\0]*)\r\n$"); match message_parser.captures(msg) { Some(captures) => { let from = captures.at(1); let from = if from.len() > 0 { Some(from.to_string()) } else { None }; let command = captures.at(2); let params = Message::parse_params(captures.at(3)); match from_str(command) { Some(c) => Ok(Message::new(from, c, params)), None => Err("command parsing failed"), } }, None => Err("message parsing failed"), } } pub fn from (&self) -> &Option { &self.from } pub fn message_type (&self) -> &MessageType { &self.message_type } pub fn params (&self) -> &Vec { &self.params } pub fn is_reply (&self) -> bool { self.message_type.is_reply() } pub fn write_protocol_string (&self, w: &mut W) -> io::IoResult<()> { let mut buf = [0u8, ..MAX_MESSAGE_LENGTH]; { let mut bufw = io::BufWriter::new(buf); match self.from { Some(ref f) => { try!(write!(bufw, ":{} ", f)) }, None => {}, } try!(write!(bufw, "{}", self.message_type)); for param in self.params.iter() { if param.as_slice().contains_char(' ') { try!(write!(bufw, " :{}", param)); } else { try!(write!(bufw, " {}", param)); } } try!(write!(bufw, "\r\n")); } let len = buf.iter().position(|&c| c == 0).unwrap_or(MAX_MESSAGE_LENGTH); try!(w.write(buf.slice(0, len))); try!(w.flush()); Ok(()) } pub fn to_protocol_string (&self) -> String { let mut w = io::MemWriter::new(); // this should never fail, so unwrap is fine self.write_protocol_string(&mut w).unwrap(); // XXX encoding String::from_utf8_lossy(w.unwrap().as_slice()).into_string() } fn parse_params(params: &str) -> Vec { let mut offset = 0; let len = params.len(); let mut ret = vec![]; loop { if offset >= len { return ret; } if params.char_at(offset) == ':' { ret.push(params.slice(offset + 1, len).to_string()); return ret; } let remaining = params.slice(offset, len); match remaining.find(' ') { Some(next) => { ret.push(remaining.slice(0, next).to_string()); offset += next + 1; }, None => { ret.push(remaining.to_string()); return ret; } } } } } #[test] fn test_message_parser () { use constants::*; { let msg = "PASS secretpasswordhere\r\n"; assert_eq!( Message::parse(msg), Ok( Message { from: None, message_type: Pass, params: vec!["secretpasswordhere".to_string()], } ) ); } { let msg = ":WiZ NICK Kilroy\r\n"; assert_eq!( Message::parse(msg), Ok( Message { from: Some("WiZ".to_string()), message_type: Nick, params: vec!["Kilroy".to_string()], } ) ); } { let msg = "QUIT :Gone to have lunch\r\n"; assert_eq!( Message::parse(msg), Ok( Message { from: None, message_type: Quit, params: vec!["Gone to have lunch".to_string()], } ) ); } { let msg = ":Trillian SQUIT cm22.eng.umd.edu :Server out of control\r\n"; assert_eq!( Message::parse(msg), Ok( Message { from: Some("Trillian".to_string()), message_type: Squit, params: vec![ "cm22.eng.umd.edu".to_string(), "Server out of control".to_string(), ], } ) ); } { let msg = "401 doy :No such nick/channel\r\n"; assert_eq!( Message::parse(msg), Ok( Message { from: None, message_type: Reply(ERR_NOSUCHNICK), params: vec![ "doy".to_string(), "No such nick/channel".to_string(), ], } ) ); } }