Skip to content

Commit

Permalink
Add more cases to escapeFormulae and allow to pass RegExp (#904)
Browse files Browse the repository at this point in the history
  • Loading branch information
caub committed Dec 10, 2021
1 parent 26a86fd commit 1f2c733
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/docs.html
Expand Up @@ -343,7 +343,7 @@ <h5>Unparse Config Options</h5>
<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.
If <code>true</code>, field values that begin with <code>=</code>, <code>+</code>, <code>-</code>, <code>@</code>, <code>\t</code>, or <code>\r</code>, will be prepended with a <code>'</code> to defend against <a href="https://owasp.org/www-community/attacks/CSV_Injection" target="_blank" rel="noopener">injection attacks</a>, because Excel and LibreOffice will automatically parse such cells as formulae. You can override those values by setting this option to a regular expression
</td>
</tr>
</table>
Expand Down
14 changes: 9 additions & 5 deletions papaparse.js
Expand Up @@ -367,11 +367,11 @@ License: MIT
_escapedQuote = _config.escapeChar + _quoteChar;
}

if (typeof _config.escapeFormulae === 'boolean')
_escapeFormulae = _config.escapeFormulae;
if (typeof _config.escapeFormulae === 'boolean' || _config.escapeFormulae instanceof RegExp) {
_escapeFormulae = _config.escapeFormulae instanceof RegExp ? _config.escapeFormulae : /^[=+\-@\t\r].*$/;
}
}


/** The double for loop that iterates the data and writes out a CSV string including header row */
function serialize(fields, data, skipEmptyLines)
{
Expand Down Expand Up @@ -444,13 +444,17 @@ License: MIT
if (str.constructor === Date)
return JSON.stringify(str).slice(1, 25);

if (_escapeFormulae === true && typeof str === "string" && (str.match(/^[=+\-@].*$/) !== null)) {
var needsQuotes = false;

if (_escapeFormulae && typeof str === "string" && _escapeFormulae.test(str)) {
str = "'" + str;
needsQuotes = true;
}

var escapedQuoteStr = str.toString().replace(quoteCharRegex, _escapedQuote);

var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
needsQuotes = needsQuotes
|| _quotes === true
|| (typeof _quotes === 'function' && _quotes(str, col))
|| (Array.isArray(_quotes) && _quotes[col])
|| hasAny(escapedQuoteStr, Papa.BAD_DELIMITERS)
Expand Down
29 changes: 27 additions & 2 deletions tests/test-cases.js
Expand Up @@ -1881,7 +1881,7 @@ var UNPARSE_TESTS = [
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"'
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",
Expand All @@ -1898,14 +1898,39 @@ var UNPARSE_TESTS = [
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\''
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\''
},
// new escapeFormulae values:
{
description: "Escape formulae with tab and carriage-return",
input: [{ "Col1": "\tdanger", "Col2": "\rdanger,", "Col3": "safe\t\r" }],
config: { escapeFormulae: true },
expected: 'Col1,Col2,Col3\r\n"\'\tdanger","\'\rdanger,","safe\t\r"'
},
{
description: "Escape formulae with tab and carriage-return, with forced quotes",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe\t\r" }],
config: { escapeFormulae: true, quotes: true },
expected: '"Col1","Col2","Col3"\r\n"\'\tdanger","\'\rdanger,","safe\t\r"'
},
{
description: "Escape formulae with tab and carriage-return, with single-quote quoteChar and escapeChar",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe, \t\r" }],
config: { escapeFormulae: true, quoteChar: "'", escapeChar: "'" },
expected: 'Col1,Col2,Col3\r\n\'\'\'\tdanger\',\'\'\'\rdanger,\',\'safe, \t\r\''
},
{
description: "Escape formulae with tab and carriage-return, with single-quote quoteChar and escapeChar and forced quotes",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe, \t\r" }],
config: { escapeFormulae: true, quotes: true, quoteChar: "'", escapeChar: "'" },
expected: '\'Col1\',\'Col2\',\'Col3\'\r\n\'\'\'\tdanger\',\'\'\'\rdanger,\',\'safe, \t\r\''
},
];

describe('Unparse Tests', function() {
Expand Down

0 comments on commit 1f2c733

Please sign in to comment.