Fix security issue on CsvEncoder
This commit is contained in:
parent
8776ccee03
commit
a1b0bdbbac
@ -8,6 +8,7 @@ CHANGELOG
|
|||||||
of objects that needs data insertion in constructor
|
of objects that needs data insertion in constructor
|
||||||
* added an optional `default_constructor_arguments` option of context to specify a default data in
|
* added an optional `default_constructor_arguments` option of context to specify a default data in
|
||||||
case the object is not initializable by its constructor because of data missing
|
case the object is not initializable by its constructor because of data missing
|
||||||
|
* added optional `bool $escapeFormulas = false` argument to `CsvEncoder::__construct`
|
||||||
|
|
||||||
4.0.0
|
4.0.0
|
||||||
-----
|
-----
|
||||||
|
@ -27,18 +27,22 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
const ESCAPE_CHAR_KEY = 'csv_escape_char';
|
const ESCAPE_CHAR_KEY = 'csv_escape_char';
|
||||||
const KEY_SEPARATOR_KEY = 'csv_key_separator';
|
const KEY_SEPARATOR_KEY = 'csv_key_separator';
|
||||||
const HEADERS_KEY = 'csv_headers';
|
const HEADERS_KEY = 'csv_headers';
|
||||||
|
const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas';
|
||||||
|
|
||||||
private $delimiter;
|
private $delimiter;
|
||||||
private $enclosure;
|
private $enclosure;
|
||||||
private $escapeChar;
|
private $escapeChar;
|
||||||
private $keySeparator;
|
private $keySeparator;
|
||||||
|
private $escapeFormulas;
|
||||||
|
private $formulasStartCharacters = array('=', '-', '+', '@');
|
||||||
|
|
||||||
public function __construct(string $delimiter = ',', string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.')
|
public function __construct(string $delimiter = ',', string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.', bool $escapeFormulas = false)
|
||||||
{
|
{
|
||||||
$this->delimiter = $delimiter;
|
$this->delimiter = $delimiter;
|
||||||
$this->enclosure = $enclosure;
|
$this->enclosure = $enclosure;
|
||||||
$this->escapeChar = $escapeChar;
|
$this->escapeChar = $escapeChar;
|
||||||
$this->keySeparator = $keySeparator;
|
$this->keySeparator = $keySeparator;
|
||||||
|
$this->escapeFormulas = $escapeFormulas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,11 +69,11 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers) = $this->getCsvOptions($context);
|
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas) = $this->getCsvOptions($context);
|
||||||
|
|
||||||
foreach ($data as &$value) {
|
foreach ($data as &$value) {
|
||||||
$flattened = array();
|
$flattened = array();
|
||||||
$this->flatten($value, $flattened, $keySeparator);
|
$this->flatten($value, $flattened, $keySeparator, '', $escapeFormulas);
|
||||||
$value = $flattened;
|
$value = $flattened;
|
||||||
}
|
}
|
||||||
unset($value);
|
unset($value);
|
||||||
@ -172,13 +176,17 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
/**
|
/**
|
||||||
* Flattens an array and generates keys including the path.
|
* Flattens an array and generates keys including the path.
|
||||||
*/
|
*/
|
||||||
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '')
|
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
|
||||||
{
|
{
|
||||||
foreach ($array as $key => $value) {
|
foreach ($array as $key => $value) {
|
||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
|
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
|
||||||
} else {
|
} else {
|
||||||
$result[$parentKey.$key] = $value;
|
if ($escapeFormulas && \in_array(substr($value, 0, 1), $this->formulasStartCharacters, true)) {
|
||||||
|
$result[$parentKey.$key] = "\t".$value;
|
||||||
|
} else {
|
||||||
|
$result[$parentKey.$key] = $value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,12 +198,13 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
|
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
|
||||||
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
|
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
|
||||||
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
|
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
|
||||||
|
$escapeFormulas = isset($context[self::ESCAPE_FORMULAS_KEY]) ? $context[self::ESCAPE_FORMULAS_KEY] : $this->escapeFormulas;
|
||||||
|
|
||||||
if (!is_array($headers)) {
|
if (!is_array($headers)) {
|
||||||
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, gettype($headers)));
|
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, gettype($headers)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers);
|
return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,6 +173,109 @@ CSV;
|
|||||||
$this->assertEquals($csv, $this->encoder->encode($value, 'csv', $context));
|
$this->assertEquals($csv, $this->encoder->encode($value, 'csv', $context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testEncodeFormulas()
|
||||||
|
{
|
||||||
|
$this->encoder = new CsvEncoder(',', '"', '\\', '.', true);
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" =2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('=2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" -2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('-2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" +2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('+2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" @MyDataColumn"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('@MyDataColumn'), 'csv'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoNotEncodeFormulas()
|
||||||
|
{
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
=2+3
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('=2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
-2+3
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('-2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
+2+3
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('+2+3'), 'csv'));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
@MyDataColumn
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('@MyDataColumn'), 'csv'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEncodeFormulasWithSettingsPassedInContext()
|
||||||
|
{
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" =2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('=2+3'), 'csv', array(
|
||||||
|
CsvEncoder::ESCAPE_FORMULAS_KEY => true,
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" -2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('-2+3'), 'csv', array(
|
||||||
|
CsvEncoder::ESCAPE_FORMULAS_KEY => true,
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" +2+3"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('+2+3'), 'csv', array(
|
||||||
|
CsvEncoder::ESCAPE_FORMULAS_KEY => true,
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->assertSame(<<<'CSV'
|
||||||
|
0
|
||||||
|
" @MyDataColumn"
|
||||||
|
|
||||||
|
CSV
|
||||||
|
, $this->encoder->encode(array('@MyDataColumn'), 'csv', array(
|
||||||
|
CsvEncoder::ESCAPE_FORMULAS_KEY => true,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
public function testSupportsDecoding()
|
public function testSupportsDecoding()
|
||||||
{
|
{
|
||||||
$this->assertTrue($this->encoder->supportsDecoding('csv'));
|
$this->assertTrue($this->encoder->supportsDecoding('csv'));
|
||||||
|
Reference in New Issue
Block a user