2014-11-06 21:23:02 +01:00
|
|
|
//! Messages to and from the server.
|
2014-12-23 18:53:30 +01:00
|
|
|
use std::borrow::ToOwned;
|
2016-01-30 10:56:32 +01:00
|
|
|
use std::io::{Result as IoResult};
|
2014-11-19 01:00:18 +01:00
|
|
|
use std::str::FromStr;
|
2016-01-30 10:56:32 +01:00
|
|
|
use client::data::Command;
|
2014-11-03 00:08:56 +01:00
|
|
|
|
2014-11-06 21:23:02 +01:00
|
|
|
/// IRC Message data.
|
2015-02-03 19:11:33 +01:00
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
2014-11-02 23:25:45 +01:00
|
|
|
pub struct Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
/// Message tags as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
|
|
|
pub tags: Option<Vec<Tag>>,
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
2014-11-02 23:25:45 +01:00
|
|
|
pub prefix: Option<String>,
|
2016-01-30 10:56:32 +01:00
|
|
|
/// The IRC command.
|
|
|
|
pub command: Command,
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Message {
|
2014-11-06 21:23:02 +01:00
|
|
|
/// Creates a new Message.
|
2016-01-30 10:56:32 +01:00
|
|
|
pub fn new(prefix: Option<&str>, command: &str, args: Vec<&str>, suffix: Option<&str>)
|
|
|
|
-> IoResult<Message> {
|
2015-06-04 18:35:43 +02:00
|
|
|
Message::with_tags(None, prefix, command, args, suffix)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new Message optionally including IRCv3.2 message tags.
|
|
|
|
pub fn with_tags(tags: Option<Vec<Tag>>, prefix: Option<&str>, command: &str,
|
2016-01-30 10:56:32 +01:00
|
|
|
args: Vec<&str>, suffix: Option<&str>) -> IoResult<Message> {
|
|
|
|
Ok(Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: tags,
|
2014-12-23 18:53:30 +01:00
|
|
|
prefix: prefix.map(|s| s.to_owned()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: try!(Command::new(command, args, suffix)),
|
|
|
|
})
|
2015-04-26 05:43:16 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 18:03:57 +02:00
|
|
|
/// Gets the nickname of the message source, if it exists.
|
2016-01-30 17:59:57 +01:00
|
|
|
pub fn source_nickname(&self) -> Option<&str> {
|
2016-02-07 09:01:05 +01:00
|
|
|
// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
|
|
|
|
// <servername> ::= <host>
|
2015-11-26 19:11:02 +01:00
|
|
|
self.prefix.as_ref().and_then(|s|
|
|
|
|
match (s.find('!'), s.find('@'), s.find('.')) {
|
2016-02-07 17:33:45 +01:00
|
|
|
(Some(i), _, _) => Some(&s[..i]), // <nick> '!' <user> [ '@' <host> ]
|
|
|
|
(None, Some(i), _) => Some(&s[..i]), // <nick> '@' <host>
|
|
|
|
(None, None, None) => Some(&s), // <nick>
|
|
|
|
_ => None // <servername>
|
2015-11-26 19:11:02 +01:00
|
|
|
}
|
|
|
|
)
|
2015-01-02 21:20:26 +01:00
|
|
|
}
|
|
|
|
|
2014-11-06 21:23:02 +01:00
|
|
|
/// Converts a Message into a String according to the IRC protocol.
|
2014-11-02 23:25:45 +01:00
|
|
|
pub fn into_string(&self) -> String {
|
2016-01-30 10:56:32 +01:00
|
|
|
// TODO: tags
|
2014-11-02 23:25:45 +01:00
|
|
|
let mut ret = String::new();
|
|
|
|
if let Some(ref prefix) = self.prefix {
|
|
|
|
ret.push(':');
|
2015-02-21 15:28:12 +01:00
|
|
|
ret.push_str(&prefix);
|
2014-11-02 23:25:45 +01:00
|
|
|
ret.push(' ');
|
|
|
|
}
|
2016-01-30 10:56:32 +01:00
|
|
|
let cmd: String = From::from(&self.command);
|
|
|
|
ret.push_str(&cmd);
|
2014-11-03 09:02:35 +01:00
|
|
|
ret.push_str("\r\n");
|
2014-11-02 23:25:45 +01:00
|
|
|
ret
|
|
|
|
}
|
|
|
|
}
|
2014-11-03 00:08:56 +01:00
|
|
|
|
2016-01-30 10:56:32 +01:00
|
|
|
impl From<Command> for Message {
|
|
|
|
fn from(cmd: Command) -> Message {
|
|
|
|
Message { tags: None, prefix: None, command: cmd }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-03 00:08:56 +01:00
|
|
|
impl FromStr for Message {
|
2015-02-03 19:11:33 +01:00
|
|
|
type Err = &'static str;
|
|
|
|
fn from_str(s: &str) -> Result<Message, &'static str> {
|
2014-11-03 00:08:56 +01:00
|
|
|
let mut state = s.clone();
|
2015-02-03 19:11:33 +01:00
|
|
|
if s.len() == 0 { return Err("Cannot parse an empty string as a message.") }
|
2015-06-04 18:35:43 +02:00
|
|
|
let tags = if state.starts_with("@") {
|
|
|
|
let tags = state.find(' ').map(|i| &state[1..i]);
|
|
|
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
|
|
|
tags.map(|ts| ts.split(";").filter(|s| s.len() != 0).map(|s: &str| {
|
|
|
|
let mut iter = s.splitn(2, "=");
|
|
|
|
let (fst, snd) = (iter.next(), iter.next());
|
|
|
|
Tag(fst.unwrap_or("").to_owned(), snd.map(|s| s.to_owned()))
|
|
|
|
}).collect::<Vec<_>>())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2014-11-03 00:08:56 +01:00
|
|
|
let prefix = if state.starts_with(":") {
|
2015-01-09 23:38:46 +01:00
|
|
|
let prefix = state.find(' ').map(|i| &state[1..i]);
|
|
|
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
2014-11-03 00:08:56 +01:00
|
|
|
prefix
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2015-03-10 03:06:43 +01:00
|
|
|
let suffix = if state.contains(" :") {
|
|
|
|
let suffix = state.find(" :").map(|i| &state[i+2..state.len()-2]);
|
|
|
|
state = state.find(" :").map_or("", |i| &state[..i+1]);
|
2014-11-03 00:08:56 +01:00
|
|
|
suffix
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
let command = match state.find(' ').map(|i| &state[..i]) {
|
2014-11-03 08:02:29 +01:00
|
|
|
Some(cmd) => {
|
2015-01-09 23:38:46 +01:00
|
|
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
2014-11-03 08:02:29 +01:00
|
|
|
cmd
|
|
|
|
}
|
2015-02-03 19:11:33 +01:00
|
|
|
_ => return Err("Cannot parse a message without a command.")
|
2014-11-03 00:08:56 +01:00
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
if suffix.is_none() { state = &state[..state.len() - 2] }
|
2014-11-03 08:30:58 +01:00
|
|
|
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
|
2016-01-30 10:56:32 +01:00
|
|
|
Message::with_tags(
|
|
|
|
tags, prefix, command, args, suffix
|
|
|
|
).map_err(|_| "Invalid input for Command.")
|
2014-11-03 00:08:56 +01:00
|
|
|
}
|
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
|
2015-04-26 06:01:33 +02:00
|
|
|
impl<'a> From<&'a str> for Message {
|
|
|
|
fn from(s: &'a str) -> Message {
|
|
|
|
s.parse().unwrap()
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-04 18:35:43 +02:00
|
|
|
/// A message tag as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
|
|
pub struct Tag(String, Option<String>);
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2015-06-04 18:35:43 +02:00
|
|
|
use super::{Message, Tag};
|
2016-01-30 10:56:32 +01:00
|
|
|
use client::data::Command::{PRIVMSG, Raw};
|
2014-11-05 07:45:17 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2016-01-30 10:56:32 +01:00
|
|
|
assert_eq!(Message::new(None, "PRIVMSG", vec!["test"], Some("Testing!")).unwrap(), message)
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
|
|
|
|
2015-01-02 21:20:26 +01:00
|
|
|
#[test]
|
2016-01-30 17:59:57 +01:00
|
|
|
fn source_nickname() {
|
2015-01-02 21:20:26 +01:00
|
|
|
assert_eq!(Message::new(
|
2016-01-30 11:00:16 +01:00
|
|
|
None, "PING", vec![], Some("data")
|
2016-01-30 17:59:57 +01:00
|
|
|
).unwrap().source_nickname(), None);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2015-01-02 21:20:26 +01:00
|
|
|
assert_eq!(Message::new(
|
2016-01-30 11:00:16 +01:00
|
|
|
Some("irc.test.net"), "PING", vec![], Some("data")
|
2016-01-30 17:59:57 +01:00
|
|
|
).unwrap().source_nickname(), None);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2016-01-30 11:00:16 +01:00
|
|
|
assert_eq!(Message::new(
|
|
|
|
Some("test!test@test"), "PING", vec![], Some("data")
|
2016-01-30 17:59:57 +01:00
|
|
|
).unwrap().source_nickname(), Some("test"));
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2015-11-26 19:11:02 +01:00
|
|
|
assert_eq!(Message::new(
|
2016-01-30 11:00:16 +01:00
|
|
|
Some("test@test"), "PING", vec![], Some("data")
|
2016-01-30 17:59:57 +01:00
|
|
|
).unwrap().source_nickname(), Some("test"));
|
2016-02-07 09:00:35 +01:00
|
|
|
|
|
|
|
assert_eq!(Message::new(
|
2016-02-07 17:33:45 +01:00
|
|
|
Some("test!test@irc.test.com"), "PING", vec![], Some("data")
|
2016-02-07 09:00:35 +01:00
|
|
|
).unwrap().source_nickname(), Some("test"));
|
|
|
|
|
|
|
|
assert_eq!(Message::new(
|
|
|
|
Some("test!test@127.0.0.1"), "PING", vec![], Some("data")
|
|
|
|
).unwrap().source_nickname(), Some("test"));
|
|
|
|
|
|
|
|
assert_eq!(Message::new(
|
|
|
|
Some("test@test.com"), "PING", vec![], Some("data")
|
|
|
|
).unwrap().source_nickname(), Some("test"));
|
|
|
|
|
2015-11-26 19:11:02 +01:00
|
|
|
assert_eq!(Message::new(
|
2016-01-30 11:00:16 +01:00
|
|
|
Some("test"), "PING", vec![], Some("data")
|
2016-01-30 17:59:57 +01:00
|
|
|
).unwrap().source_nickname(), Some("test"));
|
2015-01-02 21:20:26 +01:00
|
|
|
}
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[test]
|
|
|
|
fn into_string() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2015-02-21 16:31:46 +01:00
|
|
|
assert_eq!(&message.into_string()[..], "PRIVMSG test :Testing!\r\n");
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: Some(format!("test!test@test")),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2015-02-21 16:31:46 +01:00
|
|
|
assert_eq!(&message.into_string()[..], ":test!test@test PRIVMSG test :Still testing!\r\n");
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn from_string() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2015-02-03 19:11:33 +01:00
|
|
|
assert_eq!("PRIVMSG test :Testing!\r\n".parse(), Ok(message));
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: Some(format!("test!test@test")),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2015-02-03 19:11:33 +01:00
|
|
|
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".parse(), Ok(message));
|
2015-06-04 18:35:43 +02:00
|
|
|
let message = Message {
|
|
|
|
tags: Some(vec![Tag(format!("aaa"), Some(format!("bbb"))),
|
|
|
|
Tag(format!("ccc"), None),
|
|
|
|
Tag(format!("example.com/ddd"), Some(format!("eee")))]),
|
|
|
|
prefix: Some(format!("test!test@test")),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing with tags!")),
|
2015-06-04 18:35:43 +02:00
|
|
|
};
|
|
|
|
assert_eq!("@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \
|
|
|
|
tags!\r\n".parse(), Ok(message))
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
2015-01-01 02:05:10 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_message() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2015-01-01 02:05:10 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2015-01-01 02:05:10 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = "PRIVMSG test :Testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-01-01 02:05:10 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2015-01-01 02:05:10 +01:00
|
|
|
prefix: Some(format!("test!test@test")),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2015-01-01 02:05:10 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
|
|
|
|
2015-03-10 03:06:43 +01:00
|
|
|
#[test]
|
|
|
|
fn to_message_with_colon_in_arg() {
|
|
|
|
// Apparently, UnrealIRCd (and perhaps some others) send some messages that include
|
|
|
|
// colons within individual parameters. So, let's make sure it parses correctly.
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2015-03-10 03:06:43 +01:00
|
|
|
prefix: Some(format!("test!test@test")),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: Raw(
|
|
|
|
format!("COMMAND"), vec![format!("ARG:test")], Some(format!("Testing!"))
|
|
|
|
),
|
2015-03-10 03:06:43 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = ":test!test@test COMMAND ARG:test :Testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-03-10 03:06:43 +01:00
|
|
|
}
|
|
|
|
|
2015-01-01 02:05:10 +01:00
|
|
|
#[test]
|
2015-03-22 04:08:41 +01:00
|
|
|
#[should_panic]
|
2015-01-01 02:05:10 +01:00
|
|
|
fn to_message_invalid_format() {
|
2015-04-26 06:11:51 +02:00
|
|
|
let _: Message = ":invalid :message".into();
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|