From 4dcb24b0694ed56a799c583cb40406e1920ba65e Mon Sep 17 00:00:00 2001 From: Roland Boon Date: Fri, 2 Oct 2026 11:21:41 +0200 Subject: [PATCH] Fix XML escaping for invalid Unicode characters Lone surrogates and U+FFFE/U+FFFF (non-characters) are not valid XML 1.0 characters. Strip them during serialization so arbitrary input cannot produce a corrupt workbook. --- test/index.js | 11 +++++++++++ xlsx.js | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 8ab460f..2010a43 100644 --- a/test/index.js +++ b/test/index.js @@ -4,6 +4,17 @@ describe("xlsx", function() { var { createFiles, createXlsx } = require("..") , compressionSuported = typeof CompressionStream !== "undefined" && typeof Response !== "undefined" + test("XML 1.0 characters", function(assert) { + var text = 'A\u0000\u0001\u0008\u000b\u000c\u000e\u001f\ud800B\udfff\ufffe\uffffC\t\n\r\ud83d\ude00' + , clean = 'ABC\t\n\r\ud83d\ude00' + , files = createFiles({ sheets: [{ name: text, data: [[text]] }] }) + , workbook = files.find(f => f.name === 'xl/workbook.xml').content + , sheet = files.find(f => f.name === 'xl/worksheets/sheet1.xml').content + assert.ok(workbook.includes('name="' + clean + '"'), 'attributes omit invalid code points') + assert.ok(sheet.includes('>' + clean + ''), 'text preserves whitespace and supplementary characters') + assert.end() + }) + function sheet1(data, sheet) { return createFiles({ sheets: [{ data, ...sheet }] }).find(f => f.name === 'xl/worksheets/sheet1.xml').content } diff --git a/xlsx.js b/xlsx.js index 05ddbe2..0aab81b 100644 --- a/xlsx.js +++ b/xlsx.js @@ -10,7 +10,7 @@ , excelEpoch = Date.UTC(1899, 11, 30) , assign = Object.assign , dataArr = arr => Array.isArray(arr) ? { data: arr } : arr - , esc = val => ('' + val).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '') + , esc = val => ('' + val).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\uFFFE\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g, c => c.length === 2 ? c : '') , isNum = num => num === num && typeof num === 'number' , isObj = obj => !!obj && obj.constructor === Object , isStr = str => typeof str === 'string' @@ -207,4 +207,3 @@ // this is `exports` in module and `window` in browser })(this, Object) // jshint ignore:line -