Webdesign in Siegen

FTP-Klasse

Fragen zum Thema PHP können hier gestellt werden

Moderator: Basti

FTP-Klasse

Beitragvon Avedo am 11.12.2007, 19:29

Abend!
Ich habe ein kleines Problem. Ich muss im Rahmen eines kleines Projektes einige Klassen schreiben, die leider von mir selbst kommen müssen, da sie sonst nicht so zum Einsatz kommen können wie ich das möchte. Mit OOP und ähnlichem habe ich mich nun lange beschäftigt und schon einige Klassen geschrieben. Nun setze ich mich seit neuestem mit den verschiedenen Transfer Protokollen auseinander, momentan speziell mit dem FTP (File Transfer Protocol). Wo liegt nun eigentlich mein Problem? Ganz einfach, ich schreibe gerade eine FTP-Klasse, die mir für Uploads zu Verfügung stehen soll, da zum Beispiel die ftp()-Funktionen nicht immer verfügbar sind. Das Error-Handling löse ich wie immer mit Exceptions. Nun gibt es allerdings fünf verschiedene Arten von Responses vom FTP-Server.

1yz Kommando erfolgreich ausgeführt, erwartet weiteres Kommando
2yz Kommando erfolgreich ausgeführt
3yz Kommando erfolgreich ausgeführt, Server erwartet weitere Angaben zur weiteren Bearbeitung.
4yz Nichtausführung des Kommandos, handelt sich um temporäres Problem
5yz Nichtausführung des Kommandos


Zu diesen fünf Kategorien gibt es weitere fünf Unterkategorien.
x0z Syntax - Syntax errors, syntactically correct commands that don't fit any functional category, unimplemented or superfluous commands.
x1z Information - These are replies to requests for information, such as status or help.
x2z Connections - Replies referring to the control and data connections.
x3z Authentication and accounting - Replies for the login process and accounting procedures.
x4z Unspecified as yet.
x5z File system - These replies indicate the status of the Server file system vis-a-vis the requested transfer or other file system action.


Mein Problem ist jetzt, dass ich zuerst folgende Fehlerbehandlung hatte:
Code: Alles auswählen
   /**
   * Checks if the response of a command is ok
   * @name: FtpConnect::checkResponse()
   * @access: public
   * @param Sting $reply
   * @return mixed
   */     
   public function checkResponse($reply)
   {
      $reply = trim($reply);
      (int) $response = substr($reply, 0, 1);
      if (is_numeric($response))
      {
         if ( != 2)
         {
            throw new Exception("$response");
         }

         else
         {
            return true;
         }
      }
   }

Dummerweise geht dass so nicht ganz, da nicht nur alle Kommandos mit dem Response 200-299 richtig ausgeführt wurden, sondern alle von 100-399. Jedoch muss ich für die von 100-199 und die von 300-399 ja noch eine zusätzliche Behandlung einbauen. Dazu muss ich nun allerdings wissen, was damit gemeint ist, dass noch ein weiteres Kommando erwartet wird oder weitere Angaben. Welche Kommandos sind dabei gemeint? Kann mir da jemand ein paar Beispiele geben?

Nun drängt sich bei mir auch gleich noch eine andere Frage bei mir auf. Und zwar kann man mit Kommandos wie zum Beispiel DELE Dateien löschen, verschieben, erstellen etc., das Gleiche gilt auch für Verzeichnisse. Welche Dateien bzw. Verzeichnisse sind das allerdings? Die auf dem FTP-Client oder die auf dem FTP-Server. Sind es nämlich die auf dem FTP-Server kann ich mir die betreffenden Methoden in der Klasse sparen, da die von meiner FileHandler-Klasse erledigt wird, die auf Basis der file()-Funktionen arbeitet.
Ich wäre euch für eure Hilfe in diesen Fragen sehr dankbar.
MfG, Andy
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Manuel am 11.12.2007, 19:56

Hallo Andy!

Ich bin mir nicht ganz sicher ob ich dein Problem verstehe. Du führst beispielsweise eine Aktion aus und erhälst eine Fehlermeldung mit der Nummer 180. Diese wird dann aber nicht ausgegeben, weils als korrekt durchlaufen gilt? Wo genau ist der Unterschied dann zwischen 100-199/300-399 und 200-299? Wieso werden sie bei dem mittleren Zahlenraum erkannt und bei den äußeren beiden nicht? Ich hätte es eher anders geschätzt, da bei 2yz ja scheinbar das Kommando erfolgreich ausgeführt wurde. Wie schaut denn beispielsweise dann eine Exception aus die geworfen wird? Wäre das nicht genau die spezielle Fehlermeldung mit beispielsweise einer Zusammensetzung aus 3yz + x1z?

Ich habe sowas bisher noch nicht in PHP gemacht, aber vielleicht finden wir da ja gemeinsam eine Lösung oder jemand anderes hat einen Vorschlag dazu.

Lg,
Manuel ;-]
Benutzeravatar
Manuel
Site Admin
 
Beiträge: 8912
Registriert: 10.12.2004
Wohnort: Asbach

Re: FTP-Klasse

Beitragvon Avedo am 12.12.2007, 00:06

Abend!
Danke erstmal für deine Schnelle Antwort. Das Ding ist, dass bei 200-299 keine Exception geworfen werden muss, da ja das Kommando korrekt ausgeführt wurde. So bekomme ich zum Beispiel für den Befehl CDUP eine Rückmeldung, dass das Kommando richtig ausgeführt wurde und ich nun statt im Verzeichnis web/html/ in web/ bin.

Bei 100-199 würde eigentlich auch keine Exception geworfen, jedoch wird ein zweites Kommando erwartet. Was heißt das? Heißt das, dass der Befehl, den ich an den Server übermittelt habe zwar korrekt ausgeführt wurde, dieser aber einen zweiten Befehl erwartet, da diese Aktion sonst unsinnig ist? Zum Beispiel stelle ich eine Verbindung mit dem FTP-Server her und erhalte eine Willkommensmeldung. Wird als weiteres Kommando ein USER, zur Übermittlung des Nicks erwartet und dann ein PASS zur Übermittlung des zugehörigen Passwortes? Das heißt ein Befehl bedingt den anderen?

Ähnliches gilt für den Rückgabebereich 300-399. Das Kommando wurde richtig ausgeführt, jedoch wird eine weitere Angabe erwartet, damit der Vorgang abgeschlossen werden kann. Zum Beispiel soll mit dem Kommando MKD ein Verzeichnis erstellt werden, das Kommando wird richtig ausgeführt erwartet jedoch einen Namen als weitere Angabe. So begründet hier der Befehl die Anfrage nach einer weiteren Angabe?

Müsste ich also dann ein Exception werfen, wenn das Kommando nicht richtig ausgeführt wurde, ein erforderliches zweites Kommando fehlt oder eine erforderliche Angabe nicht übermittelt wird? Dieses Exception könnte dann in seiner Meldung spezifiziert werden, indem ich mir die zweite stelle des Fehlercodes ansehe, dass mir den Grund, den Bereich weiter spezifiziert.

Meine letzte Frage, bezog sich auf den Handlungsraum der Befehle bzw. Aktionen. werden File spezifische auf dem FTP-Server oder dem FTP-Client ausgeführt? Das heißt, erstelle ich mit MKD einen Ordner bei mir auf dem Rechner oder auf dem Server? Auf dem Server wäre naheliegender, jedoch auch sinnfreier, da ich solche Aktionen auch mit den file()-Funktionen erledigen kann (FileHandler-Klasse).

MfG, Andy
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Ingo am 12.12.2007, 02:09

Hoi.
Hab mich vor einer Weile mit den Statuscodes einiger gängiger Protokolle beschäftigt.
Wenn ich mich noch recht erinnere, werden die 1yz-Codes gesendet, wenn der Client zu
'ungeduldig' war, er also ein Kommando gesendet hat, bevor der Server die letzte Aktion
beendet hat, evtl. auch einfach als 'Lebenszeichen' seitens des FTP-Servers. Es wird also kein
weiteres Kommando vom Client erwartet, sondern allenfalls Geduld + Lauschen auf eingehende
Tranfers oder eine weitere Meldung des FTP-Servers ;o).

Einzig die 3yz-Gruppe erwartet irgendeine weitere Aktion des Clients, bevor der Server
bereit ist für weitere Befehle. Typisches Beispiel ist 331 - Password required:

Verbindungsanfrage des Clients an den Server
=> 220 ok, ready for new user
=> USER abc
=> 331 password required for user abc
=> PASS geheim
=> 230 User logged in, proceed
=> ...

MKD, RMD, DELE oder auch PWD, CWD, CDUP sind Teile des Protokolls, also dazu gedacht,
dem FTP-Server zugeschickt zu werden. Sie beziehen sich also auf den 'Arbeitsbereich'
des FTP-Servers, auf den man typischerweise eben keinen anderen Zugriff hat. Es handelt
sich ja meist um Dateisysteme auf remote-Hosts, die nur per Netzwerk zu erreichen sind.

Ich hoffe, ich hab das richtig aus dem Gedächtnis gekramt :)
Gruß, ip
Die beste Browserweiche ... sitzt zwischen den Ohren ;o]
Benutzeravatar
Ingo
Moderator
 
Beiträge: 625
Registriert: 01.04.2007
Wohnort: Neuss/NRW

Re: FTP-Klasse

Beitragvon Avedo am 16.12.2007, 21:17

Abend!
Ich habe mich eben, nachdem mein Webspace endlich wieder funktioniert, mal wieder an die Klasse gemacht und auch eure Einwände und anregungen berücksichtigt. Ich wollte dabei gerne die Rückmeldungen in den Prozeduren auch ausgeben. Find ich immer ganz praktisch und sinnvoll. Zudem habe ich die Überprüfung der Verbindung, sowie das Catchen der Responses in zwei getrennte Methoden gepackt um das ganze etwas zu verkürzen. Kann man das alles so machen oder sollte man das ändern bzw. gibt es eine schönere Variante. Würde mich über Rückmeldung freuen.
MfG, Andy

PS: Wollt ihr das Forum nicht mal um die BBCode PHP-Highligther erweitern? Pls? :cry:
Code: Alles auswählen
<pre>
<?php
error_reporting(E_ALL);

/* The FtpConnect class */
class FtpConnect
{
   private $connection;
   
   /**
   * Constructor - Is called when the class is instanced
   * @name: FtpConnect::__construct()
   * @access: public
   * @param Sting $host
   * @param Sting $name
   * @param Sting $pwd
   * @return NONE
   */     
   public function __construct($host='localhost', $name, $pwd)
   {
      $this->connection = @fsockopen($host, 21);
      $this->checkResponse();      
      
      switch($this->isConnected())
      {
         // Connected
         case true:
         stream_set_timeout($this->connection, 0, 3600);
         $this->checkResponse();      
         
         fwrite($this->connection, "USER ".$name."\r\n");
         $this->checkResponse();      
         
         fwrite($this->connection, "PASS ".$pwd."\r\n");
         $this->checkResponse();      
         
         while ($this->isConnected() == false)
         {
         }      
         $this->checkResponse();      
            
         fwrite($this->connection, "TYPE I\r\n");
         $this->checkResponse();      
         break;
         
         // Disconnected
         case false:
         throw new Exception("Connection failed.");      
         break;
      }
   }
   
   /**
   * Destructor - Is called when the instance of the class is closed
   * @name: FtpConnect::__destruct()
   * @access: public
   * @return NONE
   */     
   public function __destruct()
   {      
      switch($this->isConnected())
      {
         // Connected
         case true:
         fwrite($this->connection, "QUIT\r\n");
         $this->checkResponse();      
         fclose($this->connection);               
         break;
         
         // Disconnected
         case false:
         throw new Exception("Access denied.");               
         break;
      }      
   }
   
   /**
   * Checks if there is a connection to a server
   * @name: FtpConnect::isConnected()
   * @access: public
   * @return boolean
   */     
   public function isConnected()
   {
      return ($this->connection == true);
   }
   
   /**
   * Checks if the response of a command is ok
   * @name: FtpConnect::checkResponse()
   * @access: public
   * @return mixed
   */     
   public function checkResponse()
   {
      $reply = fgets($this->connection);
      $reply = trim($reply);
      (int) $type = substr($reply, 0, 1);
      
      if (is_numeric($type))
      {
         switch($type)
         {
            // Command OK need new command
            case '1':   
            echo $reply.'<br />';            
            break;
            
            // Command OK
            case '2':
            echo $reply.'<br />';                     
            break;
            
            // Command OK need attribute
            case '3':   
            echo "Warning: ".$reply.'<br />';                     
            break;
            
            // Command denied - temporary problem
            case '4':   
            echo "Error: ".$reply.'<br />';                     
            break;
            
            // Command denied
            case '5':   
            echo "Error: ".$reply.'<br />';                     
            break;
         }
      }
      
      return true;
   }
   
   /**
   * Returns the current position
   * @name: FtpConnect::getPath()
   * @access: public
   * @return String
   */     
   public function getPath()
   {
      $this->isConnected();      
      fwrite($this->connection,"PWD\r\n");
      $response = $this->checkResponse();      
      return $response;
   }    
}

try
{
   $ftp = new FtpConnect('localhost', 'user', 'pwd', 21);
}
catch(Exception $e)
{
   echo $e->getMessage();
}
?>
</pre>
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Ingo am 17.12.2007, 16:23

Hi, Avedo.
Auch ohne php-Highlighter ein übersichtlicher Code; sehr hilfreich bei der Analyse. :)
Ich sehe ein Problem bei der checkResponse():
Insbesondere die 220-Begrüßung besteht oft aus mehr als 1 Zeile; ein einziges
fgets() reicht dann nicht; vllt. eher eine while(fgets())-Schleife, bis der Puffer
leer ist? (Mein lokaler Filezilla schickt z.B. einen 3-Zeiler.)

Edit1: Hatte da noch Bedenken wegen einer möglichen race-condition; bin mir aber
da nicht sicher - vllt. später.

Edit2:
Der true-Zweig im switch (Konstruktor) ruft zwar immer gewissenhaft chekResponse(),
regiert aber nicht auf die Response - d.h. Fehlerfälle (4yz/5yz) werden ignoriert. Der
Code fährt fort, obwohl z.B. die Verbindung gerade außerplanmäßig beendet wurde oder
Login-Daten falsch waren.

Gruß, Ingo
Die beste Browserweiche ... sitzt zwischen den Ohren ;o]
Benutzeravatar
Ingo
Moderator
 
Beiträge: 625
Registriert: 01.04.2007
Wohnort: Neuss/NRW

Re: FTP-Klasse

Beitragvon Avedo am 17.12.2007, 18:09

Abend!
Danke erstmal für deine Mühe. Stellst du dir bei der Response-Ausgabe etwa folgendes vor:
Code: Alles auswählen
/**
   * Checks if the response of a command is ok
   * @name: FtpConnect::checkResponse()
   * @access: public
   * @return mixed
   */     
   public function checkResponse()
   {
      for (!empty(fgets($this->connection)))
      {
         $reply = fgets($this->connection);
         $reply = trim($reply);
         (int) $type = substr($reply, 0, 1);
     
         if (is_numeric($type))
         {
            switch($type)
            {
               // Command OK need new command
               case '1':   
               echo $reply.'<br />'; 
               return true;                                   
               break;
           
               // Command OK
               case '2':
               echo $reply.'<br />';
               return true;                                             
               break;
           
               // Command OK need attribute
               case '3':   
               echo "Warning: ".$reply.'<br />';     
               return true;                                         
               break;
           
               // Command denied - temporary problem
               case '4':   
               echo "Error: ".$reply.'<br />';
               return false;                         
               break;
           
               // Command denied
               case '5':   
               echo "Error: ".$reply.'<br />';               
               return false;     
               break;
            }
         }
      }
      return true;
   }

Weiß leider nicht ob das so möglich ist, kann es leider gerade nicht testen, da mein Server gerade Probleme hat. Gibt es denn generelle Einwände gegen eine solche Lösung? Dann hätt ich noch eine Frage und zwar was meinst du in Edit2? Geht es dir um die switch -Abfrage im Konstruktor, oder geht es dir um checkResponse()?

Edit: Jetzt weiß ich was du mit Edit2 meinst. Ich glaube, es reicht wenn ich einfach ans ende nochmal jeweils ein return setze. Funktioniert es dann? Kriege meinen Server leider immer noch nicht zum Laufen. :(

Edit2: Hab mich grad nochmal hingesetzt und meinen Grundgedanken nochmal überarbeitet und nochmal überlegt, was mir die weiteren Schritte leichter machen kann bzw. was ich selbst bei FTP-Upload-Programmen, wie mein Favorit Filezilla (*SCHLEICHWERBUNG*), besonders gut finde. So bin ich auf den Trichter gekommen, dass ich eine sehr genaue Dokumentation der Vorgänge liebe. Damit ich das bei so vielen verschiedenen Kommandos bewältigen kann, habe ich eine setCommand()-Methode eingebaut, mit deren Hilfe ich die Kommandos an den FTP-Server übermittle und gleichzeitig das Kommando ausgebe. Die ehemalige Funktion $check Response, habe ich in die getReply()- und die checkReply()-Methode aufgeteilt. Durch die Aufteilung bleiben mir andere Anwendungsmöglichkeiten speziell für getReply() offen.
Code: Alles auswählen
<pre>
<?php
error_reporting(E_ALL);

/* The FtpConnect class */
class FtpConnect
{
   private $connection;
   
   /**
   * Constructor - Is called when the class is instanced
   * @name: FtpConnect::__construct()
   * @access: public
   * @param Sting $host
   * @param Sting $name
   * @param Sting $pwd
   * @return NONE
   */     
   public function __construct($host='localhost', $name, $pwd)
   {
      $this->connection = @fsockopen($host, 21);
      set_socket_blocking($this->connection, false);
      stream_set_timeout($this->connection, 0, 3600);   
      
      switch($this->isConnected())
      {
         // Connected
         case true:
         
         if($this->connection)
         {
            if(checkReply())
            {
               sock_write("User $name");

               if(checkReply())
               {
                  sock_write("PASS $pwd");
                  
                  if(checkReply())
                  {
                     sock_write("TYPE I\r\n");
                     checkReply();
                  }
               }
            }
         }   
         break;
         
         // Disconnected
         case false:
         throw new Exception("Connection failed.");      
         break;
      }
   }
   
   /**
   * Destructor - Is called when the instance of the class is closed
   * @name: FtpConnect::__destruct()
   * @access: public
   * @return NONE
   */     
   public function __destruct()
   {      
      switch($this->checkReply())
      {
         // Connected
         case true:
         sock_write("QUIT\r\n");
         checkReply();         
         break;
         
         // Disconnected
         case false:
         throw new Exception("No Connetion running");               
         break;
      }      
   }
   
   /**
   * Gets the reply of a command
   * @name: FtpConnect::getReply()
   * @access: public
   * @return mixed
   */     
   public function getReply()
   {
      $reply = trim(fgets($this->connection));   
      
      if(empty($reply))
      {
         if($i == 15)
         {
            break;
         }
         
         else
         {
            sleep(1);
            return getReply(++$i);
         }
      }
      return $reply;
   }
   
   /**
   * Checks if the response of a command is ok
   * @name: FtpConnect::checkReply()
   * @param Sting $reply
   * @access: public
   * @return mixed
   */     
   public function checkReply($reply)
   {
      $reply = getReply();

      if(empty($reply) OR preg_match('/^5/',$reply) )
      {
         return false;
      }
      
      else
      {
         return true;
      }
   }
   
   /**
   * Sets a ftp-command given by the user
   * @name: FtpConnect::setCommand()
   * @param Sting $command
   * @access: public
   * @return boolean
   */     
   public function setCommand($command)
   {
      $command = trim($command);      
      echo "< $command\r\n";
      fputs($this->connection,"$command\r\n");
      
      return true;
   }
}

try
{
   $ftp = new FtpConnect('localhost', 'test', 'test', 21);
}
catch(Exception $e)
{
   echo $e->getMessage();
}
?>
</pre>

MfG, Andy
Zuletzt geändert von Avedo am 18.02.2008, 17:17, insgesamt 1-mal geändert.
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Ingo am 19.12.2007, 00:02

Hi, Avedo.
Ich bin auch für möglichst kleinschrittige Rückmeldungen des Skripts, jedenfalls solange, bis alles läuft :wink: .
Ich hab deinen letzten Entwurf mal etwas abgewandelt und ihn soweit ans Laufen gebracht - Ausgabe (bis auf
die Reihenfolge sieht das ok aus):
Code: Alles auswählen
> USER newuser
220-FileZilla Server version 0.9.23 beta
220-written by Tim Kosse (Tim.Kosse@gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for newuser
> PASS wampp
230 Logged on
> TYPE I
200 Type set to I
> QUIT 221 Goodbye

Noch 2 Dinge, die ich beim Probieren mit deinem Entwurf gelernt habe:
1. Im Destruktor sollte man keine Exception werfen; das führt wohl zu einem "fatal error".
2. fsockopen() liefert ein stream-Objekt (oder File-Pointer), das man dann mit Funktionen wie fgets, fwrite!,
fclose etc. oder mit der Gruppe der stream_xyz-Funktionen nutzen kann. Die socket_xyz-Funktionen erwarten
hingegen ein Socket-Objekt, das von socket_create erzeugt wird. - Ich benutze hier fsockopen und fwrite.

In meiner Version habe ich versucht, a) bei deinem Konzept zu bleiben (vor allem die praktische setCommand
einzubeziehen) und b) alles möglichst einfach zu halten. Die Fehlerbehandlung ist noch nicht 100%ig. Das RFC 959
gibt übrigens in 5.4 eine Liste der Befehle mit den jeweils möglichen Antworten an, und 4.2.1/2 nennt die
einzelnen für FTP relevanten Fehlercodes. - Here we go:

Code: Alles auswählen
<pre>

<?php  error_reporting(E_ALL);     

/* The FtpConnect class */
class FtpConnect
{
   private $connection;

   /**
   * Constructor - Is called when the class is instanced
   * @name: FtpConnect::__construct()
   * @access: public
   * @param String $host
   * @param String $name
   * @param String $pwd
   * @return NONE
   */     
   public function __construct($host='localhost', $name, $pwd)
   {
      $this->connection = @fsockopen($host, 21);
      //set_socket_blocking($this->connection, false);   // ?
      stream_set_timeout($this->connection, 0, 3600);   
     
      if($this->connection)
      {   
        $commands = array("USER $name","PASS $pwd","TYPE I");
       
        foreach ( $commands as $c )
        {
          $this->setCommand($c); 
          if (!$this->checkReply()) break;        // evtl. exception?
        };
     
      }
      else  throw new Exception("Connection failed.");     
   
   }
   
   /**
   * Destructor - Is called when the instance of the class is closed
   * @name: FtpConnect::__destruct()
   * @access: public
   * @return NONE
   */     
   public function __destruct()
   {     
      if($this->connection)
      {
         $this->setCommand("QUIT");
         $this->checkReply();         
      }   
   }
   
   /**
   * Gets the reply of a command
   * @name: FtpConnect::getReply()
   * @access: public
   * @return string
   */     
   public function getReply()
   {  // solange keine Daten im stream lauern, sollte das klappen:
      $reply = stream_get_contents($this->connection);   
      echo $reply."<br />";
     
      // ansonsten mit fgets zeilenweise?

      return $reply;
   }
   
   /**
   * Checks if the response of a command is ok
   * @name: FtpConnect::checkReply()
   * @param Sting $reply
   * @access: public
   * @return mixed
   */     
   public function checkReply()
   {
      $reply = $this->getReply();
     
      return preg_match('/^[45]/',$reply) ? false : true;
   }
   
   /**
   * Sets a ftp-command given by the user
   * @name: FtpConnect::setCommand()
   * @param Sting $command
   * @access: public
   * @return boolean
   */     
   public function setCommand($command)
   {
      $command = trim($command);     
      echo "&gt; $command\r\n";
      fwrite($this->connection,"$command\r\n");
     
      return true;
   }

}

//--------- testgebiet ----------------
try
{
   $ftp = new FtpConnect('localhost', 'newuser', 'wampp', 21);
}
catch(Exception $e)
{
   echo $e->getMessage();
}
?>

</pre>

Soweit meine Idee - :) Ingo
Die beste Browserweiche ... sitzt zwischen den Ohren ;o]
Benutzeravatar
Ingo
Moderator
 
Beiträge: 625
Registriert: 01.04.2007
Wohnort: Neuss/NRW

Re: FTP-Klasse

Beitragvon Avedo am 23.12.2007, 01:57

Abend!
Ich bin begeistert. Das war ja eine schnelle Antwort und auch noch so perfekt. Ich bin überwältigt. Ist mir leider noch nie in einem Forum vorher passiert. Respekt! Mein Server läuft auch wieder und es funktioniert alles wunderbar.
Wollte noch sagen, dass stream_set_blocking() sehr wichtig ist. Hatte leider die alte Schreibweise dieser Funktion verwendet. Entschuldigung. Wenn man das ganze auf false setzt, geben fget() und die anderen file()-Funktionen nicht immer automatisch true zurück. Kannst dir das ganze mal hier ansehen http://de.php.net/manual/de/function.stream-set-blocking.php.
MfG, Andy
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Avedo am 17.02.2008, 23:20

Abend!
Habe mich mal wieder an die Klasse gesetzt, nachdem andere Sachen etwas wichtiger waren. Möchte das Reply nun eigentlich zeilenweise via fgets() einlesen, da stream_get_contents deutlich langsamer als fgets, jedoch funktioniert mein Ansatz nicht wirklich. Woran kann das liegen?
Code: Alles auswählen
      while(!feof($this->connection))
      {
         $reply = fgets($this->connection);
         echo $reply."<br />";
      }

      return $reply;

MfG, Andy

//EDIT: Folgender Code-Snipet sollte doch funktionieren?
Code: Alles auswählen
return (empty($this->response) || preg_match('/^[5]/', $this->response) ? false : true;
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Avedo am 08.03.2008, 11:30

Morgen!
Habe mich mal wieder an die Klasse gesetzt und habe auch in der Grundstruktur noch einige Änderungen vor genommen. Allerdings will es nun nicht ganz so laufen, wie ich das möchte. Findet vielleicht jemand von euch den Fehler? Bin echt ein bisschen am Verzweifeln.
Code: Alles auswählen
<pre>

<?php 
error_reporting(E_ALL);

/* The FtpConnect class */
class FtpConnect
{
   // public class variables
   protected $host;
   protected $port;
   
   protected $ctrlCon;
   protected $dataCon;
   
   protected $response;
   
   public $logStats = 0;

   /**
   * Constructor - Is called when the class is instanced
   *
   * @name: FtpConnect::__construct()
   * @access: public
   * @param Str $host
   * @param Int $port
   * @param Str $name
   * @param Str $pwd
   * @return NONE
   */
   public function __construct($host='localhost', $port=21, $user, $pwd)
   {
      // control-connection handle is save to $ctrlCon
      $this->ctrlCon = @fsockopen($host, $port);
      
      // switch to non-blocking mode - just return data no response
      // @set_socket_blocking($this->ctrlCon, false);
      
      // set timeout of the ctrlCon
      @stream_set_timeout($this->ctrlCon, 0, 3600);
      
      if($this->ctrlCon)
      {                
         // send username to the server         
         $this->sendCmd("USER $user");
         if ($this->valid())
            throw new Exception("User not accepted: $user"); 
               
         // send password to the server
         $this->sendCmd("PASS $pwd");
         if ($this->valid())
            throw new Exception("Wrong password."); 
         
         // set type of data
         $this->sendCmd("TYPE I");
         if ($this->valid())
            throw new Exception("Cannot switch to TYPE I.");     
      }
      else  throw new Exception("Connection failed.");   
   }

   /**
   * Destructor - Is called when the instance of the class is closed
   *
   * @name: FtpConnect::__destruct()
   * @access: public
   * @return NONE
   */
   public function __destruct()
   {
      if($this->ctrlCon)
      {
         $this->sendCmd("QUIT");
         fclose($this->ctrlCon);
      }
   }      
   
   /**
   * Opens connection in passive mode for file transferes
   *
   * @name: FtpConnect::getReply()
   * @access: public
   * @return boolean
   */
   public function getReply()
   {
      // get response from ftp-connection
      $reply = stream_get_contents($this->ctrlCon);
            
      $this->log($reply);
      
      return $reply;
   }

   /**
   * Checks if the response of a command is ok
   *
   * @name: FtpConnect::valid()
   * @param Sting $reply
   * @access: public
   * @return mixed
   */
   public function valid()
   {
      // get response of the server
      $this->response = $this->getReply();
      
      // check the response and say if everything is allright
      return (empty($this->response) || preg_match('/^[5]/', $this->response)) ? false : true;
   }

   /**
   * Sets a ftp-command given by the user
   *
   * @name: FtpConnect::sendCmd()
   * @param Sting $command
   * @access: public
   * @return boolean
   */
   public function sendCmd($command)
   {
      fwrite($this->ctrlCon, "$command\r\n");
      $this->log("&gt; $command");
   }
   
   /**
   * Lists the directory $path
   *
   * @name: FtpConnect::listPath()
   * @access: public
   * @return boolean
   */
   public function listPath($path = "")
   {
      // initiate the cariable to save the listing of $path
      $list = "";
      if($this->pasv())
      {
         if(empty($path))
         {
            // ask for listing
            $this->sendCmd("LIST");
         }
         
         else
         {   
            // ask for listing of $path
            $this->sendCmd("LIST $path");
         }
         
         if($this->valid())
         {
            // save response from server to $list
            while(true)
            {
               $line = fgets($this->dataCon);
               $list .= $line;
               if($line =='')
                  break;
            }
         }
      }
      
      return $list;
   }
   
   /**
   * Opens connection in passive mode for file transferes
   * so now there are two connections - the control- and the data-connection
   *
   * @name: FtpConnect::pasv()
   * @access: public
   * @return boolean
   */
   public function pasv()
   {
      // send command for passive connection
      $this->sendCmd("PASV");
      if($this->valid())
      {
         // get response for PASV
         $offset = strpos($this->response, "(");   
         $res = substr($this->response, ++$offset, strlen($this->response)-2);
         
         // split string to array by delimiter ","         
         $parts = explode(",", trim($res));
         
         // save host and port for passive connection
         $host = "$parts[0].$parts[1].$parts[2].$parts[3]";
         $port = ((int)$parts[4] << 8) + (int) $parts[5];
         
         // data-connection handle is save to $dataCon
         $this->dataCon = fsockopen($host, $port);
         
         return $this->dataCon;
      }
   }
         
   /**
   * Prints out all requests to the server and their responses
   *
   * @name: FtpConnect::log()
   * @access: public
   * @return boolean
   */
   private function log($str)
   {
      if($this->logStats)
      {
         echo "$str<br>";
      }
   }
}

//---------------- testgebiet ----------------
try
{
   $ftp = new FtpConnect($host, 21, $user, $pwd);
   $ftp->logStats = 1;
   $ftp->pasv();
}
catch(Exception $e)
{
   echo $e->getMessage();
}
?>

</pre>

MfG, Andy
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Ingo am 09.03.2008, 00:08

Hi.
Ich hab's noch nicht ausprobiert; aber vorweg sehe ich da was:
Code: Alles auswählen
         if ($this->valid())
            throw new Exception("User not accepted: $user"); 

        // darunter ähnlich

Soweit ich den Code überblicke, sollte es eher if( ! $this->valid()) heißen? 'Wenn nicht 'valid',
dann wirf ne Exception'. - Mal weitersehen ...

Gruß, Ingo :)
Die beste Browserweiche ... sitzt zwischen den Ohren ;o]
Benutzeravatar
Ingo
Moderator
 
Beiträge: 625
Registriert: 01.04.2007
Wohnort: Neuss/NRW

Re: FTP-Klasse

Beitragvon Avedo am 09.03.2008, 00:17

Hatte ich zuerst, doch da ging garnichts. Muss es gleich nochmal versuchen. Was aber auf jeden Fall nicht so richtig funktioniert, ist die Ausgabe des Arbeitsprozesses via log(). Da sollen ja sowohl die Befehle als auch die Antworten des Servers ausgegeben werden.
MfG, Andy
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Re: FTP-Klasse

Beitragvon Ingo am 09.03.2008, 02:13

(Das wird der Thread der langen Postings ;o))

Hm. Seltsam. Mit leichter Änderung im Konstruktor ( ! eingefügt und else-Zweige):
Code: Alles auswählen
     if($this->ctrlCon)
      {               
         // send username to the server         
         $this->sendCmd("USER $user");
         if (!$this->valid())
           throw new Exception("User not accepted: $user\n"); 
         else
           $this->log("USER: ok\n");

         
         // send password to the server
         $this->sendCmd("PASS $pwd");
         if (!$this->valid())
            throw new Exception("Wrong password."); 
         else
            $this->log("PASS: ok\n");
         
         // set type of data
         $this->sendCmd("TYPE I");
         if (!$this->valid())
            throw new Exception("Cannot switch to TYPE I.");     
         else
            $this->log("TYPE I: ok\n");
      }

starte ich auf meinem lokalen XAMPP folgenden Versuch
Code: Alles auswählen
//---------------- testgebiet ----------------
$host = 'localhost';
$port = 21;
$user = 'newuser';
$pwd = 'wampp';
try

   $ftp = new FtpConnect($host, 21, $user, $pwd);
   $ftp->logStats = 1;
   $ftp->pasv(); 
}
catch(Exception $e)
{
   echo $e->getMessage();
}

mit folgendem -positiven- Ergebnis:
Code: Alles auswählen
log: > USER newuser
log: 220-FileZilla Server version 0.9.23 beta
220-written by Tim Kosse (Tim.Kosse@gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for newuser
log: USER: ok
log: > PASS wampp
log: 230 Logged on
log: PASS: ok
log: > TYPE I
log: 200 Type set to I
log: TYPE I: ok
log: > PASV
log: 227 Entering Passive Mode (127,0,0,1,15,182)
log: > QUIT

Das 'log' in der log-Ausgabe habe ich testhalber hinzugefügt, um diese Ausgaben von anderen
unterscheiden zu können.
In der log-Methode habe ich auch das if() auskommentiert. logStats wird ja erst auf 1 gesetzt,
nachdem der Konstruktor ausgeführt ist, wenn also schon einige interessante Dinge passiert
sind. Wenn also im Konstruktor was schiefläuft, bekommt man keine Rückmeldung; im Erfolgsfall
ursprünglich ja sowieso nicht. - Zumindest für Testzwecke schadet die Dauerausgabe ja nicht ;o)

Soweit meine Versuche. Klappt's denn inzwischen?

Gruß, Ingo :)
Die beste Browserweiche ... sitzt zwischen den Ohren ;o]
Benutzeravatar
Ingo
Moderator
 
Beiträge: 625
Registriert: 01.04.2007
Wohnort: Neuss/NRW

Re: FTP-Klasse

Beitragvon Avedo am 09.03.2008, 22:47

Abend!
Oh ja! Das wird der Thread der langen Posts.
Ich habe nun herausgefunden, warum das mit den Rückmeldungen auf meinem Server nicht so schön geklappt hat, wie auf deinem. Die Zeit hat einfach gefehlt. Er hat einfach immer sofort ein Exception geworfen, weil stream_get_contents() immer sofort einen leeren String zurück gab, da die Antwort von meinem Server einfach so lange dauerte. Naja ich habe auf jeden Fall das Problem behoben und eine kleine Demo für alle gemacht. Nichts tolles, aber man kann was sehen.
HIer für dich nochmals die Änderungen im Script. Hätte übrigens gerne noch eine Rückmeldung zur Kommentierung des Quellcodes. Ist die so gut lesbar und verständlich? Habe sie auch gerade nochmal frisch überarbeitet.
Code: Alles auswählen
<pre>

<?php 
error_reporting(E_ALL);

/***
* FtpConnect class allows easy connecting to a ftp-server.

* @package FtpConnect
* @version 1.0
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm
**/ 
class FtpConnect
{
   // public class variables
   protected $host;
   protected $port;
   
   protected $ctrlCon;
   protected $dataCon;
   
   protected $response;
   
   protected $logStats;

   /**
   * Constructor - Is called when the class is instanced
   *
   * @access: public
   * @param Str $host
   * @param Int $port
   * @param Str $name
   * @param Str $pwd
   * @return NONE
   */
   public function __construct($host='localhost', $port=21, $user, $pwd, $log=false)
   {
      // set if the connection with the ftp-server should be logged
      $this->logStats = $log;
   
      // control-connection handle is save to $ctrlCon
      $this->ctrlCon = @fsockopen($host, $port);
      if (!$this->valid())
         throw new Exception("Connection failed.\n");
      
      // switch to non-blocking mode - just return data no response
      @set_socket_blocking($this->ctrlCon, false);
      
      // set timeout of the ctrlCon
      @stream_set_timeout($this->ctrlCon, 0, 3600);
      
      if($this->ctrlCon)
      {               
         // send username to the server         
         $this->sendCmd("USER $user");
         if (!$this->valid())
            throw new Exception("User not accepted: $user");

         
         // send password to the server
         $this->sendCmd("PASS $pwd");
         if (!$this->valid())
            throw new Exception("Wrong password.");
         
         // set type of data
         $this->sendCmd("TYPE I");
         if (!$this->valid())
            throw new Exception("Cannot switch to TYPE I.");     
      }   
   }

   /**
   * Destructor - Is called when the instance of the class is closed
   *
   * @access: public
   * @return NONE
   */
   public function __destruct()
   {
      if($this->ctrlCon)
      {
         $this->sendCmd("QUIT");
         fclose($this->ctrlCon);
      }
   }      
   
   /**
   * getReply() - Gets the reply of the ftp-server
   *
   * @access: public
   * @param Int $i
   * @return String
   */
   public function getReply($i = 0)
   {
      // get response from ftp-connection
      $reply = fgets($this->ctrlCon);
      if($reply == '')
      {
         if($i == 30)
         {
            return "";
         }
         
         else
         {
            sleep(1);
            return $this->getReply($i + 1);
         }
      }      
      $this->log($reply);
      
      return $reply;   
   }

   /**
   * valid() - Checks if the response of a command is ok
   *
   * @param Sting $reply
   * @access: public
   * @return boolean
   */
   public function valid()
   {
      // get response of the server
      $this->response = $this->getReply();
      
      // check the response and say if everything is allright
      return (empty($this->response) || preg_match('/^[5]/', $this->response)) ? false : true;
   }

   /**
   * sendCmd() - Sets a ftp-command given by the user
   *
   * @param Sting $command
   * @access: public
   * @return NONE
   */
   public function sendCmd($command)
   {
      fwrite($this->ctrlCon, "$command\r\n");
      $this->log("&gt; $command");
   }
   
   /**
   * listPath() - Lists the directory $path
   *
   * @access: public
   * @return String
   */
   public function listPath($path = "")
   {
      // initiate the cariable to save the listing of $path
      $list = "";
      if($this->pasv())
      {
         if(empty($path))
         {
            // ask for listing
            $this->sendCmd("LIST");
         }
         
         else
         {   
            // ask for listing of $path
            $this->sendCmd("LIST $path");
         }
         
         if($this->valid())
         {
            // save response from server to $list
            while(true)
            {
               $line = fgets($this->dataCon);
               $list .= $line;
               if($line == '')
                  break;
            }
         }
      }
      
      return $list;
   }
   
   /**
   * pasv() - Opens connection in passive mode for file transferes
   *
   * @access: public
   * @return handle
   */
   public function pasv()
   {
      // send command for passive connection
      $this->sendCmd("PASV");
      if($this->valid())
      {
         // get response for PASV
         $offset = strpos($this->response, "(");   
         $res = substr($this->response, ++$offset, strlen($this->response)-2);
         
         // split string to array by delimiter ","         
         $parts = explode(",", trim($res));
         
         // save host and port for passive connection
         $host = "$parts[0].$parts[1].$parts[2].$parts[3]";
         $port = ((int)$parts[4] << 8) + (int) $parts[5];
         
         // data-connection handle is save to $dataCon
         $this->dataCon = fsockopen($host, $port);
         
         return $this->dataCon;
      }
   }
         
   /**
   * log() - Prints out all requests to the server and their responses
   *
   * @access: public
   * @return NONE
   */
   private function log($str)
   {
      if($this->logStats)
      {
         echo "$str<br>";
      }
   }
}

//---------------- testgebiet ----------------
try
{
   if(isset($_POST['host']) && isset($_POST['user']) && isset($_POST['password']))
   {
      $ftp = new FtpConnect($_POST['host'], 21, $_POST['user'], $_POST['password'], true);
      echo $ftp->listPath();
   }
   
   else
   {
      echo '<form action="ftp2.php" method="post">';
      echo 'host : ftp://<input name="host" type="text"><br />';
      echo 'User: <input name="user" type="text"><br />';
      echo 'password: <input name="password" type="password"><br />';
      echo '<input name="submit" value="connect" type="submit"><br />';
      echo '</form>';   
   }
}
catch(Exception $e)
{
   echo $e->getMessage();
}
?>

</pre>

MfG, Andy
Zuletzt geändert von Avedo am 24.03.2008, 18:23, insgesamt 1-mal geändert.
Ich bin zu Mimis Religion konvertiert!
I'm so tired of slitting the throats of people calling me a violent psychopath.
Benutzeravatar
Avedo
Mitglied
 
Beiträge: 596
Registriert: 09.12.2007
Wohnort: Göttingen

Nächste

Zurück zu PHP

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 2 Gäste