diff --git a/news/1828.bugfix b/news/1828.bugfix new file mode 100644 index 0000000000..dfc2a5ef4e --- /dev/null +++ b/news/1828.bugfix @@ -0,0 +1 @@ +`@search` service: Remove parentheses from search query. @tedw87 \ No newline at end of file diff --git a/src/plone/restapi/search/handler.py b/src/plone/restapi/search/handler.py index 8764a773df..2a362e05d5 100644 --- a/src/plone/restapi/search/handler.py +++ b/src/plone/restapi/search/handler.py @@ -75,6 +75,10 @@ def _constrain_query_by_path(self, query): path = "/".join(self.context.getPhysicalPath()) query["path"]["query"] = path + def quote_chars(self, query): + # Remove parentheses from the query + return query.replace("(", " ").replace(")", " ").strip() + def search(self, query=None): if query is None: query = {} @@ -93,6 +97,12 @@ def search(self, query=None): if use_site_search_settings: query = self.filter_query(query) + if "SearchableText" in query: + # Sanitize SearchableText by removing parentheses + query["SearchableText"] = self.quote_chars(query["SearchableText"]) + if not query["SearchableText"] or query["SearchableText"] == "*": + return [] + self._constrain_query_by_path(query) query = self._parse_query(query) @@ -100,7 +110,6 @@ def search(self, query=None): results = getMultiAdapter((lazy_resultset, self.request), ISerializeToJson)( fullobjects=fullobjects ) - return results def filter_types(self, types): diff --git a/src/plone/restapi/tests/test_search.py b/src/plone/restapi/tests/test_search.py index e4ddb4c38d..84b6e0b480 100644 --- a/src/plone/restapi/tests/test_search.py +++ b/src/plone/restapi/tests/test_search.py @@ -151,6 +151,29 @@ def test_search_on_context_constrains_query_by_path(self): set(result_paths(response.json())), ) + def test_search_with_parentheses(self): + query = {"SearchableText": "("} + response = self.api_session.get("/@search", params=query) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), [], "Expected no items for query with only parentheses" + ) + + query = {"SearchableText": ")"} + response = self.api_session.get("/@search", params=query) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), [], "Expected no items for query with only parentheses" + ) + + query = {"SearchableText": "lorem(ipsum)"} + response = self.api_session.get("/@search", params=query) + self.assertEqual(response.status_code, 200) + items = [item["title"] for item in response.json().get("items", [])] + self.assertIn( + "Lorem Ipsum", items, "Expected 'Lorem Ipsum' to be found in search results" + ) + def test_search_in_vhm(self): # Install a Virtual Host Monster if "virtual_hosting" not in self.app.objectIds():