Skip to content

Commit

Permalink
Do not separate mode flags with spaces when stringifying MODE commands
Browse files Browse the repository at this point in the history
RFC2812 expects all mode flags to be in a single <modestring> parameter,
but irc-proto was sending each mode flag as a separate parameter, e.g.

  MODE user +i +x

Instead of:

  MODE user +i+x
  • Loading branch information
zetafunction committed Sep 25, 2024
1 parent 3b9ab62 commit 30cb2fd
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 21 deletions.
26 changes: 13 additions & 13 deletions irc-proto/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,13 @@ impl<'a> From<&'a Command> for String {
Command::NICK(ref n) => stringify("NICK", &[n]),
Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*", r]),
Command::OPER(ref u, ref p) => stringify("OPER", &[u, p]),
Command::UserMODE(ref u, ref m) => format!(
"MODE {}{}",
u,
m.iter().fold(String::new(), |mut acc, mode| {
acc.push(' ');
acc.push_str(&mode.to_string());
Command::UserMODE(ref u, ref m) => {
// User modes never have arguments.
m.iter().fold(format!("MODE {u} "), |mut acc, m| {
acc.push_str(&m.flag());
acc
})
),
}
Command::SERVICE(ref nick, ref r0, ref dist, ref typ, ref r1, ref info) => {
stringify("SERVICE", &[nick, r0, dist, typ, r1, info])
}
Expand All @@ -248,15 +246,17 @@ impl<'a> From<&'a Command> for String {
Command::JOIN(ref c, None, None) => stringify("JOIN", &[c]),
Command::PART(ref c, Some(ref m)) => stringify("PART", &[c, m]),
Command::PART(ref c, None) => stringify("PART", &[c]),
Command::ChannelMODE(ref u, ref m) => format!(
"MODE {}{}",
u,
m.iter().fold(String::new(), |mut acc, mode| {
Command::ChannelMODE(ref c, ref m) => {
let cmd = m.iter().fold(format!("MODE {c} "), |mut acc, m| {
acc.push_str(&m.flag());
acc
});
m.iter().filter_map(|m| m.arg()).fold(cmd, |mut acc, arg| {
acc.push(' ');
acc.push_str(&mode.to_string());
acc.push_str(arg);
acc
})
),
}
Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c, t]),
Command::TOPIC(ref c, None) => stringify("TOPIC", &[c]),
Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c, t]),
Expand Down
27 changes: 22 additions & 5 deletions irc-proto/src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,24 @@ where
pub fn no_prefix(inner: T) -> Mode<T> {
Mode::NoPrefix(inner)
}

/// Gets the mode flag associated with this mode with a + or - prefix as needed.
pub fn flag(&self) -> String {
match self {
Mode::Plus(mode, _) => format!("+{}", mode),
Mode::Minus(mode, _) => format!("-{}", mode),
Mode::NoPrefix(mode) => mode.to_string(),
}
}

/// Gets the arg associated with this mode, if any. Only some channel modes support arguments,
/// e.g. b (ban) or o (oper).
pub fn arg(&self) -> Option<&str> {
match self {
Mode::Plus(_, arg) | Mode::Minus(_, arg) => arg.as_deref(),
_ => None,
}
}
}

impl<T> fmt::Display for Mode<T>
Expand All @@ -246,11 +264,10 @@ where
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Mode::Plus(ref mode, Some(ref arg)) => write!(f, "+{} {}", mode, arg),
Mode::Minus(ref mode, Some(ref arg)) => write!(f, "-{} {}", mode, arg),
Mode::Plus(ref mode, None) => write!(f, "+{}", mode),
Mode::Minus(ref mode, None) => write!(f, "-{}", mode),
Mode::NoPrefix(ref mode) => write!(f, "{}", mode),
Mode::Plus(_, Some(ref arg)) | Mode::Minus(_, Some(ref arg)) => {
write!(f, "{} {}", self.flag(), arg)
}
_ => write!(f, "{}", self.flag()),
}
}
}
Expand Down
27 changes: 24 additions & 3 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ mod test {
error::Error,
proto::{
command::Command::{Raw, PRIVMSG},
ChannelMode, IrcCodec, Mode,
ChannelMode, IrcCodec, Mode, UserMode,
},
};
use anyhow::Result;
Expand Down Expand Up @@ -1807,10 +1807,31 @@ mod test {
let mut client = Client::from_config(test_config()).await?;
client.send_mode(
"#test",
&[Mode::Plus(ChannelMode::Oper, Some("test".to_owned()))],
&[
Mode::Plus(ChannelMode::Oper, Some("test".to_owned())),
Mode::Minus(ChannelMode::Oper, Some("test2".to_owned())),
],
)?;
client.stream()?.collect().await?;
assert_eq!(&get_client_value(client)[..], "MODE #test +o test\r\n");
assert_eq!(
&get_client_value(client)[..],
"MODE #test +o-o test test2\r\n"
);
Ok(())
}

#[tokio::test]
async fn send_umode() -> Result<()> {
let mut client = Client::from_config(test_config()).await?;
client.send_mode(
"test",
&[
Mode::Plus(UserMode::Invisible, None),
Mode::Plus(UserMode::MaskedHost, None),
],
)?;
client.stream()?.collect().await?;
assert_eq!(&get_client_value(client)[..], "MODE test +i+x\r\n");
Ok(())
}

Expand Down

0 comments on commit 30cb2fd

Please sign in to comment.