Skip to content

Commit 84b203e

Browse files
committed
boxes/model: Validate private message recipients more thoroughly.
This commit configures a new helper method to assist with a more thorough validation of the recipients in the `to_write_box`. The `tidy_valid_recipients_and_notify_invalid_ones` helper is used to extract individual recipients, discard any unnecessary text after the email and then call the `is_valid_private_recipient` helper from the model. The `is_valid_private_recipient` helper in `model.py` now also ensures that the name of the recipient extracted matches the corresponding name in the `user_dict`. Tests added.
1 parent 0f979f9 commit 84b203e

File tree

3 files changed

+133
-17
lines changed

3 files changed

+133
-17
lines changed

tests/ui_tools/test_boxes.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,71 @@ def test_not_calling_send_private_message_without_recipients(
7878

7979
assert not write_box.model.send_private_message.called
8080

81+
@pytest.mark.parametrize(
82+
["raw_recipients", "tidied_recipients"],
83+
[
84+
("Human 1 [email protected]", "Human 1 <[email protected]>"),
85+
(
86+
"Human 2 [email protected] random text",
87+
"Human 2 <[email protected]>",
88+
),
89+
(
90+
"Human Myself [email protected] random, Human 1 <[email protected]>",
91+
"Human Myself <[email protected]>, Human 1 <[email protected]>",
92+
),
93+
(
94+
"Human Myself <[email protected]>, Human 1 [email protected] random",
95+
"Human Myself <[email protected]>, Human 1 <[email protected]>",
96+
),
97+
(
98+
"Human Myself [email protected] random,"
99+
"Human 1 [email protected] random",
100+
"Human Myself <[email protected]>, Human 1 <[email protected]>",
101+
),
102+
(
103+
"Human Myself [email protected] random, Human 1 [email protected] "
104+
"random, Human 2 [email protected] random",
105+
"Human Myself <[email protected]>, Human 1 <[email protected]>, "
106+
"Human 2 <[email protected]>",
107+
),
108+
(
109+
"Human Myself [email protected], Human 1 [email protected] random, "
110+
111+
"Human Myself <[email protected]>, Human 1 <[email protected]>, "
112+
"Human2 <[email protected]>",
113+
),
114+
],
115+
ids=[
116+
"untidy_with_improper_formatting",
117+
"untidy_with_extra_text",
118+
"untidy_first_recipient_out_of_two",
119+
"untidy_second_recipient_out_of_two",
120+
"two_untidy_recipients",
121+
"three_untidy_recipients",
122+
"untidy_middle_recipient_out_of_three",
123+
],
124+
)
125+
@pytest.mark.parametrize(
126+
"key",
127+
keys_for_command("SEND_MESSAGE")
128+
+ keys_for_command("SAVE_AS_DRAFT")
129+
+ keys_for_command("CYCLE_COMPOSE_FOCUS"),
130+
)
131+
def test_tidying_recipients_on_keypresses(
132+
self, write_box, widget_size, key, raw_recipients, tidied_recipients
133+
):
134+
write_box.private_box_view()
135+
write_box.focus_position = write_box.FOCUS_CONTAINER_HEADER
136+
write_box.header_write_box.focus_col = write_box.FOCUS_HEADER_BOX_RECIPIENT
137+
138+
write_box.to_write_box.set_edit_text(raw_recipients)
139+
write_box.to_write_box.set_edit_pos(len(raw_recipients))
140+
141+
size = widget_size(write_box)
142+
write_box.keypress(size, key)
143+
144+
assert write_box.to_write_box.edit_text == tidied_recipients
145+
81146
@pytest.mark.parametrize(
82147
"text, state",
83148
[

zulipterminal/model.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,15 @@ def _handle_typing_event(self, event: Event) -> None:
10191019
else:
10201020
raise RuntimeError("Unknown typing event operation")
10211021

1022-
def is_valid_private_recipient(self, recipient_email: str) -> bool:
1023-
return recipient_email in self.user_dict
1022+
def is_valid_private_recipient(
1023+
self,
1024+
recipient_email: str,
1025+
recipient_name: str,
1026+
) -> bool:
1027+
return (
1028+
recipient_email in self.user_dict
1029+
and self.user_dict[recipient_email]["full_name"] == recipient_name
1030+
)
10241031

10251032
def is_valid_stream(self, stream_name: str) -> bool:
10261033
for stream in self.stream_dict.values():

zulipterminal/ui_tools/boxes.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,50 @@ def track_idleness_and_update_status() -> None:
216216
def update_recipient_emails(self, write_box: ReadlineEdit) -> None:
217217
self.recipient_emails = re.findall(r"[\w\.-]+@[\w\.-]+", write_box.edit_text)
218218

219+
def tidy_valid_recipients_and_notify_invalid_ones(
220+
self, write_box: ReadlineEdit
221+
) -> bool:
222+
tidied_recipients = list()
223+
invalid_recipients = list()
224+
225+
recipients = [
226+
recipient.strip()
227+
for recipient in write_box.edit_text.split(",")
228+
if recipient.strip() # This condition avoids whitespace recipients (", ,")
229+
]
230+
231+
for recipient in recipients:
232+
cleaned_recipient_list = re.findall(
233+
r"^(.*?)(?:\s*?<?([\w\.-]+@[\w\.-]+)>?(.*))?$", recipient
234+
)
235+
recipient_name, recipient_email, invalid_text = cleaned_recipient_list[0]
236+
# Discard invalid_text as part of tidying up the recipient.
237+
238+
if recipient_email:
239+
is_valid = self.model.is_valid_private_recipient(
240+
recipient_name, recipient_email
241+
)
242+
else:
243+
is_valid = False
244+
245+
if is_valid:
246+
tidied_recipients.append(f"{recipient_name} <{recipient_email}>")
247+
else:
248+
invalid_recipients.append(recipient)
249+
tidied_recipients.append(recipient)
250+
251+
write_box.edit_text = ", ".join(tidied_recipients)
252+
write_box.edit_pos = len(write_box.edit_text)
253+
254+
if invalid_recipients:
255+
invalid_recipients_error = (
256+
f"Invalid recipient(s) - {', '.join(invalid_recipients)}"
257+
)
258+
self.view.controller.report_error(invalid_recipients_error)
259+
return False
260+
261+
return True
262+
219263
def stream_box_view(
220264
self, stream_id: int, caption: str = "", title: str = ""
221265
) -> None:
@@ -561,6 +605,11 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
561605
msg_id=self.msg_edit_id,
562606
)
563607
else:
608+
all_valid = self.tidy_valid_recipients_and_notify_invalid_ones(
609+
self.to_write_box
610+
)
611+
if not all_valid:
612+
return key
564613
self.update_recipient_emails(self.to_write_box)
565614
if self.recipient_emails:
566615
success = self.model.send_private_message(
@@ -587,6 +636,11 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
587636
elif is_command_key("SAVE_AS_DRAFT", key):
588637
if not self.msg_edit_id:
589638
if self.to_write_box:
639+
all_valid = self.tidy_valid_recipients_and_notify_invalid_ones(
640+
self.to_write_box
641+
)
642+
if not all_valid:
643+
return key
590644
self.update_recipient_emails(self.to_write_box)
591645
this_draft: Composition = PrivateComposition(
592646
type="private",
@@ -650,22 +704,12 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
650704
else:
651705
header.focus_col = self.FOCUS_HEADER_BOX_STREAM
652706
else:
653-
self.update_recipient_emails(self.to_write_box)
654-
invalid_emails = list()
655-
656-
for recipient_email in self.recipient_emails:
657-
is_valid = self.model.is_valid_private_recipient(
658-
recipient_email
659-
)
660-
if not is_valid:
661-
invalid_emails.append(recipient_email)
662-
663-
if invalid_emails:
664-
invalid_emails_error = (
665-
f"Invalid recipient(s) - {', '.join(invalid_emails)}"
666-
)
667-
self.view.controller.report_error(invalid_emails_error)
707+
all_valid = self.tidy_valid_recipients_and_notify_invalid_ones(
708+
self.to_write_box
709+
)
710+
if not all_valid:
668711
return key
712+
self.update_recipient_emails(self.to_write_box)
669713
users = self.model.user_dict
670714
self.recipient_user_ids = [
671715
users[email]["user_id"] for email in self.recipient_emails

0 commit comments

Comments
 (0)