diff --git a/static/js/poll_widget.js b/static/js/poll_widget.js
index 0dfe1ede9533a..74be0d7408604 100644
--- a/static/js/poll_widget.js
+++ b/static/js/poll_widget.js
@@ -59,7 +59,6 @@ class PollData {
for (const [key, obj] of this.key_to_option) {
const voters = Array.from(obj.votes.keys());
const current_user_vote = voters.includes(this.me);
-
options.push({
option: obj.option,
names: people.safe_full_names(voters),
@@ -87,7 +86,6 @@ class PollData {
};
this.my_idx += 1;
-
return event;
},
@@ -208,7 +206,7 @@ exports.activate = function (opts) {
const author_help = is_my_poll && !has_question;
elem.find(".poll-question-header").toggle(!input_mode);
- elem.find(".poll-question-header").text(question);
+ elem.find(".poll-question-header").html(question);
elem.find(".poll-edit-question").toggle(can_edit);
update_edit_controls();
@@ -245,9 +243,9 @@ exports.activate = function (opts) {
new_question = old_question;
}
- // Optimistically set the question locally.
- poll_data.set_question(new_question);
- render_question();
+ // Decided not to do this to prevent XSS attacks, so that escaping could happen
+ // poll_data.set_question(new_question);
+ // render_question();
// If there were no actual edits, we can exit now.
if (new_question === old_question) {
diff --git a/static/templates/widgets/poll_widget_results.hbs b/static/templates/widgets/poll_widget_results.hbs
index 64ca819002713..158e7d8eb72bf 100644
--- a/static/templates/widgets/poll_widget_results.hbs
+++ b/static/templates/widgets/poll_widget_results.hbs
@@ -3,7 +3,7 @@
- {{ option }}
+ {{{ option }}}
{{#if names}}
({{ names }})
{{/if}}
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
index b5ce2ba4ee07c..e9f7398ee8567 100644
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -1,5 +1,6 @@
import datetime
import itertools
+import json
import logging
import os
import time
@@ -173,7 +174,7 @@
)
from zerver.lib.utils import generate_api_key, log_statsd_event
from zerver.lib.validator import check_widget_content
-from zerver.lib.widget import do_widget_post_save_actions
+from zerver.lib.widget import do_widget_post_save_actions, filter_and_render_string
from zerver.models import (
MAX_MESSAGE_LENGTH,
Attachment,
@@ -1803,6 +1804,14 @@ def do_add_submessage(realm: Realm,
msg_type: str,
content: str,
) -> None:
+ content_json = json.loads(content)
+
+ if "option" in content_json:
+ content_json["option"] = filter_and_render_string(content_json["option"])
+ elif "question" in content_json:
+ content_json["question"] = filter_and_render_string(content_json["question"])
+ content = json.dumps(content_json)
+
submessage = SubMessage(
sender_id=sender_id,
message_id=message_id,
@@ -1810,7 +1819,6 @@ def do_add_submessage(realm: Realm,
content=content,
)
submessage.save()
-
event = dict(
type="submessage",
msg_type=msg_type,
diff --git a/zerver/lib/widget.py b/zerver/lib/widget.py
index c87635b07cd99..e77e72848bbcb 100644
--- a/zerver/lib/widget.py
+++ b/zerver/lib/widget.py
@@ -2,9 +2,17 @@
import re
from typing import Any, MutableMapping, Optional, Tuple
-from zerver.models import SubMessage
+from zerver.lib.markdown import markdown_convert
+from zerver.models import SubMessage, get_realm
+def filter_and_render_string(input: str) -> str:
+ # Run through the markdown engine so that links will work
+ output = markdown_convert(input, message_realm=get_realm('zulip'),)
+ # Remove p tags from render output, so the options do not create new lines
+ output = re.sub(r'<\/*p>', '', output)
+ return output
+
def get_widget_data(content: str) -> Tuple[Optional[str], Optional[str]]:
valid_widget_types = ['poll', 'todo']
tokens = content.split(' ')
@@ -28,17 +36,18 @@ def get_extra_data_from_widget_type(content: str,
question = ''
options = []
if lines and lines[0]:
- question = lines.pop(0).strip()
+ question = filter_and_render_string(lines.pop(0).strip())
for line in lines:
# If someone is using the list syntax, we remove it
# before adding an option.
option = re.sub(r'(\s*[-*]?\s*)', '', line.strip(), 1)
if len(option) > 0:
- options.append(option)
+ options.append(filter_and_render_string(option))
extra_data = {
'question': question,
'options': options,
}
+
return extra_data
return None