Stream.php 21 KB

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