From f12c4cf0cfbd7ee4a9f1cb3638fb27398f079524 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 25 Aug 2026 21:52:21 -0700 Subject: [PATCH] Fix RegexpError 500 in search highlighting when a term contains regex metacharacters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SearchesHelper#whole_word_matchers interpolated a term straight into /\b#{term}\b/ without escaping it. The terms come from SQLite FTS5 highlight() spans, which can carry document punctuation — a phrase match spanning "foo) bar" yields a term with an unbalanced parenthesis, so the Regexp constructor raises RegexpError and the leaf page 500s. Escape the term with Regexp.escape so metacharacters are matched literally. Adds a helper test and a controller test covering a phrase match whose highlight span contains metacharacters. --- app/helpers/searches_helper.rb | 5 ++++- test/controllers/leafables_controller_test.rb | 14 ++++++++++++++ test/helpers/searches_helper_test.rb | 11 +++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/helpers/searches_helper.rb b/app/helpers/searches_helper.rb index f3b22e69..72d9966d 100644 --- a/app/helpers/searches_helper.rb +++ b/app/helpers/searches_helper.rb @@ -22,7 +22,10 @@ def highlight_searched_content(leaf, content, query) end private + # Terms come from FTS5 highlight() spans, which can include document + # punctuation (e.g. a phrase match spanning "foo) bar"). Escape them so + # metacharacters are matched literally instead of raising a RegexpError. def whole_word_matchers(terms) - terms.map { |term| /\b#{term}\b/ } + terms.map { |term| /\b#{Regexp.escape(term)}\b/ } end end diff --git a/test/controllers/leafables_controller_test.rb b/test/controllers/leafables_controller_test.rb index d9e383c7..0d180e57 100644 --- a/test/controllers/leafables_controller_test.rb +++ b/test/controllers/leafables_controller_test.rb @@ -30,6 +30,20 @@ class LeafablesControllerTest < ActionDispatch::IntegrationTest assert_select "mark", "great" end + test "show highlights a phrase match whose span contains regex metacharacters" do + sign_out + books(:handbook).update!(published: true) + + section = Section.new(body: "alpha) omega in the body") + books(:handbook).press(section, title: "Punctuated") + section.leaf.reindex + + get leafable_slug_path(section.leaf), params: { search: '"alpha omega"' } + + assert_response :success + assert_select "mark", text: /alpha\) omega/ + end + test "show does not allow public access to an unpublished book" do sign_out diff --git a/test/helpers/searches_helper_test.rb b/test/helpers/searches_helper_test.rb index 7b144406..a71c9571 100644 --- a/test/helpers/searches_helper_test.rb +++ b/test/helpers/searches_helper_test.rb @@ -1,6 +1,7 @@ require "test_helper" class SearchesHelperTest < ActionView::TestCase + include PagesHelper test "sanitize_search_result preserves mark tags" do assert_equal "findme text", sanitize_search_result("findme text") end @@ -16,4 +17,14 @@ class SearchesHelperTest < ActionView::TestCase test "sanitize_search_result strips attributes from mark tags" do assert_equal "findme text", sanitize_search_result(' text') end + + test "highlight_searched_content handles matched terms containing regex metacharacters" do + leaf = Struct.new(:terms) do + def matches_for_highlight(_query) = terms + end.new([ "foo) bar" ]) + + result = highlight_searched_content(leaf, "foo) bar in the body", "foo bar") + + assert_includes result, "foo) bar" + end end