Completed
Pull Request — master (#37)
by
unknown
05:29
created

SftpAdapter::setUsername()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace League\Flysystem\Sftp;
4
5
use InvalidArgumentException;
6
use League\Flysystem\Adapter\AbstractFtpAdapter;
7
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
8
use League\Flysystem\AdapterInterface;
9
use League\Flysystem\Config;
10
use League\Flysystem\Util;
11
use LogicException;
12
use phpseclib\Net\SFTP;
13
use phpseclib\Crypt\RSA;
14
use phpseclib\System\SSH\Agent;
15
use RuntimeException;
16
17
class SftpAdapter extends AbstractFtpAdapter
18
{
19
    use StreamedCopyTrait;
20
21
    /**
22
     * @var int
23
     */
24
    protected $port = 22;
25
26
    /**
27
     * @var string
28
     */
29
    protected $hostFingerprint;
30
31
    /**
32
     * @var string
33
     */
34
    protected $privatekey;
35
36
    /**
37
     * @var bool
38
     */
39
    protected $useAgent = false;
40
41
    /**
42
     * @var Agent
43
     */
44
    private $agent;
45
46
    /**
47
     * @var array
48
     */
49
    protected $configurable = ['host', 'hostFingerprint', 'port', 'username', 'password', 'useAgent', 'agent', 'timeout', 'root', 'privateKey', 'permPrivate', 'permPublic', 'directoryPerm', 'NetSftpConnection'];
50
51
    /**
52
     * @var array
53
     */
54
    protected $statMap = ['mtime' => 'timestamp', 'size' => 'size'];
55
56
    /**
57
     * @var int
58
     */
59
    protected $directoryPerm = 0744;
60
61
    /**
62
     * Prefix a path.
63
     *
64
     * @param string $path
65
     *
66
     * @return string
67
     */
68 6
    protected function prefix($path)
69
    {
70 6
        return $this->root.ltrim($path, $this->separator);
71
    }
72
    
73
    /**
74
     * Set the username (string).
75
     *
76
     * @param string $val
77
     *
78
     * @return $this
79
     */
80 9
    public function setUsername($val) {
81 9
        $this->username = $val;
0 ignored issues
show
Bug introduced by
The property username does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
82
        
83 9
        return $this;
84
    }
85
    
86
    /**
87
     * Set the password (string).
88
     *
89
     * @param string $val
90
     *
91
     * @return $this
92
     */
93 9
    public function setPassword($val) {
94 9
        $this->password = $val;
0 ignored issues
show
Bug introduced by
The property password does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
95
        
96 9
        return $this;
97
    }
98
99
    /**
100
     * Set the finger print of the public key of the host you are connecting to.
101
     *
102
     * If the key does not match the server identification, the connection will
103
     * be aborted.
104
     *
105
     * @param string $fingerprint Example: '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'.
106
     *
107
     * @return $this
108
     */
109 6
    public function setHostFingerprint($fingerprint)
110
    {
111 6
        $this->hostFingerprint = $fingerprint;
112
113 6
        return $this;
114
    }
115
116
    /**
117
     * Set the private key (string or path to local file).
118
     *
119
     * @param string $key
120
     *
121
     * @return $this
122
     */
123 9
    public function setPrivateKey($key)
124
    {
125 9
        $this->privatekey = $key;
126
127 9
        return $this;
128
    }
129
130
    /**
131
     * @param boolean $useAgent
132
     *
133
     * @return $this
134
     */
135
    public function setUseAgent($useAgent)
136
    {
137
        $this->useAgent = (bool) $useAgent;
138
139
        return $this;
140
    }
141
142
    /**
143
     * @param Agent $agent
144
     *
145
     * @return $this
146
     */
147
    public function setAgent(Agent $agent)
148
    {
149
        $this->agent = $agent;
150
151
        return $this;
152
    }
153
154
    /**
155
     * Set permissions for new directory
156
     *
157
     * @param int $directoryPerm
158
     *
159
     * @return $this
160
     */
161 6
    public function setDirectoryPerm($directoryPerm)
162
    {
163 6
        $this->directoryPerm = $directoryPerm;
164
165 6
        return $this;
166
    }
167
168
    /**
169
     * Get permissions for new directory
170
     *
171
     * @return int
172
     */
173 3
    public function getDirectoryPerm()
174
    {
175 3
        return $this->directoryPerm;
176
    }
177
178
    /**
179
     * Inject the SFTP instance.
180
     *
181
     * @param SFTP $connection
182
     *
183
     * @return $this
184
     */
185 30
    public function setNetSftpConnection(SFTP $connection)
186
    {
187 30
        $this->connection = $connection;
188
189 30
        return $this;
190
    }
191
192
    /**
193
     * Connect.
194
     */
195 21
    public function connect()
196
    {
197 21
        $this->connection = $this->connection ?: new SFTP($this->host, $this->port, $this->timeout);
198 21
        $this->login();
199 15
        $this->setConnectionRoot();
200 12
    }
201
202
    /**
203
     * Login.
204
     *
205
     * @throws LogicException
206
     */
207 21
    protected function login()
208
    {
209 21
        if ($this->hostFingerprint) {
210 6
            $actualFingerprint = $this->getHexFingerprintFromSshPublicKey($this->connection->getServerPublicHostKey());
211
212 6
            if (0 !== strcasecmp($this->hostFingerprint, $actualFingerprint)) {
213 3
                throw new LogicException('The authenticity of host '.$this->host.' can\'t be established.');
214
            }
215 1
        }
216
217 18
        $authentication = $this->getAuthentication();
218
219 18
        if (! $this->connection->login($this->getUsername(), $authentication)) {
220 3
            throw new LogicException('Could not login with username: '.$this->getUsername().', host: '.$this->host);
221
        }
222
223 15
        if ($authentication instanceof Agent) {
0 ignored issues
show
Bug introduced by
The class phpseclib\System\SSH\Agent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
224
            $authentication->startSSHForwarding($this->connection);
225
        }
226 15
    }
227
228
    /**
229
     * Convert the SSH RSA public key into a hex formatted fingerprint.
230
     *
231
     * @param string $publickey
232
     * @return string Hex formatted fingerprint, e.g. '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'.
233
     */
234 6
    private function getHexFingerprintFromSshPublicKey ($publickey)
235
    {
236 6
        $content = explode(' ', $publickey, 3);
237 6
        return implode(':', str_split(md5(base64_decode($content[1])), 2));
238
    }
239
240
    /**
241
     * Set the connection root.
242
     */
243 15
    protected function setConnectionRoot()
244
    {
245 15
        $root = $this->getRoot();
246
247 15
        if (! $root) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $root of type string|null is loosely compared to false; 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...
248 9
            return;
249
        }
250
251 6
        if (! $this->connection->chdir($root)) {
252 3
            throw new RuntimeException('Root is invalid or does not exist: '.$root);
253
        }
254 3
        $this->root = $this->connection->pwd() . $this->separator;
255 3
    }
256
257
    /**
258
     * Get the password, either the private key or a plain text password.
259
     *
260
     * @return Agent|RSA|string
261
     */
262 21
    public function getAuthentication()
263
    {
264 21
        if ($this->useAgent) {
265
            return $this->getAgent();
266
        }
267
268 21
        if ($this->privatekey) {
269 3
            return $this->getPrivateKey();
270
        }
271
272 18
        return $this->getPassword();
273
    }
274
275
    /**
276
     * Get the private get with the password or private key contents.
277
     *
278
     * @return RSA
279
     */
280 9
    public function getPrivateKey()
281
    {
282 9
        if (@is_file($this->privatekey)) {
283 3
            $this->privatekey = file_get_contents($this->privatekey);
284 1
        }
285
286 9
        $key = new RSA();
287
288 9
        if ($password = $this->getPassword()) {
289 9
            $key->setPassword($password);
290 3
        }
291
292 9
        $key->loadKey($this->privatekey);
293
294 9
        return $key;
295
    }
296
297
    /**
298
     * @return Agent|bool
299
     */
300
    public function getAgent()
301
    {
302
        if ( ! $this->agent instanceof Agent) {
0 ignored issues
show
Bug introduced by
The class phpseclib\System\SSH\Agent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
303
            $this->agent = new Agent();
304
        }
305
306
        return $this->agent;
307
    }
308
309
    /**
310
     * List the contents of a directory.
311
     *
312
     * @param string $directory
313
     * @param bool   $recursive
314
     *
315
     * @return array
316
     */
317 6
    protected function listDirectoryContents($directory, $recursive = true)
318
    {
319 6
        $result = [];
320 6
        $connection = $this->getConnection();
321 6
        $location = $this->prefix($directory);
322 6
        $listing = $connection->rawlist($location);
323
324 6
        if ($listing === false) {
325 3
            return [];
326
        }
327
328 6
        foreach ($listing as $filename => $object) {
329 6
            if (in_array($filename, ['.', '..'])) {
330 3
                continue;
331
            }
332
333 6
            $path = empty($directory) ? $filename : ($directory.'/'.$filename);
334 6
            $result[] = $this->normalizeListingObject($path, $object);
335
336 6
            if ($recursive && isset($object['type']) && $object['type'] === NET_SFTP_TYPE_DIRECTORY) {
337 5
                $result = array_merge($result, $this->listDirectoryContents($path));
338 1
            }
339 2
        }
340
341 6
        return $result;
342
    }
343
344
    /**
345
     * Normalize a listing response.
346
     *
347
     * @param string $path
348
     * @param array  $object
349
     *
350
     * @return array
351
     */
352 6
    protected function normalizeListingObject($path, array $object)
353
    {
354 6
        $permissions = $this->normalizePermissions($object['permissions']);
355 6
        $type = isset($object['type']) && ($object['type'] === 2) ?  'dir' : 'file';
356
357 6
        $timestamp = $object['mtime'];
358
359 6
        if ($type === 'dir') {
360 6
            return compact('path', 'timestamp', 'type');
361
        }
362
363 6
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
364 6
        $size = (int) $object['size'];
365
366 6
        return compact('path', 'timestamp', 'type', 'visibility', 'size');
367
    }
368
369
    /**
370
     * Disconnect.
371
     */
372 15
    public function disconnect()
373
    {
374 15
        $this->connection = null;
375 15
    }
376
377
    /**
378
     * @inheritdoc
379
     */
380 6
    public function write($path, $contents, Config $config)
381
    {
382 6
        if ($this->upload($path, $contents, $config) === false) {
383 6
            return false;
384
        }
385
386 6
        return compact('contents', 'visibility', 'path');
387
    }
388
389
    /**
390
     * @inheritdoc
391
     */
392 6
    public function writeStream($path, $resource, Config $config)
393
    {
394 6
        if ($this->upload($path, $resource, $config) === false) {
395 6
            return false;
396
        }
397
398 6
        return compact('visibility', 'path');
399
    }
400
401
    /**
402
     * Upload a file.
403
     *
404
     * @param string          $path
405
     * @param string|resource $contents
406
     * @param Config          $config
407
     * @return bool
408
     */
409 12
    public function upload($path, $contents, Config $config)
410
    {
411 12
        $connection = $this->getConnection();
412 12
        $this->ensureDirectory(Util::dirname($path));
413 12
        $config = Util::ensureConfig($config);
414
415 12
        if (! $connection->put($path, $contents, SFTP::SOURCE_STRING)) {
416 12
            return false;
417
        }
418
419 12
        if ($config && $visibility = $config->get('visibility')) {
420 6
            $this->setVisibility($path, $visibility);
421 2
        }
422
423 12
        return true;
424
    }
425
426
    /**
427
     * @inheritdoc
428
     */
429 6
    public function read($path)
430
    {
431 6
        $connection = $this->getConnection();
432
433 6
        if (($contents = $connection->get($path)) === false) {
434 6
            return false;
435
        }
436
437 6
        return compact('contents');
438
    }
439
440
    /**
441
     * @inheritdoc
442
     */
443 3
    public function readStream($path)
444
    {
445 3
        $stream = tmpfile();
446 3
        $connection = $this->getConnection();
447
448 3
        if ($connection->get($path, $stream) === false) {
449 3
            fclose($stream);
450 3
            return false;
451
        }
452
453 3
        rewind($stream);
454
455 3
        return compact('stream');
456
    }
457
458
    /**
459
     * @inheritdoc
460
     */
461 3
    public function update($path, $contents, Config $config)
462
    {
463 3
        return $this->write($path, $contents, $config);
464
    }
465
466
    /**
467
     * @inheritdoc
468
     */
469 3
    public function updateStream($path, $contents, Config $config)
470
    {
471 3
        return $this->writeStream($path, $contents, $config);
472
    }
473
474
    /**
475
     * @inheritdoc
476
     */
477 3
    public function delete($path)
478
    {
479 3
        $connection = $this->getConnection();
480
481 3
        return $connection->delete($path);
482
    }
483
484
    /**
485
     * @inheritdoc
486
     */
487 3
    public function rename($path, $newpath)
488
    {
489 3
        $connection = $this->getConnection();
490
491 3
        return $connection->rename($path, $newpath);
492
    }
493
494
    /**
495
     * @inheritdoc
496
     */
497 3
    public function deleteDir($dirname)
498
    {
499 3
        $connection = $this->getConnection();
500
501 3
        return $connection->delete($dirname, true);
502
    }
503
504
    /**
505
     * @inheritdoc
506
     */
507 39
    public function has($path)
508
    {
509 39
        return $this->getMetadata($path);
510
    }
511
512
    /**
513
     * @inheritdoc
514
     */
515 49
    public function getMetadata($path)
516
    {
517 48
        $connection = $this->getConnection();
518 48
        $info = $connection->stat($path);
519
520 49
        if ($info === false) {
521 12
            return false;
522
        }
523
524 39
        $result = Util::map($info, $this->statMap);
525 39
        $result['type'] = $info['type'] === NET_SFTP_TYPE_DIRECTORY ? 'dir' : 'file';
526 39
        $result['visibility'] = $info['permissions'] & $this->permPublic ? 'public' : 'private';
527
528 39
        return $result;
529
    }
530
531
    /**
532
     * @inheritdoc
533
     */
534 6
    public function getTimestamp($path)
535
    {
536 6
        return $this->getMetadata($path);
537
    }
538
539
    /**
540
     * @inheritdoc
541
     */
542 3
    public function getMimetype($path)
543
    {
544 3
        if (! $data = $this->read($path)) {
545 3
            return false;
546
        }
547
548 3
        $data['mimetype'] = Util::guessMimeType($path, $data['contents']);
549
550 3
        return $data;
551
    }
552
553
    /**
554
     * @inheritdoc
555
     */
556 3
    public function createDir($dirname, Config $config)
557
    {
558 3
        $connection = $this->getConnection();
559
560 3
        if (! $connection->mkdir($dirname, $this->directoryPerm, true)) {
561 3
            return false;
562
        }
563
564 3
        return ['path' => $dirname];
565
    }
566
567
    /**
568
     * @inheritdoc
569
     */
570 6
    public function getVisibility($path)
571
    {
572 6
        return $this->getMetadata($path);
573
    }
574
575
    /**
576
     * @inheritdoc
577
     */
578 12
    public function setVisibility($path, $visibility)
579
    {
580 12
        $visibility = ucfirst($visibility);
581
582 12
        if (! isset($this->{'perm'.$visibility})) {
583 3
            throw new InvalidArgumentException('Unknown visibility: '.$visibility);
584
        }
585
586 9
        $connection = $this->getConnection();
587
588 9
        return $connection->chmod($this->{'perm'.$visibility}, $path);
589
    }
590
591
    /**
592
     * @inheritdoc
593
     */
594 6
    public function isConnected()
595
    {
596 6
        if ($this->connection instanceof SFTP && $this->connection->isConnected()) {
0 ignored issues
show
Bug introduced by
The class phpseclib\Net\SFTP does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
597 3
            return true;
598
        }
599
600 3
        return false;
601
    }
602
}
603