/phpseclib/Net/SFTP.php

http://github.com/phpseclib/phpseclib · PHP · 3532 lines · 1954 code · 352 blank · 1226 comment · 404 complexity · 93d5ca36f0d39437f9d98f0a1889f5df MD5 · raw file

  1. <?php
  2. /**
  3. * Pure-PHP implementation of SFTP.
  4. *
  5. * PHP version 5
  6. *
  7. * Supports SFTPv2/3/4/5/6. Defaults to v3.
  8. *
  9. * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
  10. *
  11. * Here's a short example of how to use this library:
  12. * <code>
  13. * <?php
  14. * include 'vendor/autoload.php';
  15. *
  16. * $sftp = new \phpseclib3\Net\SFTP('www.domain.tld');
  17. * if (!$sftp->login('username', 'password')) {
  18. * exit('Login Failed');
  19. * }
  20. *
  21. * echo $sftp->pwd() . "\r\n";
  22. * $sftp->put('filename.ext', 'hello, world!');
  23. * print_r($sftp->nlist());
  24. * ?>
  25. * </code>
  26. *
  27. * @category Net
  28. * @package SFTP
  29. * @author Jim Wigginton <terrafrost@php.net>
  30. * @copyright 2009 Jim Wigginton
  31. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  32. * @link http://phpseclib.sourceforge.net
  33. */
  34. namespace phpseclib3\Net;
  35. use phpseclib3\Exception\FileNotFoundException;
  36. use phpseclib3\Common\Functions\Strings;
  37. use phpseclib3\Crypt\Common\AsymmetricKey;
  38. use phpseclib3\System\SSH\Agent;
  39. /**
  40. * Pure-PHP implementations of SFTP.
  41. *
  42. * @package SFTP
  43. * @author Jim Wigginton <terrafrost@php.net>
  44. * @access public
  45. */
  46. class SFTP extends SSH2
  47. {
  48. /**
  49. * SFTP channel constant
  50. *
  51. * \phpseclib3\Net\SSH2::exec() uses 0 and \phpseclib3\Net\SSH2::read() / \phpseclib3\Net\SSH2::write() use 1.
  52. *
  53. * @see \phpseclib3\Net\SSH2::send_channel_packet()
  54. * @see \phpseclib3\Net\SSH2::get_channel_packet()
  55. * @access private
  56. */
  57. const CHANNEL = 0x100;
  58. /**
  59. * Reads data from a local file.
  60. *
  61. * @access public
  62. * @see \phpseclib3\Net\SFTP::put()
  63. */
  64. const SOURCE_LOCAL_FILE = 1;
  65. /**
  66. * Reads data from a string.
  67. *
  68. * @access public
  69. * @see \phpseclib3\Net\SFTP::put()
  70. */
  71. // this value isn't really used anymore but i'm keeping it reserved for historical reasons
  72. const SOURCE_STRING = 2;
  73. /**
  74. * Reads data from callback:
  75. * function callback($length) returns string to proceed, null for EOF
  76. *
  77. * @access public
  78. * @see \phpseclib3\Net\SFTP::put()
  79. */
  80. const SOURCE_CALLBACK = 16;
  81. /**
  82. * Resumes an upload
  83. *
  84. * @access public
  85. * @see \phpseclib3\Net\SFTP::put()
  86. */
  87. const RESUME = 4;
  88. /**
  89. * Append a local file to an already existing remote file
  90. *
  91. * @access public
  92. * @see \phpseclib3\Net\SFTP::put()
  93. */
  94. const RESUME_START = 8;
  95. /**
  96. * Packet Types
  97. *
  98. * @see self::__construct()
  99. * @var array
  100. * @access private
  101. */
  102. private $packet_types = [];
  103. /**
  104. * Status Codes
  105. *
  106. * @see self::__construct()
  107. * @var array
  108. * @access private
  109. */
  110. private $status_codes = [];
  111. /**
  112. * The Request ID
  113. *
  114. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  115. * concurrent actions, so it's somewhat academic, here.
  116. *
  117. * @var boolean
  118. * @see self::_send_sftp_packet()
  119. * @access private
  120. */
  121. private $use_request_id = false;
  122. /**
  123. * The Packet Type
  124. *
  125. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  126. * concurrent actions, so it's somewhat academic, here.
  127. *
  128. * @var int
  129. * @see self::_get_sftp_packet()
  130. * @access private
  131. */
  132. private $packet_type = -1;
  133. /**
  134. * Packet Buffer
  135. *
  136. * @var string
  137. * @see self::_get_sftp_packet()
  138. * @access private
  139. */
  140. private $packet_buffer = '';
  141. /**
  142. * Extensions supported by the server
  143. *
  144. * @var array
  145. * @see self::_initChannel()
  146. * @access private
  147. */
  148. private $extensions = [];
  149. /**
  150. * Server SFTP version
  151. *
  152. * @var int
  153. * @see self::_initChannel()
  154. * @access private
  155. */
  156. private $version;
  157. /**
  158. * Default Server SFTP version
  159. *
  160. * @var int
  161. * @see self::_initChannel()
  162. * @access private
  163. */
  164. private $defaultVersion;
  165. /**
  166. * Preferred SFTP version
  167. *
  168. * @var int
  169. * @see self::_initChannel()
  170. * @access private
  171. */
  172. private $preferredVersion = 3;
  173. /**
  174. * Current working directory
  175. *
  176. * @var string
  177. * @see self::realpath()
  178. * @see self::chdir()
  179. * @access private
  180. */
  181. private $pwd = false;
  182. /**
  183. * Packet Type Log
  184. *
  185. * @see self::getLog()
  186. * @var array
  187. * @access private
  188. */
  189. private $packet_type_log = [];
  190. /**
  191. * Packet Log
  192. *
  193. * @see self::getLog()
  194. * @var array
  195. * @access private
  196. */
  197. private $packet_log = [];
  198. /**
  199. * Error information
  200. *
  201. * @see self::getSFTPErrors()
  202. * @see self::getLastSFTPError()
  203. * @var array
  204. * @access private
  205. */
  206. private $sftp_errors = [];
  207. /**
  208. * Stat Cache
  209. *
  210. * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
  211. * we'll cache the results.
  212. *
  213. * @see self::_update_stat_cache()
  214. * @see self::_remove_from_stat_cache()
  215. * @see self::_query_stat_cache()
  216. * @var array
  217. * @access private
  218. */
  219. private $stat_cache = [];
  220. /**
  221. * Max SFTP Packet Size
  222. *
  223. * @see self::__construct()
  224. * @see self::get()
  225. * @var array
  226. * @access private
  227. */
  228. private $max_sftp_packet;
  229. /**
  230. * Stat Cache Flag
  231. *
  232. * @see self::disableStatCache()
  233. * @see self::enableStatCache()
  234. * @var bool
  235. * @access private
  236. */
  237. private $use_stat_cache = true;
  238. /**
  239. * Sort Options
  240. *
  241. * @see self::_comparator()
  242. * @see self::setListOrder()
  243. * @var array
  244. * @access private
  245. */
  246. protected $sortOptions = [];
  247. /**
  248. * Canonicalization Flag
  249. *
  250. * Determines whether or not paths should be canonicalized before being
  251. * passed on to the remote server.
  252. *
  253. * @see self::enablePathCanonicalization()
  254. * @see self::disablePathCanonicalization()
  255. * @see self::realpath()
  256. * @var bool
  257. * @access private
  258. */
  259. private $canonicalize_paths = true;
  260. /**
  261. * Request Buffers
  262. *
  263. * @see self::_get_sftp_packet()
  264. * @var array
  265. * @access private
  266. */
  267. private $requestBuffer = [];
  268. /**
  269. * Preserve timestamps on file downloads / uploads
  270. *
  271. * @see self::get()
  272. * @see self::put()
  273. * @var bool
  274. * @access private
  275. */
  276. private $preserveTime = false;
  277. /**
  278. * Arbitrary Length Packets Flag
  279. *
  280. * Determines whether or not packets of any length should be allowed,
  281. * in cases where the server chooses the packet length (such as
  282. * directory listings). By default, packets are only allowed to be
  283. * 256 * 1024 bytes (SFTP_MAX_MSG_LENGTH from OpenSSH's sftp-common.h)
  284. *
  285. * @see self::enableArbitraryLengthPackets()
  286. * @see self::_get_sftp_packet()
  287. * @var bool
  288. * @access private
  289. */
  290. private $allow_arbitrary_length_packets = false;
  291. /**
  292. * Was the last packet due to the channels being closed or not?
  293. *
  294. * @see self::get()
  295. * @see self::get_sftp_packet()
  296. * @var bool
  297. * @access private
  298. */
  299. private $channel_close = false;
  300. /**
  301. * Has the SFTP channel been partially negotiated?
  302. *
  303. * @var bool
  304. * @access private
  305. */
  306. private $partial_init = false;
  307. /**
  308. * Default Constructor.
  309. *
  310. * Connects to an SFTP server
  311. *
  312. * @param string $host
  313. * @param int $port
  314. * @param int $timeout
  315. * @return \phpseclib3\Net\SFTP
  316. * @access public
  317. */
  318. public function __construct($host, $port = 22, $timeout = 10)
  319. {
  320. parent::__construct($host, $port, $timeout);
  321. $this->max_sftp_packet = 1 << 15;
  322. $this->packet_types = [
  323. 1 => 'NET_SFTP_INIT',
  324. 2 => 'NET_SFTP_VERSION',
  325. 3 => 'NET_SFTP_OPEN',
  326. 4 => 'NET_SFTP_CLOSE',
  327. 5 => 'NET_SFTP_READ',
  328. 6 => 'NET_SFTP_WRITE',
  329. 7 => 'NET_SFTP_LSTAT',
  330. 9 => 'NET_SFTP_SETSTAT',
  331. 10 => 'NET_SFTP_FSETSTAT',
  332. 11 => 'NET_SFTP_OPENDIR',
  333. 12 => 'NET_SFTP_READDIR',
  334. 13 => 'NET_SFTP_REMOVE',
  335. 14 => 'NET_SFTP_MKDIR',
  336. 15 => 'NET_SFTP_RMDIR',
  337. 16 => 'NET_SFTP_REALPATH',
  338. 17 => 'NET_SFTP_STAT',
  339. 18 => 'NET_SFTP_RENAME',
  340. 19 => 'NET_SFTP_READLINK',
  341. 20 => 'NET_SFTP_SYMLINK',
  342. 21 => 'NET_SFTP_LINK',
  343. 101=> 'NET_SFTP_STATUS',
  344. 102=> 'NET_SFTP_HANDLE',
  345. 103=> 'NET_SFTP_DATA',
  346. 104=> 'NET_SFTP_NAME',
  347. 105=> 'NET_SFTP_ATTRS',
  348. 200=> 'NET_SFTP_EXTENDED'
  349. ];
  350. $this->status_codes = [
  351. 0 => 'NET_SFTP_STATUS_OK',
  352. 1 => 'NET_SFTP_STATUS_EOF',
  353. 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
  354. 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
  355. 4 => 'NET_SFTP_STATUS_FAILURE',
  356. 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
  357. 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
  358. 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
  359. 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
  360. 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
  361. 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
  362. 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
  363. 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
  364. 13 => 'NET_SFTP_STATUS_NO_MEDIA',
  365. 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
  366. 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
  367. 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
  368. 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
  369. 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
  370. 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
  371. 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
  372. 21 => 'NET_SFTP_STATUS_LINK_LOOP',
  373. 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
  374. 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
  375. 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
  376. 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
  377. 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
  378. 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
  379. 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
  380. 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
  381. 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
  382. 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
  383. ];
  384. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
  385. // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
  386. $this->attributes = [
  387. 0x00000001 => 'NET_SFTP_ATTR_SIZE',
  388. 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
  389. 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+
  390. 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
  391. 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
  392. 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+
  393. 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
  394. 0x00000040 => 'NET_SFTP_ATTR_ACL',
  395. 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
  396. 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+
  397. 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
  398. 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
  399. 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
  400. 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
  401. 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
  402. 0x00008000 => 'NET_SFTP_ATTR_CTIME',
  403. // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
  404. // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
  405. // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
  406. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
  407. (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
  408. ];
  409. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
  410. // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
  411. // the array for that $this->open5_flags and similarly alter the constant names.
  412. $this->open_flags = [
  413. 0x00000001 => 'NET_SFTP_OPEN_READ',
  414. 0x00000002 => 'NET_SFTP_OPEN_WRITE',
  415. 0x00000004 => 'NET_SFTP_OPEN_APPEND',
  416. 0x00000008 => 'NET_SFTP_OPEN_CREATE',
  417. 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
  418. 0x00000020 => 'NET_SFTP_OPEN_EXCL',
  419. 0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
  420. ];
  421. // SFTPv5+ changed the flags up:
  422. // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
  423. $this->open_flags5 = [
  424. // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
  425. 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
  426. 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
  427. 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
  428. 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
  429. 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
  430. // the rest of the flags are not supported
  431. 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
  432. 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
  433. 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
  434. 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
  435. 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
  436. 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
  437. 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
  438. 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
  439. 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
  440. 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
  441. 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
  442. 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
  443. 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
  444. ];
  445. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
  446. // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
  447. $this->file_types = [
  448. 1 => 'NET_SFTP_TYPE_REGULAR',
  449. 2 => 'NET_SFTP_TYPE_DIRECTORY',
  450. 3 => 'NET_SFTP_TYPE_SYMLINK',
  451. 4 => 'NET_SFTP_TYPE_SPECIAL',
  452. 5 => 'NET_SFTP_TYPE_UNKNOWN',
  453. // the following types were first defined for use in SFTPv5+
  454. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  455. 6 => 'NET_SFTP_TYPE_SOCKET',
  456. 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
  457. 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
  458. 9 => 'NET_SFTP_TYPE_FIFO'
  459. ];
  460. $this->define_array(
  461. $this->packet_types,
  462. $this->status_codes,
  463. $this->attributes,
  464. $this->open_flags,
  465. $this->open_flags5,
  466. $this->file_types
  467. );
  468. if (!defined('NET_SFTP_QUEUE_SIZE')) {
  469. define('NET_SFTP_QUEUE_SIZE', 32);
  470. }
  471. if (!defined('NET_SFTP_UPLOAD_QUEUE_SIZE')) {
  472. define('NET_SFTP_UPLOAD_QUEUE_SIZE', 1024);
  473. }
  474. }
  475. /**
  476. * Check a few things before SFTP functions are called
  477. *
  478. * @return bool
  479. * @access public
  480. */
  481. private function precheck()
  482. {
  483. if (!($this->bitmap & SSH2::MASK_LOGIN)) {
  484. return false;
  485. }
  486. if ($this->pwd === false) {
  487. return $this->init_sftp_connection();
  488. }
  489. return true;
  490. }
  491. /**
  492. * Partially initialize an SFTP connection
  493. *
  494. * @throws \UnexpectedValueException on receipt of unexpected packets
  495. * @return bool
  496. * @access public
  497. */
  498. private function partial_init_sftp_connection()
  499. {
  500. $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
  501. $packet = Strings::packSSH2(
  502. 'CsN3',
  503. NET_SSH2_MSG_CHANNEL_OPEN,
  504. 'session',
  505. self::CHANNEL,
  506. $this->window_size,
  507. 0x4000
  508. );
  509. $this->send_binary_packet($packet);
  510. $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
  511. $response = $this->get_channel_packet(self::CHANNEL, true);
  512. if ($response === true && $this->isTimeout()) {
  513. return false;
  514. }
  515. $packet = Strings::packSSH2(
  516. 'CNsbs',
  517. NET_SSH2_MSG_CHANNEL_REQUEST,
  518. $this->server_channels[self::CHANNEL],
  519. 'subsystem',
  520. true,
  521. 'sftp'
  522. );
  523. $this->send_binary_packet($packet);
  524. $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  525. $response = $this->get_channel_packet(self::CHANNEL, true);
  526. if ($response === false) {
  527. // from PuTTY's psftp.exe
  528. $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
  529. "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
  530. "exec sftp-server";
  531. // 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
  532. // is redundant
  533. $packet = Strings::packSSH2(
  534. 'CNsCs',
  535. NET_SSH2_MSG_CHANNEL_REQUEST,
  536. $this->server_channels[self::CHANNEL],
  537. 'exec',
  538. 1,
  539. $command
  540. );
  541. $this->send_binary_packet($packet);
  542. $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  543. $response = $this->get_channel_packet(self::CHANNEL, true);
  544. if ($response === false) {
  545. return false;
  546. }
  547. } else if ($response === true && $this->isTimeout()) {
  548. return false;
  549. }
  550. $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
  551. $this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3");
  552. $response = $this->get_sftp_packet();
  553. if ($this->packet_type != NET_SFTP_VERSION) {
  554. throw new \UnexpectedValueException('Expected NET_SFTP_VERSION. '
  555. . 'Got packet type: ' . $this->packet_type);
  556. }
  557. $this->use_request_id = true;
  558. list($this->defaultVersion) = Strings::unpackSSH2('N', $response);
  559. while (!empty($response)) {
  560. list($key, $value) = Strings::unpackSSH2('ss', $response);
  561. $this->extensions[$key] = $value;
  562. }
  563. $this->partial_init = true;
  564. return true;
  565. }
  566. /**
  567. * (Re)initializes the SFTP channel
  568. *
  569. * @return bool
  570. * @access private
  571. */
  572. private function init_sftp_connection()
  573. {
  574. if (!$this->partial_init && !$this->partial_init_sftp_connection()) {
  575. return false;
  576. }
  577. /*
  578. A Note on SFTPv4/5/6 support:
  579. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
  580. "If the client wishes to interoperate with servers that support noncontiguous version
  581. numbers it SHOULD send '3'"
  582. Given that the server only sends its version number after the client has already done so, the above
  583. seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
  584. most popular.
  585. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
  586. "If the server did not send the "versions" extension, or the version-from-list was not included, the
  587. server MAY send a status response describing the failure, but MUST then close the channel without
  588. processing any further requests."
  589. So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
  590. a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
  591. v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
  592. in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib3\Net\SFTP would do is close the
  593. channel and reopen it with a new and updated SSH_FXP_INIT packet.
  594. */
  595. $this->version = $this->defaultVersion;
  596. if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) {
  597. $versions = explode(',', $this->extensions['versions']);
  598. $supported = [6, 5, 4];
  599. if ($this->preferredVersion) {
  600. $supported = array_diff($supported, [$this->preferredVersion]);
  601. array_unshift($supported, $this->preferredVersion);
  602. }
  603. foreach ($supported as $ver) {
  604. if (in_array($ver, $versions)) {
  605. if ($ver === $this->version) {
  606. break;
  607. }
  608. $this->version = (int) $ver;
  609. $packet = Strings::packSSH2('ss', 'version-select', "$ver");
  610. $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
  611. $response = $this->get_sftp_packet();
  612. if ($this->packet_type != NET_SFTP_STATUS) {
  613. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  614. . 'Got packet type: ' . $this->packet_type);
  615. }
  616. list($status) = Strings::unpackSSH2('N', $response);
  617. if ($status != NET_SFTP_STATUS_OK) {
  618. $this->logError($response, $status);
  619. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS_OK. '
  620. . ' Got ' . $status);
  621. }
  622. break;
  623. }
  624. }
  625. }
  626. /*
  627. SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
  628. however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
  629. not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
  630. one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
  631. 'newline@vandyke.com' would.
  632. */
  633. /*
  634. if (isset($this->extensions['newline@vandyke.com'])) {
  635. $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
  636. unset($this->extensions['newline@vandyke.com']);
  637. }
  638. */
  639. if ($this->version < 2 || $this->version > 6) {
  640. return false;
  641. }
  642. $this->pwd = true;
  643. $this->pwd = $this->realpath('.');
  644. $this->update_stat_cache($this->pwd, []);
  645. return true;
  646. }
  647. /**
  648. * Disable the stat cache
  649. *
  650. * @access public
  651. */
  652. public function disableStatCache()
  653. {
  654. $this->use_stat_cache = false;
  655. }
  656. /**
  657. * Enable the stat cache
  658. *
  659. * @access public
  660. */
  661. public function enableStatCache()
  662. {
  663. $this->use_stat_cache = true;
  664. }
  665. /**
  666. * Clear the stat cache
  667. *
  668. * @access public
  669. */
  670. public function clearStatCache()
  671. {
  672. $this->stat_cache = [];
  673. }
  674. /**
  675. * Enable path canonicalization
  676. *
  677. * @access public
  678. */
  679. public function enablePathCanonicalization()
  680. {
  681. $this->canonicalize_paths = true;
  682. }
  683. /**
  684. * Enable path canonicalization
  685. *
  686. * @access public
  687. */
  688. public function disablePathCanonicalization()
  689. {
  690. $this->canonicalize_paths = false;
  691. }
  692. /**
  693. * Enable arbitrary length packets
  694. *
  695. * @access public
  696. */
  697. public function enableArbitraryLengthPackets()
  698. {
  699. $this->allow_arbitrary_length_packets = true;
  700. }
  701. /**
  702. * Disable arbitrary length packets
  703. *
  704. * @access public
  705. */
  706. public function disableArbitraryLengthPackets()
  707. {
  708. $this->allow_arbitrary_length_packets = false;
  709. }
  710. /**
  711. * Returns the current directory name
  712. *
  713. * @return mixed
  714. * @access public
  715. */
  716. public function pwd()
  717. {
  718. if (!$this->precheck()) {
  719. return false;
  720. }
  721. return $this->pwd;
  722. }
  723. /**
  724. * Logs errors
  725. *
  726. * @param string $response
  727. * @param int $status
  728. * @access private
  729. */
  730. private function logError($response, $status = -1)
  731. {
  732. if ($status == -1) {
  733. list($status) = Strings::unpackSSH2('N', $response);
  734. }
  735. $error = $this->status_codes[$status];
  736. if ($this->version > 2) {
  737. list($message) = Strings::unpackSSH2('s', $response);
  738. $this->sftp_errors[] = "$error: $message";
  739. } else {
  740. $this->sftp_errors[] = $error;
  741. }
  742. }
  743. /**
  744. * Canonicalize the Server-Side Path Name
  745. *
  746. * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
  747. * the absolute (canonicalized) path.
  748. *
  749. * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
  750. *
  751. * @see self::chdir()
  752. * @see self::disablePathCanonicalization()
  753. * @param string $path
  754. * @throws \UnexpectedValueException on receipt of unexpected packets
  755. * @return mixed
  756. * @access public
  757. */
  758. public function realpath($path)
  759. {
  760. if ($this->precheck() === false) {
  761. return false;
  762. }
  763. if (!$this->canonicalize_paths) {
  764. return $path;
  765. }
  766. if ($this->pwd === true) {
  767. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
  768. $this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path));
  769. $response = $this->get_sftp_packet();
  770. switch ($this->packet_type) {
  771. case NET_SFTP_NAME:
  772. // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
  773. // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
  774. // at is the first part and that part is defined the same in SFTP versions 3 through 6.
  775. list(, $filename) = Strings::unpackSSH2('Ns', $response);
  776. return $filename;
  777. case NET_SFTP_STATUS:
  778. $this->logError($response);
  779. return false;
  780. default:
  781. throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
  782. . 'Got packet type: ' . $this->packet_type);
  783. }
  784. }
  785. if (!strlen($path) || $path[0] != '/') {
  786. $path = $this->pwd . '/' . $path;
  787. }
  788. $path = explode('/', $path);
  789. $new = [];
  790. foreach ($path as $dir) {
  791. if (!strlen($dir)) {
  792. continue;
  793. }
  794. switch ($dir) {
  795. case '..':
  796. array_pop($new);
  797. case '.':
  798. break;
  799. default:
  800. $new[] = $dir;
  801. }
  802. }
  803. return '/' . implode('/', $new);
  804. }
  805. /**
  806. * Changes the current directory
  807. *
  808. * @param string $dir
  809. * @throws \UnexpectedValueException on receipt of unexpected packets
  810. * @return bool
  811. * @access public
  812. */
  813. public function chdir($dir)
  814. {
  815. if (!$this->precheck()) {
  816. return false;
  817. }
  818. // assume current dir if $dir is empty
  819. if ($dir === '') {
  820. $dir = './';
  821. // suffix a slash if needed
  822. } elseif ($dir[strlen($dir) - 1] != '/') {
  823. $dir.= '/';
  824. }
  825. $dir = $this->realpath($dir);
  826. // confirm that $dir is, in fact, a valid directory
  827. if ($this->use_stat_cache && is_array($this->query_stat_cache($dir))) {
  828. $this->pwd = $dir;
  829. return true;
  830. }
  831. // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
  832. // the currently logged in user has the appropriate permissions or not. maybe you could see if
  833. // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
  834. // way to get those with SFTP
  835. $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
  836. // see \phpseclib3\Net\SFTP::nlist() for a more thorough explanation of the following
  837. $response = $this->get_sftp_packet();
  838. switch ($this->packet_type) {
  839. case NET_SFTP_HANDLE:
  840. $handle = substr($response, 4);
  841. break;
  842. case NET_SFTP_STATUS:
  843. $this->logError($response);
  844. return false;
  845. default:
  846. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS' .
  847. 'Got packet type: ' . $this->packet_type);
  848. }
  849. if (!$this->close_handle($handle)) {
  850. return false;
  851. }
  852. $this->update_stat_cache($dir, []);
  853. $this->pwd = $dir;
  854. return true;
  855. }
  856. /**
  857. * Returns a list of files in the given directory
  858. *
  859. * @param string $dir
  860. * @param bool $recursive
  861. * @return mixed
  862. * @access public
  863. */
  864. public function nlist($dir = '.', $recursive = false)
  865. {
  866. return $this->nlist_helper($dir, $recursive, '');
  867. }
  868. /**
  869. * Helper method for nlist
  870. *
  871. * @param string $dir
  872. * @param bool $recursive
  873. * @param string $relativeDir
  874. * @return mixed
  875. * @access private
  876. */
  877. private function nlist_helper($dir, $recursive, $relativeDir)
  878. {
  879. $files = $this->readlist($dir, false);
  880. if (!$recursive || $files === false) {
  881. return $files;
  882. }
  883. $result = [];
  884. foreach ($files as $value) {
  885. if ($value == '.' || $value == '..') {
  886. $result[] = $relativeDir . $value;
  887. continue;
  888. }
  889. if (is_array($this->query_stat_cache($this->realpath($dir . '/' . $value)))) {
  890. $temp = $this->nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
  891. $temp = is_array($temp) ? $temp : [];
  892. $result = array_merge($result, $temp);
  893. } else {
  894. $result[] = $relativeDir . $value;
  895. }
  896. }
  897. return $result;
  898. }
  899. /**
  900. * Returns a detailed list of files in the given directory
  901. *
  902. * @param string $dir
  903. * @param bool $recursive
  904. * @return mixed
  905. * @access public
  906. */
  907. public function rawlist($dir = '.', $recursive = false)
  908. {
  909. $files = $this->readlist($dir, true);
  910. if (!$recursive || $files === false) {
  911. return $files;
  912. }
  913. static $depth = 0;
  914. foreach ($files as $key => $value) {
  915. if ($depth != 0 && $key == '..') {
  916. unset($files[$key]);
  917. continue;
  918. }
  919. $is_directory = false;
  920. if ($key != '.' && $key != '..') {
  921. if ($this->use_stat_cache) {
  922. $is_directory = is_array($this->query_stat_cache($this->realpath($dir . '/' . $key)));
  923. } else {
  924. $stat = $this->lstat($dir . '/' . $key);
  925. $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
  926. }
  927. }
  928. if ($is_directory) {
  929. $depth++;
  930. $files[$key] = $this->rawlist($dir . '/' . $key, true);
  931. $depth--;
  932. } else {
  933. $files[$key] = (object) $value;
  934. }
  935. }
  936. return $files;
  937. }
  938. /**
  939. * Reads a list, be it detailed or not, of files in the given directory
  940. *
  941. * @param string $dir
  942. * @param bool $raw
  943. * @return mixed
  944. * @throws \UnexpectedValueException on receipt of unexpected packets
  945. * @access private
  946. */
  947. private function readlist($dir, $raw = true)
  948. {
  949. if (!$this->precheck()) {
  950. return false;
  951. }
  952. $dir = $this->realpath($dir . '/');
  953. if ($dir === false) {
  954. return false;
  955. }
  956. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
  957. $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
  958. $response = $this->get_sftp_packet();
  959. switch ($this->packet_type) {
  960. case NET_SFTP_HANDLE:
  961. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
  962. // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
  963. // represent the length of the string and leave it at that
  964. $handle = substr($response, 4);
  965. break;
  966. case NET_SFTP_STATUS:
  967. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  968. $this->logError($response);
  969. return false;
  970. default:
  971. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
  972. . 'Got packet type: ' . $this->packet_type);
  973. }
  974. $this->update_stat_cache($dir, []);
  975. $contents = [];
  976. while (true) {
  977. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
  978. // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
  979. // SSH_MSG_CHANNEL_DATA messages is not known to me.
  980. $this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle));
  981. $response = $this->get_sftp_packet();
  982. switch ($this->packet_type) {
  983. case NET_SFTP_NAME:
  984. list($count) = Strings::unpackSSH2('N', $response);
  985. for ($i = 0; $i < $count; $i++) {
  986. list($shortname) = Strings::unpackSSH2('s', $response);
  987. // SFTPv4 "removed the long filename from the names structure-- it can now be
  988. // built from information available in the attrs structure."
  989. if ($this->version < 4) {
  990. list($longname) = Strings::unpackSSH2('s', $response);
  991. }
  992. $attributes = $this->parseAttributes($response);
  993. if (!isset($attributes['type']) && $this->version < 4) {
  994. $fileType = $this->parseLongname($longname);
  995. if ($fileType) {
  996. $attributes['type'] = $fileType;
  997. }
  998. }
  999. $contents[$shortname] = $attributes + ['filename' => $shortname];
  1000. if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
  1001. $this->update_stat_cache($dir . '/' . $shortname, []);
  1002. } else {
  1003. if ($shortname == '..') {
  1004. $temp = $this->realpath($dir . '/..') . '/.';
  1005. } else {
  1006. $temp = $dir . '/' . $shortname;
  1007. }
  1008. $this->update_stat_cache($temp, (object) ['lstat' => $attributes]);
  1009. }
  1010. // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
  1011. // final SSH_FXP_STATUS packet should tell us that, already.
  1012. }
  1013. break;
  1014. case NET_SFTP_STATUS:
  1015. list($status) = Strings::unpackSSH2('N', $response);
  1016. if ($status != NET_SFTP_STATUS_EOF) {
  1017. $this->logError($response, $status);
  1018. return false;
  1019. }
  1020. break 2;
  1021. default:
  1022. throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
  1023. . 'Got packet type: ' . $this->packet_type);
  1024. }
  1025. }
  1026. if (!$this->close_handle($handle)) {
  1027. return false;
  1028. }
  1029. if (count($this->sortOptions)) {
  1030. uasort($contents, [&$this, 'comparator']);
  1031. }
  1032. return $raw ? $contents : array_map('strval', array_keys($contents));
  1033. }
  1034. /**
  1035. * Compares two rawlist entries using parameters set by setListOrder()
  1036. *
  1037. * Intended for use with uasort()
  1038. *
  1039. * @param array $a
  1040. * @param array $b
  1041. * @return int
  1042. * @access private
  1043. */
  1044. private function comparator($a, $b)
  1045. {
  1046. switch (true) {
  1047. case $a['filename'] === '.' || $b['filename'] === '.':
  1048. if ($a['filename'] === $b['filename']) {
  1049. return 0;
  1050. }
  1051. return $a['filename'] === '.' ? -1 : 1;
  1052. case $a['filename'] === '..' || $b['filename'] === '..':
  1053. if ($a['filename'] === $b['filename']) {
  1054. return 0;
  1055. }
  1056. return $a['filename'] === '..' ? -1 : 1;
  1057. case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
  1058. if (!isset($b['type'])) {
  1059. return 1;
  1060. }
  1061. if ($b['type'] !== $a['type']) {
  1062. return -1;
  1063. }
  1064. break;
  1065. case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
  1066. return 1;
  1067. }
  1068. foreach ($this->sortOptions as $sort => $order) {
  1069. if (!isset($a[$sort]) || !isset($b[$sort])) {
  1070. if (isset($a[$sort])) {
  1071. return -1;
  1072. }
  1073. if (isset($b[$sort])) {
  1074. return 1;
  1075. }
  1076. return 0;
  1077. }
  1078. switch ($sort) {
  1079. case 'filename':
  1080. $result = strcasecmp($a['filename'], $b['filename']);
  1081. if ($result) {
  1082. return $order === SORT_DESC ? -$result : $result;
  1083. }
  1084. break;
  1085. case 'mode':
  1086. $a[$sort]&= 07777;
  1087. $b[$sort]&= 07777;
  1088. default:
  1089. if ($a[$sort] === $b[$sort]) {
  1090. break;
  1091. }
  1092. return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
  1093. }
  1094. }
  1095. }
  1096. /**
  1097. * Defines how nlist() and rawlist() will be sorted - if at all.
  1098. *
  1099. * If sorting is enabled directories and files will be sorted independently with
  1100. * directories appearing before files in the resultant array that is returned.
  1101. *
  1102. * Any parameter returned by stat is a valid sort parameter for this function.
  1103. * Filename comparisons are case insensitive.
  1104. *
  1105. * Examples:
  1106. *
  1107. * $sftp->setListOrder('filename', SORT_ASC);
  1108. * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
  1109. * $sftp->setListOrder(true);
  1110. * Separates directories from files but doesn't do any sorting beyond that
  1111. * $sftp->setListOrder();
  1112. * Don't do any sort of sorting
  1113. *
  1114. * @param string[] ...$args
  1115. * @access public
  1116. */
  1117. public function setListOrder(...$args)
  1118. {
  1119. $this->sortOptions = [];
  1120. if (empty($args)) {
  1121. return;
  1122. }
  1123. $len = count($args) & 0x7FFFFFFE;
  1124. for ($i = 0; $i < $len; $i+=2) {
  1125. $this->sortOptions[$args[$i]] = $args[$i + 1];
  1126. }
  1127. if (!count($this->sortOptions)) {
  1128. $this->sortOptions = ['bogus' => true];
  1129. }
  1130. }
  1131. /**
  1132. * Save files / directories to cache
  1133. *
  1134. * @param string $path
  1135. * @param mixed $value
  1136. * @access private
  1137. */
  1138. private function update_stat_cache($path, $value)
  1139. {
  1140. if ($this->use_stat_cache === false) {
  1141. return;
  1142. }
  1143. // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
  1144. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  1145. $temp = &$this->stat_cache;
  1146. $max = count($dirs) - 1;
  1147. foreach ($dirs as $i => $dir) {
  1148. // if $temp is an object that means one of two things.
  1149. // 1. a file was deleted and changed to a directory behind phpseclib's back
  1150. // 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
  1151. if (is_object($temp)) {
  1152. $temp = [];
  1153. }
  1154. if (!isset($temp[$dir])) {
  1155. $temp[$dir] = [];
  1156. }
  1157. if ($i === $max) {
  1158. if (is_object($temp[$dir]) && is_object($value)) {
  1159. if (!isset($value->stat) && isset($temp[$dir]->stat)) {
  1160. $value->stat = $temp[$dir]->stat;
  1161. }
  1162. if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
  1163. $value->lstat = $temp[$dir]->lstat;
  1164. }
  1165. }
  1166. $temp[$dir] = $value;
  1167. break;
  1168. }
  1169. $temp = &$temp[$dir];
  1170. }
  1171. }
  1172. /**
  1173. * Remove files / directories from cache
  1174. *
  1175. * @param string $path
  1176. * @return bool
  1177. * @access private
  1178. */
  1179. private function remove_from_stat_cache($path)
  1180. {
  1181. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  1182. $temp = &$this->stat_cache;
  1183. $max = count($dirs) - 1;
  1184. foreach ($dirs as $i => $dir) {
  1185. if (!is_array($temp)) {
  1186. return false;
  1187. }
  1188. if ($i === $max) {
  1189. unset($temp[$dir]);
  1190. return true;
  1191. }
  1192. if (!isset($temp[$dir])) {
  1193. return false;
  1194. }
  1195. $temp = &$temp[$dir];
  1196. }
  1197. }
  1198. /**
  1199. * Checks cache for path
  1200. *
  1201. * Mainly used by file_exists
  1202. *
  1203. * @param string $path
  1204. * @return mixed
  1205. * @access private
  1206. */
  1207. private function query_stat_cache($path)
  1208. {
  1209. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
  1210. $temp = &$this->stat_cache;
  1211. foreach ($dirs as $dir) {
  1212. if (!is_array($temp)) {
  1213. return null;
  1214. }
  1215. if (!isset($temp[$dir])) {
  1216. return null;
  1217. }
  1218. $temp = &$temp[$dir];
  1219. }
  1220. return $temp;
  1221. }
  1222. /**
  1223. * Returns general information about a file.
  1224. *
  1225. * Returns an array on success and false otherwise.
  1226. *
  1227. * @param string $filename
  1228. * @return mixed
  1229. * @access public
  1230. */
  1231. public function stat($filename)
  1232. {
  1233. if (!$this->precheck()) {
  1234. return false;
  1235. }
  1236. $filename = $this->realpath($filename);
  1237. if ($filename === false) {
  1238. return false;
  1239. }
  1240. if ($this->use_stat_cache) {
  1241. $result = $this->query_stat_cache($filename);
  1242. if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
  1243. return $result['.']->stat;
  1244. }
  1245. if (is_object($result) && isset($result->stat)) {
  1246. return $result->stat;
  1247. }
  1248. }
  1249. $stat = $this->stat_helper($filename, NET_SFTP_STAT);
  1250. if ($stat === false) {
  1251. $this->remove_from_stat_cache($filename);
  1252. return false;
  1253. }
  1254. if (isset($stat['type'])) {
  1255. if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1256. $filename.= '/.';
  1257. }
  1258. $this->update_stat_cache($filename, (object) ['stat' => $stat]);
  1259. return $stat;
  1260. }
  1261. $pwd = $this->pwd;
  1262. $stat['type'] = $this->chdir($filename) ?
  1263. NET_SFTP_TYPE_DIRECTORY :
  1264. NET_SFTP_TYPE_REGULAR;
  1265. $this->pwd = $pwd;
  1266. if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1267. $filename.= '/.';
  1268. }
  1269. $this->update_stat_cache($filename, (object) ['stat' => $stat]);
  1270. return $stat;
  1271. }
  1272. /**
  1273. * Returns general information about a file or symbolic link.
  1274. *
  1275. * Returns an array on success and false otherwise.
  1276. *
  1277. * @param string $filename
  1278. * @return mixed
  1279. * @access public
  1280. */
  1281. public function lstat($filename)
  1282. {
  1283. if (!$this->precheck()) {
  1284. return false;
  1285. }
  1286. $filename = $this->realpath($filename);
  1287. if ($filename === false) {
  1288. return false;
  1289. }
  1290. if ($this->use_stat_cache) {
  1291. $result = $this->query_stat_cache($filename);
  1292. if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
  1293. return $result['.']->lstat;
  1294. }
  1295. if (is_object($result) && isset($result->lstat)) {
  1296. return $result->lstat;
  1297. }
  1298. }
  1299. $lstat = $this->stat_helper($filename, NET_SFTP_LSTAT);
  1300. if ($lstat === false) {
  1301. $this->remove_from_stat_cache($filename);
  1302. return false;
  1303. }
  1304. if (isset($lstat['type'])) {
  1305. if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1306. $filename.= '/.';
  1307. }
  1308. $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
  1309. return $lstat;
  1310. }
  1311. $stat = $this->stat_helper($filename, NET_SFTP_STAT);
  1312. if ($lstat != $stat) {
  1313. $lstat = array_merge($lstat, ['type' => NET_SFTP_TYPE_SYMLINK]);
  1314. $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
  1315. return $stat;
  1316. }
  1317. $pwd = $this->pwd;
  1318. $lstat['type'] = $this->chdir($filename) ?
  1319. NET_SFTP_TYPE_DIRECTORY :
  1320. NET_SFTP_TYPE_REGULAR;
  1321. $this->pwd = $pwd;
  1322. if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1323. $filename.= '/.';
  1324. }
  1325. $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
  1326. return $lstat;
  1327. }
  1328. /**
  1329. * Returns general information about a file or symbolic link
  1330. *
  1331. * Determines information without calling \phpseclib3\Net\SFTP::realpath().
  1332. * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
  1333. *
  1334. * @param string $filename
  1335. * @param int $type
  1336. * @throws \UnexpectedValueException on receipt of unexpected packets
  1337. * @return mixed
  1338. * @access private
  1339. */
  1340. private function stat_helper($filename, $type)
  1341. {
  1342. // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1343. $packet = Strings::packSSH2('s', $filename);
  1344. $this->send_sftp_packet($type, $packet);
  1345. $response = $this->get_sftp_packet();
  1346. switch ($this->packet_type) {
  1347. case NET_SFTP_ATTRS:
  1348. return $this->parseAttributes($response);
  1349. case NET_SFTP_STATUS:
  1350. $this->logError($response);
  1351. return false;
  1352. }
  1353. throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
  1354. . 'Got packet type: ' . $this->packet_type);
  1355. }
  1356. /**
  1357. * Truncates a file to a given length
  1358. *
  1359. * @param string $filename
  1360. * @param int $new_size
  1361. * @return bool
  1362. * @access public
  1363. */
  1364. public function truncate($filename, $new_size)
  1365. {
  1366. $attr = Strings::packSSH2('NQ', NET_SFTP_ATTR_SIZE, $new_size);
  1367. return $this->setstat($filename, $attr, false);
  1368. }
  1369. /**
  1370. * Sets access and modification time of file.
  1371. *
  1372. * If the file does not exist, it will be created.
  1373. *
  1374. * @param string $filename
  1375. * @param int $time
  1376. * @param int $atime
  1377. * @throws \UnexpectedValueException on receipt of unexpected packets
  1378. * @return bool
  1379. * @access public
  1380. */
  1381. public function touch($filename, $time = null, $atime = null)
  1382. {
  1383. if (!$this->precheck()) {
  1384. return false;
  1385. }
  1386. $filename = $this->realpath($filename);
  1387. if ($filename === false) {
  1388. return false;
  1389. }
  1390. if (!isset($time)) {
  1391. $time = time();
  1392. }
  1393. if (!isset($atime)) {
  1394. $atime = $time;
  1395. }
  1396. $attr = $this->version < 4 ?
  1397. pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) :
  1398. Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime, $time);
  1399. $packet = Strings::packSSH2('s', $filename);
  1400. $packet.= $this->version >= 5 ?
  1401. pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) :
  1402. pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL);
  1403. $packet.= $attr;
  1404. $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
  1405. $response = $this->get_sftp_packet();
  1406. switch ($this->packet_type) {
  1407. case NET_SFTP_HANDLE:
  1408. return $this->close_handle(substr($response, 4));
  1409. case NET_SFTP_STATUS:
  1410. $this->logError($response);
  1411. break;
  1412. default:
  1413. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
  1414. . 'Got packet type: ' . $this->packet_type);
  1415. }
  1416. return $this->setstat($filename, $attr, false);
  1417. }
  1418. /**
  1419. * Changes file or directory owner
  1420. *
  1421. * $uid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
  1422. * would be of the form "user@dns_domain" but it does not need to be.
  1423. * `$sftp->getSupportedVersions()['version']` will return the specific version
  1424. * that's being used.
  1425. *
  1426. * Returns true on success or false on error.
  1427. *
  1428. * @param string $filename
  1429. * @param int|string $uid
  1430. * @param bool $recursive
  1431. * @return bool
  1432. * @access public
  1433. */
  1434. public function chown($filename, $uid, $recursive = false)
  1435. {
  1436. /*
  1437. quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
  1438. "To avoid a representation that is tied to a particular underlying
  1439. implementation at the client or server, the use of UTF-8 strings has
  1440. been chosen. The string should be of the form "user@dns_domain".
  1441. This will allow for a client and server that do not use the same
  1442. local representation the ability to translate to a common syntax that
  1443. can be interpreted by both. In the case where there is no
  1444. translation available to the client or server, the attribute value
  1445. must be constructed without the "@"."
  1446. phpseclib _could_ auto append the dns_domain to $uid BUT what if it shouldn't
  1447. have one? phpseclib would have no way of knowing so rather than guess phpseclib
  1448. will just use whatever value the user provided
  1449. */
  1450. $attr = $this->version < 4 ?
  1451. // quoting <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
  1452. // "if the owner or group is specified as -1, then that ID is not changed"
  1453. pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) :
  1454. // quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
  1455. // "If either the owner or group field is zero length, the field should be
  1456. // considered absent, and no change should be made to that specific field
  1457. // during a modification operation"
  1458. Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, $uid, '');
  1459. return $this->setstat($filename, $attr, $recursive);
  1460. }
  1461. /**
  1462. * Changes file or directory group
  1463. *
  1464. * $gid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string
  1465. * would be of the form "user@dns_domain" but it does not need to be.
  1466. * `$sftp->getSupportedVersions()['version']` will return the specific version
  1467. * that's being used.
  1468. *
  1469. * Returns true on success or false on error.
  1470. *
  1471. * @param string $filename
  1472. * @param int|string $gid
  1473. * @param bool $recursive
  1474. * @return bool
  1475. * @access public
  1476. */
  1477. public function chgrp($filename, $gid, $recursive = false)
  1478. {
  1479. $attr = $this->version < 4 ?
  1480. pack('N3', NET_SFTP_ATTR_UIDGID, $gid, -1) :
  1481. Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, '', $gid);
  1482. return $this->setstat($filename, $attr, $recursive);
  1483. }
  1484. /**
  1485. * Set permissions on a file.
  1486. *
  1487. * Returns the new file permissions on success or false on error.
  1488. * If $recursive is true than this just returns true or false.
  1489. *
  1490. * @param int $mode
  1491. * @param string $filename
  1492. * @param bool $recursive
  1493. * @throws \UnexpectedValueException on receipt of unexpected packets
  1494. * @return mixed
  1495. * @access public
  1496. */
  1497. public function chmod($mode, $filename, $recursive = false)
  1498. {
  1499. if (is_string($mode) && is_int($filename)) {
  1500. $temp = $mode;
  1501. $mode = $filename;
  1502. $filename = $temp;
  1503. }
  1504. $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
  1505. if (!$this->setstat($filename, $attr, $recursive)) {
  1506. return false;
  1507. }
  1508. if ($recursive) {
  1509. return true;
  1510. }
  1511. $filename = $this->realpath($filename);
  1512. // rather than return what the permissions *should* be, we'll return what they actually are. this will also
  1513. // tell us if the file actually exists.
  1514. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1515. $packet = pack('Na*', strlen($filename), $filename);
  1516. $this->send_sftp_packet(NET_SFTP_STAT, $packet);
  1517. $response = $this->get_sftp_packet();
  1518. switch ($this->packet_type) {
  1519. case NET_SFTP_ATTRS:
  1520. $attrs = $this->parseAttributes($response);
  1521. return $attrs['mode'];
  1522. case NET_SFTP_STATUS:
  1523. $this->logError($response);
  1524. return false;
  1525. }
  1526. throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
  1527. . 'Got packet type: ' . $this->packet_type);
  1528. }
  1529. /**
  1530. * Sets information about a file
  1531. *
  1532. * @param string $filename
  1533. * @param string $attr
  1534. * @param bool $recursive
  1535. * @throws \UnexpectedValueException on receipt of unexpected packets
  1536. * @return bool
  1537. * @access private
  1538. */
  1539. private function setstat($filename, $attr, $recursive)
  1540. {
  1541. if (!$this->precheck()) {
  1542. return false;
  1543. }
  1544. $filename = $this->realpath($filename);
  1545. if ($filename === false) {
  1546. return false;
  1547. }
  1548. $this->remove_from_stat_cache($filename);
  1549. if ($recursive) {
  1550. $i = 0;
  1551. $result = $this->setstat_recursive($filename, $attr, $i);
  1552. $this->read_put_responses($i);
  1553. return $result;
  1554. }
  1555. $packet = Strings::packSSH2('s', $filename);
  1556. $packet.= $this->version >= 4 ?
  1557. pack('a*Ca*', substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) :
  1558. $attr;
  1559. $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
  1560. /*
  1561. "Because some systems must use separate system calls to set various attributes, it is possible that a failure
  1562. response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
  1563. servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
  1564. -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
  1565. */
  1566. $response = $this->get_sftp_packet();
  1567. if ($this->packet_type != NET_SFTP_STATUS) {
  1568. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  1569. . 'Got packet type: ' . $this->packet_type);
  1570. }
  1571. list($status) = Strings::unpackSSH2('N', $response);
  1572. if ($status != NET_SFTP_STATUS_OK) {
  1573. $this->logError($response, $status);
  1574. return false;
  1575. }
  1576. return true;
  1577. }
  1578. /**
  1579. * Recursively sets information on directories on the SFTP server
  1580. *
  1581. * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  1582. *
  1583. * @param string $path
  1584. * @param string $attr
  1585. * @param int $i
  1586. * @return bool
  1587. * @access private
  1588. */
  1589. private function setstat_recursive($path, $attr, &$i)
  1590. {
  1591. if (!$this->read_put_responses($i)) {
  1592. return false;
  1593. }
  1594. $i = 0;
  1595. $entries = $this->readlist($path, true);
  1596. if ($entries === false) {
  1597. return $this->setstat($path, $attr, false);
  1598. }
  1599. // normally $entries would have at least . and .. but it might not if the directories
  1600. // permissions didn't allow reading
  1601. if (empty($entries)) {
  1602. return false;
  1603. }
  1604. unset($entries['.'], $entries['..']);
  1605. foreach ($entries as $filename => $props) {
  1606. if (!isset($props['type'])) {
  1607. return false;
  1608. }
  1609. $temp = $path . '/' . $filename;
  1610. if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1611. if (!$this->setstat_recursive($temp, $attr, $i)) {
  1612. return false;
  1613. }
  1614. } else {
  1615. $packet = Strings::packSSH2('s', $temp);
  1616. $packet.= $this->version >= 4 ?
  1617. pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
  1618. $attr;
  1619. $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
  1620. $i++;
  1621. if ($i >= NET_SFTP_QUEUE_SIZE) {
  1622. if (!$this->read_put_responses($i)) {
  1623. return false;
  1624. }
  1625. $i = 0;
  1626. }
  1627. }
  1628. }
  1629. $packet = Strings::packSSH2('s', $path);
  1630. $packet.= $this->version >= 4 ?
  1631. pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
  1632. $atr;
  1633. $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
  1634. $i++;
  1635. if ($i >= NET_SFTP_QUEUE_SIZE) {
  1636. if (!$this->read_put_responses($i)) {
  1637. return false;
  1638. }
  1639. $i = 0;
  1640. }
  1641. return true;
  1642. }
  1643. /**
  1644. * Return the target of a symbolic link
  1645. *
  1646. * @param string $link
  1647. * @throws \UnexpectedValueException on receipt of unexpected packets
  1648. * @return mixed
  1649. * @access public
  1650. */
  1651. public function readlink($link)
  1652. {
  1653. if (!$this->precheck()) {
  1654. return false;
  1655. }
  1656. $link = $this->realpath($link);
  1657. $this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link));
  1658. $response = $this->get_sftp_packet();
  1659. switch ($this->packet_type) {
  1660. case NET_SFTP_NAME:
  1661. break;
  1662. case NET_SFTP_STATUS:
  1663. $this->logError($response);
  1664. return false;
  1665. default:
  1666. throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
  1667. . 'Got packet type: ' . $this->packet_type);
  1668. }
  1669. list($count) = Strings::unpackSSH2('N', $response);
  1670. // the file isn't a symlink
  1671. if (!$count) {
  1672. return false;
  1673. }
  1674. list($filename) = Strings::unpackSSH2('s', $response);
  1675. return $filename;
  1676. }
  1677. /**
  1678. * Create a symlink
  1679. *
  1680. * symlink() creates a symbolic link to the existing target with the specified name link.
  1681. *
  1682. * @param string $target
  1683. * @param string $link
  1684. * @throws \UnexpectedValueException on receipt of unexpected packets
  1685. * @return bool
  1686. * @access public
  1687. */
  1688. public function symlink($target, $link)
  1689. {
  1690. if (!$this->precheck()) {
  1691. return false;
  1692. }
  1693. //$target = $this->realpath($target);
  1694. $link = $this->realpath($link);
  1695. /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 :
  1696. Changed the SYMLINK packet to be LINK and give it the ability to
  1697. create hard links. Also change it's packet number because many
  1698. implementation implemented SYMLINK with the arguments reversed.
  1699. Hopefully the new argument names make it clear which way is which.
  1700. */
  1701. if ($this->version == 6) {
  1702. $type = NET_SFTP_LINK;
  1703. $packet = Strings::packSSH2('ssC', $link, $target, 1);
  1704. } else {
  1705. $type = NET_SFTP_SYMLINK;
  1706. /* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 :
  1707. 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
  1708. When OpenSSH's sftp-server was implemented, the order of the arguments
  1709. to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately,
  1710. the reversal was not noticed until the server was widely deployed. Since
  1711. fixing this to follow the specification would cause incompatibility, the
  1712. current order was retained. For correct operation, clients should send
  1713. SSH_FXP_SYMLINK as follows:
  1714. uint32 id
  1715. string targetpath
  1716. string linkpath */
  1717. $packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ?
  1718. Strings::packSSH2('ss', $target, $link) :
  1719. Strings::packSSH2('ss', $link, $target);
  1720. }
  1721. $this->send_sftp_packet($type, $packet);
  1722. $response = $this->get_sftp_packet();
  1723. if ($this->packet_type != NET_SFTP_STATUS) {
  1724. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  1725. . 'Got packet type: ' . $this->packet_type);
  1726. }
  1727. list($status) = Strings::unpackSSH2('N', $response);
  1728. if ($status != NET_SFTP_STATUS_OK) {
  1729. $this->logError($response, $status);
  1730. return false;
  1731. }
  1732. return true;
  1733. }
  1734. /**
  1735. * Creates a directory.
  1736. *
  1737. * @param string $dir
  1738. * @param int $mode
  1739. * @param bool $recursive
  1740. * @return bool
  1741. * @access public
  1742. */
  1743. public function mkdir($dir, $mode = -1, $recursive = false)
  1744. {
  1745. if (!$this->precheck()) {
  1746. return false;
  1747. }
  1748. $dir = $this->realpath($dir);
  1749. if ($recursive) {
  1750. $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
  1751. if (empty($dirs[0])) {
  1752. array_shift($dirs);
  1753. $dirs[0] = '/' . $dirs[0];
  1754. }
  1755. for ($i = 0; $i < count($dirs); $i++) {
  1756. $temp = array_slice($dirs, 0, $i + 1);
  1757. $temp = implode('/', $temp);
  1758. $result = $this->mkdir_helper($temp, $mode);
  1759. }
  1760. return $result;
  1761. }
  1762. return $this->mkdir_helper($dir, $mode);
  1763. }
  1764. /**
  1765. * Helper function for directory creation
  1766. *
  1767. * @param string $dir
  1768. * @param int $mode
  1769. * @return bool
  1770. * @access private
  1771. */
  1772. private function mkdir_helper($dir, $mode)
  1773. {
  1774. // send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
  1775. $this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0");
  1776. $response = $this->get_sftp_packet();
  1777. if ($this->packet_type != NET_SFTP_STATUS) {
  1778. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  1779. . 'Got packet type: ' . $this->packet_type);
  1780. }
  1781. list($status) = Strings::unpackSSH2('N', $response);
  1782. if ($status != NET_SFTP_STATUS_OK) {
  1783. $this->logError($response, $status);
  1784. return false;
  1785. }
  1786. if ($mode !== -1) {
  1787. $this->chmod($mode, $dir);
  1788. }
  1789. return true;
  1790. }
  1791. /**
  1792. * Removes a directory.
  1793. *
  1794. * @param string $dir
  1795. * @throws \UnexpectedValueException on receipt of unexpected packets
  1796. * @return bool
  1797. * @access public
  1798. */
  1799. public function rmdir($dir)
  1800. {
  1801. if (!$this->precheck()) {
  1802. return false;
  1803. }
  1804. $dir = $this->realpath($dir);
  1805. if ($dir === false) {
  1806. return false;
  1807. }
  1808. $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir));
  1809. $response = $this->get_sftp_packet();
  1810. if ($this->packet_type != NET_SFTP_STATUS) {
  1811. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  1812. . 'Got packet type: ' . $this->packet_type);
  1813. }
  1814. list($status) = Strings::unpackSSH2('N', $response);
  1815. if ($status != NET_SFTP_STATUS_OK) {
  1816. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
  1817. $this->logError($response, $status);
  1818. return false;
  1819. }
  1820. $this->remove_from_stat_cache($dir);
  1821. // the following will do a soft delete, which would be useful if you deleted a file
  1822. // and then tried to do a stat on the deleted file. the above, in contrast, does
  1823. // a hard delete
  1824. //$this->update_stat_cache($dir, false);
  1825. return true;
  1826. }
  1827. /**
  1828. * Uploads a file to the SFTP server.
  1829. *
  1830. * By default, \phpseclib3\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
  1831. * So, for example, if you set $data to 'filename.ext' and then do \phpseclib3\Net\SFTP::get(), you will get a file, twelve bytes
  1832. * long, containing 'filename.ext' as its contents.
  1833. *
  1834. * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will
  1835. * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
  1836. * large $remote_file will be, as well.
  1837. *
  1838. * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number
  1839. * of bytes to return, and returns a string if there is some data or null if there is no more data
  1840. *
  1841. * If $data is a resource then it'll be used as a resource instead.
  1842. *
  1843. * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
  1844. * care of that, yourself.
  1845. *
  1846. * $mode can take an additional two parameters - self::RESUME and self::RESUME_START. These are bitwise AND'd with
  1847. * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
  1848. *
  1849. * self::SOURCE_LOCAL_FILE | self::RESUME
  1850. *
  1851. * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
  1852. * self::RESUME with self::RESUME_START.
  1853. *
  1854. * If $mode & (self::RESUME | self::RESUME_START) then self::RESUME_START will be assumed.
  1855. *
  1856. * $start and $local_start give you more fine grained control over this process and take precident over self::RESUME
  1857. * when they're non-negative. ie. $start could let you write at the end of a file (like self::RESUME) or in the middle
  1858. * of one. $local_start could let you start your reading from the end of a file (like self::RESUME_START) or in the
  1859. * middle of one.
  1860. *
  1861. * Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
  1862. *
  1863. * {@internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib3\Net\SFTP::setMode().}
  1864. *
  1865. * @param string $remote_file
  1866. * @param string|resource $data
  1867. * @param int $mode
  1868. * @param int $start
  1869. * @param int $local_start
  1870. * @param callable|null $progressCallback
  1871. * @throws \UnexpectedValueException on receipt of unexpected packets
  1872. * @throws \BadFunctionCallException if you're uploading via a callback and the callback function is invalid
  1873. * @throws \phpseclib3\Exception\FileNotFoundException if you're uploading via a file and the file doesn't exist
  1874. * @return bool
  1875. * @access public
  1876. */
  1877. public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
  1878. {
  1879. if (!$this->precheck()) {
  1880. return false;
  1881. }
  1882. $remote_file = $this->realpath($remote_file);
  1883. if ($remote_file === false) {
  1884. return false;
  1885. }
  1886. $this->remove_from_stat_cache($remote_file);
  1887. if ($this->version >= 5) {
  1888. $flags = NET_SFTP_OPEN_OPEN_OR_CREATE;
  1889. } else {
  1890. $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
  1891. // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
  1892. // in practice, it doesn't seem to do that.
  1893. //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
  1894. }
  1895. if ($start >= 0) {
  1896. $offset = $start;
  1897. } elseif ($mode & self::RESUME) {
  1898. // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
  1899. $size = $this->stat($remote_file)['size'];
  1900. $offset = $size !== false ? $size : 0;
  1901. } else {
  1902. $offset = 0;
  1903. if ($this->version >= 5) {
  1904. $flags = NET_SFTP_OPEN_CREATE_TRUNCATE;
  1905. } else {
  1906. $flags|= NET_SFTP_OPEN_TRUNCATE;
  1907. }
  1908. }
  1909. $this->remove_from_stat_cache($remote_file);
  1910. $packet = Strings::packSSH2('s', $remote_file);
  1911. $packet.= $this->version >= 5 ?
  1912. pack('N3', 0, $flags, 0) :
  1913. pack('N2', $flags, 0);
  1914. $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
  1915. $response = $this->get_sftp_packet();
  1916. switch ($this->packet_type) {
  1917. case NET_SFTP_HANDLE:
  1918. $handle = substr($response, 4);
  1919. break;
  1920. case NET_SFTP_STATUS:
  1921. $this->logError($response);
  1922. return false;
  1923. default:
  1924. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
  1925. . 'Got packet type: ' . $this->packet_type);
  1926. }
  1927. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
  1928. $dataCallback = false;
  1929. switch (true) {
  1930. case $mode & self::SOURCE_CALLBACK:
  1931. if (!is_callable($data)) {
  1932. throw new \BadFunctionCallException("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
  1933. }
  1934. $dataCallback = $data;
  1935. // do nothing
  1936. break;
  1937. case is_resource($data):
  1938. $mode = $mode & ~self::SOURCE_LOCAL_FILE;
  1939. $info = stream_get_meta_data($data);
  1940. if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
  1941. $fp = fopen('php://memory', 'w+');
  1942. stream_copy_to_stream($data, $fp);
  1943. rewind($fp);
  1944. } else {
  1945. $fp = $data;
  1946. }
  1947. break;
  1948. case $mode & self::SOURCE_LOCAL_FILE:
  1949. if (!is_file($data)) {
  1950. throw new FileNotFoundException("$data is not a valid file");
  1951. }
  1952. $fp = @fopen($data, 'rb');
  1953. if (!$fp) {
  1954. return false;
  1955. }
  1956. }
  1957. if (isset($fp)) {
  1958. $stat = fstat($fp);
  1959. $size = !empty($stat) ? $stat['size'] : 0;
  1960. if ($local_start >= 0) {
  1961. fseek($fp, $local_start);
  1962. $size-= $local_start;
  1963. }
  1964. } elseif ($dataCallback) {
  1965. $size = 0;
  1966. } else {
  1967. $size = strlen($data);
  1968. }
  1969. $sent = 0;
  1970. $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
  1971. $sftp_packet_size = $this->max_sftp_packet;
  1972. // make the SFTP packet be exactly the SFTP packet size by including the bytes in the NET_SFTP_WRITE packets "header"
  1973. $sftp_packet_size-= strlen($handle) + 25;
  1974. $i = $j = 0;
  1975. while ($dataCallback || ($size === 0 || $sent < $size)) {
  1976. if ($dataCallback) {
  1977. $temp = $dataCallback($sftp_packet_size);
  1978. if (is_null($temp)) {
  1979. break;
  1980. }
  1981. } else {
  1982. $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
  1983. if ($temp === false || $temp === '') {
  1984. break;
  1985. }
  1986. }
  1987. $subtemp = $offset + $sent;
  1988. $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
  1989. try {
  1990. $this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j);
  1991. } catch (\Exception $e) {
  1992. if ($mode & self::SOURCE_LOCAL_FILE) {
  1993. fclose($fp);
  1994. }
  1995. throw $e;
  1996. }
  1997. $sent+= strlen($temp);
  1998. if (is_callable($progressCallback)) {
  1999. $progressCallback($sent);
  2000. }
  2001. $i++;
  2002. $j++;
  2003. if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) {
  2004. if (!$this->read_put_responses($i)) {
  2005. $i = 0;
  2006. break;
  2007. }
  2008. $i = 0;
  2009. }
  2010. }
  2011. $result = $this->close_handle($handle);
  2012. if (!$this->read_put_responses($i)) {
  2013. if ($mode & self::SOURCE_LOCAL_FILE) {
  2014. fclose($fp);
  2015. }
  2016. $this->close_handle($handle);
  2017. return false;
  2018. }
  2019. if ($mode & SFTP::SOURCE_LOCAL_FILE) {
  2020. if (isset($fp) && is_resource($fp)) {
  2021. fclose($fp);
  2022. }
  2023. if ($this->preserveTime) {
  2024. $stat = stat($data);
  2025. $attr = $this->version < 4 ?
  2026. pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['time']) :
  2027. Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'], $stat['time']);
  2028. if (!$this->setstat($remote_file, $attr, false)) {
  2029. throw new \RuntimeException('Error setting file time');
  2030. }
  2031. }
  2032. }
  2033. return $result;
  2034. }
  2035. /**
  2036. * Reads multiple successive SSH_FXP_WRITE responses
  2037. *
  2038. * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
  2039. * SSH_FXP_WRITEs, in succession, and then reading $i responses.
  2040. *
  2041. * @param int $i
  2042. * @return bool
  2043. * @throws \UnexpectedValueException on receipt of unexpected packets
  2044. * @access private
  2045. */
  2046. private function read_put_responses($i)
  2047. {
  2048. while ($i--) {
  2049. $response = $this->get_sftp_packet();
  2050. if ($this->packet_type != NET_SFTP_STATUS) {
  2051. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  2052. . 'Got packet type: ' . $this->packet_type);
  2053. }
  2054. list($status) = Strings::unpackSSH2('N', $response);
  2055. if ($status != NET_SFTP_STATUS_OK) {
  2056. $this->logError($response, $status);
  2057. break;
  2058. }
  2059. }
  2060. return $i < 0;
  2061. }
  2062. /**
  2063. * Close handle
  2064. *
  2065. * @param string $handle
  2066. * @return bool
  2067. * @throws \UnexpectedValueException on receipt of unexpected packets
  2068. * @access private
  2069. */
  2070. private function close_handle($handle)
  2071. {
  2072. $this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle));
  2073. // "The client MUST release all resources associated with the handle regardless of the status."
  2074. // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
  2075. $response = $this->get_sftp_packet();
  2076. if ($this->packet_type != NET_SFTP_STATUS) {
  2077. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  2078. . 'Got packet type: ' . $this->packet_type);
  2079. }
  2080. list($status) = Strings::unpackSSH2('N', $response);
  2081. if ($status != NET_SFTP_STATUS_OK) {
  2082. $this->logError($response, $status);
  2083. return false;
  2084. }
  2085. return true;
  2086. }
  2087. /**
  2088. * Downloads a file from the SFTP server.
  2089. *
  2090. * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
  2091. * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
  2092. * operation.
  2093. *
  2094. * $offset and $length can be used to download files in chunks.
  2095. *
  2096. * @param string $remote_file
  2097. * @param string|bool|resource|callable $local_file
  2098. * @param int $offset
  2099. * @param int $length
  2100. * @param callable|null $progressCallback
  2101. * @throws \UnexpectedValueException on receipt of unexpected packets
  2102. * @return mixed
  2103. * @access public
  2104. */
  2105. public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
  2106. {
  2107. if (!$this->precheck()) {
  2108. return false;
  2109. }
  2110. $remote_file = $this->realpath($remote_file);
  2111. if ($remote_file === false) {
  2112. return false;
  2113. }
  2114. $packet = Strings::packSSH2('s', $remote_file);
  2115. $packet.= $this->version >= 5 ?
  2116. pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) :
  2117. pack('N2', NET_SFTP_OPEN_READ, 0);
  2118. $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
  2119. $response = $this->get_sftp_packet();
  2120. switch ($this->packet_type) {
  2121. case NET_SFTP_HANDLE:
  2122. $handle = substr($response, 4);
  2123. break;
  2124. case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2125. $this->logError($response);
  2126. return false;
  2127. default:
  2128. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
  2129. . 'Got packet type: ' . $this->packet_type);
  2130. }
  2131. if (is_resource($local_file)) {
  2132. $fp = $local_file;
  2133. $stat = fstat($fp);
  2134. $res_offset = $stat['size'];
  2135. } else {
  2136. $res_offset = 0;
  2137. if ($local_file !== false && !is_callable($local_file)) {
  2138. $fp = fopen($local_file, 'wb');
  2139. if (!$fp) {
  2140. return false;
  2141. }
  2142. } else {
  2143. $content = '';
  2144. }
  2145. }
  2146. $fclose_check = $local_file !== false && !is_callable($local_file) && !is_resource($local_file);
  2147. $start = $offset;
  2148. $read = 0;
  2149. while (true) {
  2150. $i = 0;
  2151. while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
  2152. $tempoffset = $start + $read;
  2153. $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
  2154. $packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
  2155. try {
  2156. $this->send_sftp_packet(NET_SFTP_READ, $packet, $i);
  2157. } catch (\Exception $e) {
  2158. if ($fclose_check) {
  2159. fclose($fp);
  2160. }
  2161. throw $e;
  2162. }
  2163. $packet = null;
  2164. $read+= $packet_size;
  2165. $i++;
  2166. }
  2167. if (!$i) {
  2168. break;
  2169. }
  2170. $packets_sent = $i - 1;
  2171. $clear_responses = false;
  2172. while ($i > 0) {
  2173. $i--;
  2174. if ($clear_responses) {
  2175. $this->get_sftp_packet($packets_sent - $i);
  2176. continue;
  2177. } else {
  2178. $response = $this->get_sftp_packet($packets_sent - $i);
  2179. }
  2180. switch ($this->packet_type) {
  2181. case NET_SFTP_DATA:
  2182. $temp = substr($response, 4);
  2183. $offset+= strlen($temp);
  2184. if ($local_file === false) {
  2185. $content.= $temp;
  2186. } elseif (is_callable($local_file)) {
  2187. $local_file($temp);
  2188. } else {
  2189. fputs($fp, $temp);
  2190. }
  2191. if (is_callable($progressCallback)) {
  2192. call_user_func($progressCallback, $offset);
  2193. }
  2194. $temp = null;
  2195. break;
  2196. case NET_SFTP_STATUS:
  2197. // could, in theory, return false if !strlen($content) but we'll hold off for the time being
  2198. $this->logError($response);
  2199. $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
  2200. break;
  2201. default:
  2202. if ($fclose_check) {
  2203. fclose($fp);
  2204. }
  2205. if ($this->channel_close) {
  2206. $this->partial_init = false;
  2207. $this->init_sftp_connection();
  2208. return false;
  2209. } else {
  2210. throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. '
  2211. . 'Got packet type: ' . $this->packet_type);
  2212. }
  2213. }
  2214. $response = null;
  2215. }
  2216. if ($clear_responses) {
  2217. break;
  2218. }
  2219. }
  2220. if ($length > 0 && $length <= $offset - $start) {
  2221. if ($local_file === false) {
  2222. $content = substr($content, 0, $length);
  2223. } else {
  2224. ftruncate($fp, $length + $res_offset);
  2225. }
  2226. }
  2227. if ($fclose_check) {
  2228. fclose($fp);
  2229. if ($this->preserveTime) {
  2230. $stat = $this->stat($remote_file);
  2231. touch($local_file, $stat['mtime'], $stat['atime']);
  2232. }
  2233. }
  2234. if (!$this->close_handle($handle)) {
  2235. return false;
  2236. }
  2237. // if $content isn't set that means a file was written to
  2238. return isset($content) ? $content : true;
  2239. }
  2240. /**
  2241. * Deletes a file on the SFTP server.
  2242. *
  2243. * @param string $path
  2244. * @param bool $recursive
  2245. * @return bool
  2246. * @throws \UnexpectedValueException on receipt of unexpected packets
  2247. * @access public
  2248. */
  2249. public function delete($path, $recursive = true)
  2250. {
  2251. if (!$this->precheck()) {
  2252. return false;
  2253. }
  2254. if (is_object($path)) {
  2255. // It's an object. Cast it as string before we check anything else.
  2256. $path = (string) $path;
  2257. }
  2258. if (!is_string($path) || $path == '') {
  2259. return false;
  2260. }
  2261. $path = $this->realpath($path);
  2262. if ($path === false) {
  2263. return false;
  2264. }
  2265. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  2266. $this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path));
  2267. $response = $this->get_sftp_packet();
  2268. if ($this->packet_type != NET_SFTP_STATUS) {
  2269. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  2270. . 'Got packet type: ' . $this->packet_type);
  2271. }
  2272. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2273. list($status) = Strings::unpackSSH2('N', $response);
  2274. if ($status != NET_SFTP_STATUS_OK) {
  2275. $this->logError($response, $status);
  2276. if (!$recursive) {
  2277. return false;
  2278. }
  2279. $i = 0;
  2280. $result = $this->delete_recursive($path, $i);
  2281. $this->read_put_responses($i);
  2282. return $result;
  2283. }
  2284. $this->remove_from_stat_cache($path);
  2285. return true;
  2286. }
  2287. /**
  2288. * Recursively deletes directories on the SFTP server
  2289. *
  2290. * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  2291. *
  2292. * @param string $path
  2293. * @param int $i
  2294. * @return bool
  2295. * @access private
  2296. */
  2297. private function delete_recursive($path, &$i)
  2298. {
  2299. if (!$this->read_put_responses($i)) {
  2300. return false;
  2301. }
  2302. $i = 0;
  2303. $entries = $this->readlist($path, true);
  2304. // normally $entries would have at least . and .. but it might not if the directories
  2305. // permissions didn't allow reading
  2306. if (empty($entries)) {
  2307. return false;
  2308. }
  2309. unset($entries['.'], $entries['..']);
  2310. foreach ($entries as $filename => $props) {
  2311. if (!isset($props['type'])) {
  2312. return false;
  2313. }
  2314. $temp = $path . '/' . $filename;
  2315. if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  2316. if (!$this->delete_recursive($temp, $i)) {
  2317. return false;
  2318. }
  2319. } else {
  2320. $this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp));
  2321. $this->remove_from_stat_cache($temp);
  2322. $i++;
  2323. if ($i >= NET_SFTP_QUEUE_SIZE) {
  2324. if (!$this->read_put_responses($i)) {
  2325. return false;
  2326. }
  2327. $i = 0;
  2328. }
  2329. }
  2330. }
  2331. $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path));
  2332. $this->remove_from_stat_cache($path);
  2333. $i++;
  2334. if ($i >= NET_SFTP_QUEUE_SIZE) {
  2335. if (!$this->read_put_responses($i)) {
  2336. return false;
  2337. }
  2338. $i = 0;
  2339. }
  2340. return true;
  2341. }
  2342. /**
  2343. * Checks whether a file or directory exists
  2344. *
  2345. * @param string $path
  2346. * @return bool
  2347. * @access public
  2348. */
  2349. public function file_exists($path)
  2350. {
  2351. if ($this->use_stat_cache) {
  2352. if (!$this->precheck()) {
  2353. return false;
  2354. }
  2355. $path = $this->realpath($path);
  2356. $result = $this->query_stat_cache($path);
  2357. if (isset($result)) {
  2358. // return true if $result is an array or if it's an stdClass object
  2359. return $result !== false;
  2360. }
  2361. }
  2362. return $this->stat($path) !== false;
  2363. }
  2364. /**
  2365. * Tells whether the filename is a directory
  2366. *
  2367. * @param string $path
  2368. * @return bool
  2369. * @access public
  2370. */
  2371. public function is_dir($path)
  2372. {
  2373. $result = $this->get_stat_cache_prop($path, 'type');
  2374. if ($result === false) {
  2375. return false;
  2376. }
  2377. return $result === NET_SFTP_TYPE_DIRECTORY;
  2378. }
  2379. /**
  2380. * Tells whether the filename is a regular file
  2381. *
  2382. * @param string $path
  2383. * @return bool
  2384. * @access public
  2385. */
  2386. public function is_file($path)
  2387. {
  2388. $result = $this->get_stat_cache_prop($path, 'type');
  2389. if ($result === false) {
  2390. return false;
  2391. }
  2392. return $result === NET_SFTP_TYPE_REGULAR;
  2393. }
  2394. /**
  2395. * Tells whether the filename is a symbolic link
  2396. *
  2397. * @param string $path
  2398. * @return bool
  2399. * @access public
  2400. */
  2401. public function is_link($path)
  2402. {
  2403. $result = $this->get_lstat_cache_prop($path, 'type');
  2404. if ($result === false) {
  2405. return false;
  2406. }
  2407. return $result === NET_SFTP_TYPE_SYMLINK;
  2408. }
  2409. /**
  2410. * Tells whether a file exists and is readable
  2411. *
  2412. * @param string $path
  2413. * @return bool
  2414. * @access public
  2415. */
  2416. public function is_readable($path)
  2417. {
  2418. if (!$this->precheck()) {
  2419. return false;
  2420. }
  2421. $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
  2422. $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
  2423. $response = $this->get_sftp_packet();
  2424. switch ($this->packet_type) {
  2425. case NET_SFTP_HANDLE:
  2426. return true;
  2427. case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2428. return false;
  2429. default:
  2430. throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
  2431. . 'Got packet type: ' . $this->packet_type);
  2432. }
  2433. }
  2434. /**
  2435. * Tells whether the filename is writable
  2436. *
  2437. * @param string $path
  2438. * @return bool
  2439. * @access public
  2440. */
  2441. public function is_writable($path)
  2442. {
  2443. if (!$this->precheck()) {
  2444. return false;
  2445. }
  2446. $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
  2447. $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
  2448. $response = $this->get_sftp_packet();
  2449. switch ($this->packet_type) {
  2450. case NET_SFTP_HANDLE:
  2451. return true;
  2452. case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2453. return false;
  2454. default:
  2455. throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS. '
  2456. . 'Got packet type: ' . $this->packet_type);
  2457. }
  2458. }
  2459. /**
  2460. * Tells whether the filename is writeable
  2461. *
  2462. * Alias of is_writable
  2463. *
  2464. * @param string $path
  2465. * @return bool
  2466. * @access public
  2467. */
  2468. public function is_writeable($path)
  2469. {
  2470. return $this->is_writable($path);
  2471. }
  2472. /**
  2473. * Gets last access time of file
  2474. *
  2475. * @param string $path
  2476. * @return mixed
  2477. * @access public
  2478. */
  2479. public function fileatime($path)
  2480. {
  2481. return $this->get_stat_cache_prop($path, 'atime');
  2482. }
  2483. /**
  2484. * Gets file modification time
  2485. *
  2486. * @param string $path
  2487. * @return mixed
  2488. * @access public
  2489. */
  2490. public function filemtime($path)
  2491. {
  2492. return $this->get_stat_cache_prop($path, 'mtime');
  2493. }
  2494. /**
  2495. * Gets file permissions
  2496. *
  2497. * @param string $path
  2498. * @return mixed
  2499. * @access public
  2500. */
  2501. public function fileperms($path)
  2502. {
  2503. return $this->get_stat_cache_prop($path, 'mode');
  2504. }
  2505. /**
  2506. * Gets file owner
  2507. *
  2508. * @param string $path
  2509. * @return mixed
  2510. * @access public
  2511. */
  2512. public function fileowner($path)
  2513. {
  2514. return $this->get_stat_cache_prop($path, 'uid');
  2515. }
  2516. /**
  2517. * Gets file group
  2518. *
  2519. * @param string $path
  2520. * @return mixed
  2521. * @access public
  2522. */
  2523. public function filegroup($path)
  2524. {
  2525. return $this->get_stat_cache_prop($path, 'gid');
  2526. }
  2527. /**
  2528. * Gets file size
  2529. *
  2530. * @param string $path
  2531. * @return mixed
  2532. * @access public
  2533. */
  2534. public function filesize($path)
  2535. {
  2536. return $this->get_stat_cache_prop($path, 'size');
  2537. }
  2538. /**
  2539. * Gets file type
  2540. *
  2541. * @param string $path
  2542. * @return mixed
  2543. * @access public
  2544. */
  2545. public function filetype($path)
  2546. {
  2547. $type = $this->get_stat_cache_prop($path, 'type');
  2548. if ($type === false) {
  2549. return false;
  2550. }
  2551. switch ($type) {
  2552. case NET_SFTP_TYPE_BLOCK_DEVICE:
  2553. return 'block';
  2554. case NET_SFTP_TYPE_CHAR_DEVICE:
  2555. return 'char';
  2556. case NET_SFTP_TYPE_DIRECTORY:
  2557. return 'dir';
  2558. case NET_SFTP_TYPE_FIFO:
  2559. return 'fifo';
  2560. case NET_SFTP_TYPE_REGULAR:
  2561. return 'file';
  2562. case NET_SFTP_TYPE_SYMLINK:
  2563. return 'link';
  2564. default:
  2565. return false;
  2566. }
  2567. }
  2568. /**
  2569. * Return a stat properity
  2570. *
  2571. * Uses cache if appropriate.
  2572. *
  2573. * @param string $path
  2574. * @param string $prop
  2575. * @return mixed
  2576. * @access private
  2577. */
  2578. private function get_stat_cache_prop($path, $prop)
  2579. {
  2580. return $this->get_xstat_cache_prop($path, $prop, 'stat');
  2581. }
  2582. /**
  2583. * Return an lstat properity
  2584. *
  2585. * Uses cache if appropriate.
  2586. *
  2587. * @param string $path
  2588. * @param string $prop
  2589. * @return mixed
  2590. * @access private
  2591. */
  2592. private function get_lstat_cache_prop($path, $prop)
  2593. {
  2594. return $this->get_xstat_cache_prop($path, $prop, 'lstat');
  2595. }
  2596. /**
  2597. * Return a stat or lstat properity
  2598. *
  2599. * Uses cache if appropriate.
  2600. *
  2601. * @param string $path
  2602. * @param string $prop
  2603. * @param string $type
  2604. * @return mixed
  2605. * @access private
  2606. */
  2607. private function get_xstat_cache_prop($path, $prop, $type)
  2608. {
  2609. if (!$this->precheck()) {
  2610. return false;
  2611. }
  2612. if ($this->use_stat_cache) {
  2613. $path = $this->realpath($path);
  2614. $result = $this->query_stat_cache($path);
  2615. if (is_object($result) && isset($result->$type)) {
  2616. return $result->{$type}[$prop];
  2617. }
  2618. }
  2619. $result = $this->$type($path);
  2620. if ($result === false || !isset($result[$prop])) {
  2621. return false;
  2622. }
  2623. return $result[$prop];
  2624. }
  2625. /**
  2626. * Renames a file or a directory on the SFTP server.
  2627. *
  2628. * If the file already exists this will return false
  2629. *
  2630. * @param string $oldname
  2631. * @param string $newname
  2632. * @return bool
  2633. * @throws \UnexpectedValueException on receipt of unexpected packets
  2634. * @access public
  2635. */
  2636. public function rename($oldname, $newname)
  2637. {
  2638. if (!$this->precheck()) {
  2639. return false;
  2640. }
  2641. $oldname = $this->realpath($oldname);
  2642. $newname = $this->realpath($newname);
  2643. if ($oldname === false || $newname === false) {
  2644. return false;
  2645. }
  2646. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  2647. $packet = Strings::packSSH2('ss', $oldname, $newname);
  2648. if ($this->version >= 5) {
  2649. /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 ,
  2650. 'flags' is 0 or a combination of:
  2651. SSH_FXP_RENAME_OVERWRITE 0x00000001
  2652. SSH_FXP_RENAME_ATOMIC 0x00000002
  2653. SSH_FXP_RENAME_NATIVE 0x00000004
  2654. (none of these are currently supported) */
  2655. $packet.= "\0\0\0\0";
  2656. }
  2657. $this->send_sftp_packet(NET_SFTP_RENAME, $packet);
  2658. $response = $this->get_sftp_packet();
  2659. if ($this->packet_type != NET_SFTP_STATUS) {
  2660. throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
  2661. . 'Got packet type: ' . $this->packet_type);
  2662. }
  2663. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2664. list($status) = Strings::unpackSSH2('N', $response);
  2665. if ($status != NET_SFTP_STATUS_OK) {
  2666. $this->logError($response, $status);
  2667. return false;
  2668. }
  2669. // don't move the stat cache entry over since this operation could very well change the
  2670. // atime and mtime attributes
  2671. //$this->update_stat_cache($newname, $this->query_stat_cache($oldname));
  2672. $this->remove_from_stat_cache($oldname);
  2673. $this->remove_from_stat_cache($newname);
  2674. return true;
  2675. }
  2676. /**
  2677. * Parse Time
  2678. *
  2679. * See '7.7. Times' of draft-ietf-secsh-filexfer-13 for more info.
  2680. *
  2681. * @param string $key
  2682. * @param int $flags
  2683. * @param string $response
  2684. * @return array
  2685. * @access private
  2686. */
  2687. private function parseTime($key, $flags, &$response)
  2688. {
  2689. $attr = [];
  2690. list($attr[$key]) = Strings::unpackSSH2('Q', $response);
  2691. if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) {
  2692. list($attr[key . '-nseconds']) = Strings::unpackSSH2('N', $response);
  2693. }
  2694. return $attr;
  2695. }
  2696. /**
  2697. * Parse Attributes
  2698. *
  2699. * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
  2700. *
  2701. * @param string $response
  2702. * @return array
  2703. * @access private
  2704. */
  2705. protected function parseAttributes(&$response)
  2706. {
  2707. if ($this->version >= 4) {
  2708. list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response);
  2709. } else {
  2710. list($flags) = Strings::unpackSSH2('N', $response);
  2711. }
  2712. foreach ($this->attributes as $key => $value) {
  2713. switch ($flags & $key) {
  2714. case NET_SFTP_ATTR_SIZE: // 0x00000001
  2715. // The size attribute is defined as an unsigned 64-bit integer.
  2716. // The following will use floats on 32-bit platforms, if necessary.
  2717. // As can be seen in the BigInteger class, floats are generally
  2718. // IEEE 754 binary64 "double precision" on such platforms and
  2719. // as such can represent integers of at least 2^50 without loss
  2720. // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
  2721. list($attr['size']) = Strings::unpackSSH2('Q', $response);
  2722. break;
  2723. case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
  2724. list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
  2725. break;
  2726. case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
  2727. list($attr['mode']) = Strings::unpackSSH2('N', $response);
  2728. $fileType = $this->parseMode($attr['mode']);
  2729. if ($this->version < 4 && $fileType !== false) {
  2730. $attr+= ['type' => $fileType];
  2731. }
  2732. break;
  2733. case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
  2734. if ($this->version >= 4) {
  2735. $attr+= $this->parseTime('atime', $flags, $response);
  2736. break;
  2737. }
  2738. list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
  2739. break;
  2740. case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+)
  2741. $attr+= $this->parseTime('createtime', $flags, $response);
  2742. break;
  2743. case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020
  2744. $attr+= $this->parseTime('mtime', $flags, $response);
  2745. break;
  2746. case NET_SFTP_ATTR_ACL: // 0x00000040
  2747. // access control list
  2748. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7
  2749. // currently unsupported
  2750. list($count) = Strings::unpackSSH2('N', $response);
  2751. for ($i = 0; $i < $count; $i++) {
  2752. list($type, $flag, $mask, $who) = Strings::unpackSSH2('N3s', $result);
  2753. }
  2754. break;
  2755. case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080
  2756. list($attr['owner'], $attr['$group']) = Strings::unpackSSH2('ss', $response);
  2757. break;
  2758. case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100
  2759. break;
  2760. case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+)
  2761. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8
  2762. // currently unsupported
  2763. // tells if you file is:
  2764. // readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse
  2765. // append only, immutable, sync
  2766. list($attrib_bits, $attrib_bits_valid) = Strings::unpackSSH2('N2', $response);
  2767. // if we were actually gonna implement the above it ought to be
  2768. // $attr['attrib-bits'] and $attr['attrib-bits-valid']
  2769. // eg. - instead of _
  2770. break;
  2771. case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+)
  2772. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4
  2773. // represents the number of bytes that the file consumes on the disk. will
  2774. // usually be larger than the 'size' field
  2775. list($attr['allocation-size']) = Strings::unpack('Q', $response);
  2776. break;
  2777. case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800
  2778. // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10
  2779. // currently unsupported
  2780. // tells if file is "known text", "guessed text", "known binary", "guessed binary"
  2781. list($text_hint) = Strings::unpackSSH2('C', $response);
  2782. // the above should be $attr['text-hint']
  2783. break;
  2784. case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000
  2785. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11
  2786. list($attr['mime-type']) = Strings::unpackSSH2('s', $response);
  2787. break;
  2788. case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000
  2789. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12
  2790. list($attr['link-count']) = Strings::unpackSS2('N', $response);
  2791. break;
  2792. case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000
  2793. // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13
  2794. list($attr['untranslated-name']) = Strings::unpackSSH2('s', $response);
  2795. break;
  2796. case NET_SFTP_ATTR_CTIME: // 0x00008000
  2797. // 'ctime' contains the last time the file attributes were changed. The
  2798. // exact meaning of this field depends on the server.
  2799. $attr+= $this->parseTime('ctime', $flags, $response);
  2800. break;
  2801. case NET_SFTP_ATTR_EXTENDED: // 0x80000000
  2802. list($count) = Strings::unpackSSH2('N', $response);
  2803. for ($i = 0; $i < $count; $i++) {
  2804. list($key, $value) = Strings::unpackSSH2('ss', $response);
  2805. $attr[$key] = $value;
  2806. }
  2807. }
  2808. }
  2809. return $attr;
  2810. }
  2811. /**
  2812. * Attempt to identify the file type
  2813. *
  2814. * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
  2815. *
  2816. * @param int $mode
  2817. * @return int
  2818. * @access private
  2819. */
  2820. private function parseMode($mode)
  2821. {
  2822. // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
  2823. // see, also, http://linux.die.net/man/2/stat
  2824. switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
  2825. case 0000000: // no file type specified - figure out the file type using alternative means
  2826. return false;
  2827. case 0040000:
  2828. return NET_SFTP_TYPE_DIRECTORY;
  2829. case 0100000:
  2830. return NET_SFTP_TYPE_REGULAR;
  2831. case 0120000:
  2832. return NET_SFTP_TYPE_SYMLINK;
  2833. // new types introduced in SFTPv5+
  2834. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  2835. case 0010000: // named pipe (fifo)
  2836. return NET_SFTP_TYPE_FIFO;
  2837. case 0020000: // character special
  2838. return NET_SFTP_TYPE_CHAR_DEVICE;
  2839. case 0060000: // block special
  2840. return NET_SFTP_TYPE_BLOCK_DEVICE;
  2841. case 0140000: // socket
  2842. return NET_SFTP_TYPE_SOCKET;
  2843. case 0160000: // whiteout
  2844. // "SPECIAL should be used for files that are of
  2845. // a known type which cannot be expressed in the protocol"
  2846. return NET_SFTP_TYPE_SPECIAL;
  2847. default:
  2848. return NET_SFTP_TYPE_UNKNOWN;
  2849. }
  2850. }
  2851. /**
  2852. * Parse Longname
  2853. *
  2854. * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
  2855. * a file as a directory and see if an error is returned or you could try to parse the
  2856. * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
  2857. * The result is returned using the
  2858. * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
  2859. *
  2860. * If the longname is in an unrecognized format bool(false) is returned.
  2861. *
  2862. * @param string $longname
  2863. * @return mixed
  2864. * @access private
  2865. */
  2866. private function parseLongname($longname)
  2867. {
  2868. // http://en.wikipedia.org/wiki/Unix_file_types
  2869. // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
  2870. if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
  2871. switch ($longname[0]) {
  2872. case '-':
  2873. return NET_SFTP_TYPE_REGULAR;
  2874. case 'd':
  2875. return NET_SFTP_TYPE_DIRECTORY;
  2876. case 'l':
  2877. return NET_SFTP_TYPE_SYMLINK;
  2878. default:
  2879. return NET_SFTP_TYPE_SPECIAL;
  2880. }
  2881. }
  2882. return false;
  2883. }
  2884. /**
  2885. * Sends SFTP Packets
  2886. *
  2887. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  2888. *
  2889. * @param int $type
  2890. * @param string $data
  2891. * @param int $request_id
  2892. * @see self::_get_sftp_packet()
  2893. * @see self::send_channel_packet()
  2894. * @return bool
  2895. * @access private
  2896. */
  2897. private function send_sftp_packet($type, $data, $request_id = 1)
  2898. {
  2899. // in SSH2.php the timeout is cumulative per function call. eg. exec() will
  2900. // timeout after 10s. but for SFTP.php it's cumulative per packet
  2901. $this->curTimeout = $this->timeout;
  2902. $packet = $this->use_request_id ?
  2903. pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
  2904. pack('NCa*', strlen($data) + 1, $type, $data);
  2905. $start = microtime(true);
  2906. $result = $this->send_channel_packet(self::CHANNEL, $packet);
  2907. $stop = microtime(true);
  2908. if (defined('NET_SFTP_LOGGING')) {
  2909. $packet_type = '-> ' . $this->packet_types[$type] .
  2910. ' (' . round($stop - $start, 4) . 's)';
  2911. if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
  2912. switch (PHP_SAPI) {
  2913. case 'cli':
  2914. $start = $stop = "\r\n";
  2915. break;
  2916. default:
  2917. $start = '<pre>';
  2918. $stop = '</pre>';
  2919. }
  2920. echo $start . $this->format_log([$data], [$packet_type]) . $stop;
  2921. @flush();
  2922. @ob_flush();
  2923. } else {
  2924. $this->packet_type_log[] = $packet_type;
  2925. if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
  2926. $this->packet_log[] = $data;
  2927. }
  2928. }
  2929. }
  2930. return $result;
  2931. }
  2932. /**
  2933. * Resets a connection for re-use
  2934. *
  2935. * @param int $reason
  2936. * @access private
  2937. */
  2938. protected function reset_connection($reason)
  2939. {
  2940. parent::reset_connection($reason);
  2941. $this->use_request_id = false;
  2942. $this->pwd = false;
  2943. $this->requestBuffer = [];
  2944. }
  2945. /**
  2946. * Receives SFTP Packets
  2947. *
  2948. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  2949. *
  2950. * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
  2951. * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
  2952. * messages containing one SFTP packet.
  2953. *
  2954. * @see self::_send_sftp_packet()
  2955. * @return string
  2956. * @access private
  2957. */
  2958. private function get_sftp_packet($request_id = null)
  2959. {
  2960. $this->channel_close = false;
  2961. if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
  2962. $this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
  2963. $temp = $this->requestBuffer[$request_id]['packet'];
  2964. unset($this->requestBuffer[$request_id]);
  2965. return $temp;
  2966. }
  2967. // in SSH2.php the timeout is cumulative per function call. eg. exec() will
  2968. // timeout after 10s. but for SFTP.php it's cumulative per packet
  2969. $this->curTimeout = $this->timeout;
  2970. $start = microtime(true);
  2971. // SFTP packet length
  2972. while (strlen($this->packet_buffer) < 4) {
  2973. $temp = $this->get_channel_packet(self::CHANNEL, true);
  2974. if ($temp === true) {
  2975. if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) {
  2976. $this->channel_close = true;
  2977. }
  2978. $this->packet_type = false;
  2979. $this->packet_buffer = '';
  2980. return false;
  2981. }
  2982. $this->packet_buffer.= $temp;
  2983. }
  2984. if (strlen($this->packet_buffer) < 4) {
  2985. throw new \RuntimeException('Packet is too small');
  2986. }
  2987. extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4)));
  2988. /** @var integer $length */
  2989. $tempLength = $length;
  2990. $tempLength-= strlen($this->packet_buffer);
  2991. // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
  2992. if (!$this->allow_arbitrary_length_packets && !$this->use_request_id && $tempLength > 256 * 1024) {
  2993. throw new \RuntimeException('Invalid Size');
  2994. }
  2995. // SFTP packet type and data payload
  2996. while ($tempLength > 0) {
  2997. $temp = $this->get_channel_packet(self::CHANNEL, true);
  2998. if (is_bool($temp)) {
  2999. $this->packet_type = false;
  3000. $this->packet_buffer = '';
  3001. return false;
  3002. }
  3003. $this->packet_buffer.= $temp;
  3004. $tempLength-= strlen($temp);
  3005. }
  3006. $stop = microtime(true);
  3007. $this->packet_type = ord(Strings::shift($this->packet_buffer));
  3008. if ($this->use_request_id) {
  3009. extract(unpack('Npacket_id', Strings::shift($this->packet_buffer, 4))); // remove the request id
  3010. $length-= 5; // account for the request id and the packet type
  3011. } else {
  3012. $length-= 1; // account for the packet type
  3013. }
  3014. $packet = Strings::shift($this->packet_buffer, $length);
  3015. if (defined('NET_SFTP_LOGGING')) {
  3016. $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
  3017. ' (' . round($stop - $start, 4) . 's)';
  3018. if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
  3019. switch (PHP_SAPI) {
  3020. case 'cli':
  3021. $start = $stop = "\r\n";
  3022. break;
  3023. default:
  3024. $start = '<pre>';
  3025. $stop = '</pre>';
  3026. }
  3027. echo $start . $this->format_log([$packet], [$packet_type]) . $stop;
  3028. @flush();
  3029. @ob_flush();
  3030. } else {
  3031. $this->packet_type_log[] = $packet_type;
  3032. if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
  3033. $this->packet_log[] = $packet;
  3034. }
  3035. }
  3036. }
  3037. if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
  3038. $this->requestBuffer[$packet_id] = [
  3039. 'packet_type' => $this->packet_type,
  3040. 'packet' => $packet
  3041. ];
  3042. return $this->get_sftp_packet($request_id);
  3043. }
  3044. return $packet;
  3045. }
  3046. /**
  3047. * Returns a log of the packets that have been sent and received.
  3048. *
  3049. * Returns a string if NET_SFTP_LOGGING == self::LOG_COMPLEX, an array if NET_SFTP_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
  3050. *
  3051. * @access public
  3052. * @return array|string
  3053. */
  3054. public function getSFTPLog()
  3055. {
  3056. if (!defined('NET_SFTP_LOGGING')) {
  3057. return false;
  3058. }
  3059. switch (NET_SFTP_LOGGING) {
  3060. case self::LOG_COMPLEX:
  3061. return $this->format_log($this->packet_log, $this->packet_type_log);
  3062. break;
  3063. //case self::LOG_SIMPLE:
  3064. default:
  3065. return $this->packet_type_log;
  3066. }
  3067. }
  3068. /**
  3069. * Returns all errors
  3070. *
  3071. * @return array
  3072. * @access public
  3073. */
  3074. public function getSFTPErrors()
  3075. {
  3076. return $this->sftp_errors;
  3077. }
  3078. /**
  3079. * Returns the last error
  3080. *
  3081. * @return string
  3082. * @access public
  3083. */
  3084. public function getLastSFTPError()
  3085. {
  3086. return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
  3087. }
  3088. /**
  3089. * Get supported SFTP versions
  3090. *
  3091. * @return array
  3092. * @access public
  3093. */
  3094. public function getSupportedVersions()
  3095. {
  3096. if (!($this->bitmap & SSH2::MASK_LOGIN)) {
  3097. return false;
  3098. }
  3099. if (!$this->partial_init) {
  3100. $this->partial_init_sftp_connection();
  3101. }
  3102. $temp = ['version' => $this->defaultVersion];
  3103. if (isset($this->extensions['versions'])) {
  3104. $temp['extensions'] = $this->extensions['versions'];
  3105. }
  3106. return $temp;
  3107. }
  3108. /**
  3109. * Get supported SFTP versions
  3110. *
  3111. * @return array
  3112. * @access public
  3113. */
  3114. public function getNegotiatedVersion()
  3115. {
  3116. if (!$this->precheck()) {
  3117. return false;
  3118. }
  3119. return $this->version;
  3120. }
  3121. /**
  3122. * Set preferred version
  3123. *
  3124. * If you're preferred version isn't supported then the highest supported
  3125. * version of SFTP will be utilized. Set to null or false or int(0) to
  3126. * unset the preferred version
  3127. *
  3128. * @param int $version
  3129. * @access public
  3130. */
  3131. public function setPreferredVersion($version)
  3132. {
  3133. $this->preferredVersion = $version;
  3134. }
  3135. /**
  3136. * Disconnect
  3137. *
  3138. * @param int $reason
  3139. * @return bool
  3140. * @access protected
  3141. */
  3142. protected function disconnect_helper($reason)
  3143. {
  3144. $this->pwd = false;
  3145. parent::disconnect_helper($reason);
  3146. }
  3147. /**
  3148. * Enable Date Preservation
  3149. *
  3150. * @access public
  3151. */
  3152. public function enableDatePreservation()
  3153. {
  3154. $this->preserveTime = true;
  3155. }
  3156. /**
  3157. * Disable Date Preservation
  3158. *
  3159. * @access public
  3160. */
  3161. public function disableDatePreservation()
  3162. {
  3163. $this->preserveTime = false;
  3164. }
  3165. }