From 689a3f50ae114c2e9751fe9dba2184b40d6674d9 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Tue, 2 Jun 2026 00:42:59 +0530 Subject: [PATCH] fix(pos): escape html output in pos page templates (backport #55527) (#55528) Co-authored-by: Claude Sonnet 4.6 --- .../page/point_of_sale/pos_controller.js | 4 +- .../page/point_of_sale/pos_item_cart.js | 69 +++++++++++-------- .../page/point_of_sale/pos_item_details.js | 12 ++-- .../page/point_of_sale/pos_item_selector.js | 5 +- .../page/point_of_sale/pos_past_order_list.js | 8 +-- .../point_of_sale/pos_past_order_summary.js | 24 ++++--- .../selling/page/point_of_sale/pos_payment.js | 8 ++- 7 files changed, 76 insertions(+), 54 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index d7ef86da872..e232464b53a 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -174,8 +174,8 @@ erpnext.PointOfSale.Controller = class { set_opening_entry_status() { this.page.set_title_sub( ` - - Opened at ${frappe.datetime.str_to_user(this.pos_opening_time)} + + Opened at ${frappe.utils.escape_html(frappe.datetime.str_to_user(this.pos_opening_time))} ` ); diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 63e7602a6dd..1461a3fb395 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -178,7 +178,7 @@ erpnext.PointOfSale.ItemCart = class { me.$totals_section.find(".edit-cart-btn").click(); } - const item_row_name = unescape($cart_item.attr("data-row-name")); + const item_row_name = $cart_item.attr("data-row-name"); me.events.cart_item_clicked({ name: item_row_name }); this.numpad_value = ""; }); @@ -453,10 +453,10 @@ erpnext.PointOfSale.ItemCart = class {
${this.get_customer_image()}
-
${customer_name}
+
${frappe.utils.escape_html(customer_name)}
${get_customer_description()}
-
+
@@ -473,11 +473,13 @@ erpnext.PointOfSale.ItemCart = class { if (!email_id && !mobile_no) { return `
${__("Click to add email / phone")}
`; } else if (email_id && !mobile_no) { - return `
${email_id}
`; + return `
${frappe.utils.escape_html(email_id)}
`; } else if (mobile_no && !email_id) { - return `
${mobile_no}
`; + return `
${frappe.utils.escape_html(mobile_no)}
`; } else { - return `
${email_id} - ${mobile_no}
`; + return `
${frappe.utils.escape_html( + email_id + )} - ${frappe.utils.escape_html(mobile_no)}
`; } } } @@ -485,9 +487,13 @@ erpnext.PointOfSale.ItemCart = class { get_customer_image() { const { customer, image } = this.customer_info || {}; if (image) { - return `
${image}
`; + return `
${frappe.utils.escape_html(image)}
`; } else { - return `
${frappe.get_abbr(customer)}
`; + return `
${frappe.utils.escape_html( + frappe.get_abbr(customer) + )}
`; } } @@ -549,10 +555,10 @@ erpnext.PointOfSale.ItemCart = class { if (t.tax_amount_after_discount_amount == 0.0) return; // if tax rate is 0, don't print it. const description = /[0-9]+/.test(t.description) - ? t.description + ? frappe.utils.escape_html(t.description) : t.rate != 0 - ? `${t.description} @ ${t.rate}%` - : t.description; + ? `${frappe.utils.escape_html(t.description)} @ ${t.rate}%` + : frappe.utils.escape_html(t.description); return `
${description}
${format_currency(t.tax_amount_after_discount_amount, currency)}
@@ -566,8 +572,9 @@ erpnext.PointOfSale.ItemCart = class { } get_cart_item({ name }) { - const item_selector = `.cart-item-wrapper[data-row-name="${escape(name)}"]`; - return this.$cart_items_wrapper.find(item_selector); + return this.$cart_items_wrapper.find(".cart-item-wrapper").filter(function () { + return $(this).attr("data-row-name") === name; + }); } get_item_from_frm(item) { @@ -597,7 +604,9 @@ erpnext.PointOfSale.ItemCart = class { if (!$item_to_update.length) { this.$cart_items_wrapper.append( - `
+ `
` ); $item_to_update = this.get_cart_item(item_data); @@ -607,7 +616,7 @@ erpnext.PointOfSale.ItemCart = class { `${get_item_image_html()}
- ${item_data.item_name} + ${frappe.utils.escape_html(item_data.item_name)}
${get_description_html()}
@@ -636,7 +645,7 @@ erpnext.PointOfSale.ItemCart = class { if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) { return `
-
${item_data.qty || 0} ${item_data.uom}
+
${item_data.qty || 0} ${frappe.utils.escape_html(item_data.uom)}
${format_currency(item_data.amount, currency)}
${format_currency(item_data.rate, currency)}
@@ -645,7 +654,7 @@ erpnext.PointOfSale.ItemCart = class { } else { return `
-
${item_data.qty || 0} ${item_data.uom}
+
${item_data.qty || 0} ${frappe.utils.escape_html(item_data.uom)}
${format_currency(item_data.rate, currency)}
@@ -666,7 +675,7 @@ erpnext.PointOfSale.ItemCart = class { } } item_data.description = frappe.ellipsis(item_data.description, 45); - return `
${item_data.description}
`; + return `
${frappe.utils.escape_html(item_data.description)}
`; } return ``; } @@ -678,22 +687,26 @@ erpnext.PointOfSale.ItemCart = class {
${frappe.get_abbr(item_name)} + src="${frappe.utils.escape_html(image)}" alt="${frappe.utils.escape_html(frappe.get_abbr(item_name))}">
`; } else { - return `
${frappe.get_abbr(item_name)}
`; + return `
${frappe.utils.escape_html( + frappe.get_abbr(item_name) + )}
`; } } } handle_broken_image($img) { const item_abbr = $($img).attr("alt"); - $($img).parent().replaceWith(`
${item_abbr}
`); + $($img) + .parent() + .replaceWith(`
${frappe.utils.escape_html(item_abbr)}
`); } update_selector_value_in_cart_item(selector, value, item) { const $item_to_update = this.get_cart_item(item); - $item_to_update.attr(`data-${selector}`, escape(value)); + $item_to_update.attr(`data-${selector}`, value); } toggle_checkout_btn(show_checkout) { @@ -892,8 +905,8 @@ erpnext.PointOfSale.ItemCart = class {
${this.get_customer_image()}
-
${customer_name}
-
${customer}
+
${frappe.utils.escape_html(customer_name)}
+
${frappe.utils.escape_html(customer)}
@@ -1030,9 +1043,11 @@ erpnext.PointOfSale.ItemCart = class { }; transaction_container.append( - `
+ `
-
${invoice.name}
+
${frappe.utils.escape_html(invoice.name)}
${posting_datetime}
@@ -1040,7 +1055,7 @@ erpnext.PointOfSale.ItemCart = class { ${format_currency(invoice.grand_total, invoice.currency, frappe.sys_defaults.currency_precision) || 0}
- + ${__(invoice.status)}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 7dc780dad7e..3a518be8574 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -128,25 +128,27 @@ erpnext.PointOfSale.ItemDetails = class { return ``; } - this.$item_name.html(item_name); + this.$item_name.html(frappe.utils.escape_html(item_name)); this.$item_description.html(get_description_html()); this.$item_price.html(format_currency(price_list_rate, this.currency)); if (!this.hide_images && image) { this.$item_image.html( `${frappe.get_abbr(item_name)}` ); } else { - this.$item_image.html(`
${frappe.get_abbr(item_name)}
`); + this.$item_image.html( + `
${frappe.utils.escape_html(frappe.get_abbr(item_name))}
` + ); } } handle_broken_image($img) { const item_abbr = $($img).attr("alt"); - $($img).replaceWith(`
${item_abbr}
`); + $($img).replaceWith(`
${frappe.utils.escape_html(item_abbr)}
`); } render_discount_dom(item) { diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9b51ba26225..f6530458b07 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -134,9 +134,9 @@ erpnext.PointOfSale.ItemSelector = class {
${frappe.utils.escape_html(frappe.ellipsis(item.item_name, 18))}
-
${frappe.utils.escape_html( +
${ format_currency(price_list_rate, item.currency, precision) || 0 - )} / ${frappe.utils.escape_html(uom)}
+ } / ${frappe.utils.escape_html(uom)}
`; } @@ -265,7 +265,6 @@ erpnext.PointOfSale.ItemSelector = class { let rate = $item.attr("data-rate"); let stock_uom = $item.attr("data-stock-uom"); - // escape(undefined) returns "undefined" then unescape returns "undefined" batch_no = batch_no === "undefined" ? undefined : batch_no; serial_no = serial_no === "undefined" ? undefined : serial_no; uom = uom === "undefined" ? undefined : uom; diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index 5ea58a43c09..1e606da46c3 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -38,7 +38,7 @@ erpnext.PointOfSale.PastOrderList = class { }); const me = this; this.$invoices_container.on("click", ".invoice-wrapper", function () { - const invoice_name = unescape($(this).attr("data-invoice-name")); + const invoice_name = $(this).attr("data-invoice-name"); me.events.open_invoice_data(invoice_name); }); @@ -99,14 +99,14 @@ erpnext.PointOfSale.PastOrderList = class { const posting_datetime = frappe.datetime.str_to_user( invoice.posting_date + " " + invoice.posting_time ); - return `
+ return `
-
${invoice.name}
+
${frappe.utils.escape_html(invoice.name)}
- ${frappe.ellipsis(invoice.customer_name, 20)} + ${frappe.utils.escape_html(frappe.ellipsis(invoice.customer_name, 20))}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 0a965c47f48..515669cdfc5 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -81,23 +81,27 @@ erpnext.PointOfSale.PastOrderSummary = class { return `
-
${doc.customer_name}
- ${is_customer_naming_by_customer_name ? `
${doc.customer}
` : ""} -
${this.customer_email}
+
${frappe.utils.escape_html(doc.customer_name)}
+ ${ + is_customer_naming_by_customer_name + ? `
${frappe.utils.escape_html(doc.customer)}
` + : "" + } +
${frappe.utils.escape_html(this.customer_email)}
-
${__("Sold by")}: ${doc.owner}
+
${__("Sold by")}: ${frappe.utils.escape_html(doc.owner)}
-
${doc.name}
+
${frappe.utils.escape_html(doc.name)}
${__(doc.status)}
`; } get_item_html(doc, item_data) { return `
-
${item_data.item_name}
-
${item_data.qty || 0} ${item_data.uom}
+
${frappe.utils.escape_html(item_data.item_name)}
+
${item_data.qty || 0} ${frappe.utils.escape_html(item_data.uom)}
${get_rate_discount_html()}
`; @@ -139,10 +143,10 @@ erpnext.PointOfSale.PastOrderSummary = class { .map((t) => { // if tax rate is 0, don't print it. const description = /[0-9]+/.test(t.description) - ? t.description + ? frappe.utils.escape_html(t.description) : t.rate != 0 - ? `${t.description} @ ${t.rate}%` - : t.description; + ? `${frappe.utils.escape_html(t.description)} @ ${t.rate}%` + : frappe.utils.escape_html(t.description); return `
${description}
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 165066a151a..7d995ac131c 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -408,8 +408,10 @@ erpnext.PointOfSale.Payment = class { return `
-
- ${p.mode_of_payment} +
+ ${frappe.utils.escape_html(p.mode_of_payment)}
${amount}
@@ -544,7 +546,7 @@ erpnext.PointOfSale.Payment = class {
Redeem Loyalty Points
${amount}
-
${loyalty_program}
+
${frappe.utils.escape_html(loyalty_program)}
`