[securemail] Update Composer dependencies
[friendica-addons.git/.git] / securemail / vendor / phpseclib / phpseclib / phpseclib / Net / SFTP / Stream.php
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 }