Completed
Push — master ( 63d386...52f66e )
by Frank
10s
created

Ftp::setUtf8()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 4
Code Lines 2

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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
    ];
57
58
    /**
59
     * @var bool
60
     */
61
    protected $isPureFtpd;
62
63
    /**
64
     * Set the transfer mode.
65
     *
66
     * @param int $mode
67
     *
68
     * @return $this
69
     */
70 3
    public function setTransferMode($mode)
71
    {
72 3
        $this->transferMode = $mode;
73
74 3
        return $this;
75
    }
76
77
    /**
78
     * Set if Ssl is enabled.
79
     *
80
     * @param bool $ssl
81
     *
82
     * @return $this
83
     */
84 96
    public function setSsl($ssl)
85
    {
86 96
        $this->ssl = (bool) $ssl;
87
88 96
        return $this;
89
    }
90
91
    /**
92
     * Set if passive mode should be used.
93
     *
94
     * @param bool $passive
95
     */
96 78
    public function setPassive($passive = true)
97
    {
98 78
        $this->passive = $passive;
99 78
    }
100
101
    /**
102
     * @param bool $ignorePassiveAddress
103
     */
104 3
    public function setIgnorePassiveAddress($ignorePassiveAddress)
105
    {
106 3
        $this->ignorePassiveAddress = $ignorePassiveAddress;
107 3
    }
108
109
    /**
110
     * @param bool $recurseManually
111
     */
112 66
    public function setRecurseManually($recurseManually)
113
    {
114 66
        $this->recurseManually = $recurseManually;
115 66
    }
116
117
    /**
118
     * @param bool $utf8
119
     */
120
    public function setUtf8($utf8) 
121
    {
122
        $this->utf8 = (bool) $utf8;
123
    }
124
125
    /**
126
     * Connect to the FTP server.
127
     */
128 90
    public function connect()
129
    {
130 90
        if ($this->ssl) {
131 87
            $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
132 87
        } else {
133 3
            $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
134
        }
135
136 90
        if ( ! $this->connection) {
137 6
            throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
138
        }
139
140 84
        $this->login();
141 81
        $this->setUtf8Mode();
142 81
        $this->setConnectionPassiveMode();
143 78
        $this->setConnectionRoot();
144 75
        $this->isPureFtpd = $this->isPureFtpdServer();
145 75
    }
146
147
    /**
148
     * Set the connection to UTF-8 mode.
149
     */
150 81
    protected function setUtf8Mode()
151
    {
152 81
        if ($this->utf8) {
153
            $response = ftp_raw($this->connection, "OPTS UTF8 ON");
154
            if (substr($response[0], 0, 3) !== '200') {
155
                throw new RuntimeException(
156
                    'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
157
                );
158
            }
159
        }
160 81
    }
161
162
    /**
163
     * Set the connections to passive mode.
164
     *
165
     * @throws RuntimeException
166
     */
167 81
    protected function setConnectionPassiveMode()
168
    {
169 81
        if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
170 3
            ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
171 3
        }
172
173 81
        if ( ! ftp_pasv($this->connection, $this->passive)) {
174 3
            throw new RuntimeException(
175 3
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
176 3
            );
177
        }
178 78
    }
179
180
    /**
181
     * Set the connection root.
182
     */
183 78
    protected function setConnectionRoot()
184
    {
185 78
        $root = $this->getRoot();
186 78
        $connection = $this->connection;
187
188 78
        if (empty($root) === false && ! ftp_chdir($connection, $root)) {
189 3
            throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot());
190
        }
191
192
        // Store absolute path for further reference.
193
        // This is needed when creating directories and
194
        // initial root was a relative path, else the root
195
        // would be relative to the chdir'd path.
196 75
        $this->root = ftp_pwd($connection);
197 75
    }
198
199
    /**
200
     * Login.
201
     *
202
     * @throws RuntimeException
203
     */
204
    protected function login()
205
    {
206
        set_error_handler(function () {});
207 84
        $isLoggedIn = ftp_login(
208 84
            $this->connection,
209 84
            $this->getUsername(),
210 84
            $this->getPassword()
211 84
        );
212 84
        restore_error_handler();
213
214 84
        if ( ! $isLoggedIn) {
215 3
            $this->disconnect();
216 3
            throw new RuntimeException(
217 3
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
218 3
                ) . ', username: ' . $this->getUsername()
219 3
            );
220
        }
221 81
    }
222
223
    /**
224
     * Disconnect from the FTP server.
225
     */
226 96
    public function disconnect()
227
    {
228 96
        if (is_resource($this->connection)) {
229 9
            ftp_close($this->connection);
230 9
        }
231
232 96
        $this->connection = null;
233 96
    }
234
235
    /**
236
     * @inheritdoc
237
     */
238 9
    public function write($path, $contents, Config $config)
239
    {
240 9
        $stream = fopen('php://temp', 'w+b');
241 9
        fwrite($stream, $contents);
242 9
        rewind($stream);
243 9
        $result = $this->writeStream($path, $stream, $config);
244 9
        fclose($stream);
245
246 9
        if ($result === false) {
247 6
            return false;
248
        }
249
250 6
        $result['contents'] = $contents;
251 6
        $result['mimetype'] = Util::guessMimeType($path, $contents);
252
253 6
        return $result;
254
    }
255
256
    /**
257
     * @inheritdoc
258
     */
259 9
    public function writeStream($path, $resource, Config $config)
260
    {
261 9
        $this->ensureDirectory(Util::dirname($path));
262
263 9
        if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
264 6
            return false;
265
        }
266
267 6
        if ($visibility = $config->get('visibility')) {
268 6
            $this->setVisibility($path, $visibility);
269 6
        }
270
271 6
        $type = 'file';
272
273 6
        return compact('type', 'path', 'visibility');
274
    }
275
276
    /**
277
     * @inheritdoc
278
     */
279 6
    public function update($path, $contents, Config $config)
280
    {
281 6
        return $this->write($path, $contents, $config);
282
    }
283
284
    /**
285
     * @inheritdoc
286
     */
287 3
    public function updateStream($path, $resource, Config $config)
288
    {
289 3
        return $this->writeStream($path, $resource, $config);
290
    }
291
292
    /**
293
     * @inheritdoc
294
     */
295 3
    public function rename($path, $newpath)
296
    {
297 3
        return ftp_rename($this->getConnection(), $path, $newpath);
298
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303 3
    public function delete($path)
304
    {
305 3
        return ftp_delete($this->getConnection(), $path);
306
    }
307
308
    /**
309
     * @inheritdoc
310
     */
311 3
    public function deleteDir($dirname)
312
    {
313 3
        $connection = $this->getConnection();
314 3
        $contents = array_reverse($this->listDirectoryContents($dirname));
315
316 3
        foreach ($contents as $object) {
317 3
            if ($object['type'] === 'file') {
318 3
                if ( ! ftp_delete($connection, $object['path'])) {
319 3
                    return false;
320
                }
321 3
            } elseif ( ! ftp_rmdir($connection, $object['path'])) {
322 3
                return false;
323
            }
324 3
        }
325
326 3
        return ftp_rmdir($connection, $dirname);
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332 6
    public function createDir($dirname, Config $config)
333
    {
334 6
        $connection = $this->getConnection();
335 6
        $directories = explode('/', $dirname);
336
337 6
        foreach ($directories as $directory) {
338 6
            if (false === $this->createActualDirectory($directory, $connection)) {
339 3
                $this->setConnectionRoot();
340
341 3
                return false;
342
            }
343
344 6
            ftp_chdir($connection, $directory);
345 6
        }
346
347 3
        $this->setConnectionRoot();
348
349 3
        return ['type' => 'dir', 'path' => $dirname];
350
    }
351
352
    /**
353
     * Create a directory.
354
     *
355
     * @param string   $directory
356
     * @param resource $connection
357
     *
358
     * @return bool
359
     */
360 6
    protected function createActualDirectory($directory, $connection)
361
    {
362
        // List the current directory
363 6
        $listing = ftp_nlist($connection, '.') ?: [];
364
365 6
        foreach ($listing as $key => $item) {
366 6
            if (preg_match('~^\./.*~', $item)) {
367 6
                $listing[$key] = substr($item, 2);
368 6
            }
369 6
        }
370
371 6
        if (in_array($directory, $listing, true)) {
372 3
            return true;
373
        }
374
375 6
        return (boolean) ftp_mkdir($connection, $directory);
376
    }
377
378
    /**
379
     * @inheritdoc
380
     */
381 30
    public function getMetadata($path)
382
    {
383 30
        $connection = $this->getConnection();
384
385 30
        if ($path === '') {
386 3
            return ['type' => 'dir', 'path' => ''];
387
        }
388
389 27
        if (@ftp_chdir($connection, $path) === true) {
390 3
            $this->setConnectionRoot();
391
392 3
            return ['type' => 'dir', 'path' => $path];
393
        }
394
395 27
        $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path));
396
397 27
        if (empty($listing)) {
398 3
            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 3
        }
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 21
    protected function listDirectoryContents($directory, $recursive = true)
490
    {
491 21
        $directory = str_replace('*', '\\*', $directory);
492
493 21
        if ($recursive && $this->recurseManually) {
494 3
            return $this->listDirectoryContentsRecursive($directory);
495
        }
496
497 18
        $options = $recursive ? '-alnR' : '-aln';
498 18
        $listing = $this->ftpRawlist($options, $directory);
499
500 18
        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) ?: []);
511 3
        $output = [];
512
513 3
        foreach ($listing as $directory) {
514 3
            $output[] = $directory;
515 3
            if ($directory['type'] !== 'dir') continue;
516
517 3
            $output = array_merge($output, $this->listDirectoryContentsRecursive($directory['path']));
518 3
        }
519
520 3
        return $output;
521
    }
522
523
    /**
524
     * Check if the connection is open.
525
     *
526
     * @return bool
527
     * @throws ErrorException
528
     */
529 72
    public function isConnected()
530
    {
531
        try {
532 72
            return is_resource($this->connection) && ftp_rawlist($this->connection, '/') !== false;
533 6
        } catch (ErrorException $e) {
534 6
            if (strpos($e->getMessage(), 'ftp_rawlist') === false) {
535 3
                throw $e;
536
            }
537
538 3
            return false;
539
        }
540
    }
541
542
    /**
543
     * @return null|string
544
     */
545 75
    protected function isPureFtpdServer()
546
    {
547 75
        $response = ftp_raw($this->connection, 'HELP');
548
549 75
        return stripos(implode(' ', $response), 'Pure-FTPd') !== false;
550
    }
551
552
    /**
553
     * The ftp_rawlist function with optional escaping.
554
     *
555
     * @param string $options
556
     * @param string $path
557
     *
558
     * @return array
559
     */
560 45
    protected function ftpRawlist($options, $path)
561
    {
562 45
        $connection = $this->getConnection();
563
        
564 45
        if ($this->isPureFtpd) {
565
            $path = str_replace(' ', '\ ', $path);
566
        }
567 45
        return ftp_rawlist($connection, $options . ' ' . $path);
568
    }
569
}
570