Issues (6)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Writer.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * League.Csv (https://csv.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Csv;
15
16
use function array_reduce;
17
use function implode;
18
use function preg_match;
19
use function preg_quote;
20
use function str_replace;
21
use function strlen;
22
use const PHP_VERSION_ID;
23
use const SEEK_CUR;
24
use const STREAM_FILTER_WRITE;
25
26
/**
27
 * A class to insert records into a CSV Document.
28
 */
29
class Writer extends AbstractCsv
30
{
31
    /**
32
     * callable collection to format the record before insertion.
33
     *
34
     * @var callable[]
35
     */
36
    protected $formatters = [];
37
38
    /**
39
     * callable collection to validate the record before insertion.
40
     *
41
     * @var callable[]
42
     */
43
    protected $validators = [];
44
45
    /**
46
     * newline character.
47
     *
48
     * @var string
49
     */
50
    protected $newline = "\n";
51
52
    /**
53
     * Insert records count for flushing.
54
     *
55
     * @var int
56
     */
57
    protected $flush_counter = 0;
58
59
    /**
60
     * Buffer flush threshold.
61
     *
62
     * @var int|null
63
     */
64
    protected $flush_threshold;
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    protected $stream_filter_mode = STREAM_FILTER_WRITE;
70
71
    /**
72
     * Regular expression used to detect if RFC4180 formatting is necessary.
73
     *
74
     * @var string
75
     */
76
    protected $rfc4180_regexp;
77
78
    /**
79
     * double enclosure for RFC4180 compliance.
80
     *
81
     * @var string
82
     */
83
    protected $rfc4180_enclosure;
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 15
    protected function resetProperties(): void
89
    {
90 15
        parent::resetProperties();
91 15
        $characters = preg_quote($this->delimiter, '/').'|'.preg_quote($this->enclosure, '/');
92 15
        $this->rfc4180_regexp = '/[\s|'.$characters.']/x';
93 15
        $this->rfc4180_enclosure = $this->enclosure.$this->enclosure;
94 15
    }
95
96
    /**
97
     * Returns the current newline sequence characters.
98
     */
99 3
    public function getNewline(): string
100
    {
101 3
        return $this->newline;
102
    }
103
104
    /**
105
     * Get the flush threshold.
106
     *
107
     * @return int|null
108
     */
109 3
    public function getFlushThreshold()
110
    {
111 3
        return $this->flush_threshold;
112
    }
113
114
    /**
115
     * Adds multiple records to the CSV document.
116
     *
117
     * @see Writer::insertOne
118
     */
119 6
    public function insertAll(iterable $records): int
120
    {
121 6
        $bytes = 0;
122 6
        foreach ($records as $record) {
123 6
            $bytes += $this->insertOne($record);
124
        }
125
126 6
        $this->flush_counter = 0;
127 6
        $this->document->fflush();
128
129 6
        return $bytes;
130
    }
131
132
    /**
133
     * Adds a single record to a CSV document.
134
     *
135
     * A record is an array that can contains scalar types values, NULL values
136
     * or objects implementing the __toString method.
137
     *
138
     * @throws CannotInsertRecord If the record can not be inserted
139
     */
140 57
    public function insertOne(array $record): int
141
    {
142 57
        $method = 'addRecord';
143 57
        if (70400 > PHP_VERSION_ID && '' === $this->escape) {
144 20
            $method = 'addRFC4180CompliantRecord';
145
        }
146
147 57
        $record = array_reduce($this->formatters, [$this, 'formatRecord'], $record);
148 57
        $this->validateRecord($record);
149 54
        $bytes = $this->$method($record);
150 52
        if (false === $bytes || 0 >= $bytes) {
151 4
            throw CannotInsertRecord::triggerOnInsertion($record);
152
        }
153
154 48
        return $bytes + $this->consolidate();
155
    }
156
157
    /**
158
     * Adds a single record to a CSV Document using PHP algorithm.
159
     *
160
     * @see https://php.net/manual/en/function.fputcsv.php
161
     *
162
     * @return int|false
163
     */
164 22
    protected function addRecord(array $record)
165
    {
166 22
        return $this->document->fputcsv($record, $this->delimiter, $this->enclosure, $this->escape);
167
    }
168
169
    /**
170
     * Adds a single record to a CSV Document using RFC4180 algorithm.
171
     *
172
     * @see https://php.net/manual/en/function.fputcsv.php
173
     * @see https://php.net/manual/en/function.fwrite.php
174
     * @see https://tools.ietf.org/html/rfc4180
175
     * @see http://edoceo.com/utilitas/csv-file-format
176
     *
177
     * String conversion is done without any check like fputcsv.
178
     *
179
     *     - Emits E_NOTICE on Array conversion (returns the 'Array' string)
180
     *     - Throws catchable fatal error on objects that can not be converted
181
     *     - Returns resource id without notice or error (returns 'Resource id #2')
182
     *     - Converts boolean true to '1', boolean false to the empty string
183
     *     - Converts null value to the empty string
184
     *
185
     * Fields must be delimited with enclosures if they contains :
186
     *
187
     *     - Embedded whitespaces
188
     *     - Embedded delimiters
189
     *     - Embedded line-breaks
190
     *     - Embedded enclosures.
191
     *
192
     * Embedded enclosures must be doubled.
193
     *
194
     * The LF character is added at the end of each record to mimic fputcsv behavior
195
     *
196
     * @return int|false
197
     */
198 20
    protected function addRFC4180CompliantRecord(array $record)
199
    {
200 20
        foreach ($record as &$field) {
201 20
            $field = (string) $field;
202 20
            if (1 === preg_match($this->rfc4180_regexp, $field)) {
203 20
                $field = $this->enclosure.str_replace($this->enclosure, $this->rfc4180_enclosure, $field).$this->enclosure;
204
            }
205
        }
206 20
        unset($field);
207
208 20
        return $this->document->fwrite(implode($this->delimiter, $record)."\n");
209
    }
210
211
    /**
212
     * Format a record.
213
     *
214
     * The returned array must contain
215
     *   - scalar types values,
216
     *   - NULL values,
217
     *   - or objects implementing the __toString() method.
218
     */
219 3
    protected function formatRecord(array $record, callable $formatter): array
220
    {
221 3
        return $formatter($record);
222
    }
223
224
    /**
225
     * Validate a record.
226
     *
227
     * @throws CannotInsertRecord If the validation failed
228
     */
229 12
    protected function validateRecord(array $record): void
230
    {
231 12
        foreach ($this->validators as $name => $validator) {
232 3
            if (true !== $validator($record)) {
233 3
                throw CannotInsertRecord::triggerOnValidation($name, $record);
234
            }
235
        }
236 9
    }
237
238
    /**
239
     * Apply post insertion actions.
240
     */
241 12
    protected function consolidate(): int
242
    {
243 12
        $bytes = 0;
244 12
        if ("\n" !== $this->newline) {
245 3
            $this->document->fseek(-1, SEEK_CUR);
246
            /** @var int $newlineBytes */
247 3
            $newlineBytes = $this->document->fwrite($this->newline, strlen($this->newline));
248 3
            $bytes =  $newlineBytes - 1;
249
        }
250
251 12
        if (null === $this->flush_threshold) {
252 9
            return $bytes;
253
        }
254
255 3
        ++$this->flush_counter;
256 3
        if (0 === $this->flush_counter % $this->flush_threshold) {
257 3
            $this->flush_counter = 0;
258 3
            $this->document->fflush();
259
        }
260
261 3
        return $bytes;
262
    }
263
264
    /**
265
     * Adds a record formatter.
266
     */
267 3
    public function addFormatter(callable $formatter): self
268
    {
269 3
        $this->formatters[] = $formatter;
270
271 3
        return $this;
272
    }
273
274
    /**
275
     * Adds a record validator.
276
     */
277 3
    public function addValidator(callable $validator, string $validator_name): self
278
    {
279 3
        $this->validators[$validator_name] = $validator;
280
281 3
        return $this;
282
    }
283
284
    /**
285
     * Sets the newline sequence.
286
     */
287 3
    public function setNewline(string $newline): self
288
    {
289 3
        $this->newline = $newline;
290
291 3
        return $this;
292
    }
293
294
    /**
295
     * Set the flush threshold.
296
     *
297
     *
298
     * @param  ?int      $threshold
0 ignored issues
show
The doc-type ?int could not be parsed: Unknown type name "?int" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
299
     * @throws Exception if the threshold is a integer lesser than 1
300
     */
301 9
    public function setFlushThreshold(?int $threshold): self
302
    {
303 9
        if ($threshold === $this->flush_threshold) {
304 3
            return $this;
305
        }
306
307 9
        if (null !== $threshold && 1 > $threshold) {
308 3
            throw new InvalidArgument(__METHOD__.'() expects 1 Argument to be null or a valid integer greater or equal to 1');
309
        }
310
311 9
        $this->flush_threshold = $threshold;
312 9
        $this->flush_counter = 0;
313 9
        $this->document->fflush();
314
315 9
        return $this;
316
    }
317
}
318