6 * Creates an sftp:// protocol handler that can be used with, for example, fopen(), dir(), etc.
10 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 * @package Net_SFTP_Stream
30 * @author Jim Wigginton <terrafrost@php.net>
31 * @copyright 2013 Jim Wigginton
32 * @license http://www.opensource.org/licenses/mit-license.html MIT License
33 * @link http://phpseclib.sourceforge.net
39 * @package Net_SFTP_Stream
40 * @author Jim Wigginton <terrafrost@php.net>
48 * Rather than re-create the connection we re-use instances if possible
113 * Technically this needs to be publically accessible so PHP can set it directly
121 * Notification callback function
129 * Registers this class as a URL wrapper.
131 * @param optional String $protocol The wrapper name to be registered.
132 * @return Boolean True on success, false otherwise.
135 static function register($protocol = 'sftp')
137 if (in_array($protocol, stream_get_wrappers(), true)) {
140 $class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
141 return stream_wrapper_register($protocol, $class);
149 function Net_SFTP_Stream()
151 if (defined('NET_SFTP_STREAM_LOGGING')) {
152 echo "__construct()\r\n";
155 if (!class_exists('Net_SFTP')) {
156 include_once 'Net/SFTP.php';
163 * Extract a path from a URI and actually connect to an SSH server if appropriate
165 * If "notification" is set as a context parameter the message code for successful login is
166 * NET_SSH2_MSG_USERAUTH_SUCCESS. For a failed login it's NET_SSH2_MSG_USERAUTH_FAILURE.
168 * @param String $path
172 function _parse_path($path)
174 extract(parse_url($path) + array('port' => 22));
180 if (isset($this->context)) {
181 $context = stream_context_get_params($this->context);
182 if (isset($context['notification'])) {
183 $this->notification = $context['notification'];
187 if ($host[0] == '$') {
188 $host = substr($host, 1);
190 if (!is_object($$host) || get_class($$host) != 'Net_SFTP') {
193 $this->sftp = $$host;
195 if (isset($this->context)) {
196 $context = stream_context_get_options($this->context);
198 if (isset($context[$scheme]['session'])) {
199 $sftp = $context[$scheme]['session'];
201 if (isset($context[$scheme]['sftp'])) {
202 $sftp = $context[$scheme]['sftp'];
204 if (isset($sftp) && is_object($sftp) && get_class($sftp) == 'Net_SFTP') {
208 if (isset($context[$scheme]['username'])) {
209 $user = $context[$scheme]['username'];
211 if (isset($context[$scheme]['password'])) {
212 $pass = $context[$scheme]['password'];
214 if (isset($context[$scheme]['privkey']) && is_object($context[$scheme]['privkey']) && get_Class($context[$scheme]['privkey']) == 'Crypt_RSA') {
215 $pass = $context[$scheme]['privkey'];
218 if (!isset($user) || !isset($pass)) {
222 // casting $pass to a string is necessary in the event that it's a Crypt_RSA object
223 if (isset(self::$instances[$host][$port][$user][(string) $pass])) {
224 $this->sftp = self::$instances[$host][$port][$user][(string) $pass];
226 $this->sftp = new Net_SFTP($host, $port);
227 $this->sftp->disableStatCache();
228 if (isset($this->notification) && is_callable($this->notification)) {
229 /* if !is_callable($this->notification) we could do this:
231 user_error('fopen(): failed to call user notifier', E_USER_WARNING);
233 the ftp wrapper gives errors like that when the notifier isn't callable.
234 i've opted not to do that, however, since the ftp wrapper gives the line
235 on which the fopen occurred as the line number - not the line that the
238 call_user_func($this->notification, STREAM_NOTIFY_CONNECT, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
239 call_user_func($this->notification, STREAM_NOTIFY_AUTH_REQUIRED, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
240 if (!$this->sftp->login($user, $pass)) {
241 call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_ERR, 'Login Failure', NET_SSH2_MSG_USERAUTH_FAILURE, 0, 0);
244 call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_INFO, 'Login Success', NET_SSH2_MSG_USERAUTH_SUCCESS, 0, 0);
246 if (!$this->sftp->login($user, $pass)) {
250 self::$instances[$host][$port][$user][(string) $pass] = $this->sftp;
260 * @param String $path
261 * @param String $mode
262 * @param Integer $options
263 * @param String $opened_path
267 function _stream_open($path, $mode, $options, &$opened_path)
269 $path = $this->_parse_path($path);
271 if ($path === false) {
276 $this->size = $this->sftp->size($path);
277 $this->mode = preg_replace('#[bt]$#', '', $mode);
280 if ($this->size === false) {
281 if ($this->mode[0] == 'r') {
285 switch ($this->mode[0]) {
290 $this->sftp->truncate($path, 0);
294 $this->pos = $this->mode[0] != 'a' ? 0 : $this->size;
302 * @param Integer $count
306 function _stream_read($count)
308 switch ($this->mode) {
316 // commented out because some files - eg. /dev/urandom - will say their size is 0 when in fact it's kinda infinite
317 //if ($this->pos >= $this->size) {
318 // $this->eof = true;
322 $result = $this->sftp->get($this->path, false, $this->pos, $count);
323 if (isset($this->notification) && is_callable($this->notification)) {
324 if ($result === false) {
325 call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
328 // seems that PHP calls stream_read in 8k chunks
329 call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($result), $this->size);
332 if (empty($result)) { // ie. false or empty string
336 $this->pos+= strlen($result);
344 * @param String $data
348 function _stream_write($data)
350 switch ($this->mode) {
355 $result = $this->sftp->put($this->path, $data, NET_SFTP_STRING, $this->pos);
356 if (isset($this->notification) && is_callable($this->notification)) {
358 call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
361 // seems that PHP splits up strings into 8k blocks before calling stream_write
362 call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($data), strlen($data));
365 if ($result === false) {
368 $this->pos+= strlen($data);
369 if ($this->pos > $this->size) {
370 $this->size = $this->pos;
373 return strlen($data);
377 * Retrieve the current position of a stream
382 function _stream_tell()
388 * Tests for end-of-file on a file pointer
390 * In my testing there are four classes functions that normally effect the pointer:
391 * fseek, fputs / fwrite, fgets / fread and ftruncate.
393 * Only fgets / fread, however, results in feof() returning true. do fputs($fp, 'aaa') on a blank file and feof()
394 * will return false. do fread($fp, 1) and feof() will then return true. do fseek($fp, 10) on ablank file and feof()
395 * will return false. do fread($fp, 1) and feof() will then return true.
400 function _stream_eof()
406 * Seeks to specific location in a stream
408 * @param Integer $offset
409 * @param Integer $whence
413 function _stream_seek($offset, $whence)
417 if ($offset >= $this->size || $offset < 0) {
422 $offset+= $this->pos;
425 $offset+= $this->size;
428 $this->pos = $offset;
434 * Change stream options
436 * @param String $path
437 * @param Integer $option
442 function _stream_metadata($path, $option, $var)
444 $path = $this->_parse_path($path);
445 if ($path === false) {
449 // stream_metadata was introduced in PHP 5.4.0 but as of 5.4.11 the constants haven't been defined
450 // see http://www.php.net/streamwrapper.stream-metadata and https://bugs.php.net/64246
451 // and https://github.com/php/php-src/blob/master/main/php_streams.h#L592
453 case 1: // PHP_STREAM_META_TOUCH
454 return $this->sftp->touch($path, $var[0], $var[1]);
455 case 2: // PHP_STREAM_OWNER_NAME
456 case 3: // PHP_STREAM_GROUP_NAME
458 case 4: // PHP_STREAM_META_OWNER
459 return $this->sftp->chown($path, $var);
460 case 5: // PHP_STREAM_META_GROUP
461 return $this->sftp->chgrp($path, $var);
462 case 6: // PHP_STREAM_META_ACCESS
463 return $this->sftp->chmod($path, $var) !== false;
468 * Retrieve the underlaying resource
470 * @param Integer $cast_as
474 function _stream_cast($cast_as)
476 return $this->sftp->fsock;
480 * Advisory file locking
482 * @param Integer $operation
486 function _stream_lock($operation)
492 * Renames a file or directory
494 * Attempts to rename oldname to newname, moving it between directories if necessary.
495 * If newname exists, it will be overwritten. This is a departure from what Net_SFTP
498 * @param String $path_from
499 * @param String $path_to
503 function _rename($path_from, $path_to)
505 $path1 = parse_url($path_from);
506 $path2 = parse_url($path_to);
507 unset($path1['path'], $path2['path']);
508 if ($path1 != $path2) {
512 $path_from = $this->_parse_path($path_from);
513 $path_to = parse_url($path_to);
514 if ($path_from == false) {
518 $path_to = $path_to['path']; // the $component part of parse_url() was added in PHP 5.1.2
519 // "It is an error if there already exists a file with the name specified by newpath."
520 // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.5
521 if (!$this->sftp->rename($path_from, $path_to)) {
522 if ($this->sftp->stat($path_to)) {
523 return $this->sftp->delete($path_to, true) && $this->sftp->rename($path_from, $path_to);
532 * Open directory handle
534 * The only $options is "whether or not to enforce safe_mode (0x04)". Since safe mode was deprecated in 5.3 and
535 * removed in 5.4 I'm just going to ignore it.
537 * Also, nlist() is the best that this function is realistically going to be able to do. When an SFTP client
538 * sends a SSH_FXP_READDIR packet you don't generally get info on just one file but on multiple files. Quoting
541 * The SSH_FXP_NAME response has the following format:
545 * repeats count times:
550 * @param String $path
551 * @param Integer $options
555 function _dir_opendir($path, $options)
557 $path = $this->_parse_path($path);
558 if ($path === false) {
562 $this->entries = $this->sftp->nlist($path);
563 return $this->entries !== false;
567 * Read entry from directory handle
572 function _dir_readdir()
574 if (isset($this->entries[$this->pos])) {
575 return $this->entries[$this->pos++];
581 * Rewind directory handle
586 function _dir_rewinddir()
593 * Close directory handle
598 function _dir_closedir()
606 * Only valid $options is STREAM_MKDIR_RECURSIVE
608 * @param String $path
609 * @param Integer $mode
610 * @param Integer $options
614 function _mkdir($path, $mode, $options)
616 $path = $this->_parse_path($path);
617 if ($path === false) {
621 return $this->sftp->mkdir($path, $mode, $options & STREAM_MKDIR_RECURSIVE);
625 * Removes a directory
627 * Only valid $options is STREAM_MKDIR_RECURSIVE per <http://php.net/streamwrapper.rmdir>, however,
628 * <http://php.net/rmdir> does not have a $recursive parameter as mkdir() does so I don't know how
629 * STREAM_MKDIR_RECURSIVE is supposed to be set. Also, when I try it out with rmdir() I get 8 as
630 * $options. What does 8 correspond to?
632 * @param String $path
633 * @param Integer $mode
634 * @param Integer $options
638 function _rmdir($path, $options)
640 $path = $this->_parse_path($path);
641 if ($path === false) {
645 return $this->sftp->rmdir($path);
651 * See <http://php.net/fflush>. Always returns true because Net_SFTP doesn't cache stuff before writing
656 function _stream_flush()
662 * Retrieve information about a file resource
667 function _stream_stat()
669 $results = $this->sftp->stat($this->path);
670 if ($results === false) {
679 * @param String $path
683 function _unlink($path)
685 $path = $this->_parse_path($path);
686 if ($path === false) {
690 return $this->sftp->delete($path, false);
694 * Retrieve information about a file
696 * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of Net_SFTP_Stream is quiet by default
697 * might be worthwhile to reconstruct bits 12-16 (ie. the file type) if mode doesn't have them but we'll
698 * cross that bridge when and if it's reached
700 * @param String $path
701 * @param Integer $flags
705 function _url_stat($path, $flags)
707 $path = $this->_parse_path($path);
708 if ($path === false) {
712 $results = $flags & STREAM_URL_STAT_LINK ? $this->sftp->lstat($path) : $this->sftp->stat($path);
713 if ($results === false) {
723 * @param Integer $new_size
727 function _stream_truncate($new_size)
729 if (!$this->sftp->truncate($this->path, $new_size)) {
734 $this->size = $new_size;
740 * Change stream options
742 * STREAM_OPTION_WRITE_BUFFER isn't supported for the same reason stream_flush isn't.
743 * The other two aren't supported because of limitations in Net_SFTP.
745 * @param Integer $option
746 * @param Integer $arg1
747 * @param Integer $arg2
751 function _stream_set_option($option, $arg1, $arg2)
761 function _stream_close()
766 * __call Magic Method
768 * When you're utilizing an SFTP stream you're not calling the methods in this class directly - PHP is calling them for you.
769 * Which kinda begs the question... what methods is PHP calling and what parameters is it passing to them? This function
770 * lets you figure that out.
772 * If NET_SFTP_STREAM_LOGGING is defined all calls will be output on the screen and then (regardless of whether or not
773 * NET_SFTP_STREAM_LOGGING is enabled) the parameters will be passed through to the appropriate method.
780 function __call($name, $arguments)
782 if (defined('NET_SFTP_STREAM_LOGGING')) {
784 $last = count($arguments) - 1;
785 foreach ($arguments as $i => $argument) {
786 var_export($argument);
794 if (!method_exists($this, $name)) {
797 return call_user_func_array(array($this, $name), $arguments);
801 Net_SFTP_Stream::register();