diff --git a/zerver/lib/url_decoding.py b/zerver/lib/url_decoding.py new file mode 100644 index 0000000000..4a5aff455e --- /dev/null +++ b/zerver/lib/url_decoding.py @@ -0,0 +1,26 @@ +from urllib.parse import urlsplit + +from django.conf import settings + + +def is_same_server_message_link(url: str) -> bool: + split_result = urlsplit(url) + hostname = split_result.hostname + fragment = split_result.fragment + + if hostname not in {None, settings.EXTERNAL_HOST_WITHOUT_PORT}: + return False + + # A message link always has category `narrow`, section `stream` + # or `dm`, and ends with `/near/`, where + # is a sequence of digits. The URL fragment of a message link has + # at least 5 parts. e.g. '#narrow/dm/9,15-dm/near/43' + fragment_parts = fragment.split("/") + if len(fragment_parts) < 5: + return False + + category = fragment_parts[0] + section = fragment_parts[1] + ends_with_near_message_id = fragment_parts[-2] == "near" and fragment_parts[-1].isdigit() + + return category == "narrow" and section in {"stream", "dm"} and ends_with_near_message_id diff --git a/zerver/tests/test_url_decoding.py b/zerver/tests/test_url_decoding.py new file mode 100644 index 0000000000..b9a542c83b --- /dev/null +++ b/zerver/tests/test_url_decoding.py @@ -0,0 +1,13 @@ +import orjson + +from zerver.lib.test_classes import ZulipTestCase +from zerver.lib.url_decoding import is_same_server_message_link + + +class URLDecodeTest(ZulipTestCase): + def test_is_same_server_message_link(self) -> None: + tests = orjson.loads(self.fixture_data("message_link_test_cases.json")) + for test in tests: + self.assertEqual( + is_same_server_message_link(test["message_link"]), test["expected_output"] + )