This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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\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 phpseclib\Crypt\RSA; |
||
12 | use phpseclib\Net\SFTP; |
||
13 | use phpseclib\System\SSH\Agent; |
||
14 | |||
15 | class SftpAdapter extends AbstractFtpAdapter |
||
16 | { |
||
17 | use StreamedCopyTrait; |
||
18 | |||
19 | /** |
||
20 | * @var SFTP |
||
21 | */ |
||
22 | protected $connection; |
||
23 | |||
24 | /** |
||
25 | * @var int |
||
26 | */ |
||
27 | protected $port = 22; |
||
28 | |||
29 | /** |
||
30 | * @var string |
||
31 | */ |
||
32 | protected $hostFingerprint; |
||
33 | |||
34 | /** |
||
35 | * @var string |
||
36 | */ |
||
37 | protected $privateKey; |
||
38 | |||
39 | /** |
||
40 | * @var bool |
||
41 | */ |
||
42 | protected $useAgent = false; |
||
43 | |||
44 | /** |
||
45 | * @var Agent |
||
46 | */ |
||
47 | private $agent; |
||
48 | |||
49 | /** |
||
50 | * @var array |
||
51 | */ |
||
52 | protected $configurable = ['host', 'hostFingerprint', 'port', 'username', 'password', 'useAgent', 'agent', 'timeout', 'root', 'privateKey', 'passphrase', 'permPrivate', 'permPublic', 'directoryPerm', 'NetSftpConnection']; |
||
53 | |||
54 | /** |
||
55 | * @var array |
||
56 | */ |
||
57 | protected $statMap = ['mtime' => 'timestamp', 'size' => 'size']; |
||
58 | |||
59 | /** |
||
60 | * @var int |
||
61 | */ |
||
62 | protected $directoryPerm = 0744; |
||
63 | |||
64 | /** |
||
65 | * @var string |
||
66 | */ |
||
67 | private $passphrase; |
||
68 | |||
69 | /** |
||
70 | * Prefix a path. |
||
71 | * |
||
72 | * @param string $path |
||
73 | * |
||
74 | * @return string |
||
75 | */ |
||
76 | 9 | protected function prefix($path) |
|
77 | { |
||
78 | 9 | return $this->root.ltrim($path, $this->separator); |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * Set the finger print of the public key of the host you are connecting to. |
||
83 | * |
||
84 | * If the key does not match the server identification, the connection will |
||
85 | * be aborted. |
||
86 | * |
||
87 | * @param string $fingerprint Example: '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'. |
||
88 | * |
||
89 | * @return $this |
||
90 | */ |
||
91 | 9 | public function setHostFingerprint($fingerprint) |
|
92 | { |
||
93 | 9 | $this->hostFingerprint = $fingerprint; |
|
94 | |||
95 | 9 | return $this; |
|
96 | } |
||
97 | |||
98 | /** |
||
99 | * Set the private key (string or path to local file). |
||
100 | * |
||
101 | * @param string $key |
||
102 | * |
||
103 | * @return $this |
||
104 | */ |
||
105 | 12 | public function setPrivateKey($key) |
|
106 | { |
||
107 | 12 | $this->privateKey = $key; |
|
108 | |||
109 | 12 | return $this; |
|
110 | } |
||
111 | |||
112 | /** |
||
113 | * Set the passphrase for the privatekey. |
||
114 | * |
||
115 | * @param string $passphrase |
||
116 | * |
||
117 | * @return $this |
||
118 | */ |
||
119 | 6 | public function setPassphrase($passphrase) |
|
120 | { |
||
121 | 6 | $this->passphrase = $passphrase; |
|
122 | |||
123 | 6 | return $this; |
|
124 | } |
||
125 | |||
126 | /** |
||
127 | * @param boolean $useAgent |
||
128 | * |
||
129 | * @return $this |
||
130 | */ |
||
131 | public function setUseAgent($useAgent) |
||
132 | { |
||
133 | $this->useAgent = (bool) $useAgent; |
||
134 | |||
135 | return $this; |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * @param Agent $agent |
||
140 | * |
||
141 | * @return $this |
||
142 | */ |
||
143 | public function setAgent(Agent $agent) |
||
144 | { |
||
145 | $this->agent = $agent; |
||
146 | |||
147 | return $this; |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Set permissions for new directory |
||
152 | * |
||
153 | * @param int $directoryPerm |
||
154 | * |
||
155 | * @return $this |
||
156 | */ |
||
157 | 6 | public function setDirectoryPerm($directoryPerm) |
|
158 | { |
||
159 | 6 | $this->directoryPerm = $directoryPerm; |
|
160 | |||
161 | 6 | return $this; |
|
162 | } |
||
163 | |||
164 | /** |
||
165 | * Get permissions for new directory |
||
166 | * |
||
167 | * @return int |
||
168 | */ |
||
169 | 3 | public function getDirectoryPerm() |
|
170 | { |
||
171 | 3 | return $this->directoryPerm; |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * Inject the SFTP instance. |
||
176 | * |
||
177 | * @param SFTP $connection |
||
178 | * |
||
179 | * @return $this |
||
180 | */ |
||
181 | 36 | public function setNetSftpConnection(SFTP $connection) |
|
182 | { |
||
183 | 36 | $this->connection = $connection; |
|
184 | |||
185 | 36 | return $this; |
|
186 | } |
||
187 | |||
188 | /** |
||
189 | * Connect. |
||
190 | */ |
||
191 | 27 | public function connect() |
|
192 | { |
||
193 | 27 | $this->connection = $this->connection ?: new SFTP($this->host, $this->port, $this->timeout); |
|
194 | 27 | $this->connection->disableStatCache(); |
|
195 | 27 | $this->login(); |
|
196 | 18 | $this->setConnectionRoot(); |
|
197 | 15 | } |
|
198 | |||
199 | /** |
||
200 | * Login. |
||
201 | * |
||
202 | * @throws ConnectionErrorException |
||
203 | */ |
||
204 | 27 | protected function login() |
|
205 | { |
||
206 | 27 | if ($this->hostFingerprint) { |
|
207 | 9 | $publicKey = $this->connection->getServerPublicHostKey(); |
|
208 | |||
209 | 9 | if ($publicKey === false) { |
|
210 | 3 | throw new ConnectionErrorException('Could not connect to server to verify public key.'); |
|
211 | } |
||
212 | |||
213 | 6 | $actualFingerprint = $this->getHexFingerprintFromSshPublicKey($publicKey); |
|
214 | |||
215 | 6 | if (0 !== strcasecmp($this->hostFingerprint, $actualFingerprint)) { |
|
216 | 3 | throw new ConnectionErrorException('The authenticity of host '.$this->host.' can\'t be established.'); |
|
217 | } |
||
218 | } |
||
219 | |||
220 | 21 | $authentication = $this->getAuthentication(); |
|
221 | |||
222 | |||
223 | 21 | if ($this->connection->login($this->getUsername(), $authentication)) { |
|
224 | 15 | goto past_login; |
|
225 | } |
||
226 | |||
227 | // try double authentication, key is already given so now give password |
||
228 | 6 | if ($authentication instanceof RSA && $this->connection->login($this->getUsername(), $this->getPassword())) { |
|
229 | 3 | goto past_login; |
|
230 | } |
||
231 | |||
232 | 3 | throw new ConnectionErrorException('Could not login with username: '.$this->getUsername().', host: '.$this->host); |
|
233 | |||
234 | past_login: |
||
235 | |||
236 | 18 | if ($authentication instanceof Agent) { |
|
237 | $authentication->startSSHForwarding($this->connection); |
||
238 | } |
||
239 | 18 | } |
|
240 | |||
241 | /** |
||
242 | * Convert the SSH RSA public key into a hex formatted fingerprint. |
||
243 | * |
||
244 | * @param string $publickey |
||
245 | * @return string Hex formatted fingerprint, e.g. '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'. |
||
246 | */ |
||
247 | 6 | private function getHexFingerprintFromSshPublicKey ($publickey) |
|
248 | { |
||
249 | 6 | $content = explode(' ', $publickey, 3); |
|
250 | 6 | return implode(':', str_split(md5(base64_decode($content[1])), 2)); |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * Set the connection root. |
||
255 | * |
||
256 | * @throws InvalidRootException |
||
257 | */ |
||
258 | 18 | protected function setConnectionRoot() |
|
259 | { |
||
260 | 18 | $root = $this->getRoot(); |
|
261 | |||
262 | 18 | if (! $root) { |
|
0 ignored issues
–
show
|
|||
263 | 12 | return; |
|
264 | } |
||
265 | |||
266 | 6 | if (! $this->connection->chdir($root)) { |
|
267 | 3 | throw new InvalidRootException('Root is invalid or does not exist: '.$root); |
|
268 | } |
||
269 | |||
270 | 3 | $this->setRoot($this->connection->pwd()); |
|
271 | 3 | } |
|
272 | |||
273 | /** |
||
274 | * Get the password, either the private key or a plain text password. |
||
275 | * |
||
276 | * @return Agent|RSA|string |
||
277 | */ |
||
278 | 24 | public function getAuthentication() |
|
279 | { |
||
280 | 24 | if ($this->useAgent) { |
|
281 | return $this->getAgent(); |
||
282 | } |
||
283 | |||
284 | 24 | if ($this->privateKey) { |
|
285 | 6 | return $this->getPrivateKey(); |
|
286 | } |
||
287 | |||
288 | 18 | return $this->getPassword(); |
|
289 | } |
||
290 | |||
291 | /** |
||
292 | * Get the private key with the password or private key contents. |
||
293 | * |
||
294 | * @return RSA |
||
295 | */ |
||
296 | 12 | public function getPrivateKey() |
|
297 | { |
||
298 | 12 | if ("---" !== substr($this->privateKey, 0, 3) && is_file($this->privateKey)) { |
|
299 | 3 | $this->privateKey = file_get_contents($this->privateKey); |
|
300 | } |
||
301 | |||
302 | 12 | $key = new RSA(); |
|
303 | |||
304 | 12 | if ($password = $this->getPassphrase()) { |
|
305 | 12 | $key->setPassword($password); |
|
306 | } |
||
307 | |||
308 | 12 | $key->loadKey($this->privateKey); |
|
309 | |||
310 | 12 | return $key; |
|
311 | } |
||
312 | |||
313 | /** |
||
314 | * @return string |
||
315 | */ |
||
316 | 21 | public function getPassphrase() |
|
317 | { |
||
318 | 21 | if ($this->passphrase === null) { |
|
319 | //Added for backward compatibility |
||
320 | 15 | return $this->getPassword(); |
|
321 | } |
||
322 | 6 | return $this->passphrase; |
|
323 | } |
||
324 | |||
325 | /** |
||
326 | * @return Agent|bool |
||
327 | */ |
||
328 | public function getAgent() |
||
329 | { |
||
330 | if ( ! $this->agent instanceof Agent) { |
||
331 | $this->agent = new Agent(); |
||
332 | } |
||
333 | |||
334 | return $this->agent; |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * List the contents of a directory. |
||
339 | * |
||
340 | * @param string $directory |
||
341 | * @param bool $recursive |
||
342 | * |
||
343 | * @return array |
||
344 | */ |
||
345 | 9 | protected function listDirectoryContents($directory, $recursive = true) |
|
346 | { |
||
347 | 9 | $result = []; |
|
348 | 9 | $connection = $this->getConnection(); |
|
349 | 9 | $location = $this->prefix($directory); |
|
350 | 9 | $listing = $connection->rawlist($location); |
|
351 | |||
352 | 9 | if ($listing === false) { |
|
353 | 3 | return []; |
|
354 | } |
||
355 | |||
356 | 9 | foreach ($listing as $filename => $object) { |
|
357 | // When directory entries have a numeric filename they are changed to int |
||
358 | 9 | $filename = (string) $filename; |
|
359 | 9 | if (in_array($filename, ['.', '..'])) { |
|
360 | 3 | continue; |
|
361 | } |
||
362 | |||
363 | 9 | $path = empty($directory) ? $filename : ($directory.'/'.$filename); |
|
364 | 9 | $result[] = $this->normalizeListingObject($path, $object); |
|
365 | |||
366 | 9 | if ($recursive && isset($object['type']) && $object['type'] === NET_SFTP_TYPE_DIRECTORY) { |
|
367 | 5 | $result = array_merge($result, $this->listDirectoryContents($path)); |
|
368 | } |
||
369 | } |
||
370 | |||
371 | 9 | return $result; |
|
372 | } |
||
373 | |||
374 | /** |
||
375 | * Normalize a listing response. |
||
376 | * |
||
377 | * @param string $path |
||
378 | * @param array $object |
||
379 | * |
||
380 | * @return array |
||
381 | */ |
||
382 | 9 | protected function normalizeListingObject($path, array $object) |
|
383 | { |
||
384 | 9 | $permissions = $this->normalizePermissions($object['permissions']); |
|
385 | 9 | $type = isset($object['type']) && ($object['type'] === 2) ? 'dir' : 'file'; |
|
386 | |||
387 | 9 | $timestamp = $object['mtime']; |
|
388 | |||
389 | 9 | if ($type === 'dir') { |
|
390 | 9 | return compact('path', 'timestamp', 'type'); |
|
391 | } |
||
392 | |||
393 | 6 | $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; |
|
394 | 6 | $size = (int) $object['size']; |
|
395 | |||
396 | 6 | return compact('path', 'timestamp', 'type', 'visibility', 'size'); |
|
397 | } |
||
398 | |||
399 | /** |
||
400 | * Disconnect. |
||
401 | */ |
||
402 | 18 | public function disconnect() |
|
403 | { |
||
404 | 18 | $this->connection = null; |
|
405 | 18 | } |
|
406 | |||
407 | /** |
||
408 | * @inheritdoc |
||
409 | */ |
||
410 | 6 | public function write($path, $contents, Config $config) |
|
411 | { |
||
412 | 6 | if ($this->upload($path, $contents, $config) === false) { |
|
413 | 6 | return false; |
|
414 | } |
||
415 | |||
416 | 6 | return compact('contents', 'path'); |
|
417 | } |
||
418 | |||
419 | /** |
||
420 | * @inheritdoc |
||
421 | */ |
||
422 | 6 | public function writeStream($path, $resource, Config $config) |
|
423 | { |
||
424 | 6 | if ($this->upload($path, $resource, $config) === false) { |
|
425 | 6 | return false; |
|
426 | } |
||
427 | |||
428 | 6 | return compact('path'); |
|
429 | } |
||
430 | |||
431 | /** |
||
432 | * Upload a file. |
||
433 | * |
||
434 | * @param string $path |
||
435 | * @param string|resource $contents |
||
436 | * @param Config $config |
||
437 | * @return bool |
||
438 | */ |
||
439 | 12 | public function upload($path, $contents, Config $config) |
|
440 | { |
||
441 | 12 | $connection = $this->getConnection(); |
|
442 | 12 | $this->ensureDirectory(Util::dirname($path)); |
|
443 | 12 | $config = Util::ensureConfig($config); |
|
444 | |||
445 | 12 | if (! $connection->put($path, $contents, SFTP::SOURCE_STRING)) { |
|
446 | 12 | return false; |
|
447 | } |
||
448 | |||
449 | 12 | if ($config && $visibility = $config->get('visibility')) { |
|
450 | 6 | $this->setVisibility($path, $visibility); |
|
451 | } |
||
452 | |||
453 | 12 | return true; |
|
454 | } |
||
455 | |||
456 | /** |
||
457 | * @inheritdoc |
||
458 | */ |
||
459 | 6 | public function read($path) |
|
460 | { |
||
461 | 6 | $connection = $this->getConnection(); |
|
462 | |||
463 | 6 | if (($contents = $connection->get($path)) === false) { |
|
464 | 6 | return false; |
|
465 | } |
||
466 | |||
467 | 6 | return compact('contents', 'path'); |
|
468 | } |
||
469 | |||
470 | /** |
||
471 | * @inheritdoc |
||
472 | */ |
||
473 | 3 | public function readStream($path) |
|
474 | { |
||
475 | 3 | $stream = tmpfile(); |
|
476 | 3 | $connection = $this->getConnection(); |
|
477 | |||
478 | 3 | if ($connection->get($path, $stream) === false) { |
|
479 | 3 | fclose($stream); |
|
480 | 3 | return false; |
|
481 | } |
||
482 | |||
483 | 3 | rewind($stream); |
|
484 | |||
485 | 3 | return compact('stream', 'path'); |
|
486 | } |
||
487 | |||
488 | /** |
||
489 | * @inheritdoc |
||
490 | */ |
||
491 | 3 | public function update($path, $contents, Config $config) |
|
492 | { |
||
493 | 3 | return $this->write($path, $contents, $config); |
|
494 | } |
||
495 | |||
496 | /** |
||
497 | * @inheritdoc |
||
498 | */ |
||
499 | 3 | public function updateStream($path, $contents, Config $config) |
|
500 | { |
||
501 | 3 | return $this->writeStream($path, $contents, $config); |
|
502 | } |
||
503 | |||
504 | /** |
||
505 | * @inheritdoc |
||
506 | */ |
||
507 | 3 | public function delete($path) |
|
508 | { |
||
509 | 3 | $connection = $this->getConnection(); |
|
510 | |||
511 | 3 | return $connection->delete($path); |
|
512 | } |
||
513 | |||
514 | /** |
||
515 | * @inheritdoc |
||
516 | */ |
||
517 | 3 | public function rename($path, $newpath) |
|
518 | { |
||
519 | 3 | $connection = $this->getConnection(); |
|
520 | |||
521 | 3 | return $connection->rename($path, $newpath); |
|
522 | } |
||
523 | |||
524 | /** |
||
525 | * @inheritdoc |
||
526 | */ |
||
527 | 3 | public function deleteDir($dirname) |
|
528 | { |
||
529 | 3 | $connection = $this->getConnection(); |
|
530 | |||
531 | 3 | return $connection->delete($dirname, true); |
|
532 | } |
||
533 | |||
534 | /** |
||
535 | * @inheritdoc |
||
536 | */ |
||
537 | 48 | public function has($path) |
|
538 | { |
||
539 | 48 | return $this->getMetadata($path); |
|
540 | } |
||
541 | |||
542 | /** |
||
543 | * @inheritdoc |
||
544 | */ |
||
545 | 54 | public function getMetadata($path) |
|
546 | { |
||
547 | 54 | $connection = $this->getConnection(); |
|
548 | 54 | $info = $connection->stat($path); |
|
549 | |||
550 | 54 | if ($info === false) { |
|
551 | 12 | return false; |
|
552 | } |
||
553 | |||
554 | 45 | $result = Util::map($info, $this->statMap); |
|
555 | 45 | $result['type'] = $info['type'] === NET_SFTP_TYPE_DIRECTORY ? 'dir' : 'file'; |
|
556 | 45 | $result['visibility'] = $info['permissions'] & $this->permPublic ? 'public' : 'private'; |
|
557 | |||
558 | 45 | return $result; |
|
559 | } |
||
560 | |||
561 | /** |
||
562 | * @inheritdoc |
||
563 | */ |
||
564 | 6 | public function getTimestamp($path) |
|
565 | { |
||
566 | 6 | return $this->getMetadata($path); |
|
567 | } |
||
568 | |||
569 | /** |
||
570 | * @inheritdoc |
||
571 | */ |
||
572 | 3 | public function getMimetype($path) |
|
573 | { |
||
574 | 3 | if (! $data = $this->read($path)) { |
|
575 | 3 | return false; |
|
576 | } |
||
577 | |||
578 | 3 | $data['mimetype'] = Util::guessMimeType($path, $data['contents']); |
|
579 | |||
580 | 3 | return $data; |
|
581 | } |
||
582 | |||
583 | /** |
||
584 | * @inheritdoc |
||
585 | */ |
||
586 | 3 | public function createDir($dirname, Config $config) |
|
587 | { |
||
588 | 3 | $connection = $this->getConnection(); |
|
589 | |||
590 | 3 | if (! $connection->mkdir($dirname, $this->directoryPerm, true)) { |
|
591 | 3 | return false; |
|
592 | } |
||
593 | |||
594 | 3 | return ['path' => $dirname]; |
|
595 | } |
||
596 | |||
597 | /** |
||
598 | * @inheritdoc |
||
599 | */ |
||
600 | 6 | public function getVisibility($path) |
|
601 | { |
||
602 | 6 | return $this->getMetadata($path); |
|
603 | } |
||
604 | |||
605 | /** |
||
606 | * @inheritdoc |
||
607 | */ |
||
608 | 12 | public function setVisibility($path, $visibility) |
|
609 | { |
||
610 | 12 | $visibility = ucfirst($visibility); |
|
611 | |||
612 | 12 | if (! isset($this->{'perm'.$visibility})) { |
|
613 | 3 | throw new InvalidArgumentException('Unknown visibility: '.$visibility); |
|
614 | } |
||
615 | |||
616 | 9 | $connection = $this->getConnection(); |
|
617 | |||
618 | 9 | return $connection->chmod($this->{'perm'.$visibility}, $path); |
|
619 | } |
||
620 | |||
621 | /** |
||
622 | * @inheritdoc |
||
623 | */ |
||
624 | 81 | public function isConnected() |
|
625 | { |
||
626 | 81 | if ($this->connection instanceof SFTP && $this->connection->isConnected()) { |
|
627 | 78 | return true; |
|
628 | } |
||
629 | |||
630 | 3 | return false; |
|
631 | } |
||
632 | } |
||
633 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
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: