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