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

asf-gitbox-commits pushed a commit to branch db/8607
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 058d5b94c7e09cf90117698e2d65850c41ae5549
Author: Dave Brondsema <[email protected]>
AuthorDate: Tue May 26 14:59:41 2026 -0400

    [#8607] solr: strip local-params syntax
---
 Allura/allura/lib/search.py           | 29 ++++++++++++++++++++++++++++-
 Allura/allura/tests/unit/test_solr.py | 29 ++++++++++++++++++++++++++++-
 2 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py
index 6dd6693db..a2371ad7b 100644
--- a/Allura/allura/lib/search.py
+++ b/Allura/allura/lib/search.py
@@ -146,8 +146,36 @@ def inject_user(q, user=None):
     return q.replace('$USER', '"%s"' % user.username) if q else q
 
 
+_LOCAL_PARAMS_RE = re.compile(r'\{![^}]*\}')
+
+
+def strip_local_params(q):
+    '''Neutralize Solr local-params
+
+    Solr 7.2+ mitigates 
https://solr.apache.org/guide/solr/latest/upgrade-notes/major-changes-in-solr-8.html#solr-7-2
+    But clean them all out just in case
+
+    Loops until idempotent: a single regex pass can leave a *new* `{!...}` in
+    the residue when the original had nested braces, e.g.
+    `{{!lucene}!type=dismax v='*:*'}` strips to `{!type=dismax v='*:*'}`
+    '''
+    if not q:
+        return q
+    orig = q
+    total = 0
+    while True:
+        q, n = _LOCAL_PARAMS_RE.subn('', q)
+        if not n:
+            break
+        total += n
+    if total:
+        log.warning(f'Stripped Solr local-params block(s) from query: 
{orig[:200]!r}')
+    return q
+
+
 def search(q, short_timeout=False, ignore_errors=True, **kw):
     q = inject_user(q)
+    q = strip_local_params(q)
     try:
         if short_timeout:
             return g.solr_short_timeout.search(q, **kw)
@@ -231,7 +259,6 @@ def site_admin_search(model, q, field, **kw):
         # use parens to group all the parts of the query with the field
         # escaping spaces with '\ ' isn't sufficient for display_name_t since 
its stored as text_general (why??)
         # and wouldn't handle [email protected] split on @ either
-        # This should work, but doesn't for unknown reasons: q = u'{!term 
f=%s}%s' % (field, q)
         q = q.replace(':', r'\:')  # Must escape the colon for IPv6 addresses
         q = obj.translate_query(f'{field}:({q})', fields)
         kw['q.op'] = 'AND'  # so that all terms within the () are required
diff --git a/Allura/allura/tests/unit/test_solr.py 
b/Allura/allura/tests/unit/test_solr.py
index 06c45f285..6b5548df7 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -26,7 +26,7 @@
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test
 from allura.lib.solr import Solr, escape_solr_arg
-from allura.lib.search import search_app, SearchIndexable
+from allura.lib.search import search, search_app, SearchIndexable, 
strip_local_params
 
 
 class TestSolr:
@@ -163,6 +163,33 @@ def test_solarize_html_in_text(self):
         assert self.obj.solarize() == dict(text='<script>a(1)</script>')
 
 
+class TestStripLocalParams:
+
+    @pytest.mark.parametrize('q,expected', [
+        ('foo bar', 'foo bar'),
+        ('', ''),
+        (None, None),
+        ('{!type=lucene}foo', 'foo'),
+        ('{!type=lucene q.op=OR}*:*', '*:*'),
+        ('  {!type=lucene}foo', '  foo'),
+        ('{!}rest', 'rest'),
+        ('text:{!type=lucene}foo', 'text:foo'),  # later in query
+        ('{!a}foo{!b}bar', 'foobar'),  # multiple
+        ("{{!lucene}!type=dismax v='*:*'}", ''),  # nested
+        ('{{{!a}!b}!c}rest', 'rest'),  # really nested
+    ])
+    def test_strip(self, q, expected):
+        assert strip_local_params(q) == expected
+
+    @mock.patch('allura.lib.search.g')
+    @mock.patch('allura.lib.search.c')
+    def test_search_strips_local_params_before_pysolr(self, c, g):
+        c.user.username = 'tester'
+        search('{!type=lucene q.op=OR}*:*')
+        # pysolr.search must receive the query without the local-params prefix
+        g.solr.search.assert_called_once_with('*:*')
+
+
 class TestSearch_app:
 
     def setup_method(self, method):

Reply via email to