PageRenderTime 50ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/ssh-sftp-updater-support/phpseclib/Net/SFTP.php

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