From ad83d72808a1751bf89445f568b92b0832ea5877 Mon Sep 17 00:00:00 2001 From: Booyaka101 Date: Sun, 7 Jun 2026 23:12:43 +0800 Subject: [PATCH] Fix tag_no_case panic on case-folding byte-length changes tag_no_case split the input at the tag's byte length, but the matched prefix can be a different length when case folding changes a character's UTF-8 length (e.g. U+212A KELVIN SIGN folds to ASCII 'k'), slicing inside a multi-byte char and panicking. Resolve the split point with Input::slice_index over the input's own elements instead. Fixes #1883 --- src/bytes/mod.rs | 9 ++++++++- tests/issues.rs | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/bytes/mod.rs b/src/bytes/mod.rs index 96732cd57..a347a99a7 100644 --- a/src/bytes/mod.rs +++ b/src/bytes/mod.rs @@ -142,7 +142,14 @@ where let t = self.tag.clone(); match i.compare_no_case(t) { - CompareResult::Ok => Ok((i.take_from(tag_len), OM::Output::bind(|| i.take(tag_len)))), + CompareResult::Ok => { + // Case folding can change a char's byte length (e.g. U+212A KELVIN SIGN folds + // to 'k'), so split on the input's element boundary, not the tag length. + let index = i + .slice_index(self.tag.iter_elements().count()) + .unwrap_or(tag_len); + Ok((i.take_from(index), OM::Output::bind(|| i.take(index)))) + } CompareResult::Incomplete => { if OM::Incomplete::is_streaming() { Err(Err::Incomplete(Needed::new(tag_len - i.input_len()))) diff --git a/tests/issues.rs b/tests/issues.rs index 9996b5ccf..602d18a2d 100644 --- a/tests/issues.rs +++ b/tests/issues.rs @@ -281,3 +281,16 @@ fn issue_1808_complete_string_parser_returns_wrong_slice() { Ok(("", "\n")) ); } + +#[test] +fn issue_1883_tag_no_case_unicode_char_boundary() { + use nom::bytes::complete::tag_no_case; + + // U+212A KELVIN SIGN (3 bytes) case-folds to ASCII 'k'; the split must land on a + // char boundary, and the returned slice is the input's own prefix. + let r: IResult<&str, &str> = tag_no_case("k")("\u{212A}xyz"); + assert_eq!(r, Ok(("xyz", "\u{212A}"))); + + let r: IResult<&str, &str> = tag_no_case("aktz")("a\u{212A}tz!"); + assert_eq!(r, Ok(("!", "a\u{212A}tz"))); +}