/phpseclib/Net/SFTP.php

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

Large files are truncated click here to view the full 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,