[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/ -> Stream.php (source)

   1  <?php
   2  
   3  /**
   4   * SFTP Stream Wrapper
   5   *
   6   * Creates an sftp:// protocol handler that can be used with, for example, fopen(), dir(), etc.
   7   *
   8   * PHP version 5
   9   *
  10   * @author    Jim Wigginton <terrafrost@php.net>
  11   * @copyright 2013 Jim Wigginton
  12   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  13   * @link      http://phpseclib.sourceforge.net
  14   */
  15  
  16  namespace phpseclib3\Net\SFTP;
  17  
  18  use phpseclib3\Crypt\Common\PrivateKey;
  19  use phpseclib3\Net\SFTP;
  20  use phpseclib3\Net\SSH2;
  21  
  22  /**
  23   * SFTP Stream Wrapper
  24   *
  25   * @author  Jim Wigginton <terrafrost@php.net>
  26   */
  27  class Stream
  28  {
  29      /**
  30       * SFTP instances
  31       *
  32       * Rather than re-create the connection we re-use instances if possible
  33       *
  34       * @var array
  35       */
  36      public static $instances;
  37  
  38      /**
  39       * SFTP instance
  40       *
  41       * @var object
  42       */
  43      private $sftp;
  44  
  45      /**
  46       * Path
  47       *
  48       * @var string
  49       */
  50      private $path;
  51  
  52      /**
  53       * Mode
  54       *
  55       * @var string
  56       */
  57      private $mode;
  58  
  59      /**
  60       * Position
  61       *
  62       * @var int
  63       */
  64      private $pos;
  65  
  66      /**
  67       * Size
  68       *
  69       * @var int
  70       */
  71      private $size;
  72  
  73      /**
  74       * Directory entries
  75       *
  76       * @var array
  77       */
  78      private $entries;
  79  
  80      /**
  81       * EOF flag
  82       *
  83       * @var bool
  84       */
  85      private $eof;
  86  
  87      /**
  88       * Context resource
  89       *
  90       * Technically this needs to be publicly accessible so PHP can set it directly
  91       *
  92       * @var resource
  93       */
  94      public $context;
  95  
  96      /**
  97       * Notification callback function
  98       *
  99       * @var callable
 100       */
 101      private $notification;
 102  
 103      /**
 104       * Registers this class as a URL wrapper.
 105       *
 106       * @param string $protocol The wrapper name to be registered.
 107       * @return bool True on success, false otherwise.
 108       */
 109      public static function register($protocol = 'sftp')
 110      {
 111          if (in_array($protocol, stream_get_wrappers(), true)) {
 112              return false;
 113          }
 114          return stream_wrapper_register($protocol, get_called_class());
 115      }
 116  
 117      /**
 118       * The Constructor
 119       *
 120       */
 121      public function __construct()
 122      {
 123          if (defined('NET_SFTP_STREAM_LOGGING')) {
 124              echo "__construct()\r\n";
 125          }
 126      }
 127  
 128      /**
 129       * Path Parser
 130       *
 131       * Extract a path from a URI and actually connect to an SSH server if appropriate
 132       *
 133       * If "notification" is set as a context parameter the message code for successful login is
 134       * NET_SSH2_MSG_USERAUTH_SUCCESS. For a failed login it's NET_SSH2_MSG_USERAUTH_FAILURE.
 135       *
 136       * @param string $path
 137       * @return string
 138       */
 139      protected function parse_path($path)
 140      {
 141          $orig = $path;
 142          extract(parse_url($path) + ['port' => 22]);
 143          if (isset($query)) {
 144              $path .= '?' . $query;
 145          } elseif (preg_match('/(\?|\?#)$/', $orig)) {
 146              $path .= '?';
 147          }
 148          if (isset($fragment)) {
 149              $path .= '#' . $fragment;
 150          } elseif ($orig[strlen($orig) - 1] == '#') {
 151              $path .= '#';
 152          }
 153  
 154          if (!isset($host)) {
 155              return false;
 156          }
 157  
 158          if (isset($this->context)) {
 159              $context = stream_context_get_params($this->context);
 160              if (isset($context['notification'])) {
 161                  $this->notification = $context['notification'];
 162              }
 163          }
 164  
 165          if (preg_match('/^{[a-z0-9]+}$/i', $host)) {
 166              $host = SSH2::getConnectionByResourceId($host);
 167              if ($host === false) {
 168                  return false;
 169              }
 170              $this->sftp = $host;
 171          } else {
 172              if (isset($this->context)) {
 173                  $context = stream_context_get_options($this->context);
 174              }
 175              if (isset($context[$scheme]['session'])) {
 176                  $sftp = $context[$scheme]['session'];
 177              }
 178              if (isset($context[$scheme]['sftp'])) {
 179                  $sftp = $context[$scheme]['sftp'];
 180              }
 181              if (isset($sftp) && $sftp instanceof SFTP) {
 182                  $this->sftp = $sftp;
 183                  return $path;
 184              }
 185              if (isset($context[$scheme]['username'])) {
 186                  $user = $context[$scheme]['username'];
 187              }
 188              if (isset($context[$scheme]['password'])) {
 189                  $pass = $context[$scheme]['password'];
 190              }
 191              if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof PrivateKey) {
 192                  $pass = $context[$scheme]['privkey'];
 193              }
 194  
 195              if (!isset($user) || !isset($pass)) {
 196                  return false;
 197              }
 198  
 199              // casting $pass to a string is necessary in the event that it's a \phpseclib3\Crypt\RSA object
 200              if (isset(self::$instances[$host][$port][$user][(string) $pass])) {
 201                  $this->sftp = self::$instances[$host][$port][$user][(string) $pass];
 202              } else {
 203                  $this->sftp = new SFTP($host, $port);
 204                  $this->sftp->disableStatCache();
 205                  if (isset($this->notification) && is_callable($this->notification)) {
 206                      /* if !is_callable($this->notification) we could do this:
 207  
 208                         user_error('fopen(): failed to call user notifier', E_USER_WARNING);
 209  
 210                         the ftp wrapper gives errors like that when the notifier isn't callable.
 211                         i've opted not to do that, however, since the ftp wrapper gives the line
 212                         on which the fopen occurred as the line number - not the line that the
 213                         user_error is on.
 214                      */
 215                      call_user_func($this->notification, STREAM_NOTIFY_CONNECT, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
 216                      call_user_func($this->notification, STREAM_NOTIFY_AUTH_REQUIRED, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
 217                      if (!$this->sftp->login($user, $pass)) {
 218                          call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_ERR, 'Login Failure', NET_SSH2_MSG_USERAUTH_FAILURE, 0, 0);
 219                          return false;
 220                      }
 221                      call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_INFO, 'Login Success', NET_SSH2_MSG_USERAUTH_SUCCESS, 0, 0);
 222                  } else {
 223                      if (!$this->sftp->login($user, $pass)) {
 224                          return false;
 225                      }
 226                  }
 227                  self::$instances[$host][$port][$user][(string) $pass] = $this->sftp;
 228              }
 229          }
 230  
 231          return $path;
 232      }
 233  
 234      /**
 235       * Opens file or URL
 236       *
 237       * @param string $path
 238       * @param string $mode
 239       * @param int $options
 240       * @param string $opened_path
 241       * @return bool
 242       */
 243      private function _stream_open($path, $mode, $options, &$opened_path)
 244      {
 245          $path = $this->parse_path($path);
 246  
 247          if ($path === false) {
 248              return false;
 249          }
 250          $this->path = $path;
 251  
 252          $this->size = $this->sftp->filesize($path);
 253          $this->mode = preg_replace('#[bt]$#', '', $mode);
 254          $this->eof = false;
 255  
 256          if ($this->size === false) {
 257              if ($this->mode[0] == 'r') {
 258                  return false;
 259              } else {
 260                  $this->sftp->touch($path);
 261                  $this->size = 0;
 262              }
 263          } else {
 264              switch ($this->mode[0]) {
 265                  case 'x':
 266                      return false;
 267                  case 'w':
 268                      $this->sftp->truncate($path, 0);
 269                      $this->size = 0;
 270              }
 271          }
 272  
 273          $this->pos = $this->mode[0] != 'a' ? 0 : $this->size;
 274  
 275          return true;
 276      }
 277  
 278      /**
 279       * Read from stream
 280       *
 281       * @param int $count
 282       * @return mixed
 283       */
 284      private function _stream_read($count)
 285      {
 286          switch ($this->mode) {
 287              case 'w':
 288              case 'a':
 289              case 'x':
 290              case 'c':
 291                  return false;
 292          }
 293  
 294          // commented out because some files - eg. /dev/urandom - will say their size is 0 when in fact it's kinda infinite
 295          //if ($this->pos >= $this->size) {
 296          //    $this->eof = true;
 297          //    return false;
 298          //}
 299  
 300          $result = $this->sftp->get($this->path, false, $this->pos, $count);
 301          if (isset($this->notification) && is_callable($this->notification)) {
 302              if ($result === false) {
 303                  call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
 304                  return 0;
 305              }
 306              // seems that PHP calls stream_read in 8k chunks
 307              call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($result), $this->size);
 308          }
 309  
 310          if (empty($result)) { // ie. false or empty string
 311              $this->eof = true;
 312              return false;
 313          }
 314          $this->pos += strlen($result);
 315  
 316          return $result;
 317      }
 318  
 319      /**
 320       * Write to stream
 321       *
 322       * @param string $data
 323       * @return int|false
 324       */
 325      private function _stream_write($data)
 326      {
 327          switch ($this->mode) {
 328              case 'r':
 329                  return false;
 330          }
 331  
 332          $result = $this->sftp->put($this->path, $data, SFTP::SOURCE_STRING, $this->pos);
 333          if (isset($this->notification) && is_callable($this->notification)) {
 334              if (!$result) {
 335                  call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
 336                  return 0;
 337              }
 338              // seems that PHP splits up strings into 8k blocks before calling stream_write
 339              call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($data), strlen($data));
 340          }
 341  
 342          if ($result === false) {
 343              return false;
 344          }
 345          $this->pos += strlen($data);
 346          if ($this->pos > $this->size) {
 347              $this->size = $this->pos;
 348          }
 349          $this->eof = false;
 350          return strlen($data);
 351      }
 352  
 353      /**
 354       * Retrieve the current position of a stream
 355       *
 356       * @return int
 357       */
 358      private function _stream_tell()
 359      {
 360          return $this->pos;
 361      }
 362  
 363      /**
 364       * Tests for end-of-file on a file pointer
 365       *
 366       * In my testing there are four classes functions that normally effect the pointer:
 367       * fseek, fputs  / fwrite, fgets / fread and ftruncate.
 368       *
 369       * Only fgets / fread, however, results in feof() returning true. do fputs($fp, 'aaa') on a blank file and feof()
 370       * will return false. do fread($fp, 1) and feof() will then return true. do fseek($fp, 10) on ablank file and feof()
 371       * will return false. do fread($fp, 1) and feof() will then return true.
 372       *
 373       * @return bool
 374       */
 375      private function _stream_eof()
 376      {
 377          return $this->eof;
 378      }
 379  
 380      /**
 381       * Seeks to specific location in a stream
 382       *
 383       * @param int $offset
 384       * @param int $whence
 385       * @return bool
 386       */
 387      private function _stream_seek($offset, $whence)
 388      {
 389          switch ($whence) {
 390              case SEEK_SET:
 391                  if ($offset < 0) {
 392                      return false;
 393                  }
 394                  break;
 395              case SEEK_CUR:
 396                  $offset += $this->pos;
 397                  break;
 398              case SEEK_END:
 399                  $offset += $this->size;
 400          }
 401  
 402          $this->pos = $offset;
 403          $this->eof = false;
 404          return true;
 405      }
 406  
 407      /**
 408       * Change stream options
 409       *
 410       * @param string $path
 411       * @param int $option
 412       * @param mixed $var
 413       * @return bool
 414       */
 415      private function _stream_metadata($path, $option, $var)
 416      {
 417          $path = $this->parse_path($path);
 418          if ($path === false) {
 419              return false;
 420          }
 421  
 422          // stream_metadata was introduced in PHP 5.4.0 but as of 5.4.11 the constants haven't been defined
 423          // see http://www.php.net/streamwrapper.stream-metadata and https://bugs.php.net/64246
 424          //     and https://github.com/php/php-src/blob/master/main/php_streams.h#L592
 425          switch ($option) {
 426              case 1: // PHP_STREAM_META_TOUCH
 427                  $time = isset($var[0]) ? $var[0] : null;
 428                  $atime = isset($var[1]) ? $var[1] : null;
 429                  return $this->sftp->touch($path, $time, $atime);
 430              case 2: // PHP_STREAM_OWNER_NAME
 431              case 3: // PHP_STREAM_GROUP_NAME
 432                  return false;
 433              case 4: // PHP_STREAM_META_OWNER
 434                  return $this->sftp->chown($path, $var);
 435              case 5: // PHP_STREAM_META_GROUP
 436                  return $this->sftp->chgrp($path, $var);
 437              case 6: // PHP_STREAM_META_ACCESS
 438                  return $this->sftp->chmod($path, $var) !== false;
 439          }
 440      }
 441  
 442      /**
 443       * Retrieve the underlaying resource
 444       *
 445       * @param int $cast_as
 446       * @return resource
 447       */
 448      private function _stream_cast($cast_as)
 449      {
 450          return $this->sftp->fsock;
 451      }
 452  
 453      /**
 454       * Advisory file locking
 455       *
 456       * @param int $operation
 457       * @return bool
 458       */
 459      private function _stream_lock($operation)
 460      {
 461          return false;
 462      }
 463  
 464      /**
 465       * Renames a file or directory
 466       *
 467       * Attempts to rename oldname to newname, moving it between directories if necessary.
 468       * If newname exists, it will be overwritten.  This is a departure from what \phpseclib3\Net\SFTP
 469       * does.
 470       *
 471       * @param string $path_from
 472       * @param string $path_to
 473       * @return bool
 474       */
 475      private function _rename($path_from, $path_to)
 476      {
 477          $path1 = parse_url($path_from);
 478          $path2 = parse_url($path_to);
 479          unset($path1['path'], $path2['path']);
 480          if ($path1 != $path2) {
 481              return false;
 482          }
 483  
 484          $path_from = $this->parse_path($path_from);
 485          $path_to = parse_url($path_to);
 486          if ($path_from === false) {
 487              return false;
 488          }
 489  
 490          $path_to = $path_to['path']; // the $component part of parse_url() was added in PHP 5.1.2
 491          // "It is an error if there already exists a file with the name specified by newpath."
 492          //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.5
 493          if (!$this->sftp->rename($path_from, $path_to)) {
 494              if ($this->sftp->stat($path_to)) {
 495                  return $this->sftp->delete($path_to, true) && $this->sftp->rename($path_from, $path_to);
 496              }
 497              return false;
 498          }
 499  
 500          return true;
 501      }
 502  
 503      /**
 504       * Open directory handle
 505       *
 506       * The only $options is "whether or not to enforce safe_mode (0x04)". Since safe mode was deprecated in 5.3 and
 507       * removed in 5.4 I'm just going to ignore it.
 508       *
 509       * Also, nlist() is the best that this function is realistically going to be able to do. When an SFTP client
 510       * sends a SSH_FXP_READDIR packet you don't generally get info on just one file but on multiple files. Quoting
 511       * the SFTP specs:
 512       *
 513       *    The SSH_FXP_NAME response has the following format:
 514       *
 515       *        uint32     id
 516       *        uint32     count
 517       *        repeats count times:
 518       *                string     filename
 519       *                string     longname
 520       *                ATTRS      attrs
 521       *
 522       * @param string $path
 523       * @param int $options
 524       * @return bool
 525       */
 526      private function _dir_opendir($path, $options)
 527      {
 528          $path = $this->parse_path($path);
 529          if ($path === false) {
 530              return false;
 531          }
 532          $this->pos = 0;
 533          $this->entries = $this->sftp->nlist($path);
 534          return $this->entries !== false;
 535      }
 536  
 537      /**
 538       * Read entry from directory handle
 539       *
 540       * @return mixed
 541       */
 542      private function _dir_readdir()
 543      {
 544          if (isset($this->entries[$this->pos])) {
 545              return $this->entries[$this->pos++];
 546          }
 547          return false;
 548      }
 549  
 550      /**
 551       * Rewind directory handle
 552       *
 553       * @return bool
 554       */
 555      private function _dir_rewinddir()
 556      {
 557          $this->pos = 0;
 558          return true;
 559      }
 560  
 561      /**
 562       * Close directory handle
 563       *
 564       * @return bool
 565       */
 566      private function _dir_closedir()
 567      {
 568          return true;
 569      }
 570  
 571      /**
 572       * Create a directory
 573       *
 574       * Only valid $options is STREAM_MKDIR_RECURSIVE
 575       *
 576       * @param string $path
 577       * @param int $mode
 578       * @param int $options
 579       * @return bool
 580       */
 581      private function _mkdir($path, $mode, $options)
 582      {
 583          $path = $this->parse_path($path);
 584          if ($path === false) {
 585              return false;
 586          }
 587  
 588          return $this->sftp->mkdir($path, $mode, $options & STREAM_MKDIR_RECURSIVE);
 589      }
 590  
 591      /**
 592       * Removes a directory
 593       *
 594       * Only valid $options is STREAM_MKDIR_RECURSIVE per <http://php.net/streamwrapper.rmdir>, however,
 595       * <http://php.net/rmdir>  does not have a $recursive parameter as mkdir() does so I don't know how
 596       * STREAM_MKDIR_RECURSIVE is supposed to be set. Also, when I try it out with rmdir() I get 8 as
 597       * $options. What does 8 correspond to?
 598       *
 599       * @param string $path
 600       * @param int $options
 601       * @return bool
 602       */
 603      private function _rmdir($path, $options)
 604      {
 605          $path = $this->parse_path($path);
 606          if ($path === false) {
 607              return false;
 608          }
 609  
 610          return $this->sftp->rmdir($path);
 611      }
 612  
 613      /**
 614       * Flushes the output
 615       *
 616       * See <http://php.net/fflush>. Always returns true because \phpseclib3\Net\SFTP doesn't cache stuff before writing
 617       *
 618       * @return bool
 619       */
 620      private function _stream_flush()
 621      {
 622          return true;
 623      }
 624  
 625      /**
 626       * Retrieve information about a file resource
 627       *
 628       * @return mixed
 629       */
 630      private function _stream_stat()
 631      {
 632          $results = $this->sftp->stat($this->path);
 633          if ($results === false) {
 634              return false;
 635          }
 636          return $results;
 637      }
 638  
 639      /**
 640       * Delete a file
 641       *
 642       * @param string $path
 643       * @return bool
 644       */
 645      private function _unlink($path)
 646      {
 647          $path = $this->parse_path($path);
 648          if ($path === false) {
 649              return false;
 650          }
 651  
 652          return $this->sftp->delete($path, false);
 653      }
 654  
 655      /**
 656       * Retrieve information about a file
 657       *
 658       * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib3\Net\SFTP\Stream is quiet by default
 659       * might be worthwhile to reconstruct bits 12-16 (ie. the file type) if mode doesn't have them but we'll
 660       * cross that bridge when and if it's reached
 661       *
 662       * @param string $path
 663       * @param int $flags
 664       * @return mixed
 665       */
 666      private function _url_stat($path, $flags)
 667      {
 668          $path = $this->parse_path($path);
 669          if ($path === false) {
 670              return false;
 671          }
 672  
 673          $results = $flags & STREAM_URL_STAT_LINK ? $this->sftp->lstat($path) : $this->sftp->stat($path);
 674          if ($results === false) {
 675              return false;
 676          }
 677  
 678          return $results;
 679      }
 680  
 681      /**
 682       * Truncate stream
 683       *
 684       * @param int $new_size
 685       * @return bool
 686       */
 687      private function _stream_truncate($new_size)
 688      {
 689          if (!$this->sftp->truncate($this->path, $new_size)) {
 690              return false;
 691          }
 692  
 693          $this->eof = false;
 694          $this->size = $new_size;
 695  
 696          return true;
 697      }
 698  
 699      /**
 700       * Change stream options
 701       *
 702       * STREAM_OPTION_WRITE_BUFFER isn't supported for the same reason stream_flush isn't.
 703       * The other two aren't supported because of limitations in \phpseclib3\Net\SFTP.
 704       *
 705       * @param int $option
 706       * @param int $arg1
 707       * @param int $arg2
 708       * @return bool
 709       */
 710      private function _stream_set_option($option, $arg1, $arg2)
 711      {
 712          return false;
 713      }
 714  
 715      /**
 716       * Close an resource
 717       *
 718       */
 719      private function _stream_close()
 720      {
 721      }
 722  
 723      /**
 724       * __call Magic Method
 725       *
 726       * When you're utilizing an SFTP stream you're not calling the methods in this class directly - PHP is calling them for you.
 727       * Which kinda begs the question... what methods is PHP calling and what parameters is it passing to them? This function
 728       * lets you figure that out.
 729       *
 730       * If NET_SFTP_STREAM_LOGGING is defined all calls will be output on the screen and then (regardless of whether or not
 731       * NET_SFTP_STREAM_LOGGING is enabled) the parameters will be passed through to the appropriate method.
 732       *
 733       * @param string $name
 734       * @param array $arguments
 735       * @return mixed
 736       */
 737      public function __call($name, array $arguments)
 738      {
 739          if (defined('NET_SFTP_STREAM_LOGGING')) {
 740              echo $name . '(';
 741              $last = count($arguments) - 1;
 742              foreach ($arguments as $i => $argument) {
 743                  var_export($argument);
 744                  if ($i != $last) {
 745                      echo ',';
 746                  }
 747              }
 748              echo ")\r\n";
 749          }
 750          $name = '_' . $name;
 751          if (!method_exists($this, $name)) {
 752              return false;
 753          }
 754          return $this->$name(...$arguments);
 755      }
 756  }