Bugzilla – Attachment 188514 Details for
Bug 40525
CSV formula injection - client side (DataTables) in OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 40525: [24.11] Update spreadsheet format to escape formulas
Bug-40525-2411-Update-spreadsheet-format-to-escape.patch (text/plain), 6.88 KB, created by
Baptiste Wojtkowski (bwoj)
on 2025-10-28 14:40:11 UTC
(
hide
)
Description:
Bug 40525: [24.11] Update spreadsheet format to escape formulas
Filename:
MIME Type:
Creator:
Baptiste Wojtkowski (bwoj)
Created:
2025-10-28 14:40:11 UTC
Size:
6.88 KB
patch
obsolete
>From c85f488bc1b66cfad3bf6a1f6911d1b7bbd81ba9 Mon Sep 17 00:00:00 2001 >From: David Cook <dcook@prosentient.com.au> >Date: Tue, 28 Oct 2025 05:55:49 +0000 >Subject: [PATCH] Bug 40525: [24.11] Update spreadsheet format to escape > formulas > >--- > .../en/includes/columns_settings.inc | 6 +- > .../bootstrap/en/includes/datatables.inc | 1 + > .../bootstrap/en/modules/opac-user.tt | 5 +- > .../bootstrap/js/datatables_buttons_shim.js | 112 ++++++++++++++++++ > 4 files changed, 121 insertions(+), 3 deletions(-) > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/datatables_buttons_shim.js > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/columns_settings.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/columns_settings.inc >index 0c5317adf90..58a548b7550 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/columns_settings.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/columns_settings.inc >@@ -1,5 +1,5 @@ > [% USE TablesSettings %] >- >+[% Asset.js("js/datatables_buttons_shim.js") | $raw %] > <script> > > function _dt_visibility(table_settings, table_dt){ >@@ -47,6 +47,8 @@ function KohaTable(selector, dt_parameters, table_settings) { > } > } > >+ const export_format_spreadsheet = ButtonsShim.export_format_spreadsheet; >+ > // Add a "Clear filter" button to table filter form field > dt_parameters[ "buttons" ] = [ > { >@@ -65,7 +67,7 @@ function KohaTable(selector, dt_parameters, table_settings) { > text: _("CSV"), > exportOptions: { > columns: exportColumns, >- format: export_format >+ format: export_format_spreadsheet > }, > }, > { >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/datatables.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/datatables.inc >index bf2b35f1114..9d052a8ef55 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/datatables.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/datatables.inc >@@ -1,4 +1,5 @@ > [% USE raw %] > [% USE Asset %] >+[% Asset.js("js/datatables_buttons_shim.js") | $raw %] > [% Asset.js("lib/datatables/datatables.min.js") | $raw %] > [% Asset.js("js/datatables.js") | $raw %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >index cf589af77be..6c9986da70d 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >@@ -1200,6 +1200,8 @@ > ); > }); > >+ const export_format_spreadsheet = ButtonsShim.export_format_spreadsheet; >+ > var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table"); > dTables.each(function(){ > var thIndex = $(this).find("th.psort").index(); >@@ -1239,7 +1241,8 @@ > extend: "csv", > exportOptions: { > /* CSV export should include all columns (even invisible ones) unless they are .noExport */ >- columns: ":not(.noExport)" >+ columns: ":not(.noExport)", >+ format: export_format_spreadsheet > } > } > ] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/datatables_buttons_shim.js b/koha-tmpl/opac-tmpl/bootstrap/js/datatables_buttons_shim.js >new file mode 100644 >index 00000000000..0167c523391 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/datatables_buttons_shim.js >@@ -0,0 +1,112 @@ >+window.ButtonsShim = {}; >+ >+ButtonsShim.export_format_spreadsheet = { >+ body: function (data, row, column, node) { >+ const newnode = node.cloneNode(true); >+ const no_export_nodes = newnode.querySelectorAll(".no-export"); >+ no_export_nodes.forEach(child => { >+ child.parentNode.removeChild(child); >+ }); >+ //Note: innerHTML is the same thing as the data variable, >+ //minus the ".no-export" nodes that we've removed >+ //Note: See dataTables.buttons.js for original function usage >+ const str = ButtonsShim.stripData(newnode.innerHTML, { >+ decodeEntities: false, >+ stripHtml: true, >+ stripNewlines: true, >+ trim: true, >+ escapeExcelFormula: true >+ }); >+ return str; >+ }, >+}; >+ >+ButtonsShim._stripHtml = function (input) { >+ var _max_str_len = Math.pow(2, 28); >+ var _re_html = /<([^>]*>)/g; >+ if (! input || typeof input !== 'string') { >+ return input; >+ } >+ // Irrelevant check to workaround CodeQL's false positive on the regex >+ if (input.length > _max_str_len) { >+ throw new Error('Exceeded max str len'); >+ } >+ var previous; >+ input = input.replace(_re_html, ''); // Complete tags >+ // Safety for incomplete script tag - use do / while to ensure that >+ // we get all instances >+ do { >+ previous = input; >+ input = input.replace(/<script/i, ''); >+ } while (input !== previous); >+ return previous; >+}; >+ >+ButtonsShim.stripHtml = function (mixed) { >+ var type = typeof mixed; >+ >+ if (type === 'function') { >+ ButtonsShim._stripHtml = mixed; >+ return; >+ } >+ else if (type === 'string') { >+ return ButtonsShim._stripHtml(mixed); >+ } >+ return mixed; >+}; >+ButtonsShim.stripHtmlScript = function (input) { >+ var previous; >+ do { >+ previous = input; >+ input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, ''); >+ } while (input !== previous); >+ return input; >+}; >+ButtonsShim.stripHtmlComments = function (input) { >+ var previous; >+ do { >+ previous = input; >+ input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, ''); >+ } while (input !== previous); >+ return input; >+}; >+ButtonsShim.stripData = function (str, config) { >+ // If the input is an HTML element, we can use the HTML from it (HTML might be stripped below). >+ if (str !== null && typeof str === 'object' && str.nodeName && str.nodeType) { >+ str = str.innerHTML; >+ } >+ >+ if (typeof str !== 'string') { >+ return str; >+ } >+ >+ // Always remove script tags >+ //str = Buttons.stripHtmlScript(str); >+ str = ButtonsShim.stripHtmlScript(str); >+ >+ // Always remove comments >+ //str = Buttons.stripHtmlComments(str); >+ str = ButtonsShim.stripHtmlComments(str); >+ >+ if (!config || config.stripHtml) { >+ //str = DataTable.util.stripHtml(str); >+ str = ButtonsShim.stripHtml(str); >+ } >+ >+ if (!config || config.trim) { >+ str = str.trim(); >+ } >+ >+ if (!config || config.stripNewlines) { >+ str = str.replace(/\n/g, ' '); >+ } >+ >+ // Prevent Excel from running a formula >+ if (!config || config.escapeExcelFormula) { >+ if (str.match(/^[=@\t\r]/)) { >+ str = "'" + str; >+ } >+ } >+ >+ return str; >+}; >-- >2.43.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 40525
:
184698
|
187596
|
188052
|
188494
|
188496
| 188514 |
188534
|
188535