PageRenderTime 152ms CodeModel.GetById 20ms RepoModel.GetById 2ms app.codeStats 1ms

/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php

https://bitbucket.org/Kamor/nexway
PHP | 2778 lines | 1538 code | 287 blank | 953 comment | 322 complexity | 6bc2a83cb408b8f5bf9d6cec971e3d45 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Pure-PHP implementation of SFTP.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
  8. * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
  9. * to an SFTPv4/5/6 server.
  10. *
  11. * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
  12. *
  13. * Here's a short example of how to use this library:
  14. * <code>
  15. * <?php
  16. * include 'Net/SFTP.php';
  17. *
  18. * $sftp = new Net_SFTP('www.domain.tld');
  19. * if (!$sftp->login('username', 'password')) {
  20. * exit('Login Failed');
  21. * }
  22. *
  23. * echo $sftp->pwd() . "\r\n";
  24. * $sftp->put('filename.ext', 'hello, world!');
  25. * print_r($sftp->nlist());
  26. * ?>
  27. * </code>
  28. *
  29. * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
  30. * of this software and associated documentation files (the "Software"), to deal
  31. * in the Software without restriction, including without limitation the rights
  32. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  33. * copies of the Software, and to permit persons to whom the Software is
  34. * furnished to do so, subject to the following conditions:
  35. *
  36. * The above copyright notice and this permission notice shall be included in
  37. * all copies or substantial portions of the Software.
  38. *
  39. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  40. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  41. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  42. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  43. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  44. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  45. * THE SOFTWARE.
  46. *
  47. * @category Net
  48. * @package Net_SFTP
  49. * @author Jim Wigginton <terrafrost@php.net>
  50. * @copyright MMIX Jim Wigginton
  51. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  52. * @link http://phpseclib.sourceforge.net
  53. */
  54. /**
  55. * Include Net_SSH2
  56. */
  57. if (!class_exists('Net_SSH2')) {
  58. include_once 'SSH2.php';
  59. }
  60. /**#@+
  61. * @access public
  62. * @see Net_SFTP::getLog()
  63. */
  64. /**
  65. * Returns the message numbers
  66. */
  67. define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
  68. /**
  69. * Returns the message content
  70. */
  71. define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
  72. /**
  73. * Outputs the message content in real-time.
  74. */
  75. define('NET_SFTP_LOG_REALTIME', 3);
  76. /**#@-*/
  77. /**
  78. * SFTP channel constant
  79. *
  80. * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
  81. *
  82. * @see Net_SSH2::_send_channel_packet()
  83. * @see Net_SSH2::_get_channel_packet()
  84. * @access private
  85. */
  86. define('NET_SFTP_CHANNEL', 0x100);
  87. /**#@+
  88. * @access public
  89. * @see Net_SFTP::put()
  90. */
  91. /**
  92. * Reads data from a local file.
  93. */
  94. define('NET_SFTP_LOCAL_FILE', 1);
  95. /**
  96. * Reads data from a string.
  97. */
  98. // this value isn't really used anymore but i'm keeping it reserved for historical reasons
  99. define('NET_SFTP_STRING', 2);
  100. /**
  101. * Resumes an upload
  102. */
  103. define('NET_SFTP_RESUME', 4);
  104. /**
  105. * Append a local file to an already existing remote file
  106. */
  107. define('NET_SFTP_RESUME_START', 8);
  108. /**#@-*/
  109. /**
  110. * Pure-PHP implementations of SFTP.
  111. *
  112. * @package Net_SFTP
  113. * @author Jim Wigginton <terrafrost@php.net>
  114. * @access public
  115. */
  116. class Net_SFTP extends Net_SSH2
  117. {
  118. /**
  119. * Packet Types
  120. *
  121. * @see Net_SFTP::Net_SFTP()
  122. * @var Array
  123. * @access private
  124. */
  125. var $packet_types = array();
  126. /**
  127. * Status Codes
  128. *
  129. * @see Net_SFTP::Net_SFTP()
  130. * @var Array
  131. * @access private
  132. */
  133. var $status_codes = array();
  134. /**
  135. * The Request ID
  136. *
  137. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  138. * concurrent actions, so it's somewhat academic, here.
  139. *
  140. * @var Integer
  141. * @see Net_SFTP::_send_sftp_packet()
  142. * @access private
  143. */
  144. var $request_id = false;
  145. /**
  146. * The Packet Type
  147. *
  148. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  149. * concurrent actions, so it's somewhat academic, here.
  150. *
  151. * @var Integer
  152. * @see Net_SFTP::_get_sftp_packet()
  153. * @access private
  154. */
  155. var $packet_type = -1;
  156. /**
  157. * Packet Buffer
  158. *
  159. * @var String
  160. * @see Net_SFTP::_get_sftp_packet()
  161. * @access private
  162. */
  163. var $packet_buffer = '';
  164. /**
  165. * Extensions supported by the server
  166. *
  167. * @var Array
  168. * @see Net_SFTP::_initChannel()
  169. * @access private
  170. */
  171. var $extensions = array();
  172. /**
  173. * Server SFTP version
  174. *
  175. * @var Integer
  176. * @see Net_SFTP::_initChannel()
  177. * @access private
  178. */
  179. var $version;
  180. /**
  181. * Current working directory
  182. *
  183. * @var String
  184. * @see Net_SFTP::_realpath()
  185. * @see Net_SFTP::chdir()
  186. * @access private
  187. */
  188. var $pwd = false;
  189. /**
  190. * Packet Type Log
  191. *
  192. * @see Net_SFTP::getLog()
  193. * @var Array
  194. * @access private
  195. */
  196. var $packet_type_log = array();
  197. /**
  198. * Packet Log
  199. *
  200. * @see Net_SFTP::getLog()
  201. * @var Array
  202. * @access private
  203. */
  204. var $packet_log = array();
  205. /**
  206. * Error information
  207. *
  208. * @see Net_SFTP::getSFTPErrors()
  209. * @see Net_SFTP::getLastSFTPError()
  210. * @var String
  211. * @access private
  212. */
  213. var $sftp_errors = array();
  214. /**
  215. * Stat Cache
  216. *
  217. * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
  218. * we'll cache the results.
  219. *
  220. * @see Net_SFTP::_update_stat_cache()
  221. * @see Net_SFTP::_remove_from_stat_cache()
  222. * @see Net_SFTP::_query_stat_cache()
  223. * @var Array
  224. * @access private
  225. */
  226. var $stat_cache = array();
  227. /**
  228. * Max SFTP Packet Size
  229. *
  230. * @see Net_SFTP::Net_SFTP()
  231. * @see Net_SFTP::get()
  232. * @var Array
  233. * @access private
  234. */
  235. var $max_sftp_packet;
  236. /**
  237. * Stat Cache Flag
  238. *
  239. * @see Net_SFTP::disableStatCache()
  240. * @see Net_SFTP::enableStatCache()
  241. * @var Boolean
  242. * @access private
  243. */
  244. var $use_stat_cache = true;
  245. /**
  246. * Sort Options
  247. *
  248. * @see Net_SFTP::_comparator()
  249. * @see Net_SFTP::setListOrder()
  250. * @var Array
  251. * @access private
  252. */
  253. var $sortOptions = array();
  254. /**
  255. * Default Constructor.
  256. *
  257. * Connects to an SFTP server
  258. *
  259. * @param String $host
  260. * @param optional Integer $port
  261. * @param optional Integer $timeout
  262. * @return Net_SFTP
  263. * @access public
  264. */
  265. function Net_SFTP($host, $port = 22, $timeout = 10)
  266. {
  267. parent::Net_SSH2($host, $port, $timeout);
  268. $this->max_sftp_packet = 1 << 15;
  269. $this->packet_types = array(
  270. 1 => 'NET_SFTP_INIT',
  271. 2 => 'NET_SFTP_VERSION',
  272. /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
  273. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
  274. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
  275. 3 => 'NET_SFTP_OPEN',
  276. 4 => 'NET_SFTP_CLOSE',
  277. 5 => 'NET_SFTP_READ',
  278. 6 => 'NET_SFTP_WRITE',
  279. 7 => 'NET_SFTP_LSTAT',
  280. 9 => 'NET_SFTP_SETSTAT',
  281. 11 => 'NET_SFTP_OPENDIR',
  282. 12 => 'NET_SFTP_READDIR',
  283. 13 => 'NET_SFTP_REMOVE',
  284. 14 => 'NET_SFTP_MKDIR',
  285. 15 => 'NET_SFTP_RMDIR',
  286. 16 => 'NET_SFTP_REALPATH',
  287. 17 => 'NET_SFTP_STAT',
  288. /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
  289. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  290. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
  291. 18 => 'NET_SFTP_RENAME',
  292. 19 => 'NET_SFTP_READLINK',
  293. 20 => 'NET_SFTP_SYMLINK',
  294. 101=> 'NET_SFTP_STATUS',
  295. 102=> 'NET_SFTP_HANDLE',
  296. /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
  297. SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
  298. pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
  299. 103=> 'NET_SFTP_DATA',
  300. 104=> 'NET_SFTP_NAME',
  301. 105=> 'NET_SFTP_ATTRS',
  302. 200=> 'NET_SFTP_EXTENDED'
  303. );
  304. $this->status_codes = array(
  305. 0 => 'NET_SFTP_STATUS_OK',
  306. 1 => 'NET_SFTP_STATUS_EOF',
  307. 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
  308. 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
  309. 4 => 'NET_SFTP_STATUS_FAILURE',
  310. 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
  311. 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
  312. 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
  313. 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
  314. 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
  315. 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
  316. 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
  317. 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
  318. 13 => 'NET_SFTP_STATUS_NO_MEDIA',
  319. 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
  320. 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
  321. 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
  322. 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
  323. 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
  324. 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
  325. 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
  326. 21 => 'NET_SFTP_STATUS_LINK_LOOP',
  327. 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
  328. 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
  329. 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
  330. 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
  331. 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
  332. 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
  333. 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
  334. 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
  335. 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
  336. 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
  337. );
  338. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
  339. // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
  340. $this->attributes = array(
  341. 0x00000001 => 'NET_SFTP_ATTR_SIZE',
  342. 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
  343. 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
  344. 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
  345. // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
  346. // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
  347. // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
  348. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
  349. -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
  350. );
  351. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
  352. // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
  353. // the array for that $this->open5_flags and similarily alter the constant names.
  354. $this->open_flags = array(
  355. 0x00000001 => 'NET_SFTP_OPEN_READ',
  356. 0x00000002 => 'NET_SFTP_OPEN_WRITE',
  357. 0x00000004 => 'NET_SFTP_OPEN_APPEND',
  358. 0x00000008 => 'NET_SFTP_OPEN_CREATE',
  359. 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
  360. 0x00000020 => 'NET_SFTP_OPEN_EXCL'
  361. );
  362. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
  363. // see Net_SFTP::_parseLongname() for an explanation
  364. $this->file_types = array(
  365. 1 => 'NET_SFTP_TYPE_REGULAR',
  366. 2 => 'NET_SFTP_TYPE_DIRECTORY',
  367. 3 => 'NET_SFTP_TYPE_SYMLINK',
  368. 4 => 'NET_SFTP_TYPE_SPECIAL',
  369. 5 => 'NET_SFTP_TYPE_UNKNOWN',
  370. // the followin types were first defined for use in SFTPv5+
  371. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  372. 6 => 'NET_SFTP_TYPE_SOCKET',
  373. 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
  374. 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
  375. 9 => 'NET_SFTP_TYPE_FIFO'
  376. );
  377. $this->_define_array(
  378. $this->packet_types,
  379. $this->status_codes,
  380. $this->attributes,
  381. $this->open_flags,
  382. $this->file_types
  383. );
  384. if (!defined('NET_SFTP_QUEUE_SIZE')) {
  385. define('NET_SFTP_QUEUE_SIZE', 50);
  386. }
  387. }
  388. /**
  389. * Login
  390. *
  391. * @param String $username
  392. * @param optional String $password
  393. * @return Boolean
  394. * @access public
  395. */
  396. function login($username)
  397. {
  398. $args = func_get_args();
  399. if (!call_user_func_array(array(&$this, '_login'), $args)) {
  400. return false;
  401. }
  402. $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
  403. $packet = pack('CNa*N3',
  404. NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
  405. if (!$this->_send_binary_packet($packet)) {
  406. return false;
  407. }
  408. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
  409. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  410. if ($response === false) {
  411. return false;
  412. }
  413. $packet = pack('CNNa*CNa*',
  414. NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
  415. if (!$this->_send_binary_packet($packet)) {
  416. return false;
  417. }
  418. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  419. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  420. if ($response === false) {
  421. // from PuTTY's psftp.exe
  422. $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
  423. "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
  424. "exec sftp-server";
  425. // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
  426. // is redundant
  427. $packet = pack('CNNa*CNa*',
  428. NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('exec'), 'exec', 1, strlen($command), $command);
  429. if (!$this->_send_binary_packet($packet)) {
  430. return false;
  431. }
  432. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  433. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  434. if ($response === false) {
  435. return false;
  436. }
  437. }
  438. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
  439. if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
  440. return false;
  441. }
  442. $response = $this->_get_sftp_packet();
  443. if ($this->packet_type != NET_SFTP_VERSION) {
  444. user_error('Expected SSH_FXP_VERSION');
  445. return false;
  446. }
  447. extract(unpack('Nversion', $this->_string_shift($response, 4)));
  448. $this->version = $version;
  449. while (!empty($response)) {
  450. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  451. $key = $this->_string_shift($response, $length);
  452. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  453. $value = $this->_string_shift($response, $length);
  454. $this->extensions[$key] = $value;
  455. }
  456. /*
  457. SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
  458. however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
  459. not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
  460. one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
  461. 'newline@vandyke.com' would.
  462. */
  463. /*
  464. if (isset($this->extensions['newline@vandyke.com'])) {
  465. $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
  466. unset($this->extensions['newline@vandyke.com']);
  467. }
  468. */
  469. $this->request_id = 1;
  470. /*
  471. A Note on SFTPv4/5/6 support:
  472. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
  473. "If the client wishes to interoperate with servers that support noncontiguous version
  474. numbers it SHOULD send '3'"
  475. Given that the server only sends its version number after the client has already done so, the above
  476. seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
  477. most popular.
  478. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
  479. "If the server did not send the "versions" extension, or the version-from-list was not included, the
  480. server MAY send a status response describing the failure, but MUST then close the channel without
  481. processing any further requests."
  482. So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
  483. a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
  484. v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
  485. in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
  486. channel and reopen it with a new and updated SSH_FXP_INIT packet.
  487. */
  488. switch ($this->version) {
  489. case 2:
  490. case 3:
  491. break;
  492. default:
  493. return false;
  494. }
  495. $this->pwd = $this->_realpath('.');
  496. $this->_update_stat_cache($this->pwd, array());
  497. return true;
  498. }
  499. /**
  500. * Disable the stat cache
  501. *
  502. * @access public
  503. */
  504. function disableStatCache()
  505. {
  506. $this->use_stat_cache = false;
  507. }
  508. /**
  509. * Enable the stat cache
  510. *
  511. * @access public
  512. */
  513. function enableStatCache()
  514. {
  515. $this->use_stat_cache = true;
  516. }
  517. /**
  518. * Clear the stat cache
  519. *
  520. * @access public
  521. */
  522. function clearStatCache()
  523. {
  524. $this->stat_cache = array();
  525. }
  526. /**
  527. * Returns the current directory name
  528. *
  529. * @return Mixed
  530. * @access public
  531. */
  532. function pwd()
  533. {
  534. return $this->pwd;
  535. }
  536. /**
  537. * Logs errors
  538. *
  539. * @param String $response
  540. * @param optional Integer $status
  541. * @access public
  542. */
  543. function _logError($response, $status = -1)
  544. {
  545. if ($status == -1) {
  546. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  547. }
  548. $error = $this->status_codes[$status];
  549. if ($this->version > 2) {
  550. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  551. $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
  552. } else {
  553. $this->sftp_errors[] = $error;
  554. }
  555. }
  556. /**
  557. * Canonicalize the Server-Side Path Name
  558. *
  559. * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
  560. * the absolute (canonicalized) path.
  561. *
  562. * @see Net_SFTP::chdir()
  563. * @param String $path
  564. * @return Mixed
  565. * @access private
  566. */
  567. function _realpath($path)
  568. {
  569. if ($this->pwd === false) {
  570. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
  571. if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
  572. return false;
  573. }
  574. $response = $this->_get_sftp_packet();
  575. switch ($this->packet_type) {
  576. case NET_SFTP_NAME:
  577. // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
  578. // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
  579. // at is the first part and that part is defined the same in SFTP versions 3 through 6.
  580. $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
  581. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  582. return $this->_string_shift($response, $length);
  583. case NET_SFTP_STATUS:
  584. $this->_logError($response);
  585. return false;
  586. default:
  587. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  588. return false;
  589. }
  590. }
  591. if ($path[0] != '/') {
  592. $path = $this->pwd . '/' . $path;
  593. }
  594. $path = explode('/', $path);
  595. $new = array();
  596. foreach ($path as $dir) {
  597. if (!strlen($dir)) {
  598. continue;
  599. }
  600. switch ($dir) {
  601. case '..':
  602. array_pop($new);
  603. case '.':
  604. break;
  605. default:
  606. $new[] = $dir;
  607. }
  608. }
  609. return '/' . implode('/', $new);
  610. }
  611. /**
  612. * Changes the current directory
  613. *
  614. * @param String $dir
  615. * @return Boolean
  616. * @access public
  617. */
  618. function chdir($dir)
  619. {
  620. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  621. return false;
  622. }
  623. // assume current dir if $dir is empty
  624. if ($dir === '') {
  625. $dir = './';
  626. // suffix a slash if needed
  627. } elseif ($dir[strlen($dir) - 1] != '/') {
  628. $dir.= '/';
  629. }
  630. $dir = $this->_realpath($dir);
  631. // confirm that $dir is, in fact, a valid directory
  632. if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
  633. $this->pwd = $dir;
  634. return true;
  635. }
  636. // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
  637. // the currently logged in user has the appropriate permissions or not. maybe you could see if
  638. // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
  639. // way to get those with SFTP
  640. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  641. return false;
  642. }
  643. // see Net_SFTP::nlist() for a more thorough explanation of the following
  644. $response = $this->_get_sftp_packet();
  645. switch ($this->packet_type) {
  646. case NET_SFTP_HANDLE:
  647. $handle = substr($response, 4);
  648. break;
  649. case NET_SFTP_STATUS:
  650. $this->_logError($response);
  651. return false;
  652. default:
  653. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  654. return false;
  655. }
  656. if (!$this->_close_handle($handle)) {
  657. return false;
  658. }
  659. $this->_update_stat_cache($dir, array());
  660. $this->pwd = $dir;
  661. return true;
  662. }
  663. /**
  664. * Returns a list of files in the given directory
  665. *
  666. * @param optional String $dir
  667. * @param optional Boolean $recursive
  668. * @return Mixed
  669. * @access public
  670. */
  671. function nlist($dir = '.', $recursive = false)
  672. {
  673. return $this->_nlist_helper($dir, $recursive, '');
  674. }
  675. /**
  676. * Helper method for nlist
  677. *
  678. * @param String $dir
  679. * @param Boolean $recursive
  680. * @param String $relativeDir
  681. * @return Mixed
  682. * @access private
  683. */
  684. function _nlist_helper($dir, $recursive, $relativeDir)
  685. {
  686. $files = $this->_list($dir, false);
  687. if (!$recursive) {
  688. return $files;
  689. }
  690. $result = array();
  691. foreach ($files as $value) {
  692. if ($value == '.' || $value == '..') {
  693. if ($relativeDir == '') {
  694. $result[] = $value;
  695. }
  696. continue;
  697. }
  698. if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
  699. $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
  700. $result = array_merge($result, $temp);
  701. } else {
  702. $result[] = $relativeDir . $value;
  703. }
  704. }
  705. return $result;
  706. }
  707. /**
  708. * Returns a detailed list of files in the given directory
  709. *
  710. * @param optional String $dir
  711. * @param optional Boolean $recursive
  712. * @return Mixed
  713. * @access public
  714. */
  715. function rawlist($dir = '.', $recursive = false)
  716. {
  717. $files = $this->_list($dir, true);
  718. if (!$recursive || $files === false) {
  719. return $files;
  720. }
  721. static $depth = 0;
  722. foreach ($files as $key=>$value) {
  723. if ($depth != 0 && $key == '..') {
  724. unset($files[$key]);
  725. continue;
  726. }
  727. if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)))) {
  728. $depth++;
  729. $files[$key] = $this->rawlist($dir . '/' . $key, true);
  730. $depth--;
  731. } else {
  732. $files[$key] = (object) $value;
  733. }
  734. }
  735. return $files;
  736. }
  737. /**
  738. * Reads a list, be it detailed or not, of files in the given directory
  739. *
  740. * @param String $dir
  741. * @param optional Boolean $raw
  742. * @return Mixed
  743. * @access private
  744. */
  745. function _list($dir, $raw = true)
  746. {
  747. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  748. return false;
  749. }
  750. $dir = $this->_realpath($dir . '/');
  751. if ($dir === false) {
  752. return false;
  753. }
  754. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
  755. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  756. return false;
  757. }
  758. $response = $this->_get_sftp_packet();
  759. switch ($this->packet_type) {
  760. case NET_SFTP_HANDLE:
  761. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
  762. // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
  763. // represent the length of the string and leave it at that
  764. $handle = substr($response, 4);
  765. break;
  766. case NET_SFTP_STATUS:
  767. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  768. $this->_logError($response);
  769. return false;
  770. default:
  771. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  772. return false;
  773. }
  774. $this->_update_stat_cache($dir, array());
  775. $contents = array();
  776. while (true) {
  777. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
  778. // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
  779. // SSH_MSG_CHANNEL_DATA messages is not known to me.
  780. if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
  781. return false;
  782. }
  783. $response = $this->_get_sftp_packet();
  784. switch ($this->packet_type) {
  785. case NET_SFTP_NAME:
  786. extract(unpack('Ncount', $this->_string_shift($response, 4)));
  787. for ($i = 0; $i < $count; $i++) {
  788. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  789. $shortname = $this->_string_shift($response, $length);
  790. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  791. $longname = $this->_string_shift($response, $length);
  792. $attributes = $this->_parseAttributes($response);
  793. if (!isset($attributes['type'])) {
  794. $fileType = $this->_parseLongname($longname);
  795. if ($fileType) {
  796. $attributes['type'] = $fileType;
  797. }
  798. }
  799. $contents[$shortname] = $attributes + array('filename' => $shortname);
  800. if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
  801. $this->_update_stat_cache($dir . '/' . $shortname, array());
  802. } else {
  803. if ($shortname == '..') {
  804. $temp = $this->_realpath($dir . '/..') . '/.';
  805. } else {
  806. $temp = $dir . '/' . $shortname;
  807. }
  808. $this->_update_stat_cache($temp, (object) $attributes);
  809. }
  810. // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
  811. // final SSH_FXP_STATUS packet should tell us that, already.
  812. }
  813. break;
  814. case NET_SFTP_STATUS:
  815. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  816. if ($status != NET_SFTP_STATUS_EOF) {
  817. $this->_logError($response, $status);
  818. return false;
  819. }
  820. break 2;
  821. default:
  822. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  823. return false;
  824. }
  825. }
  826. if (!$this->_close_handle($handle)) {
  827. return false;
  828. }
  829. if (count($this->sortOptions)) {
  830. uasort($contents, array(&$this, '_comparator'));
  831. }
  832. return $raw ? $contents : array_keys($contents);
  833. }
  834. /**
  835. * Compares two rawlist entries using parameters set by setListOrder()
  836. *
  837. * Intended for use with uasort()
  838. *
  839. * @param Array $a
  840. * @param Array $b
  841. * @return Integer
  842. * @access private
  843. */
  844. function _comparator($a, $b)
  845. {
  846. switch (true) {
  847. case $a['filename'] === '.' || $b['filename'] === '.':
  848. if ($a['filename'] === $b['filename']) {
  849. return 0;
  850. }
  851. return $a['filename'] === '.' ? -1 : 1;
  852. case $a['filename'] === '..' || $b['filename'] === '..':
  853. if ($a['filename'] === $b['filename']) {
  854. return 0;
  855. }
  856. return $a['filename'] === '..' ? -1 : 1;
  857. case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
  858. if (!isset($b['type'])) {
  859. return 1;
  860. }
  861. if ($b['type'] !== $a['type']) {
  862. return -1;
  863. }
  864. break;
  865. case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
  866. return 1;
  867. }
  868. foreach ($this->sortOptions as $sort => $order) {
  869. if (!isset($a[$sort]) || !isset($b[$sort])) {
  870. if (isset($a[$sort])) {
  871. return -1;
  872. }
  873. if (isset($b[$sort])) {
  874. return 1;
  875. }
  876. return 0;
  877. }
  878. switch ($sort) {
  879. case 'filename':
  880. $result = strcasecmp($a['filename'], $b['filename']);
  881. if ($result) {
  882. return $order === SORT_DESC ? -$result : $result;
  883. }
  884. break;
  885. case 'permissions':
  886. case 'mode':
  887. $a[$sort]&= 07777;
  888. $b[$sort]&= 07777;
  889. default:
  890. if ($a[$sort] === $b[$sort]) {
  891. break;
  892. }
  893. return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
  894. }
  895. }
  896. }
  897. /**
  898. * Defines how nlist() and rawlist() will be sorted - if at all.
  899. *
  900. * If sorting is enabled directories and files will be sorted independently with
  901. * directories appearing before files in the resultant array that is returned.
  902. *
  903. * Any parameter returned by stat is a valid sort parameter for this function.
  904. * Filename comparisons are case insensitive.
  905. *
  906. * Examples:
  907. *
  908. * $sftp->setListOrder('filename', SORT_ASC);
  909. * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
  910. * $sftp->setListOrder(true);
  911. * Separates directories from files but doesn't do any sorting beyond that
  912. * $sftp->setListOrder();
  913. * Don't do any sort of sorting
  914. *
  915. * @access public
  916. */
  917. function setListOrder()
  918. {
  919. $this->sortOptions = array();
  920. $args = func_get_args();
  921. if (empty($args)) {
  922. return;
  923. }
  924. $len = count($args) & 0x7FFFFFFE;
  925. for ($i = 0; $i < $len; $i+=2) {
  926. $this->sortOptions[$args[$i]] = $args[$i + 1];
  927. }
  928. if (!count($this->sortOptions)) {
  929. $this->sortOptions = array('bogus' => true);
  930. }
  931. }
  932. /**
  933. * Returns the file size, in bytes, or false, on failure
  934. *
  935. * Files larger than 4GB will show up as being exactly 4GB.
  936. *
  937. * @param String $filename
  938. * @return Mixed
  939. * @access public
  940. */
  941. function size($filename)
  942. {
  943. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  944. return false;
  945. }
  946. $result = $this->stat($filename);
  947. if ($result === false) {
  948. return false;
  949. }
  950. return isset($result['size']) ? $result['size'] : -1;
  951. }
  952. /**
  953. * Save files / directories to cache
  954. *
  955. * @param String $path
  956. * @param Mixed $value
  957. * @access private
  958. */
  959. function _update_stat_cache($path, $value)
  960. {
  961. // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
  962. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  963. $temp = &$this->stat_cache;
  964. $max = count($dirs) - 1;
  965. foreach ($dirs as $i=>$dir) {
  966. if (!isset($temp[$dir])) {
  967. $temp[$dir] = array();
  968. }
  969. if ($i === $max) {
  970. $temp[$dir] = $value;
  971. break;
  972. }
  973. $temp = &$temp[$dir];
  974. }
  975. }
  976. /**
  977. * Remove files / directories from cache
  978. *
  979. * @param String $path
  980. * @return Boolean
  981. * @access private
  982. */
  983. function _remove_from_stat_cache($path)
  984. {
  985. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  986. $temp = &$this->stat_cache;
  987. $max = count($dirs) - 1;
  988. foreach ($dirs as $i=>$dir) {
  989. if ($i === $max) {
  990. unset($temp[$dir]);
  991. return true;
  992. }
  993. if (!isset($temp[$dir])) {
  994. return false;
  995. }
  996. $temp = &$temp[$dir];
  997. }
  998. }
  999. /**
  1000. * Checks cache for path
  1001. *
  1002. * Mainly used by file_exists
  1003. *
  1004. * @param String $dir
  1005. * @return Mixed
  1006. * @access private
  1007. */
  1008. function _query_stat_cache($path)
  1009. {
  1010. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  1011. $temp = &$this->stat_cache;
  1012. foreach ($dirs as $dir) {
  1013. if (!isset($temp[$dir])) {
  1014. return null;
  1015. }
  1016. $temp = &$temp[$dir];
  1017. }
  1018. return $temp;
  1019. }
  1020. /**
  1021. * Returns general information about a file.
  1022. *
  1023. * Returns an array on success and false otherwise.
  1024. *
  1025. * @param String $filename
  1026. * @return Mixed
  1027. * @access public
  1028. */
  1029. function stat($filename)
  1030. {
  1031. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1032. return false;
  1033. }
  1034. $filename = $this->_realpath($filename);
  1035. if ($filename === false) {
  1036. return false;
  1037. }
  1038. if ($this->use_stat_cache) {
  1039. $result = $this->_query_stat_cache($filename);
  1040. if (is_array($result) && isset($result['.'])) {
  1041. return (array) $result['.'];
  1042. }
  1043. if (is_object($result)) {
  1044. return (array) $result;
  1045. }
  1046. }
  1047. $stat = $this->_stat($filename, NET_SFTP_STAT);
  1048. if ($stat === false) {
  1049. $this->_remove_from_stat_cache($filename);
  1050. return false;
  1051. }
  1052. if (isset($stat['type'])) {
  1053. if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1054. $filename.= '/.';
  1055. }
  1056. $this->_update_stat_cache($filename, (object) $stat);
  1057. return $stat;
  1058. }
  1059. $pwd = $this->pwd;
  1060. $stat['type'] = $this->chdir($filename) ?
  1061. NET_SFTP_TYPE_DIRECTORY :
  1062. NET_SFTP_TYPE_REGULAR;
  1063. $this->pwd = $pwd;
  1064. if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1065. $filename.= '/.';
  1066. }
  1067. $this->_update_stat_cache($filename, (object) $stat);
  1068. return $stat;
  1069. }
  1070. /**
  1071. * Returns general information about a file or symbolic link.
  1072. *
  1073. * Returns an array on success and false otherwise.
  1074. *
  1075. * @param String $filename
  1076. * @return Mixed
  1077. * @access public
  1078. */
  1079. function lstat($filename)
  1080. {
  1081. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1082. return false;
  1083. }
  1084. $filename = $this->_realpath($filename);
  1085. if ($filename === false) {
  1086. return false;
  1087. }
  1088. if ($this->use_stat_cache) {
  1089. $result = $this->_query_stat_cache($filename);
  1090. if (is_array($result) && isset($result['.'])) {
  1091. return (array) $result['.'];
  1092. }
  1093. if (is_object($result)) {
  1094. return (array) $result;
  1095. }
  1096. }
  1097. $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
  1098. if ($lstat === false) {
  1099. $this->_remove_from_stat_cache($filename);
  1100. return false;
  1101. }
  1102. if (isset($lstat['type'])) {
  1103. if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1104. $filename.= '/.';
  1105. }
  1106. $this->_update_stat_cache($filename, (object) $lstat);
  1107. return $lstat;
  1108. }
  1109. $stat = $this->_stat($filename, NET_SFTP_STAT);
  1110. if ($lstat != $stat) {
  1111. $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
  1112. $this->_update_stat_cache($filename, (object) $lstat);
  1113. return $stat;
  1114. }
  1115. $pwd = $this->pwd;
  1116. $lstat['type'] = $this->chdir($filename) ?
  1117. NET_SFTP_TYPE_DIRECTORY :
  1118. NET_SFTP_TYPE_REGULAR;
  1119. $this->pwd = $pwd;
  1120. if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1121. $filename.= '/.';
  1122. }
  1123. $this->_update_stat_cache($filename, (object) $lstat);
  1124. return $lstat;
  1125. }
  1126. /**
  1127. * Returns general information about a file or symbolic link
  1128. *
  1129. * Determines information without calling Net_SFTP::_realpath().
  1130. * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
  1131. *
  1132. * @param String $filename
  1133. * @param Integer $type
  1134. * @return Mixed
  1135. * @access private
  1136. */
  1137. function _stat($filename, $type)
  1138. {
  1139. // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1140. $packet = pack('Na*', strlen($filename), $filename);
  1141. if (!$this->_send_sftp_packet($type, $packet)) {
  1142. return false;
  1143. }
  1144. $response = $this->_get_sftp_packet();
  1145. switch ($this->packet_type) {
  1146. case NET_SFTP_ATTRS:
  1147. return $this->_parseAttributes($response);
  1148. case NET_SFTP_STATUS:
  1149. $this->_logError($response);
  1150. return false;
  1151. }
  1152. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  1153. return false;
  1154. }
  1155. /**
  1156. * Truncates a file to a given length
  1157. *
  1158. * @param String $filename
  1159. * @param Integer $new_size
  1160. * @return Boolean
  1161. * @access public
  1162. */
  1163. function truncate($filename, $new_size)
  1164. {
  1165. $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
  1166. return $this->_setstat($filename, $attr, false);
  1167. }
  1168. /**
  1169. * Sets access and modification time of file.
  1170. *
  1171. * If the file does not exist, it will be created.
  1172. *
  1173. * @param String $filename
  1174. * @param optional Integer $time
  1175. * @param optional Integer $atime
  1176. * @return Boolean
  1177. * @access public
  1178. */
  1179. function touch($filename, $time = null, $atime = null)
  1180. {
  1181. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1182. return false;
  1183. }
  1184. $filename = $this->_realpath($filename);
  1185. if ($filename === false) {
  1186. return false;
  1187. }
  1188. if (!isset($time)) {
  1189. $time = time();
  1190. }
  1191. if (!isset($atime)) {
  1192. $atime = $time;
  1193. }
  1194. $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
  1195. $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
  1196. $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
  1197. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  1198. return false;
  1199. }
  1200. $response = $this->_get_sftp_packet();
  1201. switch ($this->packet_type) {
  1202. case NET_SFTP_HANDLE:
  1203. return $this->_close_handle(substr($response, 4));
  1204. case NET_SFTP_STATUS:
  1205. $this->_logError($response);
  1206. break;
  1207. default:
  1208. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  1209. return false;
  1210. }
  1211. return $this->_setstat($filename, $attr, false);
  1212. }
  1213. /**
  1214. * Changes file or directory owner
  1215. *
  1216. * Returns true on success or false on error.
  1217. *
  1218. * @param String $filename
  1219. * @param Integer $uid
  1220. * @param optional Boolean $recursive
  1221. * @return Boolean
  1222. * @access public
  1223. */
  1224. function chown($filename, $uid, $recursive = false)
  1225. {
  1226. // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
  1227. // "if the owner or group is specified as -1, then that ID is not changed"
  1228. $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
  1229. return $this->_setstat($filename, $attr, $recursive);
  1230. }
  1231. /**
  1232. * Changes file or directory group
  1233. *
  1234. * Returns true on success or false on error.
  1235. *
  1236. * @param String $filename
  1237. * @param Integer $gid
  1238. * @param optional Boolean $recursive
  1239. * @return Boolean
  1240. * @access public
  1241. */
  1242. function chgrp($filename, $gid, $recursive = false)
  1243. {
  1244. $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
  1245. return $this->_setstat($filename, $attr, $recursive);
  1246. }
  1247. /**
  1248. * Set permissions on a file.
  1249. *
  1250. * Returns the new file permissions on success or false on error.
  1251. * If $recursive is true than this just returns true or false.
  1252. *
  1253. * @param Integer $mode
  1254. * @param String $filename
  1255. * @param optional Boolean $recursive
  1256. * @return Mixed
  1257. * @access public
  1258. */
  1259. function chmod($mode, $filename, $recursive = false)
  1260. {
  1261. if (is_string($mode) && is_int($filename)) {
  1262. $temp = $mode;
  1263. $mode = $filename;
  1264. $filename = $temp;
  1265. }
  1266. $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
  1267. if (!$this->_setstat($filename, $attr, $recursive)) {
  1268. return false;
  1269. }
  1270. if ($recursive) {
  1271. return true;
  1272. }
  1273. // rather than return what the permissions *should* be, we'll return what they actually are. this will also
  1274. // tell us if the file actually exists.
  1275. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1276. $packet = pack('Na*', strlen($filename), $filename);
  1277. if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
  1278. return false;
  1279. }
  1280. $response = $this->_get_sftp_packet();
  1281. switch ($this->packet_type) {
  1282. case NET_SFTP_ATTRS:
  1283. $attrs = $this->_parseAttributes($response);
  1284. return $attrs['permissions'];
  1285. case NET_SFTP_STATUS:
  1286. $this->_logError($response);
  1287. return false;
  1288. }
  1289. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  1290. return false;
  1291. }
  1292. /**
  1293. * Sets information about a file
  1294. *
  1295. * @param String $filename
  1296. * @param String $attr
  1297. * @param Boolean $recursive
  1298. * @return Boolean
  1299. * @access private
  1300. */
  1301. function _setstat($filename, $attr, $recursive)
  1302. {
  1303. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1304. return false;
  1305. }
  1306. $filename = $this->_realpath($filename);
  1307. if ($filename === false) {
  1308. return false;
  1309. }
  1310. $this->_remove_from_stat_cache($filename);
  1311. if ($recursive) {
  1312. $i = 0;
  1313. $result = $this->_setstat_recursive($filename, $attr, $i);
  1314. $this->_read_put_responses($i);
  1315. return $result;
  1316. }
  1317. // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
  1318. // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
  1319. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
  1320. return false;
  1321. }
  1322. /*
  1323. "Because some systems must use separate system calls to set various attributes, it is possible that a failure
  1324. response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
  1325. servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
  1326. -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
  1327. */
  1328. $response = $this->_get_sftp_packet();
  1329. if ($this->packet_type != NET_SFTP_STATUS) {
  1330. user_error('Expected SSH_FXP_STATUS');
  1331. return false;
  1332. }
  1333. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1334. if ($status != NET_SFTP_STATUS_OK) {
  1335. $this->_logError($response, $status);
  1336. return false;
  1337. }
  1338. return true;
  1339. }
  1340. /**
  1341. * Recursively sets information on directories on the SFTP server
  1342. *
  1343. * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  1344. *
  1345. * @param String $path
  1346. * @param String $attr
  1347. * @param Integer $i
  1348. * @return Boolean
  1349. * @access private
  1350. */
  1351. function _setstat_recursive($path, $attr, &$i)
  1352. {
  1353. if (!$this->_read_put_responses($i)) {
  1354. return false;
  1355. }
  1356. $i = 0;
  1357. $entries = $this->_list($path, true);
  1358. if ($entries === false) {
  1359. return $this->_setstat($path, $attr, false);
  1360. }
  1361. // normally $entries would have at least . and .. but it might not if the directories
  1362. // permissions didn't allow reading
  1363. if (empty($entries)) {
  1364. return false;
  1365. }
  1366. unset($entries['.'], $entries['..']);
  1367. foreach ($entries as $filename=>$props) {
  1368. if (!isset($props['type'])) {
  1369. return false;
  1370. }
  1371. $temp = $path . '/' . $filename;
  1372. if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1373. if (!$this->_setstat_recursive($temp, $attr, $i)) {
  1374. return false;
  1375. }
  1376. } else {
  1377. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
  1378. return false;
  1379. }
  1380. $i++;
  1381. if ($i >= NET_SFTP_QUEUE_SIZE) {
  1382. if (!$this->_read_put_responses($i)) {
  1383. return false;
  1384. }
  1385. $i = 0;
  1386. }
  1387. }
  1388. }
  1389. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
  1390. return false;
  1391. }
  1392. $i++;
  1393. if ($i >= NET_SFTP_QUEUE_SIZE) {
  1394. if (!$this->_read_put_responses($i)) {
  1395. return false;
  1396. }
  1397. $i =

Large files files are truncated, but you can click here to view the full file