u_ftp.php 9.4 KB

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