From 11852332dcaac1fe2d32c308cc244f13ae8bd986 Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:41:22 +0000 Subject: [PATCH] =?UTF-8?q?message:=20parse=20on=20space=20(0x20)=20only?= =?UTF-8?q?=20per=20RFC=20=E2=80=94=20trim=5Fstart()=20also=20ate=20tabs,?= =?UTF-8?q?=20so=20a=20param=20containing=20a=20tab=20(e.g.=20a=20trailing?= =?UTF-8?q?=20':\t')=20didn't=20round-trip=20through=20to=5Fwire/parse;=20?= =?UTF-8?q?the=20parser=20fuzz=20proptest=20found=20it.=20trim=5Fstart=5Fm?= =?UTF-8?q?atches('=20')=20makes=20tabs=20ordinary=20param=20content;=20re?= =?UTF-8?q?gression=20case=20pinned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- proptest-regressions/message.txt | 7 +++++++ src/message.rs | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 proptest-regressions/message.txt diff --git a/proptest-regressions/message.txt b/proptest-regressions/message.txt new file mode 100644 index 0000000..87b8d47 --- /dev/null +++ b/proptest-regressions/message.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 19cc356a7360943bfb23bdff0f579425f8957a37395bc952950f8f2250d047a3 # shrinks to line = "0 :\t" diff --git a/src/message.rs b/src/message.rs index 5cdb3b1..bc0d002 100644 --- a/src/message.rs +++ b/src/message.rs @@ -48,7 +48,7 @@ impl Message { /// Parse one wire line. Returns `None` for an empty/garbage line. pub fn parse(line: &str) -> Option { - let mut rest = line.trim_start(); + let mut rest = line.trim_start_matches(' '); // IRCv3 message tags — keep the client-only (`+`) tags for relay, drop the rest. let mut ctags = String::new(); @@ -71,18 +71,18 @@ pub fn parse(line: &str) -> Option { .find_map(|t| t.strip_prefix("batch=")) .map(|v| v.to_string()); concat = tags.split(';').any(|t| t == "draft/multiline-concat"); - rest = r.trim_start(); + rest = r.trim_start_matches(' '); } let mut source = None; if let Some(after_colon) = rest.strip_prefix(':') { let (src, r) = after_colon.split_once(' ')?; source = Some(src.to_string()); - rest = r.trim_start(); + rest = r.trim_start_matches(' '); } let (cmd, mut rest) = match rest.split_once(' ') { - Some((c, r)) => (c, r.trim_start()), + Some((c, r)) => (c, r.trim_start_matches(' ')), None => (rest, ""), }; if cmd.is_empty() { @@ -98,7 +98,7 @@ pub fn parse(line: &str) -> Option { match rest.split_once(' ') { Some((p, r)) => { params.push(p.to_string()); - rest = r.trim_start(); + rest = r.trim_start_matches(' '); } None => { params.push(rest.to_string());