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