/
EscapeFormula.php
150 lines (137 loc) · 3.55 KB
/
EscapeFormula.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php
namespace Concrete\Core\Csv;
/**
* This file was ported from League CSV to be compatible with PHP 5.5.9 minimum
*
* League.Csv (https://csv.thephpleague.com).
*
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
* @license https://github.com/thephpleague/csv/blob/master/LICENSE (MIT License)
* @version 9.1.5
* @link https://github.com/thephpleague/csv
*/
use \InvalidArgumentException;
/**
* A League CSV formatter to tackle CSV Formula Injection.
*
* @see http://georgemauer.net/2017/10/07/csv-injection.html
*
* @package League.csv
* @since 9.1.0
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
*/
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($escape = "\t", array $special_chars = [])
{
$this->escape = $escape;
if ([] !== $special_chars) {
$special_chars = $this->filterSpecialCharacters($special_chars);
}
$chars = array_merge(str_split(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(array $characters)
{
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()
{
return array_keys($this->special_chars);
}
/**
* Returns the escape character.
*
* @return string
*/
public function getEscape()
{
return $this->escape;
}
/**
* League CSV formatter hook.
*
* @see escapeRecord
*/
public function __invoke(array $record)
{
return $this->escapeRecord($record);
}
/**
* Escape a CSV record.
*
* @return array
*/
public function escapeRecord(array $record)
{
return array_map([$this, 'escapeField'], $record);
}
/**
* Escape a CSV cell.
*
* @return string
*/
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;
}
/**
* Tell whether the submitted value is stringable.
*
* @return bool
*/
protected function isStringable($value)
{
return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
}
}