Skip to content

Commit

Permalink
Implement escapeFormulae option (#796)
Browse files Browse the repository at this point in the history
Closes #793
  • Loading branch information
wcerfgba committed May 24, 2020
1 parent 4edef1b commit 6f997ef
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 1 deletion.
8 changes: 8 additions & 0 deletions docs/docs.html
Expand Up @@ -337,6 +337,14 @@ <h5>Unparse Config Options</h5>
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.
</td>
</tr>
<tr>
<td>
<code>escapeFormulae</code>
</td>
<td>
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.
</td>
</tr>
</table>
</div>
<div class="clear"></div>
Expand Down
10 changes: 10 additions & 0 deletions papaparse.js
Expand Up @@ -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');
Expand Down Expand Up @@ -361,6 +364,9 @@ License: MIT
if (_config.escapeChar !== undefined) {
_escapedQuote = _config.escapeChar + _quoteChar;
}

if (typeof _config.escapeFormulae === 'boolean')
_escapeFormulae = _config.escapeFormulae;
}


Expand Down Expand Up @@ -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)
Expand Down
31 changes: 30 additions & 1 deletion tests/test-cases.js
Expand Up @@ -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() {
Expand Down

0 comments on commit 6f997ef

Please sign in to comment.