PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Filesystem/Clients/FtpClient.php

https://github.com/piotr-cz/joomla-framework
PHP | 1929 lines | 1701 code | 65 blank | 163 comment | 31 complexity | 715ac9cd2d55b81fb14ad616b7edeb47 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework Client Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\Filesystem\Clients;
  9. use Joomla\Log\Log;
  10. /** Error Codes:
  11. * - 30 : Unable to connect to host
  12. * - 31 : Not connected
  13. * - 32 : Unable to send command to server
  14. * - 33 : Bad username
  15. * - 34 : Bad password
  16. * - 35 : Bad response
  17. * - 36 : Passive mode failed
  18. * - 37 : Data transfer error
  19. * - 38 : Local filesystem error
  20. */
  21. if (!defined('CRLF'))
  22. {
  23. define('CRLF', "\r\n");
  24. }
  25. if (!defined("FTP_AUTOASCII"))
  26. {
  27. define("FTP_AUTOASCII", -1);
  28. }
  29. if (!defined("FTP_BINARY"))
  30. {
  31. define("FTP_BINARY", 1);
  32. }
  33. if (!defined("FTP_ASCII"))
  34. {
  35. define("FTP_ASCII", 0);
  36. }
  37. if (!defined('FTP_NATIVE'))
  38. {
  39. define('FTP_NATIVE', (function_exists('ftp_connect')) ? 1 : 0);
  40. }
  41. /**
  42. * FTP client class
  43. *
  44. * @since 1.0
  45. */
  46. class FtpClient
  47. {
  48. /**
  49. * @var resource Socket resource
  50. * @since 1.0
  51. */
  52. private $conn = null;
  53. /**
  54. * @var resource Data port connection resource
  55. * @since 1.0
  56. */
  57. private $dataconn = null;
  58. /**
  59. * @var array Passive connection information
  60. * @since 1.0
  61. */
  62. private $pasv = null;
  63. /**
  64. * @var string Response Message
  65. * @since 1.0
  66. */
  67. private $response = null;
  68. /**
  69. * @var integer Timeout limit
  70. * @since 1.0
  71. */
  72. private $timeout = 15;
  73. /**
  74. * @var integer Transfer Type
  75. * @since 1.0
  76. */
  77. private $type = null;
  78. /**
  79. * @var array Array to hold ascii format file extensions
  80. * @since 1.0
  81. */
  82. private $autoAscii = array(
  83. "asp",
  84. "bat",
  85. "c",
  86. "cpp",
  87. "csv",
  88. "h",
  89. "htm",
  90. "html",
  91. "shtml",
  92. "ini",
  93. "inc",
  94. "log",
  95. "php",
  96. "php3",
  97. "pl",
  98. "perl",
  99. "sh",
  100. "sql",
  101. "txt",
  102. "xhtml",
  103. "xml");
  104. /**
  105. * Array to hold native line ending characters
  106. *
  107. * @var array
  108. * @since 1.0
  109. */
  110. private $lineEndings = array('UNIX' => "\n", 'WIN' => "\r\n");
  111. /**
  112. * @var array JClientFtp instances container.
  113. * @since 1.0
  114. */
  115. protected static $instances = array();
  116. /**
  117. * JClientFtp object constructor
  118. *
  119. * @param array $options Associative array of options to set
  120. *
  121. * @since 1.0
  122. */
  123. public function __construct(array $options = array())
  124. {
  125. // If default transfer type is not set, set it to autoascii detect
  126. if (!isset($options['type']))
  127. {
  128. $options['type'] = FTP_BINARY;
  129. }
  130. $this->setOptions($options);
  131. if (FTP_NATIVE)
  132. {
  133. // Autoloading fails for Buffer as the class is used as a stream handler
  134. class_exists('Joomla\\Filesystem\\Buffer');
  135. }
  136. }
  137. /**
  138. * JClientFtp object destructor
  139. *
  140. * Closes an existing connection, if we have one
  141. *
  142. * @since 1.0
  143. */
  144. public function __destruct()
  145. {
  146. if (is_resource($this->conn))
  147. {
  148. $this->quit();
  149. }
  150. }
  151. /**
  152. * Returns the global FTP connector object, only creating it
  153. * if it doesn't already exist.
  154. *
  155. * You may optionally specify a username and password in the parameters. If you do so,
  156. * you may not login() again with different credentials using the same object.
  157. * If you do not use this option, you must quit() the current connection when you
  158. * are done, to free it for use by others.
  159. *
  160. * @param string $host Host to connect to
  161. * @param string $port Port to connect to
  162. * @param array $options Array with any of these options: type=>[FTP_AUTOASCII|FTP_ASCII|FTP_BINARY], timeout=>(int)
  163. * @param string $user Username to use for a connection
  164. * @param string $pass Password to use for a connection
  165. *
  166. * @return FtpClient The FTP Client object.
  167. *
  168. * @since 1.0
  169. */
  170. public static function getInstance($host = '127.0.0.1', $port = '21', array $options = array(), $user = null, $pass = null)
  171. {
  172. $signature = $user . ':' . $pass . '@' . $host . ":" . $port;
  173. // Create a new instance, or set the options of an existing one
  174. if (!isset(self::$instances[$signature]) || !is_object(self::$instances[$signature]))
  175. {
  176. self::$instances[$signature] = new static($options);
  177. }
  178. else
  179. {
  180. self::$instances[$signature]->setOptions($options);
  181. }
  182. // Connect to the server, and login, if requested
  183. if (!self::$instances[$signature]->isConnected())
  184. {
  185. $return = self::$instances[$signature]->connect($host, $port);
  186. if ($return && $user !== null && $pass !== null)
  187. {
  188. self::$instances[$signature]->login($user, $pass);
  189. }
  190. }
  191. return self::$instances[$signature];
  192. }
  193. /**
  194. * Set client options
  195. *
  196. * @param array $options Associative array of options to set
  197. *
  198. * @return boolean True if successful
  199. *
  200. * @since 1.0
  201. */
  202. public function setOptions(array $options)
  203. {
  204. if (isset($options['type']))
  205. {
  206. $this->type = $options['type'];
  207. }
  208. if (isset($options['timeout']))
  209. {
  210. $this->timeout = $options['timeout'];
  211. }
  212. return true;
  213. }
  214. /**
  215. * Method to connect to a FTP server
  216. *
  217. * @param string $host Host to connect to [Default: 127.0.0.1]
  218. * @param integer $port Port to connect on [Default: port 21]
  219. *
  220. * @return boolean True if successful
  221. *
  222. * @since 1.0
  223. */
  224. public function connect($host = '127.0.0.1', $port = 21)
  225. {
  226. $errno = null;
  227. $err = null;
  228. // If already connected, return
  229. if (is_resource($this->conn))
  230. {
  231. return true;
  232. }
  233. // If native FTP support is enabled let's use it...
  234. if (FTP_NATIVE)
  235. {
  236. $this->conn = @ftp_connect($host, $port, $this->timeout);
  237. if ($this->conn === false)
  238. {
  239. Log::add(sprintf('%1$s: Could not connect to host " %2$s " on port " %3$s "', __METHOD__, $host, $port), Log::WARNING, 'jerror');
  240. return false;
  241. }
  242. // Set the timeout for this connection
  243. ftp_set_option($this->conn, FTP_TIMEOUT_SEC, $this->timeout);
  244. return true;
  245. }
  246. // Connect to the FTP server.
  247. $this->conn = @ fsockopen($host, $port, $errno, $err, $this->timeout);
  248. if (!$this->conn)
  249. {
  250. Log::add(
  251. sprintf(
  252. '%1$s: Could not connect to host " %2$s " on port " %3$s ". Socket error number: %4$s and error message: %5$s',
  253. __METHOD__,
  254. $host,
  255. $port,
  256. $errno,
  257. $err
  258. ),
  259. Log::WARNING,
  260. 'jerror');
  261. return false;
  262. }
  263. // Set the timeout for this connection
  264. socket_set_timeout($this->conn, $this->timeout, 0);
  265. // Check for welcome response code
  266. if (!$this->_verifyResponse(220))
  267. {
  268. Log::add(sprintf('%1$s: Bad response. Server response: %2$s [Expected: 220]', __METHOD__, $this->response), Log::WARNING, 'jerror');
  269. return false;
  270. }
  271. return true;
  272. }
  273. /**
  274. * Method to determine if the object is connected to an FTP server
  275. *
  276. * @return boolean True if connected
  277. *
  278. * @since 1.0
  279. */
  280. public function isConnected()
  281. {
  282. return is_resource($this->conn);
  283. }
  284. /**
  285. * Method to login to a server once connected
  286. *
  287. * @param string $user Username to login to the server
  288. * @param string $pass Password to login to the server
  289. *
  290. * @return boolean True if successful
  291. *
  292. * @since 1.0
  293. */
  294. public function login($user = 'anonymous', $pass = 'jftp@joomla.org')
  295. {
  296. // If native FTP support is enabled let's use it...
  297. if (FTP_NATIVE)
  298. {
  299. if (@ftp_login($this->conn, $user, $pass) === false)
  300. {
  301. Log::add('JFTP::login: Unable to login', Log::WARNING, 'jerror');
  302. return false;
  303. }
  304. return true;
  305. }
  306. // Send the username
  307. if (!$this->_putCmd('USER ' . $user, array(331, 503)))
  308. {
  309. Log::add(
  310. sprintf('%1$s: Bad Username. Server response: %2$s [Expected: 331]. Username sent: %3$s', __METHOD__, $this->response, $user),
  311. Log::WARNING, 'jerror'
  312. );
  313. return false;
  314. }
  315. // If we are already logged in, continue :)
  316. if ($this->_responseCode == 503)
  317. {
  318. return true;
  319. }
  320. // Send the password
  321. if (!$this->_putCmd('PASS ' . $pass, 230))
  322. {
  323. Log::add(sprintf('%1$s: Bad Password. Server response: %2$s [Expected: 230].', __METHOD__, $this->response), Log::WARNING, 'jerror');
  324. return false;
  325. }
  326. return true;
  327. }
  328. /**
  329. * Method to quit and close the connection
  330. *
  331. * @return boolean True if successful
  332. *
  333. * @since 1.0
  334. */
  335. public function quit()
  336. {
  337. // If native FTP support is enabled lets use it...
  338. if (FTP_NATIVE)
  339. {
  340. @ftp_close($this->conn);
  341. return true;
  342. }
  343. // Logout and close connection
  344. @fwrite($this->conn, "QUIT\r\n");
  345. @fclose($this->conn);
  346. return true;
  347. }
  348. /**
  349. * Method to retrieve the current working directory on the FTP server
  350. *
  351. * @return string Current working directory
  352. *
  353. * @since 1.0
  354. */
  355. public function pwd()
  356. {
  357. // If native FTP support is enabled let's use it...
  358. if (FTP_NATIVE)
  359. {
  360. if (($ret = @ftp_pwd($this->conn)) === false)
  361. {
  362. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  363. return false;
  364. }
  365. return $ret;
  366. }
  367. $match = array(null);
  368. // Send print working directory command and verify success
  369. if (!$this->_putCmd('PWD', 257))
  370. {
  371. Log::add(sprintf('%1$s: Bad response. Server response: %2$s [Expected: 257]', __METHOD__, $this->response), Log::WARNING, 'jerror');
  372. return false;
  373. }
  374. // Match just the path
  375. preg_match('/"[^"\r\n]*"/', $this->response, $match);
  376. // Return the cleaned path
  377. return preg_replace("/\"/", "", $match[0]);
  378. }
  379. /**
  380. * Method to system string from the FTP server
  381. *
  382. * @return string System identifier string
  383. *
  384. * @since 1.0
  385. */
  386. public function syst()
  387. {
  388. // If native FTP support is enabled lets use it...
  389. if (FTP_NATIVE)
  390. {
  391. if (($ret = @ftp_systype($this->conn)) === false)
  392. {
  393. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  394. return false;
  395. }
  396. }
  397. else
  398. {
  399. // Send print working directory command and verify success
  400. if (!$this->_putCmd('SYST', 215))
  401. {
  402. Log::add(sprintf('%1$s: Bad response. Server response: %2$s [Expected: 215]', __METHOD__, $this->response), Log::WARNING, 'jerror');
  403. return false;
  404. }
  405. $ret = $this->response;
  406. }
  407. // Match the system string to an OS
  408. if (strpos(strtoupper($ret), 'MAC') !== false)
  409. {
  410. $ret = 'MAC';
  411. }
  412. elseif (strpos(strtoupper($ret), 'WIN') !== false)
  413. {
  414. $ret = 'WIN';
  415. }
  416. else
  417. {
  418. $ret = 'UNIX';
  419. }
  420. // Return the os type
  421. return $ret;
  422. }
  423. /**
  424. * Method to change the current working directory on the FTP server
  425. *
  426. * @param string $path Path to change into on the server
  427. *
  428. * @return boolean True if successful
  429. *
  430. * @since 1.0
  431. */
  432. public function chdir($path)
  433. {
  434. // If native FTP support is enabled lets use it...
  435. if (FTP_NATIVE)
  436. {
  437. if (@ftp_chdir($this->conn, $path) === false)
  438. {
  439. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  440. return false;
  441. }
  442. return true;
  443. }
  444. // Send change directory command and verify success
  445. if (!$this->_putCmd('CWD ' . $path, 250))
  446. {
  447. Log::add(
  448. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 250]. Sent path: %3$s', __METHOD__, $this->response, $path),
  449. Log::WARNING, 'jerror'
  450. );
  451. return false;
  452. }
  453. return true;
  454. }
  455. /**
  456. * Method to reinitialise the server, ie. need to login again
  457. *
  458. * NOTE: This command not available on all servers
  459. *
  460. * @return boolean True if successful
  461. *
  462. * @since 1.0
  463. */
  464. public function reinit()
  465. {
  466. // If native FTP support is enabled let's use it...
  467. if (FTP_NATIVE)
  468. {
  469. if (@ftp_site($this->conn, 'REIN') === false)
  470. {
  471. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  472. return false;
  473. }
  474. return true;
  475. }
  476. // Send reinitialise command to the server
  477. if (!$this->_putCmd('REIN', 220))
  478. {
  479. Log::add(sprintf('%1$s: Bad response. Server response: %2$s [Expected: 220]', __METHOD__, $this->response), Log::WARNING, 'jerror');
  480. return false;
  481. }
  482. return true;
  483. }
  484. /**
  485. * Method to rename a file/folder on the FTP server
  486. *
  487. * @param string $from Path to change file/folder from
  488. * @param string $to Path to change file/folder to
  489. *
  490. * @return boolean True if successful
  491. *
  492. * @since 1.0
  493. */
  494. public function rename($from, $to)
  495. {
  496. // If native FTP support is enabled let's use it...
  497. if (FTP_NATIVE)
  498. {
  499. if (@ftp_rename($this->conn, $from, $to) === false)
  500. {
  501. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  502. return false;
  503. }
  504. return true;
  505. }
  506. // Send rename from command to the server
  507. if (!$this->_putCmd('RNFR ' . $from, 350))
  508. {
  509. Log::add(
  510. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 350]. From path sent: %3$s', __METHOD__, $this->response, $from),
  511. Log::WARNING, 'jerror'
  512. );
  513. return false;
  514. }
  515. // Send rename to command to the server
  516. if (!$this->_putCmd('RNTO ' . $to, 250))
  517. {
  518. Log::add(
  519. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 250]. To path sent: %3$s', __METHOD__, $this->response, $to),
  520. Log::WARNING, 'jerror'
  521. );
  522. return false;
  523. }
  524. return true;
  525. }
  526. /**
  527. * Method to change mode for a path on the FTP server
  528. *
  529. * @param string $path Path to change mode on
  530. * @param mixed $mode Octal value to change mode to, e.g. '0777', 0777 or 511 (string or integer)
  531. *
  532. * @return boolean True if successful
  533. *
  534. * @since 1.0
  535. */
  536. public function chmod($path, $mode)
  537. {
  538. // If no filename is given, we assume the current directory is the target
  539. if ($path == '')
  540. {
  541. $path = '.';
  542. }
  543. // Convert the mode to a string
  544. if (is_int($mode))
  545. {
  546. $mode = decoct($mode);
  547. }
  548. // If native FTP support is enabled let's use it...
  549. if (FTP_NATIVE)
  550. {
  551. if (@ftp_site($this->conn, 'CHMOD ' . $mode . ' ' . $path) === false)
  552. {
  553. if (!defined('PHP_WINDOWS_VERSION_MAJOR'))
  554. {
  555. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  556. }
  557. return false;
  558. }
  559. return true;
  560. }
  561. // Send change mode command and verify success [must convert mode from octal]
  562. if (!$this->_putCmd('SITE CHMOD ' . $mode . ' ' . $path, array(200, 250)))
  563. {
  564. if (!defined('PHP_WINDOWS_VERSION_MAJOR'))
  565. {
  566. Log::add(
  567. sprintf(
  568. '%1$s: Bad response. Server response: %2$s [Expected: 250]. Path sent: %3$s. Mode sent: %4$s',
  569. __METHOD__,
  570. $this->response,
  571. $path,
  572. $mode
  573. ),
  574. Log::WARNING,
  575. 'jerror'
  576. );
  577. }
  578. return false;
  579. }
  580. return true;
  581. }
  582. /**
  583. * Method to delete a path [file/folder] on the FTP server
  584. *
  585. * @param string $path Path to delete
  586. *
  587. * @return boolean True if successful
  588. *
  589. * @since 1.0
  590. */
  591. public function delete($path)
  592. {
  593. // If native FTP support is enabled let's use it...
  594. if (FTP_NATIVE)
  595. {
  596. if (@ftp_delete($this->conn, $path) === false)
  597. {
  598. if (@ftp_rmdir($this->conn, $path) === false)
  599. {
  600. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  601. return false;
  602. }
  603. }
  604. return true;
  605. }
  606. // Send delete file command and if that doesn't work, try to remove a directory
  607. if (!$this->_putCmd('DELE ' . $path, 250))
  608. {
  609. if (!$this->_putCmd('RMD ' . $path, 250))
  610. {
  611. Log::add(
  612. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 250]. Path sent: %3$s', __METHOD__, $this->response, $path),
  613. Log::WARNING, 'jerror'
  614. );
  615. return false;
  616. }
  617. }
  618. return true;
  619. }
  620. /**
  621. * Method to create a directory on the FTP server
  622. *
  623. * @param string $path Directory to create
  624. *
  625. * @return boolean True if successful
  626. *
  627. * @since 1.0
  628. */
  629. public function mkdir($path)
  630. {
  631. // If native FTP support is enabled let's use it...
  632. if (FTP_NATIVE)
  633. {
  634. if (@ftp_mkdir($this->conn, $path) === false)
  635. {
  636. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  637. return false;
  638. }
  639. return true;
  640. }
  641. // Send change directory command and verify success
  642. if (!$this->_putCmd('MKD ' . $path, 257))
  643. {
  644. Log::add(
  645. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 257]. Path sent: %3$s', __METHOD__, $this->response, $path),
  646. Log::WARNING, 'jerror'
  647. );
  648. return false;
  649. }
  650. return true;
  651. }
  652. /**
  653. * Method to restart data transfer at a given byte
  654. *
  655. * @param integer $point Byte to restart transfer at
  656. *
  657. * @return boolean True if successful
  658. *
  659. * @since 1.0
  660. */
  661. public function restart($point)
  662. {
  663. // If native FTP support is enabled let's use it...
  664. if (FTP_NATIVE)
  665. {
  666. if (@ftp_site($this->conn, 'REST ' . $point) === false)
  667. {
  668. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  669. return false;
  670. }
  671. return true;
  672. }
  673. // Send restart command and verify success
  674. if (!$this->_putCmd('REST ' . $point, 350))
  675. {
  676. Log::add(
  677. sprintf(
  678. '%1$s: Bad response. Server response: %2$s [Expected: 350]. Restart point sent: %3$s', __METHOD__, $this->response, $point
  679. ),
  680. Log::WARNING, 'jerror'
  681. );
  682. return false;
  683. }
  684. return true;
  685. }
  686. /**
  687. * Method to create an empty file on the FTP server
  688. *
  689. * @param string $path Path local file to store on the FTP server
  690. *
  691. * @return boolean True if successful
  692. *
  693. * @since 1.0
  694. */
  695. public function create($path)
  696. {
  697. // If native FTP support is enabled let's use it...
  698. if (FTP_NATIVE)
  699. {
  700. // Turn passive mode on
  701. if (@ftp_pasv($this->conn, true) === false)
  702. {
  703. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  704. return false;
  705. }
  706. $buffer = fopen('buffer://tmp', 'r');
  707. if (@ftp_fput($this->conn, $path, $buffer, FTP_ASCII) === false)
  708. {
  709. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  710. fclose($buffer);
  711. return false;
  712. }
  713. fclose($buffer);
  714. return true;
  715. }
  716. // Start passive mode
  717. if (!$this->_passive())
  718. {
  719. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  720. return false;
  721. }
  722. if (!$this->_putCmd('STOR ' . $path, array(150, 125)))
  723. {
  724. @ fclose($this->dataconn);
  725. Log::add(
  726. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $path),
  727. Log::WARNING, 'jerror'
  728. );
  729. return false;
  730. }
  731. // To create a zero byte upload close the data port connection
  732. fclose($this->dataconn);
  733. if (!$this->_verifyResponse(226))
  734. {
  735. Log::add(
  736. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $path),
  737. Log::WARNING, 'jerror'
  738. );
  739. return false;
  740. }
  741. return true;
  742. }
  743. /**
  744. * Method to read a file from the FTP server's contents into a buffer
  745. *
  746. * @param string $remote Path to remote file to read on the FTP server
  747. * @param string &$buffer Buffer variable to read file contents into
  748. *
  749. * @return boolean True if successful
  750. *
  751. * @since 1.0
  752. */
  753. public function read($remote, &$buffer)
  754. {
  755. // Determine file type
  756. $mode = $this->_findMode($remote);
  757. // If native FTP support is enabled let's use it...
  758. if (FTP_NATIVE)
  759. {
  760. // Turn passive mode on
  761. if (@ftp_pasv($this->conn, true) === false)
  762. {
  763. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  764. return false;
  765. }
  766. $tmp = fopen('buffer://tmp', 'br+');
  767. if (@ftp_fget($this->conn, $tmp, $remote, $mode) === false)
  768. {
  769. fclose($tmp);
  770. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  771. return false;
  772. }
  773. // Read tmp buffer contents
  774. rewind($tmp);
  775. $buffer = '';
  776. while (!feof($tmp))
  777. {
  778. $buffer .= fread($tmp, 8192);
  779. }
  780. fclose($tmp);
  781. return true;
  782. }
  783. $this->_mode($mode);
  784. // Start passive mode
  785. if (!$this->_passive())
  786. {
  787. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  788. return false;
  789. }
  790. if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
  791. {
  792. @ fclose($this->dataconn);
  793. Log::add(
  794. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  795. Log::WARNING, 'jerror'
  796. );
  797. return false;
  798. }
  799. // Read data from data port connection and add to the buffer
  800. $buffer = '';
  801. while (!feof($this->dataconn))
  802. {
  803. $buffer .= fread($this->dataconn, 4096);
  804. }
  805. // Close the data port connection
  806. fclose($this->dataconn);
  807. // Let's try to cleanup some line endings if it is ascii
  808. if ($mode == FTP_ASCII)
  809. {
  810. $os = 'UNIX';
  811. if (defined('PHP_WINDOWS_VERSION_MAJOR'))
  812. {
  813. $os = 'WIN';
  814. }
  815. $buffer = preg_replace("/" . CRLF . "/", $this->lineEndings[$os], $buffer);
  816. }
  817. if (!$this->_verifyResponse(226))
  818. {
  819. Log::add(
  820. sprintf(
  821. '%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Restart point sent: %3$s',
  822. __METHOD__, $this->response, $remote
  823. ),
  824. Log::WARNING, 'jerror'
  825. );
  826. return false;
  827. }
  828. return true;
  829. }
  830. /**
  831. * Method to get a file from the FTP server and save it to a local file
  832. *
  833. * @param string $local Local path to save remote file to
  834. * @param string $remote Path to remote file to get on the FTP server
  835. *
  836. * @return boolean True if successful
  837. *
  838. * @since 1.0
  839. */
  840. public function get($local, $remote)
  841. {
  842. // Determine file type
  843. $mode = $this->_findMode($remote);
  844. // If native FTP support is enabled let's use it...
  845. if (FTP_NATIVE)
  846. {
  847. // Turn passive mode on
  848. if (@ftp_pasv($this->conn, true) === false)
  849. {
  850. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  851. return false;
  852. }
  853. if (@ftp_get($this->conn, $local, $remote, $mode) === false)
  854. {
  855. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  856. return false;
  857. }
  858. return true;
  859. }
  860. $this->_mode($mode);
  861. // Check to see if the local file can be opened for writing
  862. $fp = fopen($local, "wb");
  863. if (!$fp)
  864. {
  865. Log::add(sprintf('%1$s: Unable to open local file for writing. Local path: %2$s', __METHOD__, $local), Log::WARNING, 'jerror');
  866. return false;
  867. }
  868. // Start passive mode
  869. if (!$this->_passive())
  870. {
  871. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  872. return false;
  873. }
  874. if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
  875. {
  876. @ fclose($this->dataconn);
  877. Log::add(
  878. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  879. Log::WARNING, 'jerror'
  880. );
  881. return false;
  882. }
  883. // Read data from data port connection and add to the buffer
  884. while (!feof($this->dataconn))
  885. {
  886. $buffer = fread($this->dataconn, 4096);
  887. fwrite($fp, $buffer, 4096);
  888. }
  889. // Close the data port connection and file pointer
  890. fclose($this->dataconn);
  891. fclose($fp);
  892. if (!$this->_verifyResponse(226))
  893. {
  894. Log::add(
  895. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  896. Log::WARNING, 'jerror'
  897. );
  898. return false;
  899. }
  900. return true;
  901. }
  902. /**
  903. * Method to store a file to the FTP server
  904. *
  905. * @param string $local Path to local file to store on the FTP server
  906. * @param string $remote FTP path to file to create
  907. *
  908. * @return boolean True if successful
  909. *
  910. * @since 1.0
  911. */
  912. public function store($local, $remote = null)
  913. {
  914. // If remote file is not given, use the filename of the local file in the current
  915. // working directory.
  916. if ($remote == null)
  917. {
  918. $remote = basename($local);
  919. }
  920. // Determine file type
  921. $mode = $this->_findMode($remote);
  922. // If native FTP support is enabled let's use it...
  923. if (FTP_NATIVE)
  924. {
  925. // Turn passive mode on
  926. if (@ftp_pasv($this->conn, true) === false)
  927. {
  928. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  929. return false;
  930. }
  931. if (@ftp_put($this->conn, $remote, $local, $mode) === false)
  932. {
  933. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  934. return false;
  935. }
  936. return true;
  937. }
  938. $this->_mode($mode);
  939. // Check to see if the local file exists and if so open it for reading
  940. if (@ file_exists($local))
  941. {
  942. $fp = fopen($local, "rb");
  943. if (!$fp)
  944. {
  945. Log::add(sprintf('%1$s: Unable to open local file for reading. Local path: %2$s', __METHOD__, $local), Log::WARNING, 'jerror');
  946. return false;
  947. }
  948. }
  949. else
  950. {
  951. Log::add(sprintf('%1$s: Unable to find local file. Local path: %2$s', __METHOD__, $local), Log::WARNING, 'jerror');
  952. return false;
  953. }
  954. // Start passive mode
  955. if (!$this->_passive())
  956. {
  957. @ fclose($fp);
  958. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  959. return false;
  960. }
  961. // Send store command to the FTP server
  962. if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
  963. {
  964. @ fclose($fp);
  965. @ fclose($this->dataconn);
  966. Log::add(
  967. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  968. Log::WARNING, 'jerror'
  969. );
  970. return false;
  971. }
  972. // Do actual file transfer, read local file and write to data port connection
  973. while (!feof($fp))
  974. {
  975. $line = fread($fp, 4096);
  976. do
  977. {
  978. if (($result = @ fwrite($this->dataconn, $line)) === false)
  979. {
  980. Log::add(__METHOD__ . ': Unable to write to data port socket', Log::WARNING, 'jerror');
  981. return false;
  982. }
  983. $line = substr($line, $result);
  984. }
  985. while ($line != "");
  986. }
  987. fclose($fp);
  988. fclose($this->dataconn);
  989. if (!$this->_verifyResponse(226))
  990. {
  991. Log::add(
  992. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  993. Log::WARNING, 'jerror'
  994. );
  995. return false;
  996. }
  997. return true;
  998. }
  999. /**
  1000. * Method to write a string to the FTP server
  1001. *
  1002. * @param string $remote FTP path to file to write to
  1003. * @param string $buffer Contents to write to the FTP server
  1004. *
  1005. * @return boolean True if successful
  1006. *
  1007. * @since 1.0
  1008. */
  1009. public function write($remote, $buffer)
  1010. {
  1011. // Determine file type
  1012. $mode = $this->_findMode($remote);
  1013. // If native FTP support is enabled let's use it...
  1014. if (FTP_NATIVE)
  1015. {
  1016. // Turn passive mode on
  1017. if (@ftp_pasv($this->conn, true) === false)
  1018. {
  1019. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1020. return false;
  1021. }
  1022. $tmp = fopen('buffer://tmp', 'br+');
  1023. fwrite($tmp, $buffer);
  1024. rewind($tmp);
  1025. if (@ftp_fput($this->conn, $remote, $tmp, $mode) === false)
  1026. {
  1027. fclose($tmp);
  1028. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  1029. return false;
  1030. }
  1031. fclose($tmp);
  1032. return true;
  1033. }
  1034. // First we need to set the transfer mode
  1035. $this->_mode($mode);
  1036. // Start passive mode
  1037. if (!$this->_passive())
  1038. {
  1039. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1040. return false;
  1041. }
  1042. // Send store command to the FTP server
  1043. if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
  1044. {
  1045. Log::add(
  1046. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  1047. Log::WARNING, 'jerror'
  1048. );
  1049. @ fclose($this->dataconn);
  1050. return false;
  1051. }
  1052. // Write buffer to the data connection port
  1053. do
  1054. {
  1055. if (($result = @ fwrite($this->dataconn, $buffer)) === false)
  1056. {
  1057. Log::add(__METHOD__ . ': Unable to write to data port socket.', Log::WARNING, 'jerror');
  1058. return false;
  1059. }
  1060. $buffer = substr($buffer, $result);
  1061. }
  1062. while ($buffer != "");
  1063. // Close the data connection port [Data transfer complete]
  1064. fclose($this->dataconn);
  1065. // Verify that the server recieved the transfer
  1066. if (!$this->_verifyResponse(226))
  1067. {
  1068. Log::add(
  1069. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $remote),
  1070. Log::WARNING, 'jerror'
  1071. );
  1072. return false;
  1073. }
  1074. return true;
  1075. }
  1076. /**
  1077. * Method to list the filenames of the contents of a directory on the FTP server
  1078. *
  1079. * Note: Some servers also return folder names. However, to be sure to list folders on all
  1080. * servers, you should use listDetails() instead if you also need to deal with folders
  1081. *
  1082. * @param string $path Path local file to store on the FTP server
  1083. *
  1084. * @return string Directory listing
  1085. *
  1086. * @since 1.0
  1087. */
  1088. public function listNames($path = null)
  1089. {
  1090. $data = null;
  1091. // If native FTP support is enabled let's use it...
  1092. if (FTP_NATIVE)
  1093. {
  1094. // Turn passive mode on
  1095. if (@ftp_pasv($this->conn, true) === false)
  1096. {
  1097. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1098. return false;
  1099. }
  1100. if (($list = @ftp_nlist($this->conn, $path)) === false)
  1101. {
  1102. // Workaround for empty directories on some servers
  1103. if ($this->listDetails($path, 'files') === array())
  1104. {
  1105. return array();
  1106. }
  1107. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  1108. return false;
  1109. }
  1110. $list = preg_replace('#^' . preg_quote($path, '#') . '[/\\\\]?#', '', $list);
  1111. if ($keys = array_merge(array_keys($list, '.'), array_keys($list, '..')))
  1112. {
  1113. foreach ($keys as $key)
  1114. {
  1115. unset($list[$key]);
  1116. }
  1117. }
  1118. return $list;
  1119. }
  1120. /*
  1121. * If a path exists, prepend a space
  1122. */
  1123. if ($path != null)
  1124. {
  1125. $path = ' ' . $path;
  1126. }
  1127. // Start passive mode
  1128. if (!$this->_passive())
  1129. {
  1130. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1131. return false;
  1132. }
  1133. if (!$this->_putCmd('NLST' . $path, array(150, 125)))
  1134. {
  1135. @ fclose($this->dataconn);
  1136. // Workaround for empty directories on some servers
  1137. if ($this->listDetails($path, 'files') === array())
  1138. {
  1139. return array();
  1140. }
  1141. Log::add(
  1142. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s', __METHOD__, $this->response, $path),
  1143. Log::WARNING, 'jerror'
  1144. );
  1145. return false;
  1146. }
  1147. // Read in the file listing.
  1148. while (!feof($this->dataconn))
  1149. {
  1150. $data .= fread($this->dataconn, 4096);
  1151. }
  1152. fclose($this->dataconn);
  1153. // Everything go okay?
  1154. if (!$this->_verifyResponse(226))
  1155. {
  1156. Log::add(
  1157. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $path),
  1158. Log::WARNING, 'jerror'
  1159. );
  1160. return false;
  1161. }
  1162. $data = preg_split("/[" . CRLF . "]+/", $data, -1, PREG_SPLIT_NO_EMPTY);
  1163. $data = preg_replace('#^' . preg_quote(substr($path, 1), '#') . '[/\\\\]?#', '', $data);
  1164. if ($keys = array_merge(array_keys($data, '.'), array_keys($data, '..')))
  1165. {
  1166. foreach ($keys as $key)
  1167. {
  1168. unset($data[$key]);
  1169. }
  1170. }
  1171. return $data;
  1172. }
  1173. /**
  1174. * Method to list the contents of a directory on the FTP server
  1175. *
  1176. * @param string $path Path to the local file to be stored on the FTP server
  1177. * @param string $type Return type [raw|all|folders|files]
  1178. *
  1179. * @return mixed If $type is raw: string Directory listing, otherwise array of string with file-names
  1180. *
  1181. * @since 1.0
  1182. */
  1183. public function listDetails($path = null, $type = 'all')
  1184. {
  1185. $dir_list = array();
  1186. $data = null;
  1187. $regs = null;
  1188. // TODO: Deal with recurse -- nightmare
  1189. // For now we will just set it to false
  1190. $recurse = false;
  1191. // If native FTP support is enabled let's use it...
  1192. if (FTP_NATIVE)
  1193. {
  1194. // Turn passive mode on
  1195. if (@ftp_pasv($this->conn, true) === false)
  1196. {
  1197. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1198. return false;
  1199. }
  1200. if (($contents = @ftp_rawlist($this->conn, $path)) === false)
  1201. {
  1202. Log::add(__METHOD__ . 'Bad response.', Log::WARNING, 'jerror');
  1203. return false;
  1204. }
  1205. }
  1206. else
  1207. {
  1208. // Non Native mode
  1209. // Start passive mode
  1210. if (!$this->_passive())
  1211. {
  1212. Log::add(__METHOD__ . ': Unable to use passive mode.', Log::WARNING, 'jerror');
  1213. return false;
  1214. }
  1215. // If a path exists, prepend a space
  1216. if ($path != null)
  1217. {
  1218. $path = ' ' . $path;
  1219. }
  1220. // Request the file listing
  1221. if (!$this->_putCmd(($recurse == true) ? 'LIST -R' : 'LIST' . $path, array(150, 125)))
  1222. {
  1223. @ fclose($this->dataconn);
  1224. Log::add(
  1225. sprintf(
  1226. '%1$s: Bad response. Server response: %2$s [Expected: 150 or 125]. Path sent: %3$s',
  1227. __METHOD__, $this->response, $path
  1228. ),
  1229. Log::WARNING, 'jerror'
  1230. );
  1231. return false;
  1232. }
  1233. // Read in the file listing.
  1234. while (!feof($this->dataconn))
  1235. {
  1236. $data .= fread($this->dataconn, 4096);
  1237. }
  1238. fclose($this->dataconn);
  1239. // Everything go okay?
  1240. if (!$this->_verifyResponse(226))
  1241. {
  1242. Log::add(
  1243. sprintf('%1$s: Transfer failed. Server response: %2$s [Expected: 226]. Path sent: %3$s', __METHOD__, $this->response, $path),
  1244. Log::WARNING, 'jerror'
  1245. );
  1246. return false;
  1247. }
  1248. $contents = explode(CRLF, $data);
  1249. }
  1250. // If only raw output is requested we are done
  1251. if ($type == 'raw')
  1252. {
  1253. return $data;
  1254. }
  1255. // If we received the listing of an empty directory, we are done as well
  1256. if (empty($contents[0]))
  1257. {
  1258. return $dir_list;
  1259. }
  1260. // If the server returned the number of results in the first response, let's dump it
  1261. if (strtolower(substr($contents[0], 0, 6)) == 'total ')
  1262. {
  1263. array_shift($contents);
  1264. if (!isset($contents[0]) || empty($contents[0]))
  1265. {
  1266. return $dir_list;
  1267. }
  1268. }
  1269. // Regular expressions for the directory listing parsing.
  1270. $regexps = array(
  1271. 'UNIX' => '#([-dl][rwxstST-]+).* ([0-9]*) ([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
  1272. . ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{1,2}:[0-9]{2})|[0-9]{4}) (.+)#',
  1273. 'MAC' => '#([-dl][rwxstST-]+).* ?([0-9 ]*)?([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
  1274. . ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{2}:[0-9]{2})|[0-9]{4}) (.+)#',
  1275. 'WIN' => '#([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)#'
  1276. );
  1277. // Find out the format of the directory listing by matching one of the regexps
  1278. $osType = null;
  1279. foreach ($regexps as $k => $v)
  1280. {
  1281. if (@preg_match($v, $contents[0]))
  1282. {
  1283. $osType = $k;
  1284. $regexp = $v;
  1285. break;
  1286. }
  1287. }
  1288. if (!$osType)
  1289. {
  1290. Log::add(__METHOD__ . ': Unrecognised directory listing format.', Log::WARNING, 'jerror');
  1291. return false;
  1292. }
  1293. /*
  1294. * Here is where it is going to get dirty....
  1295. */
  1296. if ($osType == 'UNIX' || $osType == 'MAC')
  1297. {
  1298. foreach ($contents as $file)
  1299. {
  1300. $tmp_array = null;
  1301. if (@preg_match($regexp, $file, $regs))
  1302. {
  1303. $fType = (int) strpos("-dl", $regs[1]{0});
  1304. // $tmp_array['line'] = $regs[0];
  1305. $tmp_array['type'] = $fType;
  1306. $tmp_array['rights'] = $regs[1];
  1307. // $tmp_array['number'] = $regs[2];
  1308. $tmp_array['user'] = $regs[3];
  1309. $tmp_array['group'] = $regs[4];
  1310. $tmp_array['size'] = $regs[5];
  1311. $tmp_array['date'] = @date("m-d", strtotime($regs[6]));
  1312. $tmp_array['time'] = $regs[7];
  1313. $tmp_array['name'] = $regs[9];
  1314. }
  1315. // If we just want files, do not add a folder
  1316. if ($type == 'files' && $tmp_array['type'] == 1)
  1317. {
  1318. continue;
  1319. }
  1320. // If we just want folders, do not add a file
  1321. if ($type == 'folders' && $tmp_array['type'] == 0)
  1322. {
  1323. continue;
  1324. }
  1325. if (is_array($tmp_array) && $tmp_array['name'] != '.' && $tmp_array['name'] != '..')
  1326. {
  1327. $dir_list[] = $tmp_array;
  1328. }
  1329. }
  1330. }
  1331. else
  1332. {
  1333. foreach ($contents as $file)
  1334. {
  1335. $tmp_array = null;
  1336. if (@preg_match($regexp, $file, $regs))
  1337. {
  1338. $fType = (int) ($regs[7] == '<DIR>');
  1339. $timestamp = strtotime("$regs[3]-$regs[1]-$regs[2] $regs[4]:$regs[5]$regs[6]");
  1340. // $tmp_array['line'] = $regs[0];
  1341. $tmp_array['type'] = $fType;
  1342. $tmp_array['rights'] = '';
  1343. // $tmp_array['number'] = 0;
  1344. $tmp_array['user'] = '';
  1345. $tmp_array['group'] = '';
  1346. $tmp_array['size'] = (int) $regs[7];
  1347. $tmp_array['date'] = date('m-d', $timestamp);
  1348. $tmp_array['time'] = date('H:i', $timestamp);
  1349. $tmp_array['name'] = $regs[8];
  1350. }
  1351. // If we just want files, do not add a folder
  1352. if ($type == 'files' && $tmp_array['type'] == 1)
  1353. {
  1354. continue;
  1355. }
  1356. // If we just want folders, do not add a file
  1357. if ($type == 'folders' && $tmp_array['type'] == 0)
  1358. {
  1359. continue;
  1360. }
  1361. if (is_array($tmp_array) && $tmp_array['name'] != '.' && $tmp_array['name'] != '..')
  1362. {
  1363. $dir_list[] = $tmp_array;
  1364. }
  1365. }
  1366. }
  1367. return $dir_list;
  1368. }
  1369. /**
  1370. * Send command to the FTP server and validate an expected response code
  1371. *
  1372. * @param string $cmd Command to send to the FTP server
  1373. * @param mixed $expectedResponse Integer response code or array of integer response codes
  1374. *
  1375. * @return boolean True if command executed successfully
  1376. *
  1377. * @since 1.0
  1378. */
  1379. protected function _putCmd($cmd, $expectedResponse)
  1380. {
  1381. // Make sure we have a connection to the server
  1382. if (!is_resource($this->conn))
  1383. {
  1384. Log::add(__METHOD__ . ': Not connected to the control port.', Log::WARNING, 'jerror');
  1385. return false;
  1386. }
  1387. // Send the command to the server
  1388. if (!fwrite($this->conn, $cmd . "\r\n"))
  1389. {
  1390. Log::add(sprintf('%1$s: Unable to send command: %2$s', __METHOD__, $cmd), Log::WARNING, 'jerror');
  1391. }
  1392. return $this->_verifyResponse($expectedResponse);
  1393. }
  1394. /**
  1395. * Verify the response code from the server and log response if flag is set
  1396. *
  1397. * @param mixed $expected Integer response code or array of integer response codes
  1398. *
  1399. * @return boolean True if response code from the server is expected
  1400. *
  1401. * @since 1.0
  1402. */
  1403. protected function _verifyResponse($expected)
  1404. {
  1405. $parts = null;
  1406. // Wait for a response from the server, but timeout after the set time limit
  1407. $endTime = time() + $this->timeout;
  1408. $this->response = '';
  1409. do
  1410. {
  1411. $this->response .= fgets($this->conn, 4096);
  1412. }
  1413. while (!preg_match("/^([0-9]{3})(-(.*" . CRLF . ")+\\1)? [^" . CRLF . "]+" . CRLF . "$/", $this->response, $parts) && time() < $endTime);
  1414. // Catch a timeout or bad response
  1415. if (!isset($parts[1]))
  1416. {
  1417. Log::add(
  1418. sprintf(
  1419. '%1$s: Timeout or unrecognised response while waiting for a response from the server. Server response: %2$s',
  1420. __METHOD__, $this->response
  1421. ),
  1422. Log::WARNING, 'jerror'
  1423. );
  1424. return false;
  1425. }
  1426. // Separate the code from the message
  1427. $this->_responseCode = $parts[1];
  1428. $this->_responseMsg = $parts[0];
  1429. // Did the server respond with the code we wanted?
  1430. if (is_array($expected))
  1431. {
  1432. if (in_array($this->_responseCode, $expected))
  1433. {
  1434. $retval = true;
  1435. }
  1436. else
  1437. {
  1438. $retval = false;
  1439. }
  1440. }
  1441. else
  1442. {
  1443. if ($this->_responseCode == $expected)
  1444. {
  1445. $retval = true;
  1446. }
  1447. else
  1448. {
  1449. $retval = false;
  1450. }
  1451. }
  1452. return $retval;
  1453. }
  1454. /**
  1455. * Set server to passive mode and open a data port connection
  1456. *
  1457. * @return boolean True if successful
  1458. *
  1459. * @since 1.0
  1460. */
  1461. protected function _passive()
  1462. {
  1463. $match = array();
  1464. $parts = array();
  1465. $errno = null;
  1466. $err = null;
  1467. // Make sure we have a connection to the server
  1468. if (!is_resource($this->conn))
  1469. {
  1470. Log::add(__METHOD__ . ': Not connected to the control port.', Log::WARNING, 'jerror');
  1471. return false;
  1472. }
  1473. // Request a passive connection - this means, we'll talk to you, you don't talk to us.
  1474. @ fwrite($this->conn, "PASV\r\n");
  1475. // Wait for a response from the server, but timeout after the set time limit
  1476. $endTime = time() + $this->timeout;
  1477. $this->response = '';
  1478. do
  1479. {
  1480. $this->response .= fgets($this->conn, 4096);
  1481. }
  1482. while (!preg_match("/^([0-9]{3})(-(.*" . CRLF . ")+\\1)? [^" . CRLF . "]+" . CRLF . "$/", $this->response, $parts) && time() < $endTime);
  1483. // Catch a timeout or bad response
  1484. if (!isset($parts[1]))
  1485. {
  1486. Log::add(
  1487. sprintf(
  1488. '%1$s: Timeout or unrecognised response while waiting for a response from the server. Server response: %2$s',
  1489. __METHOD__, $this->response
  1490. ),
  1491. Log::WARNING, 'jerror');
  1492. return false;
  1493. }
  1494. // Separate the code from the message
  1495. $this->_responseCode = $parts[1];
  1496. $this->_responseMsg = $parts[0];
  1497. // If it's not 227, we weren't given an IP and port, which means it failed.
  1498. if ($this->_responseCode != '227')
  1499. {
  1500. Log::add(
  1501. sprintf('%1$s: Unable to obtain IP and port for data transfer. Server response: %2$s', __METHOD__, $this->_responseMsg),
  1502. Log::WARNING, 'jerror'
  1503. );
  1504. return false;
  1505. }
  1506. // Snatch the IP and port information, or die horribly trying...
  1507. if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $this->_responseMsg, $match) == 0)
  1508. {
  1509. Log::add(sprintf('%1$s: IP and port for data transfer not valid. Server response: %2$s', __METHOD__, $this->_responseMsg), Log::WARNING, 'jerror');
  1510. return false;
  1511. }
  1512. // This is pretty simple - store it for later use ;).
  1513. $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]);
  1514. // Connect, assuming we've got a connection.
  1515. $this->dataconn = @fsockopen($this->pasv['ip'], $this->pasv['port'], $errno, $err, $this->timeout);
  1516. if (!$this->dataconn)
  1517. {
  1518. Log::add(
  1519. sprintf(
  1520. '%1$s: Could not connect to host %2$s on port %3$s. Socket error number: %4$s and error message: %5$s',
  1521. __METHOD__,
  1522. $this->pasv['ip'],
  1523. $this->pasv['port'],
  1524. $errno,
  1525. $err
  1526. ),
  1527. Log::WARNING,
  1528. 'jerror'
  1529. );
  1530. return false;
  1531. }
  1532. // Set the timeout for this connection
  1533. socket_set_timeout($this->conn, $this->timeout, 0);
  1534. return true;
  1535. }
  1536. /**
  1537. * Method to find out the correct transfer mode for a specific file
  1538. *
  1539. * @param string $fileName Name of the file
  1540. *
  1541. * @return integer Transfer-mode for this filetype [FTP_ASCII|FTP_BINARY]
  1542. *
  1543. * @since 1.0
  1544. */
  1545. protected function _findMode($fileName)
  1546. {
  1547. if ($this->type == FTP_AUTOASCII)
  1548. {
  1549. $dot = strrpos($fileName, '.') + 1;
  1550. $ext = substr($fileName, $dot);
  1551. if (in_array($ext, $this->autoAscii))
  1552. {
  1553. $mode = FTP_ASCII;
  1554. }
  1555. else
  1556. {
  1557. $mode = FTP_BINARY;
  1558. }
  1559. }
  1560. elseif ($this->type == FTP_ASCII)
  1561. {
  1562. $mode = FTP_ASCII;
  1563. }
  1564. else
  1565. {
  1566. $mode = FTP_BINARY;
  1567. }
  1568. return $mode;
  1569. }
  1570. /**
  1571. * Set transfer mode
  1572. *
  1573. * @param integer $mode Integer representation of data transfer mode [1:Binary|0:Ascii]
  1574. * Defined constants can also be used [FTP_BINARY|FTP_ASCII]
  1575. *
  1576. * @return boolean True if successful
  1577. *
  1578. * @since 1.0
  1579. */
  1580. protected function _mode($mode)
  1581. {
  1582. if ($mode == FTP_BINARY)
  1583. {
  1584. if (!$this->_putCmd("TYPE I", 200))
  1585. {
  1586. Log::add(
  1587. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 200]. Mode sent: Binary', __METHOD__, $this->response),
  1588. Log::WARNING, 'jerror'
  1589. );
  1590. return false;
  1591. }
  1592. }
  1593. else
  1594. {
  1595. if (!$this->_putCmd("TYPE A", 200))
  1596. {
  1597. Log::add(
  1598. sprintf('%1$s: Bad response. Server response: %2$s [Expected: 200]. Mode sent: ASCII', __METHOD__, $this->response),
  1599. Log::WARNING, 'jerror'
  1600. );
  1601. return false;
  1602. }
  1603. }
  1604. return true;
  1605. }
  1606. }