u_ftp.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * An FTP transfer wrapper using asynchronous transfer
  4. * (c) 2006 Ouest Systèmes Informatiques (OSI)
  5. * Licensed under the CeCILL 2.0 license
  6. *
  7. * $Id: u_ftp.php,v 1.3 2007-04-28 20:03:40 marand Exp $
  8. *
  9. * @todo redo to take advantage of the new FSM 1.2 features to reduce the code
  10. */
  11. require_once('u_fsm.php');
  12. /**
  13. * Class implements a finite-state-machine-based
  14. * FTP client with *very* limited functionality,
  15. * but that can display progress information
  16. * states are:
  17. * - init: not yet set up
  18. * - offline: no link established
  19. * - online: client connected to server
  20. * - live: client connected and logged in
  21. * - active: a data transfer operation is under way
  22. * - unsafe: something failed, disconnect can happen out of our control
  23. */
  24. class ftp_client extends fsm {
  25. private $f_host;
  26. private $f_user;
  27. private $f_pass;
  28. private $f_pwd;
  29. private $f_file;
  30. private $f_localwd;
  31. private $f_localfile;
  32. private $f_fp; // file pointer to local file, used in get/put/continue
  33. private $f_goal; // bytes to be transferred
  34. private $f_conn; // connection
  35. private $f_callback; // name of callback function for progress information
  36. /**
  37. * @param string $url
  38. * @return void
  39. */
  40. public function __construct(string $url = null) {
  41. if ($url)
  42. $this->set_url($url);
  43. $this->fTransitions = array(
  44. 'init' => array(
  45. 'check_params' => array(
  46. true => 'offline',
  47. false => 'init',
  48. ),
  49. 'set_progress' => array(
  50. true => 'offline',
  51. false => 'offline',
  52. ),
  53. ),
  54. 'offline' => array(
  55. 'check_params' => array(
  56. true => 'offline',
  57. false => 'init',
  58. ),
  59. 'connect' => array
  60. (
  61. true => 'online',
  62. false => 'unsafe',
  63. ),
  64. 'set_progress' => array(
  65. true => 'offline',
  66. false => 'offline',
  67. ),
  68. ),
  69. 'online' => array(
  70. 'close' => array(
  71. true => 'offline',
  72. false => 'unsafe',
  73. ),
  74. 'login' => array(
  75. true => 'live',
  76. false => 'unsafe',
  77. ),
  78. ),
  79. 'live' => array(
  80. 'close' => array(
  81. true => 'offline',
  82. false => 'unsafe',
  83. ),
  84. 'chdir' => array(
  85. true => 'live',
  86. false => 'unsafe',
  87. ),
  88. 'get' => array(
  89. FTP_FINISHED => 'live',
  90. FTP_FAILED => 'unsafe',
  91. FTP_MOREDATA => 'active',
  92. ),
  93. 'put' => array(
  94. FTP_FINISHED => 'live',
  95. FTP_FAILED => 'unsafe',
  96. FTP_MOREDATA => 'active',
  97. ),
  98. ),
  99. 'active' => array(
  100. 'close' => array(
  101. true => 'offline',
  102. false => 'unsafe',
  103. ),
  104. 'continue' => array(
  105. FTP_FINISHED => 'live',
  106. FTP_FAILED => 'unsafe',
  107. FTP_MOREDATA => 'active',
  108. ),
  109. ),
  110. 'unsafe' => array(),
  111. /**
  112. * no transition allowed
  113. * Even retrying connect would not be safe
  114. * Must destruct
  115. */
  116. );
  117. parent::__construct();
  118. }
  119. /**
  120. * close connection if it hasn't been done, to prevent connection
  121. * lingering on the server if avoidable
  122. * @return void
  123. */
  124. public function __destruct() {
  125. if ($this->is_event_allowed('close'))
  126. $this->f_close();
  127. if (is_resource($this->f_fp)) {
  128. try {
  129. fclose($this->f_fp);
  130. }
  131. catch (Exception $e) {
  132. print_r($e);
  133. }
  134. }
  135. }
  136. /**
  137. * setter for f_host. Make sur name can be resolved
  138. *
  139. * @param string $host
  140. * @return ftp_client
  141. */
  142. public function set_host($host = null) {
  143. /**
  144. * ignore hosts that don't resolve in DNS
  145. */
  146. if (is_array(gethostbynamel($host)))
  147. $this->f_host = $host;
  148. else
  149. throw new Exception(func_name() . ": cannot resolve host name \"$host\"");
  150. $this->applyEvent('check_params');
  151. return $this;
  152. }
  153. /**
  154. * setter for f_user
  155. *
  156. * @param string $user
  157. * @return ftp_client
  158. */
  159. public function set_user($user = 'anonymous') {
  160. $this->f_user = $user;
  161. $this->applyEvent('check_params');
  162. return $this;
  163. }
  164. /**
  165. * setter for f_pass
  166. *
  167. * @param string $pass
  168. * @return ftp_client
  169. */
  170. public function set_pass($pass = null) {
  171. $this->f_pass = $pass;
  172. $this->applyEvent('check_params');
  173. return $this;
  174. }
  175. /**
  176. * callback is invoked at the end of get, put
  177. * and before and after continue
  178. *
  179. * @param string $callback
  180. */
  181. public function set_callback($callback) {
  182. if ($callback && !function_exists($callback))
  183. throw new Exception(func_name() . ": cannot use undefined function $callback as callback");
  184. else
  185. $this->f_callback = $callback;
  186. /**
  187. * this setter does not cause a state change, so no call to applyEvent
  188. */
  189. return $this;
  190. }
  191. /**
  192. * implement change remote directory
  193. * @return boolean
  194. */
  195. protected function f_chdir() {
  196. $ret = ftp_chdir($this->f_conn, $this->f_pwd);
  197. return $ret;
  198. }
  199. /**
  200. * change remote directory
  201. *
  202. * @param string $pwd
  203. * @return boolean
  204. */
  205. public function chdir($pwd = null) {
  206. $this->f_pwd = $pwd;
  207. $ret = $this->applyEvent('chdir');
  208. return $ret;
  209. }
  210. /**
  211. * setter for f_file
  212. *
  213. * @param string $file
  214. * @return ftp_client
  215. */
  216. public function set_file($file = null) {
  217. $this->f_file = $file;
  218. if (!isset($this->f_localfile))
  219. $this->f_localfile = $file;
  220. $this->applyEvent('check_params');
  221. return $this;
  222. }
  223. /**
  224. * setter for f_localfile
  225. *
  226. * @param string $file
  227. * @return ftp_client
  228. */
  229. public function set_localfile($file = null) {
  230. $this->f_localfile = $file;
  231. $this->applyEvent('check_params');
  232. return $this;
  233. }
  234. /**
  235. * does the instance have all necessary info for a FTP transfer ?
  236. *
  237. * @return boolean
  238. */
  239. protected function f_check_params() {
  240. $ret = isset($this->f_host)
  241. && isset($this->f_user)
  242. && isset($this->f_pass)
  243. && isset($this->f_file)
  244. && isset($this->f_localfile)
  245. ;
  246. // echo func_name() . ", ret = " . ($ret ? 'TRUE' : 'FALSE') . PHP_EOL;
  247. return $ret;
  248. }
  249. /**
  250. * implementation of connect
  251. *
  252. * @return boolean
  253. */
  254. protected function f_connect() {
  255. // echo func_name() . "\n";
  256. $this->f_conn = ftp_connect($this->f_host); // default port, default timeout
  257. $ret = is_resource($this->f_conn);
  258. return $ret;
  259. }
  260. /**
  261. * implementation of close
  262. * @return boolean
  263. */
  264. protected function f_close() {
  265. // echo func_name() . "\n";
  266. $ret = ftp_close($this->f_conn);
  267. return $ret;
  268. }
  269. /**
  270. * implementation of login
  271. * @return boolean
  272. */
  273. protected function f_login() {
  274. // echo func_name() . "\n";
  275. $ret = ftp_login($this->f_conn, $this->f_user, $this->f_pass);
  276. return $ret;
  277. }
  278. /**
  279. * implementation of get
  280. *
  281. * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
  282. */
  283. protected function f_get() {
  284. // echo func_name() . "\n";
  285. $this->f_fp = fopen($this->f_localfile, "wb");
  286. if (!is_resource($this->f_fp)) {
  287. $ret = FTP_FAILED;
  288. throw new Exception(func_name() . ": could not create local file $this->f_file");
  289. }
  290. $this->f_goal = ftp_size($this->f_conn, $this->f_file);
  291. $ret = ftp_nb_fget($this->f_conn, $this->f_fp, $this->f_file, FTP_BINARY);
  292. if ($ret == FTP_FINISHED)
  293. fclose($this->f_fp);
  294. call_user_func($this->f_callback, $this, 'post');
  295. return $ret;
  296. }
  297. /**
  298. * implementation of continue
  299. * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
  300. */
  301. protected function f_continue() {
  302. if ($this->f_callback)
  303. call_user_func($this->f_callback, $this, 'pre');
  304. $ret = ftp_nb_continue($this->f_conn);
  305. if ($ret == FTP_FINISHED)
  306. fclose($this->f_fp);
  307. if ($this->f_callback)
  308. call_user_func($this->f_callback, $this, 'post');
  309. return $ret;
  310. }
  311. /**
  312. * interface to connect
  313. * @return void
  314. */
  315. public function connect() {
  316. // echo func_name() . "\n";
  317. return $this->applyEvent('connect');
  318. }
  319. /**
  320. * interface to login
  321. *
  322. * @return boolean
  323. */
  324. public function login() {
  325. // echo func_name() . "\n";
  326. return $this->applyEvent('login');
  327. }
  328. /**
  329. * interface to close
  330. *
  331. * @return boolean
  332. */
  333. public function close() {
  334. // echo func_name() . "\n";
  335. return $this->applyEvent('close');
  336. }
  337. /**
  338. * get a file using previously defined parameters
  339. * @return int FTP_FAILED | FTP_MOREDATA | FTP_FINISHED
  340. */
  341. public function get() {
  342. // echo func_name() . "\n";
  343. return $this->applyEvent('get');
  344. }
  345. /**
  346. * continue a current transfer
  347. *
  348. * @param string $callback name of function to be called before and after
  349. * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
  350. */
  351. // continue is a php reserved word
  352. public function cont() {
  353. // echo func_name() . "\n";
  354. $ret = $this->applyEvent('continue');
  355. $ret = $ret->fsmState;
  356. return $ret;
  357. }
  358. public function get_progress() {
  359. if ((!$this->fState == 'active') || (!is_resource($this->f_fp)))
  360. $ret = 0;
  361. else {
  362. $pos = ftell($this->f_fp);
  363. $ret = $pos / $this->f_goal;
  364. }
  365. return $ret;
  366. }
  367. /* missing: put*/
  368. }