Skip to content
Permalink
Browse files Browse the repository at this point in the history
Temporary workaround until the L6 upgrade can be merged in to use lea…
…gue/csv >= 9.1
  • Loading branch information
Luke Towers committed Mar 31, 2020
1 parent 30256ca commit c84bf03
Showing 1 changed file with 145 additions and 0 deletions.
145 changes: 145 additions & 0 deletions src/Parse/League/EscapeFormula.php
@@ -0,0 +1,145 @@
<?php namespace October\Rain\Parse\League;

/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use InvalidArgumentException;

/**
* A Formatter to tackle CSV Formula Injection.
*
* @see http://georgemauer.net/2017/10/07/csv-injection.html
*/
class EscapeFormula
{
/**
* Spreadsheet formula starting character.
*/
const FORMULA_STARTING_CHARS = ['=', '-', '+', '@'];

/**
* Effective Spreadsheet formula starting characters.
*
* @var array
*/
protected $special_chars = [];

/**
* Escape character to escape each CSV formula field.
*
* @var string
*/
protected $escape;

/**
* New instance.
*
* @param string $escape escape character to escape each CSV formula field
* @param string[] $special_chars additional spreadsheet formula starting characters
*
*/
public function __construct(string $escape = "\t", array $special_chars = [])
{
$this->escape = $escape;
if ([] !== $special_chars) {
$special_chars = $this->filterSpecialCharacters(...$special_chars);
}

$chars = array_merge(self::FORMULA_STARTING_CHARS, $special_chars);
$chars = array_unique($chars);
$this->special_chars = array_fill_keys($chars, 1);
}

/**
* Filter submitted special characters.
*
* @param string ...$characters
*
* @throws InvalidArgumentException if the string is not a single character
*
* @return string[]
*/
protected function filterSpecialCharacters(string ...$characters): array
{
foreach ($characters as $str) {
if (1 != strlen($str)) {
throw new InvalidArgumentException(sprintf('The submitted string %s must be a single character', $str));
}
}

return $characters;
}

/**
* Returns the list of character the instance will escape.
*
* @return string[]
*/
public function getSpecialCharacters(): array
{
return array_keys($this->special_chars);
}

/**
* Returns the escape character.
*/
public function getEscape(): string
{
return $this->escape;
}

/**
* League CSV formatter hook.
*
* @see escapeRecord
*/
public function __invoke(array $record): array
{
return $this->escapeRecord($record);
}

/**
* Escape a CSV record.
*/
public function escapeRecord(array $record): array
{
return array_map([$this, 'escapeField'], $record);
}

/**
* Escape a CSV cell if its content is stringable.
*
* @param mixed $cell the content of the cell
*
* @return mixed|string the escaped content
*/
protected function escapeField($cell)
{
if (!$this->isStringable($cell)) {
return $cell;
}

$str_cell = (string) $cell;
if (isset($str_cell[0], $this->special_chars[$str_cell[0]])) {
return $this->escape.$str_cell;
}

return $cell;
}

/**
* Tells whether the submitted value is stringable.
*
* @param string|object $value
*/
protected function isStringable($value): bool
{
return is_string($value) || method_exists($value, '__toString');
}
}

0 comments on commit c84bf03

Please sign in to comment.