Repository: matrix-org/synapse

Issue:
In Synapse Admin API's Rooms query seach_term don't accept non-ASCII characters
### Description

Not all rooms use English characters, but In Synapse Admin API's Rooms query seach_term don't accept non-ASCII characters.

### Steps to reproduce

For example query to search rooms with Russian symbol "ж" using path `/_synapse/admin/v1/rooms?dir=f&from=0&limit=10&order_by=name&search_term=%D0%B6` shows the error:
```json
{
  "errcode":"M_UNKNOWN",
  "error":"Query parameter 'search_term' must be ascii"
}
```
### Version information
- **Synapse Version**:
    "server_version": "1.42.0",
    "python_version": "3.8.10"



Discussion:
Thanks for the report. Looks like the call to `parse_string` here

https://github.com/matrix-org/synapse/blob/430241a1e9fd4fbb82d83958b61bbd66c9ba3505/synapse/rest/admin/rooms.py#L128 assumes an encoding of ascii by default. I wonder if it suffices to relax this to `encoding="utf-8"`? [`get_rooms_paginate`](https://github.com/matrix-org/synapse/blob/dc75fb7f0552e6a9903a7c173672c96610219ec0/synapse/storage/databases/main/room.py#L397-L404) claims to accept any string. We'll end up searching based on `room_name LIKE %search_term%`. We strongly recommend [configuring postgres](https://matrix-org.github.io/synapse/latest/postgres.html) to use the C.UTF-8 locale; if so, I think that query might just work?

For me the raw SQL query works well:
```SQL
# SELECT COUNT(*) FROM room_stats_state WHERE "name" LIKE '%ж%';
 count 
-------
    32
(1 row)

# show LC_COLLATE;
 lc_collate 
------------
 C
(1 row)

# SHOW SERVER_ENCODING;
 server_encoding 
-----------------
 UTF8
(1 row)

```
And encoding is ok too:
```
                                             List of databases
       Name       |    Owner     | Encoding |   Collate   |    Ctype    |         Access privileges         
------------------+--------------+----------+-------------+-------------+-----------------------------------
 synapse          | postgres     | UTF8     | C           | C           | =Tc/postgres                     +
```

Resolving patch:
diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py
--- a/synapse/rest/admin/rooms.py
+++ b/synapse/rest/admin/rooms.py
@@ -125,7 +125,7 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
                 errcode=Codes.INVALID_PARAM,
             )
 
-        search_term = parse_string(request, "search_term")
+        search_term = parse_string(request, "search_term", encoding="utf-8")
         if search_term == "":
             raise SynapseError(
                 400,


Test patch:
diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py
--- a/tests/rest/admin/test_room.py
+++ b/tests/rest/admin/test_room.py
@@ -941,6 +941,33 @@ def _search_test(
         _search_test(None, "bar")
         _search_test(None, "", expected_http_code=400)
 
+    def test_search_term_non_ascii(self):
+        """Test that searching for a room with non-ASCII characters works correctly"""
+
+        # Create test room
+        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
+        room_name = "ж"
+
+        # Set the name for the room
+        self.helper.send_state(
+            room_id,
+            "m.room.name",
+            {"name": room_name},
+            tok=self.admin_user_tok,
+        )
+
+        # make the request and test that the response is what we wanted
+        search_term = urllib.parse.quote("ж", "utf-8")
+        url = "/_synapse/admin/v1/rooms?search_term=%s" % (search_term,)
+        channel = self.make_request(
+            "GET",
+            url.encode("ascii"),
+            access_token=self.admin_user_tok,
+        )
+        self.assertEqual(200, channel.code, msg=channel.json_body)
+        self.assertEqual(room_id, channel.json_body.get("rooms")[0].get("room_id"))
+        self.assertEqual("ж", channel.json_body.get("rooms")[0].get("name"))
+
     def test_single_room(self):
         """Test that a single room can be requested correctly"""
         # Create two test rooms