| Total Complexity | 91 |
| Total Lines | 437 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like SFTP often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SFTP, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 46 | class SFTP extends \OC\Files\Storage\Common { |
||
| 47 | private $host; |
||
| 48 | private $user; |
||
| 49 | private $root; |
||
| 50 | private $port = 22; |
||
| 51 | |||
| 52 | private $auth = []; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @var \phpseclib\Net\SFTP |
||
| 56 | */ |
||
| 57 | protected $client; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @param string $host protocol://server:port |
||
| 61 | * @return array [$server, $port] |
||
| 62 | */ |
||
| 63 | private function splitHost($host) { |
||
| 64 | $input = $host; |
||
| 65 | if (strpos($host, '://') === false) { |
||
| 66 | // add a protocol to fix parse_url behavior with ipv6 |
||
| 67 | $host = 'http://' . $host; |
||
| 68 | } |
||
| 69 | |||
| 70 | $parsed = parse_url($host); |
||
| 71 | if(is_array($parsed) && isset($parsed['port'])) { |
||
| 72 | return [$parsed['host'], $parsed['port']]; |
||
| 73 | } else if (is_array($parsed)) { |
||
| 74 | return [$parsed['host'], 22]; |
||
| 75 | } else { |
||
| 76 | return [$input, 22]; |
||
| 77 | } |
||
| 78 | } |
||
| 79 | |||
| 80 | /** |
||
| 81 | * {@inheritdoc} |
||
| 82 | */ |
||
| 83 | public function __construct($params) { |
||
| 84 | // Register sftp:// |
||
| 85 | Stream::register(); |
||
| 86 | |||
| 87 | $parsedHost = $this->splitHost($params['host']); |
||
| 88 | |||
| 89 | $this->host = $parsedHost[0]; |
||
| 90 | $this->port = $parsedHost[1]; |
||
| 91 | |||
| 92 | if (!isset($params['user'])) { |
||
| 93 | throw new \UnexpectedValueException('no authentication parameters specified'); |
||
| 94 | } |
||
| 95 | $this->user = $params['user']; |
||
| 96 | |||
| 97 | if (isset($params['public_key_auth'])) { |
||
| 98 | $this->auth[] = $params['public_key_auth']; |
||
| 99 | } |
||
| 100 | if (isset($params['password']) && $params['password'] !== '') { |
||
| 101 | $this->auth[] = $params['password']; |
||
| 102 | } |
||
| 103 | |||
| 104 | if ($this->auth === []) { |
||
| 105 | throw new \UnexpectedValueException('no authentication parameters specified'); |
||
| 106 | } |
||
| 107 | |||
| 108 | $this->root |
||
| 109 | = isset($params['root']) ? $this->cleanPath($params['root']) : '/'; |
||
| 110 | |||
| 111 | $this->root = '/' . ltrim($this->root, '/'); |
||
| 112 | $this->root = rtrim($this->root, '/') . '/'; |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Returns the connection. |
||
| 117 | * |
||
| 118 | * @return \phpseclib\Net\SFTP connected client instance |
||
| 119 | * @throws \Exception when the connection failed |
||
| 120 | */ |
||
| 121 | public function getConnection() { |
||
| 122 | if (!is_null($this->client)) { |
||
| 123 | return $this->client; |
||
| 124 | } |
||
| 125 | |||
| 126 | $hostKeys = $this->readHostKeys(); |
||
| 127 | $this->client = new \phpseclib\Net\SFTP($this->host, $this->port); |
||
| 128 | |||
| 129 | // The SSH Host Key MUST be verified before login(). |
||
| 130 | $currentHostKey = $this->client->getServerPublicHostKey(); |
||
| 131 | if (array_key_exists($this->host, $hostKeys)) { |
||
| 132 | if ($hostKeys[$this->host] !== $currentHostKey) { |
||
| 133 | throw new \Exception('Host public key does not match known key'); |
||
| 134 | } |
||
| 135 | } else { |
||
| 136 | $hostKeys[$this->host] = $currentHostKey; |
||
| 137 | $this->writeHostKeys($hostKeys); |
||
| 138 | } |
||
| 139 | |||
| 140 | $login = false; |
||
| 141 | foreach ($this->auth as $auth) { |
||
| 142 | $login = $this->client->login($this->user, $auth); |
||
| 143 | if ($login === true) { |
||
| 144 | break; |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | if ($login === false) { |
||
| 149 | throw new \Exception('Login failed'); |
||
| 150 | } |
||
| 151 | return $this->client; |
||
| 152 | } |
||
| 153 | |||
| 154 | /** |
||
| 155 | * {@inheritdoc} |
||
| 156 | */ |
||
| 157 | public function test() { |
||
| 158 | if ( |
||
| 159 | !isset($this->host) |
||
| 160 | || !isset($this->user) |
||
| 161 | ) { |
||
| 162 | return false; |
||
| 163 | } |
||
| 164 | return $this->getConnection()->nlist() !== false; |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * {@inheritdoc} |
||
| 169 | */ |
||
| 170 | public function getId(){ |
||
| 171 | $id = 'sftp::' . $this->user . '@' . $this->host; |
||
| 172 | if ($this->port !== 22) { |
||
| 173 | $id .= ':' . $this->port; |
||
| 174 | } |
||
| 175 | // note: this will double the root slash, |
||
| 176 | // we should not change it to keep compatible with |
||
| 177 | // old storage ids |
||
| 178 | $id .= '/' . $this->root; |
||
| 179 | return $id; |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * @return string |
||
| 184 | */ |
||
| 185 | public function getHost() { |
||
| 186 | return $this->host; |
||
| 187 | } |
||
| 188 | |||
| 189 | /** |
||
| 190 | * @return string |
||
| 191 | */ |
||
| 192 | public function getRoot() { |
||
| 193 | return $this->root; |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * @return mixed |
||
| 198 | */ |
||
| 199 | public function getUser() { |
||
| 200 | return $this->user; |
||
| 201 | } |
||
| 202 | |||
| 203 | /** |
||
| 204 | * @param string $path |
||
| 205 | * @return string |
||
| 206 | */ |
||
| 207 | private function absPath($path) { |
||
| 208 | return $this->root . $this->cleanPath($path); |
||
| 209 | } |
||
| 210 | |||
| 211 | /** |
||
| 212 | * @return string|false |
||
| 213 | */ |
||
| 214 | private function hostKeysPath() { |
||
| 215 | try { |
||
| 216 | $storage_view = \OCP\Files::getStorage('files_external'); |
||
|
|
|||
| 217 | if ($storage_view) { |
||
| 218 | return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . |
||
| 219 | $storage_view->getAbsolutePath('') . |
||
| 220 | 'ssh_hostKeys'; |
||
| 221 | } |
||
| 222 | } catch (\Exception $e) { |
||
| 223 | } |
||
| 224 | return false; |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * @param $keys |
||
| 229 | * @return bool |
||
| 230 | */ |
||
| 231 | protected function writeHostKeys($keys) { |
||
| 232 | try { |
||
| 233 | $keyPath = $this->hostKeysPath(); |
||
| 234 | if ($keyPath && file_exists($keyPath)) { |
||
| 235 | $fp = fopen($keyPath, 'w'); |
||
| 236 | foreach ($keys as $host => $key) { |
||
| 237 | fwrite($fp, $host . '::' . $key . "\n"); |
||
| 238 | } |
||
| 239 | fclose($fp); |
||
| 240 | return true; |
||
| 241 | } |
||
| 242 | } catch (\Exception $e) { |
||
| 243 | } |
||
| 244 | return false; |
||
| 245 | } |
||
| 246 | |||
| 247 | /** |
||
| 248 | * @return array |
||
| 249 | */ |
||
| 250 | protected function readHostKeys() { |
||
| 251 | try { |
||
| 252 | $keyPath = $this->hostKeysPath(); |
||
| 253 | if (file_exists($keyPath)) { |
||
| 254 | $hosts = array(); |
||
| 255 | $keys = array(); |
||
| 256 | $lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); |
||
| 257 | if ($lines) { |
||
| 258 | foreach ($lines as $line) { |
||
| 259 | $hostKeyArray = explode("::", $line, 2); |
||
| 260 | if (count($hostKeyArray) === 2) { |
||
| 261 | $hosts[] = $hostKeyArray[0]; |
||
| 262 | $keys[] = $hostKeyArray[1]; |
||
| 263 | } |
||
| 264 | } |
||
| 265 | return array_combine($hosts, $keys); |
||
| 266 | } |
||
| 267 | } |
||
| 268 | } catch (\Exception $e) { |
||
| 269 | } |
||
| 270 | return array(); |
||
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * {@inheritdoc} |
||
| 275 | */ |
||
| 276 | public function mkdir($path) { |
||
| 277 | try { |
||
| 278 | return $this->getConnection()->mkdir($this->absPath($path)); |
||
| 279 | } catch (\Exception $e) { |
||
| 280 | return false; |
||
| 281 | } |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * {@inheritdoc} |
||
| 286 | */ |
||
| 287 | public function rmdir($path) { |
||
| 288 | try { |
||
| 289 | $result = $this->getConnection()->delete($this->absPath($path), true); |
||
| 290 | // workaround: stray stat cache entry when deleting empty folders |
||
| 291 | // see https://github.com/phpseclib/phpseclib/issues/706 |
||
| 292 | $this->getConnection()->clearStatCache(); |
||
| 293 | return $result; |
||
| 294 | } catch (\Exception $e) { |
||
| 295 | return false; |
||
| 296 | } |
||
| 297 | } |
||
| 298 | |||
| 299 | /** |
||
| 300 | * {@inheritdoc} |
||
| 301 | */ |
||
| 302 | public function opendir($path) { |
||
| 303 | try { |
||
| 304 | $list = $this->getConnection()->nlist($this->absPath($path)); |
||
| 305 | if ($list === false) { |
||
| 306 | return false; |
||
| 307 | } |
||
| 308 | |||
| 309 | $id = md5('sftp:' . $path); |
||
| 310 | $dirStream = array(); |
||
| 311 | foreach($list as $file) { |
||
| 312 | if ($file !== '.' && $file !== '..') { |
||
| 313 | $dirStream[] = $file; |
||
| 314 | } |
||
| 315 | } |
||
| 316 | return IteratorDirectory::wrap($dirStream); |
||
| 317 | } catch(\Exception $e) { |
||
| 318 | return false; |
||
| 319 | } |
||
| 320 | } |
||
| 321 | |||
| 322 | /** |
||
| 323 | * {@inheritdoc} |
||
| 324 | */ |
||
| 325 | public function filetype($path) { |
||
| 326 | try { |
||
| 327 | $stat = $this->getConnection()->stat($this->absPath($path)); |
||
| 328 | if ((int) $stat['type'] === NET_SFTP_TYPE_REGULAR) { |
||
| 329 | return 'file'; |
||
| 330 | } |
||
| 331 | |||
| 332 | if ((int) $stat['type'] === NET_SFTP_TYPE_DIRECTORY) { |
||
| 333 | return 'dir'; |
||
| 334 | } |
||
| 335 | } catch (\Exception $e) { |
||
| 336 | |||
| 337 | } |
||
| 338 | return false; |
||
| 339 | } |
||
| 340 | |||
| 341 | /** |
||
| 342 | * {@inheritdoc} |
||
| 343 | */ |
||
| 344 | public function file_exists($path) { |
||
| 349 | } |
||
| 350 | } |
||
| 351 | |||
| 352 | /** |
||
| 353 | * {@inheritdoc} |
||
| 354 | */ |
||
| 355 | public function unlink($path) { |
||
| 360 | } |
||
| 361 | } |
||
| 362 | |||
| 363 | /** |
||
| 364 | * {@inheritdoc} |
||
| 365 | */ |
||
| 366 | public function fopen($path, $mode) { |
||
| 367 | try { |
||
| 368 | $absPath = $this->absPath($path); |
||
| 369 | switch($mode) { |
||
| 370 | case 'r': |
||
| 371 | case 'rb': |
||
| 372 | if ( !$this->file_exists($path)) { |
||
| 373 | return false; |
||
| 374 | } |
||
| 375 | SFTPReadStream::register(); |
||
| 376 | $context = stream_context_create(['sftp' => ['session' => $this->getConnection()]]); |
||
| 377 | $handle = fopen('sftpread://' . trim($absPath, '/'), 'r', false, $context); |
||
| 378 | return RetryWrapper::wrap($handle); |
||
| 379 | case 'w': |
||
| 380 | case 'wb': |
||
| 381 | SFTPWriteStream::register(); |
||
| 382 | $context = stream_context_create(['sftp' => ['session' => $this->getConnection()]]); |
||
| 383 | return fopen('sftpwrite://' . trim($absPath, '/'), 'w', false, $context); |
||
| 384 | case 'a': |
||
| 385 | case 'ab': |
||
| 386 | case 'r+': |
||
| 387 | case 'w+': |
||
| 388 | case 'wb+': |
||
| 389 | case 'a+': |
||
| 390 | case 'x': |
||
| 391 | case 'x+': |
||
| 392 | case 'c': |
||
| 393 | case 'c+': |
||
| 394 | $context = stream_context_create(array('sftp' => array('session' => $this->getConnection()))); |
||
| 395 | $handle = fopen($this->constructUrl($path), $mode, false, $context); |
||
| 396 | return RetryWrapper::wrap($handle); |
||
| 397 | } |
||
| 398 | } catch (\Exception $e) { |
||
| 399 | } |
||
| 400 | return false; |
||
| 401 | } |
||
| 402 | |||
| 403 | /** |
||
| 404 | * {@inheritdoc} |
||
| 405 | */ |
||
| 406 | public function touch($path, $mtime=null) { |
||
| 407 | try { |
||
| 408 | if (!is_null($mtime)) { |
||
| 409 | return false; |
||
| 410 | } |
||
| 411 | if (!$this->file_exists($path)) { |
||
| 412 | $this->getConnection()->put($this->absPath($path), ''); |
||
| 413 | } else { |
||
| 414 | return false; |
||
| 415 | } |
||
| 416 | } catch (\Exception $e) { |
||
| 417 | return false; |
||
| 418 | } |
||
| 419 | return true; |
||
| 420 | } |
||
| 421 | |||
| 422 | /** |
||
| 423 | * @param string $path |
||
| 424 | * @param string $target |
||
| 425 | * @throws \Exception |
||
| 426 | */ |
||
| 427 | public function getFile($path, $target) { |
||
| 428 | $this->getConnection()->get($path, $target); |
||
| 429 | } |
||
| 430 | |||
| 431 | /** |
||
| 432 | * @param string $path |
||
| 433 | * @param string $target |
||
| 434 | * @throws \Exception |
||
| 435 | */ |
||
| 436 | public function uploadFile($path, $target) { |
||
| 438 | } |
||
| 439 | |||
| 440 | /** |
||
| 441 | * {@inheritdoc} |
||
| 442 | */ |
||
| 443 | public function rename($source, $target) { |
||
| 454 | } |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * {@inheritdoc} |
||
| 459 | */ |
||
| 460 | public function stat($path) { |
||
| 461 | try { |
||
| 462 | $stat = $this->getConnection()->stat($this->absPath($path)); |
||
| 463 | |||
| 464 | $mtime = $stat ? $stat['mtime'] : -1; |
||
| 465 | $size = $stat ? $stat['size'] : 0; |
||
| 466 | |||
| 467 | return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1); |
||
| 468 | } catch (\Exception $e) { |
||
| 469 | return false; |
||
| 470 | } |
||
| 471 | } |
||
| 472 | |||
| 473 | /** |
||
| 474 | * @param string $path |
||
| 475 | * @return string |
||
| 476 | */ |
||
| 477 | public function constructUrl($path) { |
||
| 483 | } |
||
| 484 | } |
||
| 485 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.