Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces Prefix enum #149

Merged
merged 9 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions irc-proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod irc;
pub mod line;
pub mod message;
pub mod mode;
pub mod prefix;
pub mod response;

pub use self::caps::{Capability, NegotiationVersion};
Expand All @@ -33,4 +34,5 @@ pub use self::command::{BatchSubCommand, CapSubCommand, Command};
pub use self::irc::IrcCodec;
pub use self::message::Message;
pub use self::mode::{ChannelMode, Mode, UserMode};
pub use self::prefix::Prefix;
pub use self::response::Response;
38 changes: 16 additions & 22 deletions irc-proto/src/message.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! A module providing a data structure for messages to and from IRC servers.
use std::borrow::ToOwned;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::fmt::{Display, Formatter, Result as FmtResult, Write};
use std::str::FromStr;

use error;
use error::{ProtocolError, MessageParseError};
use chan::ChannelExt;
use command::Command;
use error;
use error::{MessageParseError, ProtocolError};
use prefix::Prefix;


/// A data structure representing an IRC message according to the protocol specification. It
/// consists of a collection of IRCv3 tags, a prefix (describing the source of the message), and
Expand All @@ -20,7 +22,7 @@ pub struct Message {
/// in IRCv3 extensions to the IRC protocol.
pub tags: Option<Vec<Tag>>,
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
pub prefix: Option<String>,
pub prefix: Option<Prefix>,
/// The IRC command, parsed according to the known specifications. The command itself and its
/// arguments (including the special suffix argument) are captured in this component.
pub command: Command,
Expand Down Expand Up @@ -60,7 +62,7 @@ impl Message {
) -> Result<Message, error::MessageParseError> {
Ok(Message {
tags: tags,
prefix: prefix.map(|s| s.to_owned()),
prefix: prefix.map(|p| p.into()),
command: Command::new(command, args, suffix)?,
})
}
Expand All @@ -81,15 +83,9 @@ impl Message {
pub fn source_nickname(&self) -> Option<&str> {
// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
// <servername> ::= <host>
self.prefix.as_ref().and_then(|s| match (
s.find('!'),
s.find('@'),
s.find('.'),
) {
(Some(i), _, _) | // <nick> '!' <user> [ '@' <host> ]
(None, Some(i), _) => Some(&s[..i]), // <nick> '@' <host>
(None, None, None) => Some(s), // <nick>
_ => None, // <servername>
self.prefix.as_ref().and_then(|p| match p {
Prefix::Nickname(name, _, _) => Some(&name[..]),
_ => None
})
}

Expand Down Expand Up @@ -149,9 +145,7 @@ impl Message {
ret.push(' ');
}
if let Some(ref prefix) = self.prefix {
ret.push(':');
ret.push_str(prefix);
ret.push(' ');
write!(ret, ":{} ", prefix).unwrap();
}
let cmd: String = From::from(&self.command);
ret.push_str(&cmd);
Expand Down Expand Up @@ -362,7 +356,7 @@ mod test {
assert_eq!(&message.to_string()[..], "PRIVMSG test :Testing!\r\n");
let message = Message {
tags: None,
prefix: Some(format!("test!test@test")),
prefix: Some("test!test@test".into()),
command: PRIVMSG(format!("test"), format!("Still testing!")),
};
assert_eq!(
Expand All @@ -384,7 +378,7 @@ mod test {
);
let message = Message {
tags: None,
prefix: Some(format!("test!test@test")),
prefix: Some("test!test@test".into()),
command: PRIVMSG(format!("test"), format!("Still testing!")),
};
assert_eq!(
Expand All @@ -399,7 +393,7 @@ mod test {
Tag(format!("ccc"), None),
Tag(format!("example.com/ddd"), Some(format!("eee"))),
]),
prefix: Some(format!("test!test@test")),
prefix: Some("test!test@test".into()),
command: PRIVMSG(format!("test"), format!("Testing with tags!")),
};
assert_eq!(
Expand Down Expand Up @@ -450,7 +444,7 @@ mod test {
assert_eq!(msg, message);
let message = Message {
tags: None,
prefix: Some(format!("test!test@test")),
prefix: Some("test!test@test".into()),
command: PRIVMSG(format!("test"), format!("Still testing!")),
};
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
Expand All @@ -463,7 +457,7 @@ mod test {
// colons within individual parameters. So, let's make sure it parses correctly.
let message = Message {
tags: None,
prefix: Some(format!("test!test@test")),
prefix: Some("test!test@test".into()),
command: Raw(
format!("COMMAND"),
vec![format!("ARG:test")],
Expand Down
209 changes: 209 additions & 0 deletions irc-proto/src/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//! A module providing an enum for a message prefix.
use std::str::FromStr;
use std::fmt;

/// The Prefix indicates "the true origin of the message", according to the server.
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Prefix {
/// servername, e.g. collins.mozilla.org
ServerName(String),
/// nickname [ ["!" username] "@" hostname ]
/// i.e. Nickname(nickname, username, hostname)
/// Any of the strings may be ""
Nickname(String, String, String),
}

impl Prefix {
/// Creates a prefix by parsing a string.
///
/// # Example
/// ```
/// # extern crate irc_proto;
/// # use irc_proto::Prefix;
/// # fn main() {
/// Prefix::new_from_str("nickname!username@hostname");
/// Prefix::new_from_str("example.com");
/// # }
/// ```
pub fn new_from_str(s: &str) -> Prefix {
#[derive(Copy, Clone, Eq, PartialEq)]
enum Active {
Name,
User,
Host,
}

let mut name = String::new();
let mut user = String::new();
let mut host = String::new();
let mut active = Active::Name;
let mut is_server = false;

for c in s.chars() {
if c == '.' && active == Active::Name {
// We won't return Nickname("nick", "", "") but if @ or ! are
// encountered, then we set this back to false
is_server = true;
}

match c {
'!' if active == Active::Name => {
is_server = false;
active = Active::User;
},

'@' if active != Active::Host => {
is_server = false;
active = Active::Host;
},

_ => {
// Push onto the active buffer
match active {
Active::Name => &mut name,
Active::User => &mut user,
Active::Host => &mut host,
}.push(c)
}
}
}

if is_server {
Prefix::ServerName(name)
} else {
Prefix::Nickname(name, user, host)
}
}
}

/// This implementation never returns an error and is isomorphic with `Display`.
impl FromStr for Prefix {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Prefix::new_from_str(s))
}
}

/// This is isomorphic with `FromStr`
impl fmt::Display for Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Prefix::ServerName(name) => write!(f, "{}", name),
Prefix::Nickname(name, user, host) => match (&name[..], &user[..], &host[..]) {
("", "", "") => write!(f, ""),
(name, "", "") => write!(f, "{}", name),
(name, user, "") => write!(f, "{}!{}", name, user),
(name, "", host) => write!(f, "{}@{}", name, host),
(name, user, host) => write!(f, "{}!{}@{}", name, user, host),
},
}
}
}

impl<'a> From<&'a str> for Prefix {
fn from(s: &str) -> Self {
Prefix::new_from_str(s)
}
}

#[cfg(test)]
mod test {
use super::Prefix::{self, ServerName, Nickname};

// Checks that str -> parsed -> Display doesn't lose data
fn test_parse(s: &str) -> Prefix {
let prefix = Prefix::new_from_str(s);
let s2 = format!("{}", prefix);
assert_eq!(s, &s2);
prefix
}

#[test]
fn print() {
let s = format!("{}", Nickname("nick".into(), "".into(), "".into()));
assert_eq!(&s, "nick");
let s = format!("{}", Nickname("nick".into(), "user".into(), "".into()));
assert_eq!(&s, "nick!user");
let s = format!("{}", Nickname("nick".into(), "user".into(), "host".into()));
assert_eq!(&s, "nick!user@host");
}

#[test]
fn parse_word() {
assert_eq!(
test_parse("only_nick"),
Nickname("only_nick".into(), String::new(), String::new())
)
}

#[test]
fn parse_host() {
assert_eq!(
test_parse("host.tld"),
ServerName("host.tld".into())
)
}

#[test]
fn parse_nick_user() {
assert_eq!(
test_parse("test!nick"),
Nickname("test".into(), "nick".into(), String::new())
)
}

#[test]
fn parse_nick_user_host() {
assert_eq!(
test_parse("test!nick@host"),
Nickname("test".into(), "nick".into(), "host".into())
)
}

#[test]
fn parse_dot_and_symbols() {
assert_eq!(
test_parse("test.net@something"),
Nickname("test.net".into(), "".into(), "something".into())
)
}

#[test]
fn parse_danger_cases() {
assert_eq!(
test_parse("name@name!user"),
Nickname("name".into(), "".into(), "name!user".into())
);
assert_eq!(
// can't reverse the parse
"name!@".parse::<Prefix>().unwrap(),
Nickname("name".into(), "".into(), "".into())
);
assert_eq!(
// can't reverse the parse
"name!@hostname".parse::<Prefix>().unwrap(),
Nickname("name".into(), "".into(), "hostname".into())
);
assert_eq!(
test_parse("name!.user"),
Nickname("name".into(), ".user".into(), "".into())
);
assert_eq!(
test_parse("name!user.user"),
Nickname("name".into(), "user.user".into(), "".into())
);
assert_eq!(
test_parse("name!user@host.host"),
Nickname("name".into(), "user".into(), "host.host".into())
);
assert_eq!(
test_parse("!user"),
Nickname("".into(), "user".into(), "".into())
);
assert_eq!(
"!@host.host".parse::<Prefix>().unwrap(),
Nickname("".into(), "".into(), "host.host".into())
);
}
}
2 changes: 1 addition & 1 deletion src/client/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub use client::data::Config;
pub use client::reactor::IrcReactor;
pub use client::{EachIncomingExt, IrcClient, Client};
pub use client::ext::ClientExt;
pub use proto::{Capability, ChannelExt, Command, Message, NegotiationVersion, Response};
pub use proto::{Capability, ChannelExt, Command, Message, Prefix, NegotiationVersion, Response};
pub use proto::{ChannelMode, Mode, UserMode};

pub use futures::{Future, Stream};