Completed
Pull Request — master (#269)
by ignace nyamagana
04:24 queued 02:51
created

EscapeFormulaInjection   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 123
ccs 26
cts 26
cp 1
rs 10
c 0
b 0
f 0
wmc 13
lcom 1
cbo 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A filterSpecialCharacters() 0 4 1
A getSpecialCharacters() 0 4 1
A getEscape() 0 4 1
A __invoke() 0 4 1
A escapeRecord() 0 4 1
A escapeField() 0 13 3
A isStringable() 0 4 3
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
/**
18
 * A League CSV formatter to tackle CSV Formula Injection
19
 *
20
 * @see http://georgemauer.net/2017/10/07/csv-injection.html
21
 *
22
 * @package League.csv
23
 * @since   9.1.0
24
 * @author  Ignace Nyamagana Butera <[email protected]>
25
 */
26
class EscapeFormulaInjection
27
{
28
    /**
29
     * Special characters that will be escaped
30
     *
31
     * @var string[]
32
     */
33
    protected $special_chars = [];
34
35
    /**
36
     * Escape character
37
     *
38
     * @var string
39
     */
40
    protected $escape;
41
42
    /**
43
     * New instance
44
     *
45
     * @param string   $escape
46
     * @param string[] $special_chars additional special characters to escape
47
     */
48 6
    public function __construct(string $escape = "\t", array $special_chars = [])
49
    {
50 6
        $this->escape = $escape;
51 6
        if (!empty($special_chars)) {
52 4
            $special_chars = $this->filterSpecialCharacters(...$special_chars);
53
        }
54 4
        $special_chars = array_merge(['=', '-', '+', '@'], $special_chars);
55 4
        $this->special_chars = array_fill_keys(array_unique($special_chars), 1);
56 4
    }
57
58
    /**
59
     * Filter submitted special characters.
60
     *
61
     * @param string ...$characters
62
     *
63
     * @return string[]
64
     */
65 2
    protected function filterSpecialCharacters(string ...$characters): array
66
    {
67 2
        return $characters;
68
    }
69
70
    /**
71
     * Returns the list of character the instance will escape.
72
     *
73
     * @return string[]
74
     */
75 2
    public function getSpecialCharacters(): array
76
    {
77 2
        return array_keys($this->special_chars);
78
    }
79
80
    /**
81
     * Returns the escape character.
82
     *
83
     * @return string
84
     */
85 2
    public function getEscape(): string
86
    {
87 2
        return $this->escape;
88
    }
89
90
    /**
91
     * League CSV formatter hook.
92
     *
93
     * @see escapeRecord
94
     *
95
     * @param array $record
96
     *
97
     * @return array
98
     */
99 2
    public function __invoke(array $record): array
100
    {
101 2
        return $this->escapeRecord($record);
102
    }
103
104
    /**
105
     * Escape a CSV record.
106
     *
107
     * @param array $record
108
     *
109
     * @return array
110
     */
111 4
    public function escapeRecord(array $record): array
112
    {
113 4
        return array_map([$this, 'escapeField'], $record);
114
    }
115
116
    /**
117
     * Escape a CSV cell.
118
     *
119
     * @param mixed $cell
120
     *
121
     * @return mixed
122
     */
123 4
    protected function escapeField($cell)
124
    {
125 4
        if (!$this->isStringable($cell)) {
126 4
            return $cell;
127
        }
128
129 4
        $str_cell = (string) $cell;
130 4
        if (isset($str_cell[0], $this->special_chars[$str_cell[0]])) {
131 4
            return $this->escape.$str_cell;
132
        }
133
134 4
        return $cell;
135
    }
136
137
    /**
138
     * Tell whether the submitted value is stringable.
139
     *
140
     * @param mixed $value
141
     *
142
     * @return bool
143
     */
144 4
    protected function isStringable($value): bool
145
    {
146 4
        return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
147
    }
148
}
149