6 * Creates an sftp:// protocol handler that can be used with, for example, fopen(), dir(), etc.
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
18 namespace phpseclib\Net\SFTP;
20 use phpseclib\Crypt\RSA;
21 use phpseclib\Net\SFTP;
27 * @author Jim Wigginton <terrafrost@php.net>
35 * Rather than re-create the connection we re-use instances if possible
100 * Technically this needs to be publically accessible so PHP can set it directly
108 * Notification callback function
116 * Registers this class as a URL wrapper.
118 * @param string $protocol The wrapper name to be registered.
119 * @return bool True on success, false otherwise.
122 static function register($protocol = 'sftp')
124 if (in_array($protocol, stream_get_wrappers(), true)) {
127 return stream_wrapper_register($protocol, get_called_class());
135 function __construct()
137 if (defined('NET_SFTP_STREAM_LOGGING')) {
138 echo "__construct()\r\n";
145 * Extract a path from a URI and actually connect to an SSH server if appropriate
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.
150 * @param string $path
154 function _parse_path($path)
157 extract(parse_url($path) + array('port' => 22));
159 $path.= '?' . $query;
160 } elseif (preg_match('/(\?|\?#)$/', $orig)) {
163 if (isset($fragment)) {
164 $path.= '#' . $fragment;
165 } elseif ($orig[strlen($orig) - 1] == '#') {
173 if (isset($this->context)) {
174 $context = stream_context_get_params($this->context);
175 if (isset($context['notification'])) {
176 $this->notification = $context['notification'];
180 if ($host[0] == '$') {
181 $host = substr($host, 1);
183 if (($$host instanceof SFTP) === false) {
186 $this->sftp = $$host;
188 if (isset($this->context)) {
189 $context = stream_context_get_options($this->context);
191 if (isset($context[$scheme]['session'])) {
192 $sftp = $context[$scheme]['session'];
194 if (isset($context[$scheme]['sftp'])) {
195 $sftp = $context[$scheme]['sftp'];
197 if (isset($sftp) && $sftp instanceof SFTP) {
201 if (isset($context[$scheme]['username'])) {
202 $user = $context[$scheme]['username'];
204 if (isset($context[$scheme]['password'])) {
205 $pass = $context[$scheme]['password'];
207 if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof RSA) {
208 $pass = $context[$scheme]['privkey'];
211 if (!isset($user) || !isset($pass)) {
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];
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:
224 user_error('fopen(): failed to call user notifier', E_USER_WARNING);
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
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);
237 call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_INFO, 'Login Success', NET_SSH2_MSG_USERAUTH_SUCCESS, 0, 0);
239 if (!$this->sftp->login($user, $pass)) {
243 self::$instances[$host][$port][$user][(string) $pass] = $this->sftp;
253 * @param string $path
254 * @param string $mode
255 * @param int $options
256 * @param string $opened_path
260 function _stream_open($path, $mode, $options, &$opened_path)
262 $path = $this->_parse_path($path);
264 if ($path === false) {
269 $this->size = $this->sftp->size($path);
270 $this->mode = preg_replace('#[bt]$#', '', $mode);
273 if ($this->size === false) {
274 if ($this->mode[0] == 'r') {
277 $this->sftp->touch($path);
281 switch ($this->mode[0]) {
285 $this->sftp->truncate($path, 0);
290 $this->pos = $this->mode[0] != 'a' ? 0 : $this->size;
302 function _stream_read($count)
304 switch ($this->mode) {
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;
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);
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);
328 if (empty($result)) { // ie. false or empty string
332 $this->pos+= strlen($result);
340 * @param string $data
344 function _stream_write($data)
346 switch ($this->mode) {
351 $result = $this->sftp->put($this->path, $data, SFTP::SOURCE_STRING, $this->pos);
352 if (isset($this->notification) && is_callable($this->notification)) {
354 call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
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));
361 if ($result === false) {
364 $this->pos+= strlen($data);
365 if ($this->pos > $this->size) {
366 $this->size = $this->pos;
369 return strlen($data);
373 * Retrieve the current position of a stream
378 function _stream_tell()
384 * Tests for end-of-file on a file pointer
386 * In my testing there are four classes functions that normally effect the pointer:
387 * fseek, fputs / fwrite, fgets / fread and ftruncate.
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.
396 function _stream_eof()
402 * Seeks to specific location in a stream
409 function _stream_seek($offset, $whence)
413 if ($offset >= $this->size || $offset < 0) {
418 $offset+= $this->pos;
421 $offset+= $this->size;
424 $this->pos = $offset;
430 * Change stream options
432 * @param string $path
438 function _stream_metadata($path, $option, $var)
440 $path = $this->_parse_path($path);
441 if ($path === false) {
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
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
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;
464 * Retrieve the underlaying resource
466 * @param int $cast_as
470 function _stream_cast($cast_as)
472 return $this->sftp->fsock;
476 * Advisory file locking
478 * @param int $operation
482 function _stream_lock($operation)
488 * Renames a file or directory
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
494 * @param string $path_from
495 * @param string $path_to
499 function _rename($path_from, $path_to)
501 $path1 = parse_url($path_from);
502 $path2 = parse_url($path_to);
503 unset($path1['path'], $path2['path']);
504 if ($path1 != $path2) {
508 $path_from = $this->_parse_path($path_from);
509 $path_to = parse_url($path_to);
510 if ($path_from === false) {
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);
528 * Open directory handle
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.
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
537 * The SSH_FXP_NAME response has the following format:
541 * repeats count times:
546 * @param string $path
547 * @param int $options
551 function _dir_opendir($path, $options)
553 $path = $this->_parse_path($path);
554 if ($path === false) {
558 $this->entries = $this->sftp->nlist($path);
559 return $this->entries !== false;
563 * Read entry from directory handle
568 function _dir_readdir()
570 if (isset($this->entries[$this->pos])) {
571 return $this->entries[$this->pos++];
577 * Rewind directory handle
582 function _dir_rewinddir()
589 * Close directory handle
594 function _dir_closedir()
602 * Only valid $options is STREAM_MKDIR_RECURSIVE
604 * @param string $path
606 * @param int $options
610 function _mkdir($path, $mode, $options)
612 $path = $this->_parse_path($path);
613 if ($path === false) {
617 return $this->sftp->mkdir($path, $mode, $options & STREAM_MKDIR_RECURSIVE);
621 * Removes a directory
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?
628 * @param string $path
630 * @param int $options
634 function _rmdir($path, $options)
636 $path = $this->_parse_path($path);
637 if ($path === false) {
641 return $this->sftp->rmdir($path);
647 * See <http://php.net/fflush>. Always returns true because \phpseclib\Net\SFTP doesn't cache stuff before writing
652 function _stream_flush()
658 * Retrieve information about a file resource
663 function _stream_stat()
665 $results = $this->sftp->stat($this->path);
666 if ($results === false) {
675 * @param string $path
679 function _unlink($path)
681 $path = $this->_parse_path($path);
682 if ($path === false) {
686 return $this->sftp->delete($path, false);
690 * Retrieve information about a file
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
696 * @param string $path
701 function _url_stat($path, $flags)
703 $path = $this->_parse_path($path);
704 if ($path === false) {
708 $results = $flags & STREAM_URL_STAT_LINK ? $this->sftp->lstat($path) : $this->sftp->stat($path);
709 if ($results === false) {
719 * @param int $new_size
723 function _stream_truncate($new_size)
725 if (!$this->sftp->truncate($this->path, $new_size)) {
730 $this->size = $new_size;
736 * Change stream options
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.
747 function _stream_set_option($option, $arg1, $arg2)
757 function _stream_close()
762 * __call Magic Method
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.
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.
776 function __call($name, $arguments)
778 if (defined('NET_SFTP_STREAM_LOGGING')) {
780 $last = count($arguments) - 1;
781 foreach ($arguments as $i => $argument) {
782 var_export($argument);
790 if (!method_exists($this, $name)) {
793 return call_user_func_array(array($this, $name), $arguments);