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 6 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
204 changes: 204 additions & 0 deletions irc-proto/src/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//! A module providing an enum for a message prefix.
use std::borrow::ToOwned;
use std::string;
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;
/// # use irc::client::prelude::*;
/// # 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 = 0,
User = 1,
Host = 2,
brigand marked this conversation as resolved.
Show resolved Hide resolved
}

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

for c in s.chars() {
match c {
// We consider the '.' to be a ServerName except if a ! has already
// been encountered.
'.' if active == Active::Name => return Prefix::ServerName(s.to_owned()),
brigand marked this conversation as resolved.
Show resolved Hide resolved

'!' if active == Active::Name => {
active = Active::User;
},

// The '@' is not special until we've started the username
// portion
brigand marked this conversation as resolved.
Show resolved Hide resolved
'@' if active == Active::User => {
active = Active::Host;
},

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

Prefix::Nickname(name, user, host)
}
}

/// This implementation never returns an error and is isomorphic with `Display`.
impl FromStr for Prefix {
type Err = string::ParseError;
brigand marked this conversation as resolved.
Show resolved Hide resolved

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),
// This case shouldn't happen normally, but user!@host is invalid, so we
// format it without the host
(name, "", _host) => write!(f, "{}", name),
brigand marked this conversation as resolved.
Show resolved Hide resolved
(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"),
ServerName("test.net@something".into())
)
}

#[test]
fn parse_danger_cases() {
assert_eq!(
test_parse("name@name!user"),
Nickname("name@name".into(), "user".into(), String::new())
);
assert_eq!(
test_parse("name!@"),
Nickname("name".into(), "".into(), "".into())
);
assert_eq!(
test_parse("name!@hostname"),
Nickname("name".into(), "".into(), "hostname".into())
);
assert_eq!(
test_parse("name!.user"),
Nickname("name".into(), ".user".into(), String::new())
);
assert_eq!(
test_parse("name!user.user"),
Nickname("name".into(), "user.user".into(), String::new())
);
assert_eq!(
test_parse("[email protected]"),
Nickname("name".into(), "user".into(), "host.host".into())
);
assert_eq!(
test_parse("!user"),
Nickname("".into(), "user".into(), String::new())
);
assert_eq!(
test_parse("[email protected]"),
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};