diff --git a/frontend_tests/casper_tests/05-subscriptions.js b/frontend_tests/casper_tests/05-subscriptions.js index 251e0958e7..72b9fbf5c0 100644 --- a/frontend_tests/casper_tests/05-subscriptions.js +++ b/frontend_tests/casper_tests/05-subscriptions.js @@ -140,6 +140,16 @@ casper.then(function () { casper.click('form#stream_creation_form button.button.white'); casper.fill('form#add_new_subscription', {stream_name: ' '}); casper.click('#add_new_subscription .create_stream_button'); + casper.fill('form#stream_creation_form', {stream_name: 'Waseemio@'}); + casper.click('form#stream_creation_form button.button.sea-green'); + }); +}); +casper.then(function () { + casper.waitForSelectorText('#stream_name_error', 'Stream names cannot contain #, *, `, or @.', function () { + casper.test.assertTextExists('Stream names cannot contain #, *, `, or @.', "Can't create a stream with invalid characters"); + casper.click('form#stream_creation_form button.button.white'); + casper.fill('form#add_new_subscription', {stream_name: ' '}); + casper.click('#add_new_subscription .create_stream_button'); casper.fill('form#stream_creation_form', {stream_name: 'Waseemio'}); casper.click('form#stream_creation_form button.button.sea-green'); }); diff --git a/static/js/stream_create.js b/static/js/stream_create.js index f5b42b6d54..483af616f2 100644 --- a/static/js/stream_create.js +++ b/static/js/stream_create.js @@ -33,6 +33,11 @@ var stream_name_error = (function () { $("#stream_name_error").show(); }; + self.report_invalid_chars = function () { + $("#stream_name_error").text(i18n.t("Stream names cannot contain #, *, `, or @.")); + $("#stream_name_error").show(); + }; + self.select = function () { $("#create_stream_name").focus().select(); }; @@ -66,6 +71,13 @@ var stream_name_error = (function () { return false; } + // Keep characters in sync with Stream.NAME_INVALID_CHARS + if (/[#*`@]/.test(stream_name)) { + self.report_invalid_chars(); + self.select(); + return false; + } + // If we got this far, then we think we have a new unique stream // name, so we'll submit to the server. (It's still plausible, // however, that there's some invite-only stream that we don't diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 577c106752..1e07a0a267 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -1650,6 +1650,9 @@ def check_stream_name(stream_name: Text) -> None: raise JsonableError(_("Invalid stream name '%s'" % (stream_name))) if len(stream_name) > Stream.MAX_NAME_LENGTH: raise JsonableError(_("Stream name too long (limit: %s characters)" % (Stream.MAX_NAME_LENGTH))) + if set(stream_name).intersection(Stream.NAME_INVALID_CHARS): + raise JsonableError(_("Invalid characters in stream name (disallowed characters: %s)." + % ((', ').join(Stream.NAME_INVALID_CHARS)))) for i in stream_name: if ord(i) == 0: raise JsonableError(_("Stream name '%s' contains NULL (0x00) characters." % (stream_name))) diff --git a/zerver/models.py b/zerver/models.py index 25d9e09377..8ccb3db969 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -851,6 +851,8 @@ def generate_email_token_for_stream() -> str: class Stream(models.Model): MAX_NAME_LENGTH = 60 + # Keep in sync with stream_create.js + NAME_INVALID_CHARS = ['*', '@', '`', '#'] name = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True) # type: Text realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE) # type: Realm invite_only = models.NullBooleanField(default=False) # type: Optional[bool] diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index d0d5c31158..e33af3fb06 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -1409,6 +1409,22 @@ class SubscriptionRestApiTest(ZulipTestCase): self.assert_json_error(result, "Stream name too long (limit: 60 characters)") + def test_stream_name_has_invalid_characters(self) -> None: + email = self.example_email('hamlet') + self.login(email) + + stream_name = "a*" + request = { + 'delete': ujson.dumps([stream_name]) + } + result = self.client_patch( + "/api/v1/users/me/subscriptions", + request, + **self.api_auth(email) + ) + self.assert_json_error(result, + "Invalid characters in stream name (disallowed characters: *, @, `, #).") + def test_stream_name_contains_null(self) -> None: email = self.example_email('hamlet') self.login(email)