Completed
Push — master ( f6daa9...e4307e )
by ignace nyamagana
02:56
created

EscapeFormula::filterSpecialCharacters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
* This file is part of the League.csv library
4
*
5
* @license http://opensource.org/licenses/MIT
6
* @link https://github.com/thephpleague/csv/
7
* @version 9.1.0
8
* @package League.csv
9
*
10
* For the full copyright and license information, please view the LICENSE
11
* file that was distributed with this source code.
12
*/
13
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use InvalidArgumentException;
18
19
/**
20
 * A League CSV formatter to tackle CSV Formula Injection
21
 *
22
 * @see http://georgemauer.net/2017/10/07/csv-injection.html
23
 *
24
 * @package League.csv
25
 * @since   9.1.0
26
 * @author  Ignace Nyamagana Butera <[email protected]>
27
 */
28
class EscapeFormula
29
{
30
    /**
31
     * Special characters that will be escaped
32
     *
33
     * @var string[]
34
     */
35
    protected $special_chars = [];
36
37
    /**
38
     * Escape character
39
     *
40
     * @var string
41
     */
42
    protected $escape;
43
44
    /**
45
     * New instance
46
     *
47
     * @param string   $escape
48
     * @param string[] $special_chars additional special characters to escape
49
     */
50 8
    public function __construct(string $escape = "\t", array $special_chars = [])
51
    {
52 8
        $this->escape = $escape;
53 8
        if (!empty($special_chars)) {
54 6
            $special_chars = $this->filterSpecialCharacters(...$special_chars);
55
        }
56 4
        $special_chars = array_merge(['=', '-', '+', '@'], $special_chars);
57 4
        $this->special_chars = array_fill_keys(array_unique($special_chars), 1);
58 4
    }
59
60
    /**
61
     * Filter submitted special characters.
62
     *
63
     * @param string ...$characters
64
     *
65
     * @throws InvalidArgumentException if the string is not a single character
66
     *
67
     * @return string[]
68
     */
69 4
    protected function filterSpecialCharacters(string ...$characters): array
70
    {
71 4
        foreach ($characters as $str) {
72 4
            if (1 != mb_strlen($str)) {
73 4
                throw new InvalidArgumentException(sprintf('The submitted string %s must be a single character', $str));
74
            }
75
        }
76
77 2
        return $characters;
78
    }
79
80
    /**
81
     * Returns the list of character the instance will escape.
82
     *
83
     * @return string[]
84
     */
85 2
    public function getSpecialCharacters(): array
86
    {
87 2
        return array_keys($this->special_chars);
88
    }
89
90
    /**
91
     * Returns the escape character.
92
     *
93
     * @return string
94
     */
95 2
    public function getEscape(): string
96
    {
97 2
        return $this->escape;
98
    }
99
100
    /**
101
     * League CSV formatter hook.
102
     *
103
     * @see escapeRecord
104
     *
105
     * @param array $record
106
     *
107
     * @return array
108
     */
109 2
    public function __invoke(array $record): array
110
    {
111 2
        return $this->escapeRecord($record);
112
    }
113
114
    /**
115
     * Escape a CSV record.
116
     *
117
     * @param array $record
118
     *
119
     * @return array
120
     */
121 4
    public function escapeRecord(array $record): array
122
    {
123 4
        return array_map([$this, 'escapeField'], $record);
124
    }
125
126
    /**
127
     * Escape a CSV cell.
128
     *
129
     * @param mixed $cell
130
     *
131
     * @return mixed
132
     */
133 4
    protected function escapeField($cell)
134
    {
135 4
        if (!$this->isStringable($cell)) {
136 4
            return $cell;
137
        }
138
139 4
        $str_cell = (string) $cell;
140 4
        if (isset($str_cell[0], $this->special_chars[$str_cell[0]])) {
141 4
            return $this->escape.$str_cell;
142
        }
143
144 4
        return $cell;
145
    }
146
147
    /**
148
     * Tell whether the submitted value is stringable.
149
     *
150
     * @param mixed $value
151
     *
152
     * @return bool
153
     */
154 4
    protected function isStringable($value): bool
155
    {
156 4
        return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
157
    }
158
}
159