From 6f997ef4fb6d34b38f8eb9aa97b120b954446a9a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 24 May 2020 10:31:52 +0100 Subject: [PATCH] Implement escapeFormulae option (#796) Closes #793 --- docs/docs.html | 8 ++++++++ papaparse.js | 10 ++++++++++ tests/test-cases.js | 31 ++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/docs.html b/docs/docs.html index b7785de3..8d253a13 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -337,6 +337,14 @@
Unparse Config Options
If data is an array of objects this option can be used to manually specify the keys (columns) you expect in the objects. If not set the keys of the first objects are used as column. + + + escapeFormulae + + + If true, field values that begin with =, +, -, or @, will be prepended with a ' to defend against injection attacks, because Excel and LibreOffice will automatically parse such cells as formulae. + +
diff --git a/papaparse.js b/papaparse.js index 70dedf01..ec493732 100755 --- a/papaparse.js +++ b/papaparse.js @@ -282,6 +282,9 @@ License: MIT /** the columns (keys) we expect when we unparse objects */ var _columns = null; + /** whether to prevent outputting cells that can be parsed as formulae by spreadsheet software (Excel and LibreOffice) */ + var _escapeFormulae = false; + unpackConfig(); var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g'); @@ -361,6 +364,9 @@ License: MIT if (_config.escapeChar !== undefined) { _escapedQuote = _config.escapeChar + _quoteChar; } + + if (typeof _config.escapeFormulae === 'boolean') + _escapeFormulae = _config.escapeFormulae; } @@ -447,6 +453,10 @@ License: MIT if (str.constructor === Date) return JSON.stringify(str).slice(1, 25); + if (_escapeFormulae === true && typeof str === "string" && (str.match(/^[=+\-@].*$/) !== null)) { + str = "'" + str; + } + var escapedQuoteStr = str.toString().replace(quoteCharRegex, _escapedQuote); var needsQuotes = (typeof _quotes === 'boolean' && _quotes) diff --git a/tests/test-cases.js b/tests/test-cases.js index 18ec3801..bc0b3339 100644 --- a/tests/test-cases.js +++ b/tests/test-cases.js @@ -1842,7 +1842,36 @@ var UNPARSE_TESTS = [ input: [{a: 'foo', b: '"quoted"'}], config: {header: false}, expected: 'foo,"""quoted"""' - } + }, + { + description: "Escape formulae", + input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }], + config: { escapeFormulae: true }, + expected: 'Col1,Col2,Col3\r\n\'=danger,\'@danger,safe\r\nsafe=safe,\'+danger,"\'-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"' + }, + { + description: "Don't escape formulae by default", + input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }], + expected: 'Col1,Col2,Col3\r\n=danger,@danger,safe\r\nsafe=safe,+danger,"-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"' + }, + { + description: "Escape formulae with forced quotes", + input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }], + config: { escapeFormulae: true, quotes: true }, + expected: '"Col1","Col2","Col3"\r\n"\'=danger","\'@danger","safe"\r\n"safe=safe","\'+danger","\'-danger, danger"\r\n"\'+safe","\'@safe","safe, safe"' + }, + { + description: "Escape formulae with single-quote quoteChar and escapeChar", + input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }], + config: { escapeFormulae: true, quoteChar: "'", escapeChar: "'" }, + expected: 'Col1,Col2,Col3\r\n\'\'=danger,\'\'@danger,safe\r\nsafe=safe,\'\'+danger,\'\'\'-danger, danger\'\r\n\'\'+safe,\'\'@safe,\'safe, safe\'' + }, + { + description: "Escape formulae with single-quote quoteChar and escapeChar and forced quotes", + input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }], + config: { escapeFormulae: true, quotes: true, quoteChar: "'", escapeChar: "'" }, + expected: '\'Col1\',\'Col2\',\'Col3\'\r\n\'\'\'=danger\',\'\'\'@danger\',\'safe\'\r\n\'safe=safe\',\'\'\'+danger\',\'\'\'-danger, danger\'\r\n\'\'\'+safe\',\'\'\'@safe\',\'safe, safe\'' + }, ]; describe('Unparse Tests', function() {