js: Escape strings interpolated into CSS selectors with CSS.escape.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2021-02-03 14:23:32 -08:00
parent 9fc1adce60
commit aa650a4c88
75 changed files with 309 additions and 246 deletions

View File

@@ -286,7 +286,7 @@ function buddy_list_add(user_id, stub) {
stub.attr("data-user-id", user_id);
}
stub.length = 1;
const sel = `li.user_sidebar_entry[data-user-id='${user_id}']`;
const sel = `li.user_sidebar_entry[data-user-id='${CSS.escape(user_id)}']`;
$("#user_presences").set_find_results(sel, stub);
}

View File

@@ -254,12 +254,12 @@ run_test("set_tab", () => {
scrollTop: 0,
};
$('#upgrade-tabs.nav a[href="#billing"]').tab = (action) => {
$('#upgrade-tabs.nav a[href="\\#billing"]').tab = (action) => {
state.show_tab_billing += 1;
assert.equal(action, "show");
};
$('#upgrade-tabs.nav a[href="#payment-method"]').tab = (action) => {
$('#upgrade-tabs.nav a[href="\\#payment-method"]').tab = (action) => {
state.show_tab_payment_method += 1;
assert.equal(action, "show");
};

View File

@@ -21,8 +21,8 @@ const setup_zjquery_data = (name) => {
const input_group = $(".input_group");
const reset_button = $(".dropdown_list_reset_button");
input_group.set_find_results(".dropdown_list_reset_button:enabled", reset_button);
$(`#${name}_widget #${name}_name`).closest = () => input_group;
const $widget = $(`#${name}_widget #${name}_name`);
$(`#${CSS.escape(name)}_widget #${CSS.escape(name)}_name`).closest = () => input_group;
const $widget = $(`#${CSS.escape(name)}_widget #${CSS.escape(name)}_name`);
return {reset_button, $widget};
};

View File

@@ -349,7 +349,7 @@ run_test("get_reaction_section", () => {
const message_row = $.create("some-message-row");
const message_reactions = $.create("our-reactions-section");
message_table.set_find_results("[zid='555']", message_row);
message_table.set_find_results(`[zid='${CSS.escape(555)}']`, message_row);
message_row.set_find_results(".message_reactions", message_reactions);
const section = reactions.get_reaction_section(555);
@@ -439,7 +439,7 @@ run_test("add_and_remove_reaction", () => {
reaction_element.set_find_results(".message_reaction_count", count_element);
message_reactions.find = function (selector) {
assert.equal(selector, "[data-reaction-id='unicode_emoji,1f3b1']");
assert.equal(selector, "[data-reaction-id='unicode_emoji\\,1f3b1']");
return reaction_element;
};
@@ -506,7 +506,7 @@ run_test("add_and_remove_reaction", () => {
};
message_reactions.find = function (selector) {
assert.equal(selector, "[data-reaction-id='realm_emoji,991']");
assert.equal(selector, "[data-reaction-id='realm_emoji\\,991']");
return reaction_element;
};
reaction_element.prop = function () {};

View File

@@ -154,10 +154,10 @@ function test_realms_domain_modal(add_realm_domain) {
}
function createSaveButtons(subsection) {
const stub_save_button_header = $(`#org-${subsection}`);
const stub_save_button_header = $(`#org-${CSS.escape(subsection)}`);
const save_button_controls = $(".save-button-controls");
const stub_save_button = $(`#org-submit-${subsection}`);
const stub_discard_button = $(`#org-discard-${subsection}`);
const stub_save_button = $(`#org-submit-${CSS.escape(subsection)}`);
const stub_discard_button = $(`#org-discard-${CSS.escape(subsection)}`);
const stub_save_button_text = $(".save-discard-widget-button-text");
stub_save_button_header.set_find_results(
".subsection-failed-status p",
@@ -169,7 +169,7 @@ function createSaveButtons(subsection) {
stub_save_button_header.set_find_results(".save-button-controls", save_button_controls);
stub_save_button_header.set_find_results(
".subsection-changes-discard .button",
$(`#org-discard-${subsection}`),
$(`#org-discard-${CSS.escape(subsection)}`),
);
save_button_controls.set_find_results(".discard-button", stub_discard_button);
const props = {};
@@ -222,7 +222,7 @@ function test_submit_settings_form(submit_form) {
};
let subsection = "other-permissions";
ev.currentTarget = `#org-submit-${subsection}`;
ev.currentTarget = `#org-submit-${CSS.escape(subsection)}`;
let stubs = createSaveButtons(subsection);
let save_button = stubs.save_button;
save_button.attr("id", `org-submit-${subsection}`);
@@ -253,7 +253,7 @@ function test_submit_settings_form(submit_form) {
email_address_visibility_elem.attr("id", "id_realm_email_address_visibility");
email_address_visibility_elem.data = () => "number";
let subsection_elem = $(`#org-${subsection}`);
let subsection_elem = $(`#org-${CSS.escape(subsection)}`);
subsection_elem.closest = () => subsection_elem;
subsection_elem.set_find_results(".prop-element", [
bot_creation_policy_elem,
@@ -277,7 +277,7 @@ function test_submit_settings_form(submit_form) {
assert.deepEqual(data, expected_value);
subsection = "user-defaults";
ev.currentTarget = `#org-submit-${subsection}`;
ev.currentTarget = `#org-submit-${CSS.escape(subsection)}`;
stubs = createSaveButtons(subsection);
save_button = stubs.save_button;
save_button.attr("id", `org-submit-${subsection}`);
@@ -291,7 +291,7 @@ function test_submit_settings_form(submit_form) {
realm_default_twenty_four_hour_time_elem.attr("id", "id_realm_default_twenty_four_hour_time");
realm_default_twenty_four_hour_time_elem.data = () => "boolean";
subsection_elem = $(`#org-${subsection}`);
subsection_elem = $(`#org-${CSS.escape(subsection)}`);
subsection_elem.closest = () => subsection_elem;
subsection_elem.set_find_results(".prop-element", [
realm_default_language_elem,
@@ -1017,7 +1017,7 @@ run_test("misc", () => {
$.create("<disable button>"),
);
for (const name of widget_settings) {
const elem = $.create(`#${name}_widget #${name}_name`);
const elem = $.create(`#${CSS.escape(name)}_widget #${CSS.escape(name)}_name`);
elem.closest = () => dropdown_list_parent;
}
@@ -1030,7 +1030,7 @@ run_test("misc", () => {
settings_org.init_dropdown_widgets();
let setting_name = "realm_notifications_stream_id";
let elem = $(`#${setting_name}_widget #${setting_name}_name`);
let elem = $(`#${CSS.escape(setting_name)}_widget #${CSS.escape(setting_name)}_name`);
elem.closest = function () {
return stub_notification_disable_parent;
};
@@ -1047,7 +1047,7 @@ run_test("misc", () => {
assert(elem.hasClass("text-warning"));
setting_name = "realm_signup_notifications_stream_id";
elem = $(`#${setting_name}_widget #${setting_name}_name`);
elem = $(`#${CSS.escape(setting_name)}_widget #${CSS.escape(setting_name)}_name`);
elem.closest = function () {
return stub_notification_disable_parent;
};

View File

@@ -90,12 +90,12 @@ run_test("can_edit", () => {
assert(settings_user_groups.can_edit(1));
});
const user_group_selector = "#user-groups #1";
const cancel_selector = "#user-groups #1 .save-status.btn-danger";
const saved_selector = "#user-groups #1 .save-status.sea-green";
const name_selector = "#user-groups #1 .name";
const description_selector = "#user-groups #1 .description";
const instructions_selector = "#user-groups #1 .save-instructions";
const user_group_selector = `#user-groups #${CSS.escape(1)}`;
const cancel_selector = `#user-groups #${CSS.escape(1)} .save-status.btn-danger`;
const saved_selector = `#user-groups #${CSS.escape(1)} .save-status.sea-green`;
const name_selector = `#user-groups #${CSS.escape(1)} .name`;
const description_selector = `#user-groups #${CSS.escape(1)} .description`;
const instructions_selector = `#user-groups #${CSS.escape(1)} .save-instructions`;
run_test("populate_user_groups", () => {
const realm_user_group = {
@@ -169,7 +169,7 @@ run_test("populate_user_groups", () => {
const all_pills = new Map();
const pill_container_stub = $('.pill-container[data-group-pills="1"]');
const pill_container_stub = $(`.pill-container[data-group-pills="${CSS.escape(1)}"]`);
pills.appendValidatedData = function (item) {
const id = item.user_id;
assert(!all_pills.has(id));
@@ -397,7 +397,7 @@ run_test("with_external_user", () => {
set_global("$", make_zjquery());
let user_group_find_called = 0;
const user_group_stub = $('div.user-group[id="1"]');
const user_group_stub = $(`div.user-group[id="${CSS.escape(1)}"]`);
const name_field_stub = $.create("fake-name-field");
const description_field_stub = $.create("fake-description-field");
const input_stub = $.create("fake-input");
@@ -413,7 +413,7 @@ run_test("with_external_user", () => {
throw new Error(`Unknown element ${elem}`);
};
const pill_container_stub = $('.pill-container[data-group-pills="1"]');
const pill_container_stub = $(`.pill-container[data-group-pills="${CSS.escape(1)}"]`);
const pill_stub = $.create("fake-pill");
let pill_container_find_called = 0;
pill_container_stub.find = function (elem) {

View File

@@ -98,8 +98,9 @@ for (const sub of subs) {
const subscriptions_table_selector = "#subscriptions_table";
const input_field_stub = $.create(".input");
const sub_settings_selector =
"#subscription_overlay .subscription_settings[data-stream-id='" + denmark.stream_id + "']";
const sub_settings_selector = `#subscription_overlay .subscription_settings[data-stream-id='${CSS.escape(
denmark.stream_id,
)}']`;
const $sub_settings_container = $.create(sub_settings_selector);
$sub_settings_container.find = noop;
$sub_settings_container.find = function () {

View File

@@ -95,7 +95,7 @@ run_test("update_property", (override) => {
}
function checkbox_for(property) {
return $(`#${property}_${stream_id}`);
return $(`#${CSS.escape(property)}_${CSS.escape(stream_id)}`);
}
// Test desktop notifications

View File

@@ -111,7 +111,7 @@ run_test("filter_table", () => {
const sub_stubs = [];
for (const data of populated_subs) {
const sub_row = ".stream-row-" + data.elem;
const sub_row = `.stream-row-${CSS.escape(data.elem)}`;
sub_stubs.push(sub_row);
$(sub_row).attr("data-stream-id", data.stream_id);
@@ -149,7 +149,7 @@ run_test("filter_table", () => {
// Filtering has the side effect of setting the "active" class
// on our current stream, even if it doesn't match the filter.
const denmark_row = $(`.stream-row[data-stream-id='${denmark_stream_id}']`);
const denmark_row = $(`.stream-row[data-stream-id='${CSS.escape(denmark_stream_id)}']`);
// sanity check it's not set to active
assert(!denmark_row.hasClass("active"));

View File

@@ -62,9 +62,12 @@ run_test("get_item", () => {
$("#undo_markdown_preview"),
);
assert.equal(upload.get_item("textarea", {mode: "edit", row: 1}), $("#message_edit_content_1"));
assert.equal(
upload.get_item("textarea", {mode: "edit", row: 1}),
$(`#message_edit_content_${CSS.escape(1)}`),
);
$("#message_edit_content_2").closest = () => {
$(`#message_edit_content_${CSS.escape(2)}`).closest = () => {
$("#message_edit_form").set_find_results(".message_edit_save", $(".message_edit_save"));
return $("#message_edit_form");
};
@@ -72,14 +75,14 @@ run_test("get_item", () => {
assert.equal(
upload.get_item("send_status_identifier", {mode: "edit", row: 11}),
"#message-edit-send-status-11",
`#message-edit-send-status-${CSS.escape(11)}`,
);
assert.equal(
upload.get_item("send_status", {mode: "edit", row: 75}),
$("#message-edit-send-status-75"),
$(`#message-edit-send-status-${CSS.escape(75)}`),
);
$("#message-edit-send-status-2").set_find_results(
$(`#message-edit-send-status-${CSS.escape(2)}`).set_find_results(
".send-status-close",
$(".send-status-close"),
);
@@ -88,12 +91,15 @@ run_test("get_item", () => {
$(".send-status-close"),
);
$("#message-edit-send-status-22").set_find_results(".error-msg", $(".error-msg"));
$(`#message-edit-send-status-${CSS.escape(22)}`).set_find_results(
".error-msg",
$(".error-msg"),
);
assert.equal(upload.get_item("send_status_message", {mode: "edit", row: 22}), $(".error-msg"));
assert.equal(
upload.get_item("file_input_identifier", {mode: "edit", row: 123}),
"#message_edit_file_input_123",
`#message_edit_file_input_${CSS.escape(123)}`,
);
assert.equal(upload.get_item("source", {mode: "edit", row: 123}), "message-edit-file-input");
assert.equal(
@@ -102,7 +108,7 @@ run_test("get_item", () => {
);
assert.equal(
upload.get_item("markdown_preview_hide_button", {mode: "edit", row: 65}),
$("#undo_markdown_preview_65"),
$(`#undo_markdown_preview_${CSS.escape(65)}`),
);
assert.throws(

View File

@@ -3,6 +3,7 @@
const {strict: assert} = require("assert");
const path = require("path");
require("css.escape");
const puppeteer = require("puppeteer");
const {test_credentials} = require("../../var/puppeteer/test_credentials");
@@ -381,7 +382,7 @@ class CommonUtils {
*/
async get_rendered_messages(page, table = "zhome") {
return await page.evaluate((table) => {
const $recipient_rows = $(`#${table}`).find(".recipient_row");
const $recipient_rows = $(`#${CSS.escape(table)}`).find(".recipient_row");
return $recipient_rows.toArray().map((element) => {
const $el = $(element);
const stream_name = $el.find(".stream_label").text().trim();
@@ -410,7 +411,7 @@ class CommonUtils {
// The method will only check that all the messages in the
// messages array passed exist in the order they are passed.
async check_messages_sent(page, table, messages) {
await page.waitForSelector("#" + table, {visible: true});
await page.waitForSelector(`#${CSS.escape(table)}`, {visible: true});
const rendered_messages = await this.get_rendered_messages(page, table);
// We only check the last n messages because if we run
@@ -452,7 +453,7 @@ class CommonUtils {
const tah = $(field_selector).data().typeahead;
tah.mouseenter({
currentTarget: $('.typeahead:visible li:contains("' + item + '")')[0],
currentTarget: $(`.typeahead:visible li:contains("${CSS.escape(item)}")`)[0],
});
tah.select();
},

View File

@@ -6,7 +6,7 @@ const common = require("../puppeteer_lib/common");
async function get_stream_li(page, stream_name) {
const stream_id = await common.get_stream_id(page, stream_name);
return `#stream_filters [data-stream-id="${stream_id}"]`;
return `#stream_filters [data-stream-id="${CSS.escape(stream_id)}"]`;
}
async function expect_home(page) {
@@ -343,19 +343,24 @@ async function test_stream_search_filters_stream_list(page) {
async function test_users_search(page) {
console.log("Search users using right sidebar");
async function assert_in_list(page, name) {
await page.waitForSelector(`#user_presences li [data-name="${name}"]`, {visible: true});
await page.waitForSelector(`#user_presences li [data-name="${CSS.escape(name)}"]`, {
visible: true,
});
}
async function assert_selected(page, name) {
await page.waitForSelector(`#user_presences li.highlighted_user [data-name="${name}"]`, {
visible: true,
});
await page.waitForSelector(
`#user_presences li.highlighted_user [data-name="${CSS.escape(name)}"]`,
{
visible: true,
},
);
}
async function assert_not_selected(page, name) {
await common.assert_selector_doesnt_exist(
page,
`#user_presences li.highlighted_user [data-name="${name}"]`,
`#user_presences li.highlighted_user [data-name="${CSS.escape(name)}"]`,
);
}

View File

@@ -132,7 +132,7 @@ async function test_narrow_to_private_messages_with_cordelia(page) {
async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page) {
const recipients = ["cordelia@zulip.com", "othello@zulip.com"];
const multiple_recipients_pm = "A huddle to check spaces";
const pm_selector = `.messagebox:contains('${multiple_recipients_pm}')`;
const pm_selector = `.messagebox:contains('${CSS.escape(multiple_recipients_pm)}')`;
await common.send_message(page, "private", {
recipient: recipients.join(", "),
outside_view: true,

View File

@@ -6,7 +6,7 @@ const common = require("../puppeteer_lib/common");
async function user_checkbox(page, name) {
const user_id = await common.get_user_id_from_name(page, name);
return `#user-checkboxes [data-user-id="${user_id}"]`;
return `#user-checkboxes [data-user-id="${CSS.escape(user_id)}"]`;
}
async function user_span(page, name) {
@@ -15,7 +15,7 @@ async function user_span(page, name) {
async function stream_checkbox(page, stream_name) {
const stream_id = await common.get_stream_id(page, stream_name);
return `#stream-checkboxes [data-stream-id="${stream_id}"]`;
return `#stream-checkboxes [data-stream-id="${CSS.escape(stream_id)}"]`;
}
async function stream_span(page, stream_name) {

View File

@@ -12,7 +12,7 @@ async function stars_count(page) {
async function toggle_test_star_message(page) {
await page.evaluate((message) => {
const msg = $(`.message_content:contains(${message}):visible`).last();
const msg = $(`.message_content:contains("${CSS.escape(message)}"):visible`).last();
if (msg.length !== 1) {
throw new Error("cannot find test star message");
}

View File

@@ -5,13 +5,13 @@ const {strict: assert} = require("assert");
const common = require("../puppeteer_lib/common");
async function wait_for_tab(page, tab) {
const tab_slector = `#${tab}.tab-pane.active`;
const tab_slector = `#${CSS.escape(tab)}.tab-pane.active`;
await page.waitForSelector(tab_slector, {visible: true});
}
async function navigate_to(page, click_target, tab) {
console.log("Visiting #" + click_target);
await page.click(`a[href='#${click_target}']`);
await page.click(`a[href='#${CSS.escape(click_target)}']`);
await wait_for_tab(page, tab);
}

View File

@@ -247,7 +247,7 @@ async function select_from_suggestions(page, item) {
await page.evaluate((item) => {
const tah = $(".create_default_stream").data().typeahead;
tah.mouseenter({
currentTarget: $('.typeahead:visible li:contains("' + item + '")')[0],
currentTarget: $(`.typeahead:visible li:contains("${CSS.escape(item)}")`)[0],
});
tah.select();
}, item);
@@ -278,7 +278,7 @@ async function test_default_streams(page) {
const stream_name = "Scotland";
const stream_id = await common.get_stream_id(page, stream_name);
const row = `.default_stream_row[data-stream-id='${stream_id}']`;
const row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id)}']`;
await test_add_default_stream(page, stream_name, row);
await test_remove_default_stream(page, row);

View File

@@ -15,7 +15,7 @@ async function navigate_to_user_list(page) {
async function user_row(page, name) {
const user_id = await common.get_user_id_from_name(page, name);
return `.user_row[data-user-id="${user_id}"]`;
return `.user_row[data-user-id="${CSS.escape(user_id)}"]`;
}
async function test_deactivate_user(page) {

View File

@@ -8,7 +8,7 @@ async function copy_messages(page, start_message, end_message) {
return await page.evaluate(
(start_message, end_message) => {
function get_message_node(message) {
return $('.message_row .message_content:contains("' + message + '")').get(0);
return $(`.message_row .message_content:contains("${CSS.escape(message)}")`).get(0);
}
// select messages from start_message to end_message

View File

@@ -96,7 +96,9 @@ async function test_webhook_bot_creation(page) {
await page.click("#create_bot_button");
const bot_email = "1-bot@zulip.testserver";
const download_zuliprc_selector = '.download_bot_zuliprc[data-email="' + bot_email + '"]';
const download_zuliprc_selector = `.download_bot_zuliprc[data-email="${CSS.escape(
bot_email,
)}"]`;
const outgoing_webhook_zuliprc_regex = /^data:application\/octet-stream;charset=utf-8,\[api]\nemail=.+\nkey=.+\nsite=.+\ntoken=.+\n$/;
await page.waitForSelector(download_zuliprc_selector, {visible: true});
@@ -122,7 +124,9 @@ async function test_normal_bot_creation(page) {
await page.click("#create_bot_button");
const bot_email = "2-bot@zulip.testserver";
const download_zuliprc_selector = '.download_bot_zuliprc[data-email="' + bot_email + '"]';
const download_zuliprc_selector = `.download_bot_zuliprc[data-email="${CSS.escape(
bot_email,
)}"]`;
await page.waitForSelector(download_zuliprc_selector, {visible: true});
await page.click(download_zuliprc_selector);
@@ -143,10 +147,10 @@ async function test_botserverrc(page) {
async function test_edit_bot_form(page) {
const bot1_email = "1-bot@zulip.testserver";
const bot1_edit_btn = '.open_edit_bot_form[data-email="' + bot1_email + '"]';
const bot1_edit_btn = `.open_edit_bot_form[data-email="${CSS.escape(bot1_email)}"]`;
await page.click(bot1_edit_btn);
const edit_form_selector = '.edit_bot_form[data-email="' + bot1_email + '"]';
const edit_form_selector = `.edit_bot_form[data-email="${CSS.escape(bot1_email)}"]`;
await page.waitForSelector(edit_form_selector, {visible: true});
const name_field_selector = edit_form_selector + " [name=bot_name]";
assert(common.get_text_from_selector(page, name_field_selector), "Bot 1");
@@ -183,7 +187,7 @@ async function add_alert_word(page, word) {
}
async function check_alert_word_added(page, word) {
const added_alert_word_selector = `.alert-word-item[data-word='${word}']`;
const added_alert_word_selector = `.alert-word-item[data-word='${CSS.escape(word)}']`;
await page.waitForSelector(added_alert_word_selector, {visible: true});
}
@@ -213,7 +217,7 @@ async function test_duplicate_alert_words_cannot_be_added(page, duplicate_word)
}
async function delete_alert_word(page, word) {
const delete_btn_selector = `.remove-alert-word[data-word="${word}"]`;
const delete_btn_selector = `.remove-alert-word[data-word="${CSS.escape(word)}"]`;
await page.click(delete_btn_selector);
await common.assert_selector_doesnt_exist(page, delete_btn_selector);
}
@@ -239,7 +243,7 @@ async function change_language(page, language_data_code) {
await page.waitForSelector("#default_language", {visible: true});
await page.click("#default_language");
await page.waitForSelector("#default_language_modal", {visible: true});
const language_selector = `a[data-code="${language_data_code}"]`;
const language_selector = `a[data-code="${CSS.escape(language_data_code)}"]`;
await page.click(language_selector);
}

View File

@@ -3,6 +3,7 @@
const Module = require("module");
const path = require("path");
require("css.escape");
const Handlebars = require("handlebars/runtime");
const _ = require("lodash");

View File

@@ -535,9 +535,6 @@ exports.make_zjquery = function (opts) {
zjquery.clear_all_elements = function () {
elems.clear();
};
zjquery.escapeSelector = function (s) {
return s;
};
return zjquery;
};