Skip to content

Commit 6f997ef

Browse files
authored
Implement escapeFormulae option (#796)
Closes #793
1 parent 4edef1b commit 6f997ef

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

docs/docs.html

+8
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ <h5>Unparse Config Options</h5>
337337
If <code>data</code> 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.
338338
</td>
339339
</tr>
340+
<tr>
341+
<td>
342+
<code>escapeFormulae</code>
343+
</td>
344+
<td>
345+
If <code>true</code>, field values that begin with <code>=</code>, <code>+</code>, <code>-</code>, or <code>@</code>, will be prepended with a <code>'</code> to defend against <a href="https://www.contextis.com/en/blog/comma-separated-vulnerabilities" target="_blank" rel="noopener">injection attacks</a>, because Excel and LibreOffice will automatically parse such cells as formulae.
346+
</td>
347+
</tr>
340348
</table>
341349
</div>
342350
<div class="clear"></div>

papaparse.js

+10
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ License: MIT
282282
/** the columns (keys) we expect when we unparse objects */
283283
var _columns = null;
284284

285+
/** whether to prevent outputting cells that can be parsed as formulae by spreadsheet software (Excel and LibreOffice) */
286+
var _escapeFormulae = false;
287+
285288
unpackConfig();
286289

287290
var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
@@ -361,6 +364,9 @@ License: MIT
361364
if (_config.escapeChar !== undefined) {
362365
_escapedQuote = _config.escapeChar + _quoteChar;
363366
}
367+
368+
if (typeof _config.escapeFormulae === 'boolean')
369+
_escapeFormulae = _config.escapeFormulae;
364370
}
365371

366372

@@ -447,6 +453,10 @@ License: MIT
447453
if (str.constructor === Date)
448454
return JSON.stringify(str).slice(1, 25);
449455

456+
if (_escapeFormulae === true && typeof str === "string" && (str.match(/^[=+\-@].*$/) !== null)) {
457+
str = "'" + str;
458+
}
459+
450460
var escapedQuoteStr = str.toString().replace(quoteCharRegex, _escapedQuote);
451461

452462
var needsQuotes = (typeof _quotes === 'boolean' && _quotes)

tests/test-cases.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -1842,7 +1842,36 @@ var UNPARSE_TESTS = [
18421842
input: [{a: 'foo', b: '"quoted"'}],
18431843
config: {header: false},
18441844
expected: 'foo,"""quoted"""'
1845-
}
1845+
},
1846+
{
1847+
description: "Escape formulae",
1848+
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
1849+
config: { escapeFormulae: true },
1850+
expected: 'Col1,Col2,Col3\r\n\'=danger,\'@danger,safe\r\nsafe=safe,\'+danger,"\'-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"'
1851+
},
1852+
{
1853+
description: "Don't escape formulae by default",
1854+
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
1855+
expected: 'Col1,Col2,Col3\r\n=danger,@danger,safe\r\nsafe=safe,+danger,"-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"'
1856+
},
1857+
{
1858+
description: "Escape formulae with forced quotes",
1859+
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
1860+
config: { escapeFormulae: true, quotes: true },
1861+
expected: '"Col1","Col2","Col3"\r\n"\'=danger","\'@danger","safe"\r\n"safe=safe","\'+danger","\'-danger, danger"\r\n"\'+safe","\'@safe","safe, safe"'
1862+
},
1863+
{
1864+
description: "Escape formulae with single-quote quoteChar and escapeChar",
1865+
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
1866+
config: { escapeFormulae: true, quoteChar: "'", escapeChar: "'" },
1867+
expected: 'Col1,Col2,Col3\r\n\'\'=danger,\'\'@danger,safe\r\nsafe=safe,\'\'+danger,\'\'\'-danger, danger\'\r\n\'\'+safe,\'\'@safe,\'safe, safe\''
1868+
},
1869+
{
1870+
description: "Escape formulae with single-quote quoteChar and escapeChar and forced quotes",
1871+
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
1872+
config: { escapeFormulae: true, quotes: true, quoteChar: "'", escapeChar: "'" },
1873+
expected: '\'Col1\',\'Col2\',\'Col3\'\r\n\'\'\'=danger\',\'\'\'@danger\',\'safe\'\r\n\'safe=safe\',\'\'\'+danger\',\'\'\'-danger, danger\'\r\n\'\'\'+safe\',\'\'\'@safe\',\'safe, safe\''
1874+
},
18461875
];
18471876

18481877
describe('Unparse Tests', function() {

0 commit comments

Comments
 (0)