From 5eb8a4eb6aae1f21bf91b477c8eb4d5c939affed Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Thu, 5 Mar 2026 21:15:49 -0500 Subject: [PATCH] irc: handle non-UTF-8 input, CTCP VERSION, log outgoing messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes: 1. Use read_until + from_utf8_lossy instead of AsyncBufRead::lines(), which returns Err on invalid UTF-8. IRC isn't guaranteed UTF-8 — Latin-1, Yiddish, etc. would crash the reader loop. 2. Handle CTCP requests (messages wrapped in \x01). Reply to VERSION queries so the server stops retrying, and skip CTCP for notification generation. 3. Log outgoing messages from the "send" command with append_log() so they appear in IRC logs alongside incoming traffic. Co-Authored-By: ProofOfConcept --- src/bin/poc-daemon/modules/irc.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/bin/poc-daemon/modules/irc.rs b/src/bin/poc-daemon/modules/irc.rs index ee0d17b..a93030c 100644 --- a/src/bin/poc-daemon/modules/irc.rs +++ b/src/bin/poc-daemon/modules/irc.rs @@ -263,7 +263,7 @@ async fn connect_and_run( async fn register_and_read( state: &SharedIrc, config: &IrcConfig, - reader: BufReader, + mut reader: BufReader, notify_tx: &mpsc::UnboundedSender, ) -> io::Result<()> { // Register @@ -273,9 +273,15 @@ async fn register_and_read( s.send_raw(&format!("USER {} 0 * :{}", config.user, config.realname)).await?; } - let mut lines = reader.lines(); + let mut buf = Vec::new(); - while let Some(line) = lines.next_line().await? { + loop { + buf.clear(); + let n = reader.read_until(b'\n', &mut buf).await?; + if n == 0 { break; } + // IRC is not guaranteed UTF-8 — lossy conversion handles Latin-1 etc. + let line = String::from_utf8_lossy(&buf).trim_end().to_string(); + if line.is_empty() { continue; } let msg = match IrcMessage::parse(&line) { Some(m) => m, None => continue, @@ -306,6 +312,19 @@ async fn register_and_read( let text = msg.params.get(1).map(|s| s.as_str()).unwrap_or(""); let nick = msg.nick().unwrap_or("unknown"); + // Handle CTCP requests (wrapped in \x01) + if text.starts_with('\x01') && text.ends_with('\x01') { + let ctcp = &text[1..text.len()-1]; + if ctcp.starts_with("VERSION") { + let reply = format!( + "NOTICE {nick} :\x01VERSION poc-daemon 0.4.0\x01" + ); + state.borrow_mut().send_raw(&reply).await.ok(); + } + // Don't generate notifications for CTCP + continue; + } + // Log the message let log_line = if target.starts_with('#') { format!("[{}] <{}> {}", target, nick, text) @@ -453,11 +472,13 @@ pub async fn handle_command( } let target = &args[0]; let msg = args[1..].join(" "); + let nick = state.borrow().config.nick.clone(); state .borrow_mut() .send_privmsg(target, &msg) .await .map_err(|e| e.to_string())?; + append_log(target, &nick, &msg); Ok(format!("sent to {target}")) } "status" => {