Issues (26)

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/Adapter/Ftp.php (2 issues)

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
namespace League\Flysystem\Adapter;
4
5
use ErrorException;
6
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
7
use League\Flysystem\AdapterInterface;
8
use League\Flysystem\Config;
9
use League\Flysystem\Util;
10
use League\Flysystem\Util\MimeType;
11
use RuntimeException;
12
13
class Ftp extends AbstractFtpAdapter
14
{
15
    use StreamedCopyTrait;
16
17
    /**
18
     * @var int
19
     */
20
    protected $transferMode = FTP_BINARY;
21
22
    /**
23
     * @var null|bool
24
     */
25
    protected $ignorePassiveAddress = null;
26
27
    /**
28
     * @var bool
29
     */
30
    protected $recurseManually = false;
31
32
    /**
33
     * @var bool
34
     */
35
    protected $utf8 = false;
36
37
    /**
38
     * @var array
39
     */
40
    protected $configurable = [
41
        'host',
42
        'port',
43
        'username',
44
        'password',
45
        'ssl',
46
        'timeout',
47
        'root',
48
        'permPrivate',
49
        'permPublic',
50
        'passive',
51
        'transferMode',
52
        'systemType',
53
        'ignorePassiveAddress',
54
        'recurseManually',
55
        'utf8',
56
        'enableTimestampsOnUnixListings',
57
    ];
58
59
    /**
60
     * @var bool
61
     */
62
    protected $isPureFtpd;
63
64
    /**
65
     * Set the transfer mode.
66
     *
67
     * @param int $mode
68
     *
69
     * @return $this
70
     */
71 3
    public function setTransferMode($mode)
72
    {
73 3
        $this->transferMode = $mode;
74
75 3
        return $this;
76
    }
77
78
    /**
79
     * Set if Ssl is enabled.
80
     *
81
     * @param bool $ssl
82
     *
83
     * @return $this
84
     */
85 123
    public function setSsl($ssl)
86
    {
87 123
        $this->ssl = (bool) $ssl;
88
89 123
        return $this;
90
    }
91
92
    /**
93
     * Set if passive mode should be used.
94
     *
95
     * @param bool $passive
96
     */
97 105
    public function setPassive($passive = true)
98
    {
99 105
        $this->passive = $passive;
100 105
    }
101
102
    /**
103
     * @param bool $ignorePassiveAddress
104
     */
105 3
    public function setIgnorePassiveAddress($ignorePassiveAddress)
106
    {
107 3
        $this->ignorePassiveAddress = $ignorePassiveAddress;
108 3
    }
109
110
    /**
111
     * @param bool $recurseManually
112
     */
113 84
    public function setRecurseManually($recurseManually)
114
    {
115 84
        $this->recurseManually = $recurseManually;
116 84
    }
117
118
    /**
119
     * @param bool $utf8
120
     */
121 6
    public function setUtf8($utf8)
122
    {
123 6
        $this->utf8 = (bool) $utf8;
124 6
    }
125
126
    /**
127
     * Connect to the FTP server.
128
     */
129 114
    public function connect()
130
    {
131 114
        if ($this->ssl) {
132 108
            $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
133 36
        } else {
134 6
            $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
135
        }
136
137 114
        if ( ! $this->connection) {
138 6
            throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
139
        }
140
141 108
        $this->login();
142 105
        $this->setUtf8Mode();
143 102
        $this->setConnectionPassiveMode();
144 99
        $this->setConnectionRoot();
145 96
        $this->isPureFtpd = $this->isPureFtpdServer();
146 96
    }
147
148
    /**
149
     * Set the connection to UTF-8 mode.
150
     */
151 105
    protected function setUtf8Mode()
152
    {
153 105
        if ($this->utf8) {
154 6
            $response = ftp_raw($this->connection, "OPTS UTF8 ON");
155 6
            if (substr($response[0], 0, 3) !== '200') {
156 3
                throw new RuntimeException(
157 3
                    'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
158 1
                );
159
            }
160 1
        }
161 102
    }
162
163
    /**
164
     * Set the connections to passive mode.
165
     *
166
     * @throws RuntimeException
167
     */
168 102
    protected function setConnectionPassiveMode()
169
    {
170 102
        if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
171 3
            ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
172 1
        }
173
174 102
        if ( ! ftp_pasv($this->connection, $this->passive)) {
175 3
            throw new RuntimeException(
176 3
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
177 1
            );
178
        }
179 99
    }
180
181
    /**
182
     * Set the connection root.
183
     */
184 99
    protected function setConnectionRoot()
185
    {
186 99
        $root = $this->getRoot();
187 99
        $connection = $this->connection;
188
189 99
        if ($root && ! ftp_chdir($connection, $root)) {
0 ignored issues
show
Bug Best Practice introduced by mhlavac
The expression $root of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
190 3
            throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot());
191
        }
192
193
        // Store absolute path for further reference.
194
        // This is needed when creating directories and
195
        // initial root was a relative path, else the root
196
        // would be relative to the chdir'd path.
197 96
        $this->root = ftp_pwd($connection);
198 96
    }
199
200
    /**
201
     * Login.
202
     *
203
     * @throws RuntimeException
204
     */
205
    protected function login()
206
    {
207 108
        set_error_handler(function () {
208 108
        });
209 108
        $isLoggedIn = ftp_login(
210 108
            $this->connection,
211 108
            $this->getUsername(),
212 108
            $this->getPassword()
213 36
        );
214 108
        restore_error_handler();
215
216 108
        if ( ! $isLoggedIn) {
217 3
            $this->disconnect();
218 3
            throw new RuntimeException(
219 3
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
220 3
                ) . ', username: ' . $this->getUsername()
221 1
            );
222
        }
223 105
    }
224
225
    /**
226
     * Disconnect from the FTP server.
227
     */
228 126
    public function disconnect()
229
    {
230 126
        if (is_resource($this->connection)) {
231 9
            ftp_close($this->connection);
232 3
        }
233
234 126
        $this->connection = null;
235 126
    }
236
237
    /**
238
     * @inheritdoc
239
     */
240 9
    public function write($path, $contents, Config $config)
241
    {
242 9
        $stream = fopen('php://temp', 'w+b');
243 9
        fwrite($stream, $contents);
244 9
        rewind($stream);
245 9
        $result = $this->writeStream($path, $stream, $config);
246 9
        fclose($stream);
247
248 9
        if ($result === false) {
249 6
            return false;
250
        }
251
252 6
        $result['contents'] = $contents;
253 6
        $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents);
254
255 6
        return $result;
256
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261 9
    public function writeStream($path, $resource, Config $config)
262
    {
263 9
        $this->ensureDirectory(Util::dirname($path));
264
265 9
        if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
266 6
            return false;
267
        }
268
269 6
        if ($visibility = $config->get('visibility')) {
270 6
            $this->setVisibility($path, $visibility);
271 2
        }
272
273 6
        $type = 'file';
274
275 6
        return compact('type', 'path', 'visibility');
276
    }
277
278
    /**
279
     * @inheritdoc
280
     */
281 6
    public function update($path, $contents, Config $config)
282
    {
283 6
        return $this->write($path, $contents, $config);
284
    }
285
286
    /**
287
     * @inheritdoc
288
     */
289 3
    public function updateStream($path, $resource, Config $config)
290
    {
291 3
        return $this->writeStream($path, $resource, $config);
292
    }
293
294
    /**
295
     * @inheritdoc
296
     */
297 3
    public function rename($path, $newpath)
298
    {
299 3
        return ftp_rename($this->getConnection(), $path, $newpath);
300
    }
301
302
    /**
303
     * @inheritdoc
304
     */
305 3
    public function delete($path)
306
    {
307 3
        return ftp_delete($this->getConnection(), $path);
308
    }
309
310
    /**
311
     * @inheritdoc
312
     */
313 3
    public function deleteDir($dirname)
314
    {
315 3
        $connection = $this->getConnection();
316 3
        $contents = array_reverse($this->listDirectoryContents($dirname, false));
317
318 3
        foreach ($contents as $object) {
319 3
            if ($object['type'] === 'file') {
320 3
                if ( ! ftp_delete($connection, $object['path'])) {
321 3
                    return false;
322
                }
323 3
            } elseif ( ! $this->deleteDir($object['path'])) {
324 2
                return false;
325
            }
326 1
        }
327
328 3
        return ftp_rmdir($connection, $dirname);
329
    }
330
331
    /**
332
     * @inheritdoc
333
     */
334 3
    public function createDir($dirname, Config $config)
335
    {
336 3
        $connection = $this->getConnection();
337 3
        $directories = explode('/', $dirname);
338
339 3
        foreach ($directories as $directory) {
340 3
            if (false === $this->createActualDirectory($directory, $connection)) {
341 3
                $this->setConnectionRoot();
342
343 3
                return false;
344
            }
345
346 3
            ftp_chdir($connection, $directory);
347 1
        }
348
349
        $this->setConnectionRoot();
350
351
        return ['type' => 'dir', 'path' => $dirname];
352
    }
353
354
    /**
355
     * Create a directory.
356
     *
357
     * @param string   $directory
358
     * @param resource $connection
359
     *
360
     * @return bool
361
     */
362 3
    protected function createActualDirectory($directory, $connection)
363
    {
364
        // List the current directory
365 3
        $listing = ftp_nlist($connection, '.') ?: [];
366
367 3
        foreach ($listing as $key => $item) {
368 3
            if (preg_match('~^\./.*~', $item)) {
369 3
                $listing[$key] = substr($item, 2);
370 1
            }
371 1
        }
372
373 3
        if (in_array($directory, $listing, true)) {
374 3
            return true;
375
        }
376
377 3
        return (boolean) ftp_mkdir($connection, $directory);
378
    }
379
380
    /**
381
     * @inheritdoc
382
     */
383 33
    public function getMetadata($path)
384
    {
385 33
        if ($path === '') {
386 3
            return ['type' => 'dir', 'path' => ''];
387
        }
388
389 30 View Code Duplication
        if (@ftp_chdir($this->getConnection(), $path) === true) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
390 3
            $this->setConnectionRoot();
391
392 3
            return ['type' => 'dir', 'path' => $path];
393
        }
394
395 30
        $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path));
396
397 30
        if (empty($listing) || in_array('total 0', $listing, true)) {
398 6
            return false;
399
        }
400
401 24
        if (preg_match('/.* not found/', $listing[0])) {
402 6
            return false;
403
        }
404
405 18
        if (preg_match('/^total [0-9]*$/', $listing[0])) {
406 3
            array_shift($listing);
407 1
        }
408
409 18
        return $this->normalizeObject($listing[0], '');
410
    }
411
412
    /**
413
     * @inheritdoc
414
     */
415 9
    public function getMimetype($path)
416
    {
417 9
        if ( ! $metadata = $this->getMetadata($path)) {
418 6
            return false;
419
        }
420
421 6
        $metadata['mimetype'] = MimeType::detectByFilename($path);
422
423 6
        return $metadata;
424
    }
425
426
    /**
427
     * @inheritdoc
428
     */
429 12
    public function getTimestamp($path)
430
    {
431 12
        $timestamp = ftp_mdtm($this->getConnection(), $path);
432
433 12
        return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false;
434
    }
435
436
    /**
437
     * @inheritdoc
438
     */
439 6
    public function read($path)
440
    {
441 6
        if ( ! $object = $this->readStream($path)) {
442 3
            return false;
443
        }
444
445 3
        $object['contents'] = stream_get_contents($object['stream']);
446 3
        fclose($object['stream']);
447 3
        unset($object['stream']);
448
449 3
        return $object;
450
    }
451
452
    /**
453
     * @inheritdoc
454
     */
455 6
    public function readStream($path)
456
    {
457 6
        $stream = fopen('php://temp', 'w+b');
458 6
        $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
459 6
        rewind($stream);
460
461 6
        if ( ! $result) {
462 3
            fclose($stream);
463
464 3
            return false;
465
        }
466
467 3
        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
468
    }
469
470
    /**
471
     * @inheritdoc
472
     */
473 9
    public function setVisibility($path, $visibility)
474
    {
475 9
        $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate();
476
477 9
        if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
478 6
            return false;
479
        }
480
481 6
        return compact('path', 'visibility');
482
    }
483
484
    /**
485
     * @inheritdoc
486
     *
487
     * @param string $directory
488
     */
489 33
    protected function listDirectoryContents($directory, $recursive = true)
490
    {
491 33
        $directory = str_replace('*', '\\*', $directory);
492
493 33
        if ($recursive && $this->recurseManually) {
494 3
            return $this->listDirectoryContentsRecursive($directory);
495
        }
496
497 30
        $options = $recursive ? '-alnR' : '-aln';
498 30
        $listing = $this->ftpRawlist($options, $directory);
499
500 30
        return $listing ? $this->normalizeListing($listing, $directory) : [];
501
    }
502
503
    /**
504
     * @inheritdoc
505
     *
506
     * @param string $directory
507
     */
508 3
    protected function listDirectoryContentsRecursive($directory)
509
    {
510 3
        $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory);
511 3
        $output = [];
512
513 3
        foreach ($listing as $item) {
514 3
            $output[] = $item;
515 3
            if ($item['type'] !== 'dir') {
516 3
                continue;
517
            }
518 3
            $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
519 1
        }
520
521 3
        return $output;
522
    }
523
524
    /**
525
     * Check if the connection is open.
526
     *
527
     * @return bool
528
     *
529
     * @throws ErrorException
530
     */
531 90
    public function isConnected()
532
    {
533
        try {
534 90
            return is_resource($this->connection) && ftp_rawlist($this->connection, $this->getRoot()) !== false;
535 6
        } catch (ErrorException $e) {
536 6
            if (strpos($e->getMessage(), 'ftp_rawlist') === false) {
537 3
                throw $e;
538
            }
539
540 3
            return false;
541
        }
542
    }
543
544
    /**
545
     * @return bool
546
     */
547 96
    protected function isPureFtpdServer()
548
    {
549 96
        $response = ftp_raw($this->connection, 'HELP');
550
551 96
        return stripos(implode(' ', $response), 'Pure-FTPd') !== false;
552
    }
553
554
    /**
555
     * The ftp_rawlist function with optional escaping.
556
     *
557
     * @param string $options
558
     * @param string $path
559
     *
560
     * @return array
561
     */
562 60
    protected function ftpRawlist($options, $path)
563
    {
564 60
        $connection = $this->getConnection();
565
566 60
        if ($this->isPureFtpd) {
567
            $path = str_replace(' ', '\ ', $path);
568
        }
569
570 60
        return ftp_rawlist($connection, $options . ' ' . $path);
571
    }
572
}
573