This is an automated email from the ASF dual-hosted git repository.

moonchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new e3bd689202 Return an empty view for a non-participating capture group 
(#13441)
e3bd689202 is described below

commit e3bd6892026e9356076dfa84b0aecfa558cccacb
Author: Mo Chen <[email protected]>
AuthorDate: Fri Aug 7 09:48:52 2026 -0500

    Return an empty view for a non-participating capture group (#13441)
    
    RegexMatches::operator[] only checked the index against the ovector
    count. A group that does not participate in the match has unset
    offsets, and an optional group that precedes a participating one is
    still within that count, so the check passes and the subject pointer is
    advanced by PCRE2_UNSET.
    
    The resulting view has length zero, so callers see an empty string
    today, but the pointer is invalid.
---
 src/tsutil/Regex.cc                 |  7 +++++++
 src/tsutil/unit_tests/test_Regex.cc | 17 +++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/src/tsutil/Regex.cc b/src/tsutil/Regex.cc
index 34bfc5447d..2c84b3fa08 100644
--- a/src/tsutil/Regex.cc
+++ b/src/tsutil/Regex.cc
@@ -228,6 +228,13 @@ RegexMatches::operator[](size_t index) const
   }
 
   PCRE2_SIZE *ovector = 
pcre2_get_ovector_pointer(_MatchData::get(_match_data));
+
+  // A group that did not participate in the match has an unset offset. This 
happens for an optional
+  // group that precedes a participating one, so a valid index is not enough 
to guarantee an offset.
+  if (PCRE2_UNSET == ovector[2 * index]) {
+    return std::string_view();
+  }
+
   return std::string_view(_subject.data() + ovector[2 * index], ovector[2 * 
index + 1] - ovector[2 * index]);
 }
 
diff --git a/src/tsutil/unit_tests/test_Regex.cc 
b/src/tsutil/unit_tests/test_Regex.cc
index f5cddd47a0..76273e2322 100644
--- a/src/tsutil/unit_tests/test_Regex.cc
+++ b/src/tsutil/unit_tests/test_Regex.cc
@@ -460,6 +460,23 @@ TEST_CASE("RegexMatches edge cases", 
"[libts][Regex][RegexMatches]")
     CHECK(count >= 2); // At least whole match + first group
     CHECK(matches[1] == "foo");
   }
+
+  SECTION("RegexMatches with a non-participating group before a participating 
one")
+  {
+    // pcre2_match() returns one past the highest participating group, so an 
earlier optional group
+    // that did not participate is still within that count. Its offsets are 
unset.
+    Regex r;
+    REQUIRE(r.compile("(a)?(b)") == true);
+
+    RegexMatches matches;
+    int          count = r.exec("b", matches);
+
+    CHECK(count == 3);
+    CHECK(matches[0] == "b");
+    CHECK(matches[1] == "");
+    CHECK(matches[2] == "b");
+    CHECK(matches[1].data() == nullptr);
+  }
 }
 
 TEST_CASE("Regex with special characters", "[libts][Regex][special]")

Reply via email to