diff --git a/changelog.d/18660.bugfix b/changelog.d/18660.bugfix new file mode 100644 index 000000000..fd65b43a1 --- /dev/null +++ b/changelog.d/18660.bugfix @@ -0,0 +1 @@ +Don't allow creation of tags with names longer than 255 bytes, as per the spec. \ No newline at end of file diff --git a/synapse/rest/client/tags.py b/synapse/rest/client/tags.py index b6648f349..fb59efb11 100644 --- a/synapse/rest/client/tags.py +++ b/synapse/rest/client/tags.py @@ -20,9 +20,10 @@ # import logging +from http import HTTPStatus from typing import TYPE_CHECKING, Tuple -from synapse.api.errors import AuthError +from synapse.api.errors import AuthError, Codes, SynapseError from synapse.http.server import HttpServer from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.site import SynapseRequest @@ -35,6 +36,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) +MAX_TAG_LENGTH = 255 + class TagListServlet(RestServlet): """ @@ -86,6 +89,16 @@ class TagServlet(RestServlet): requester = await self.auth.get_user_by_req(request) if user_id != requester.user.to_string(): raise AuthError(403, "Cannot add tags for other users.") + + # check if the tag exceeds the length allowed by the matrix-specification + # as defined in: https://spec.matrix.org/v1.15/client-server-api/#events-14 + if len(tag.encode("utf-8")) > MAX_TAG_LENGTH: + raise SynapseError( + HTTPStatus.BAD_REQUEST, + "tag parameter's length is over 255 bytes", + errcode=Codes.INVALID_PARAM, + ) + # Check if the user has any membership in the room and raise error if not. # Although it's not harmful for users to tag random rooms, it's just superfluous # data we don't need to track or allow. diff --git a/tests/rest/client/test_tags.py b/tests/rest/client/test_tags.py index 5d596409e..aee2f6aff 100644 --- a/tests/rest/client/test_tags.py +++ b/tests/rest/client/test_tags.py @@ -15,6 +15,7 @@ """Tests REST events for /tags paths.""" from http import HTTPStatus +from urllib import parse as urlparse import synapse.rest.admin from synapse.rest.client import login, room, tags @@ -93,3 +94,68 @@ class RoomTaggingTestCase(unittest.HomeserverTestCase): ) # Check that the request failed with the correct error self.assertEqual(channel.code, HTTPStatus.FORBIDDEN, channel.result) + + def test_put_tag_fails_if_tag_is_too_long(self) -> None: + """ + Test that a user cannot add a tag to a room that is longer than the 255 bytes + allowed by the matrix specification. + """ + user1_id = self.register_user("user1", "pass") + user1_tok = self.login(user1_id, "pass") + room_id = self.helper.create_room_as(user1_id, tok=user1_tok) + # create a string which is larger than 255 bytes + tag = "X" * 300 + + # Make the request + channel = self.make_request( + "PUT", + f"/user/{user1_id}/rooms/{room_id}/tags/{tag}", + content={"order": 0.5}, + access_token=user1_tok, + ) + # Check that the request failed + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + + def test_put_tag_fails_if_tag_is_too_long_with_graphemes(self) -> None: + """ + Test that a user cannot add a tag to a room that contains graphemes which are in total + longer than the 255 bytes allowed by the matrix specification. + """ + user1_id = self.register_user("user1", "pass") + user1_tok = self.login(user1_id, "pass") + room_id = self.helper.create_room_as(user1_id, tok=user1_tok) + # create a string which is larger than 255 bytes (275) + tag = "👩‍🚒" * 25 + + # Make the request + channel = self.make_request( + "PUT", + f"/user/{user1_id}/rooms/{room_id}/tags/" + + urlparse.quote(tag.encode("utf-8")), + content={"order": 0.5}, + access_token=user1_tok, + ) + # Check that the request failed + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + + def test_put_tag_succeeds_with_graphemes(self) -> None: + """ + Test that a user can add a tag to a room that contains graphemes which are in total + less than the 255 bytes allowed by the matrix specification. + """ + user1_id = self.register_user("user1", "pass") + user1_tok = self.login(user1_id, "pass") + room_id = self.helper.create_room_as(user1_id, tok=user1_tok) + # create a string of acceptable length (220 bytes) + tag = "👩‍🚒" * 20 + + # Make the request + channel = self.make_request( + "PUT", + f"/user/{user1_id}/rooms/{room_id}/tags/" + + urlparse.quote(tag.encode("utf-8")), + content={"order": 0.5}, + access_token=user1_tok, + ) + # Check that the request succeeded + self.assertEqual(channel.code, HTTPStatus.OK, channel.result)