|
@@ -0,0 +1,477 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+ * An FTP transfer wrapper using asynchronous transfer
|
|
|
+ * (c) 2006 Ouest Systèmes Informatiques (OSI)
|
|
|
+ * Licensed under the CeCILL 2.0 license
|
|
|
+ *
|
|
|
+ * $Id: u_ftp.php,v 1.1 2006-12-03 23:20:08 marand Exp $
|
|
|
+ */
|
|
|
+
|
|
|
+require_once('u_fsm.php');
|
|
|
+
|
|
|
+
|
|
|
+ * Class implements a finite-state-machine-based
|
|
|
+ * FTP client with *very* limited functionality,
|
|
|
+ * but that can display progress information
|
|
|
+ * states are:
|
|
|
+ * - init: not yet set up
|
|
|
+ * - offline: no link established
|
|
|
+ * - online: client connected to server
|
|
|
+ * - live: client connected and logged in
|
|
|
+ * - active: a data transfer operation is under way
|
|
|
+ * - unsafe: something failed, disconnect can happen out of our control
|
|
|
+ */
|
|
|
+class ftp_client extends fsm
|
|
|
+ {
|
|
|
+ private $f_host;
|
|
|
+ private $f_user;
|
|
|
+ private $f_pass;
|
|
|
+ private $f_pwd;
|
|
|
+ private $f_file;
|
|
|
+ private $f_localwd;
|
|
|
+ private $f_localfile;
|
|
|
+ private $f_fp;
|
|
|
+ private $f_goal;
|
|
|
+ private $f_conn;
|
|
|
+ private $f_callback;
|
|
|
+
|
|
|
+
|
|
|
+ * @param string $url
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function __construct(string $url = null)
|
|
|
+ {
|
|
|
+ if ($url)
|
|
|
+ $this->set_url($url);
|
|
|
+ $this->f_transitions = array(
|
|
|
+ 'init' => array(
|
|
|
+ 'check_params' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'init',
|
|
|
+ ),
|
|
|
+ 'set_progress' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'offline',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 'offline' => array(
|
|
|
+ 'check_params' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'init',
|
|
|
+ ),
|
|
|
+ 'connect' => array
|
|
|
+ (
|
|
|
+ true => 'online',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ 'set_progress' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'offline',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 'online' => array(
|
|
|
+ 'close' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ 'login' => array(
|
|
|
+ true => 'live',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 'live' => array(
|
|
|
+ 'close' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ 'chdir' => array(
|
|
|
+ true => 'live',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ 'get' => array(
|
|
|
+ FTP_FINISHED => 'live',
|
|
|
+ FTP_FAILED => 'unsafe',
|
|
|
+ FTP_MOREDATA => 'active',
|
|
|
+ ),
|
|
|
+ 'put' => array(
|
|
|
+ FTP_FINISHED => 'live',
|
|
|
+ FTP_FAILED => 'unsafe',
|
|
|
+ FTP_MOREDATA => 'active',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 'active' => array(
|
|
|
+ 'close' => array(
|
|
|
+ true => 'offline',
|
|
|
+ false => 'unsafe',
|
|
|
+ ),
|
|
|
+ 'continue' => array(
|
|
|
+ FTP_FINISHED => 'live',
|
|
|
+ FTP_FAILED => 'unsafe',
|
|
|
+ FTP_MOREDATA => 'active',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 'unsafe' => array(),
|
|
|
+
|
|
|
+ * no transition allowed
|
|
|
+ * Even retrying connect would not be safe
|
|
|
+ * Must destruct
|
|
|
+ */
|
|
|
+ );
|
|
|
+ parent::__construct();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * close connection if it hasn't been done, to prevent connection
|
|
|
+ * lingering on the server if avoidable
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function __destruct()
|
|
|
+ {
|
|
|
+ if ($this->is_event_allowed('close'))
|
|
|
+ $this->f_close();
|
|
|
+
|
|
|
+ if(is_resource($this->f_fp))
|
|
|
+ try
|
|
|
+ {
|
|
|
+ fclose($this->f_fp);
|
|
|
+ }
|
|
|
+ catch (Exception $e)
|
|
|
+ {
|
|
|
+ print_r($e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * setter for f_host. Make sur name can be resolved
|
|
|
+ *
|
|
|
+ * @param string $host
|
|
|
+ * @return ftp_client
|
|
|
+ */
|
|
|
+ public function set_host($host = null)
|
|
|
+ {
|
|
|
+
|
|
|
+ * ignore hosts that don't resolve in DNS
|
|
|
+ */
|
|
|
+ if (is_array(gethostbynamel($host)))
|
|
|
+ $this->f_host = $host;
|
|
|
+ else
|
|
|
+ throw new Exception(func_name() . ": cannot resolve host name \"$host\"");
|
|
|
+
|
|
|
+ $this->apply_event('check_params');
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * setter for f_user
|
|
|
+ *
|
|
|
+ * @param string $user
|
|
|
+ * @return ftp_client
|
|
|
+ */
|
|
|
+ public function set_user($user = 'anonymous')
|
|
|
+ {
|
|
|
+ $this->f_user = $user;
|
|
|
+ $this->apply_event('check_params');
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * setter for f_pass
|
|
|
+ *
|
|
|
+ * @param string $pass
|
|
|
+ * @return ftp_client
|
|
|
+ */
|
|
|
+ public function set_pass($pass = null)
|
|
|
+ {
|
|
|
+ $this->f_pass = $pass;
|
|
|
+ $this->apply_event('check_params');
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * callback is invoked at the end of get, put
|
|
|
+ * and before and after continue
|
|
|
+ *
|
|
|
+ * @param string $callback
|
|
|
+ */
|
|
|
+ public function set_callback($callback)
|
|
|
+ {
|
|
|
+ if ($callback && !function_exists($callback))
|
|
|
+ throw new Exception(func_name() . ": cannot use undefined function $callback as callback");
|
|
|
+ else
|
|
|
+ $this->f_callback = $callback;
|
|
|
+
|
|
|
+ * this setter does not cause a state change, so no call to apply_event
|
|
|
+ */
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * implement change remote directory
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ protected function f_chdir()
|
|
|
+ {
|
|
|
+ $ret = ftp_chdir($this->f_conn, $this->f_pwd);
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * change remote directory
|
|
|
+ *
|
|
|
+ * @param string $pwd
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ public function chdir($pwd = null)
|
|
|
+ {
|
|
|
+ $this->f_pwd = $pwd;
|
|
|
+ $ret = $this->apply_event('chdir');
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * setter for f_file
|
|
|
+ *
|
|
|
+ * @param string $file
|
|
|
+ * @return ftp_client
|
|
|
+ */
|
|
|
+ public function set_file($file = null)
|
|
|
+ {
|
|
|
+ $this->f_file = $file;
|
|
|
+ if (!isset($this->f_localfile))
|
|
|
+ $this->f_localfile = $file;
|
|
|
+
|
|
|
+ $this->apply_event('check_params');
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * setter for f_localfile
|
|
|
+ *
|
|
|
+ * @param string $file
|
|
|
+ * @return ftp_client
|
|
|
+ */
|
|
|
+ public function set_localfile($file = null)
|
|
|
+ {
|
|
|
+ $this->f_localfile = $file;
|
|
|
+ $this->apply_event('check_params');
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ * does the instance have all necessary info for a FTP transfer ?
|
|
|
+ *
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ protected function f_check_params()
|
|
|
+ {
|
|
|
+
|
|
|
+ $ret = isset($this->f_host)
|
|
|
+ && isset($this->f_user)
|
|
|
+ && isset($this->f_pass)
|
|
|
+ && isset($this->f_file)
|
|
|
+ && isset($this->f_localfile)
|
|
|
+ ;
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ * implementation of connect
|
|
|
+ *
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ protected function f_connect()
|
|
|
+ {
|
|
|
+
|
|
|
+ $this->f_conn = ftp_connect($this->f_host);
|
|
|
+ $ret = is_resource($this->f_conn);
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * implementation of close
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ protected function f_close()
|
|
|
+ {
|
|
|
+ echo func_name() . "\n";
|
|
|
+ $ret = ftp_close($this->f_conn);
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * implementation of login
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ protected function f_login()
|
|
|
+ {
|
|
|
+
|
|
|
+ $ret = ftp_login($this->f_conn, $this->f_user, $this->f_pass);
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * implementation of get
|
|
|
+ *
|
|
|
+ * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
|
|
|
+ */
|
|
|
+ protected function f_get()
|
|
|
+ {
|
|
|
+ echo func_name() . "\n";
|
|
|
+ $this->f_fp = fopen($this->f_localfile, "wb");
|
|
|
+ if (!is_resource($this->f_fp))
|
|
|
+ {
|
|
|
+ $ret = FTP_FAILED;
|
|
|
+ throw new Exception(func_name() . ": could not create local file $this->f_file");
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->f_goal = ftp_size($this->f_conn, $this->f_file);
|
|
|
+
|
|
|
+ $ret = ftp_nb_fget($this->f_conn, $this->f_fp, $this->f_file, FTP_BINARY);
|
|
|
+ if ($ret == FTP_FINISHED)
|
|
|
+ fclose($this->f_fp);
|
|
|
+ call_user_func($this->f_callback, $this, 'post');
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * implementation of continue
|
|
|
+ * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
|
|
|
+ */
|
|
|
+ protected function f_continue()
|
|
|
+ {
|
|
|
+ if ($this->f_callback)
|
|
|
+ call_user_func($this->f_callback, $this, 'pre');
|
|
|
+ $ret = ftp_nb_continue($this->f_conn);
|
|
|
+ if ($ret == FTP_FINISHED)
|
|
|
+ fclose($this->f_fp);
|
|
|
+ if ($this->f_callback)
|
|
|
+ call_user_func($this->f_callback, $this, 'post');
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * interface to connect
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function connect()
|
|
|
+ {
|
|
|
+
|
|
|
+ return $this->apply_event('connect');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * interface to login
|
|
|
+ *
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ public function login()
|
|
|
+ {
|
|
|
+
|
|
|
+ return $this->apply_event('login');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * interface to close
|
|
|
+ *
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ public function close()
|
|
|
+ {
|
|
|
+ echo func_name() . "\n";
|
|
|
+ return $this->apply_event('close');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * get a file using previously defined parameters
|
|
|
+ * @return int FTP_FAILED | FTP_MOREDATA | FTP_FINISHED
|
|
|
+ */
|
|
|
+ public function get()
|
|
|
+ {
|
|
|
+ echo func_name() . "\n";
|
|
|
+ return $this->apply_event('get');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * continue a current transfer
|
|
|
+ *
|
|
|
+ * @param string $callback name of function to be called before and after
|
|
|
+ * @return int FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
|
|
|
+ */
|
|
|
+ public function cont() // continue is a php reserved word
|
|
|
+ {
|
|
|
+
|
|
|
+ return $this->apply_event('continue');
|
|
|
+ }
|
|
|
+
|
|
|
+ public function get_progress()
|
|
|
+ {
|
|
|
+ if (!$this->f_state == 'active')
|
|
|
+ $ret = 0;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $pos = ftell($this->f_fp);
|
|
|
+ $ret = $pos / $this->f_goal;
|
|
|
+ }
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * test script
|
|
|
+ */
|
|
|
+
|
|
|
+ob_end_flush();
|
|
|
+
|
|
|
+$ftp = new ftp_client();
|
|
|
+$ftp->set_host('osinet.typhon.org')
|
|
|
+ ->set_user('osinet')
|
|
|
+ ->set_pass('eev6ohbahX')
|
|
|
+ ->set_file('price.xml')
|
|
|
+ ->set_callback('progress');
|
|
|
+
|
|
|
+if ($ftp->is_event_allowed('connect'))
|
|
|
+ {
|
|
|
+ echo "Connecting...";
|
|
|
+ $ftp->connect();
|
|
|
+ }
|
|
|
+else
|
|
|
+ die('connect not allowed');
|
|
|
+
|
|
|
+if($ftp->is_event_allowed('login'))
|
|
|
+ {
|
|
|
+ echo "success\nLogging in...";
|
|
|
+ $ftp->login();
|
|
|
+ }
|
|
|
+else
|
|
|
+ die("failure\n");
|
|
|
+
|
|
|
+if ($ftp->is_event_allowed('get'))
|
|
|
+ {
|
|
|
+ echo "success\nsetting directory...";
|
|
|
+ $ftp->chdir('/osinet.fr/www/code');
|
|
|
+ }
|
|
|
+else
|
|
|
+ die("failure\n");
|
|
|
+
|
|
|
+if ($ftp->is_event_allowed('get'))
|
|
|
+ {
|
|
|
+ echo "success\nfetching";
|
|
|
+ $ftp->get();
|
|
|
+ }
|
|
|
+else
|
|
|
+ die("failure\n");
|
|
|
+
|
|
|
+do
|
|
|
+ {
|
|
|
+ $sts = $ftp->cont();
|
|
|
+ if ($sts == FTP_FAILED)
|
|
|
+ throw new Exception("main: continue failed\n");
|
|
|
+ } while ($sts == FTP_MOREDATA);
|
|
|
+*/
|