<?php
class FTPSocket {
var $connection;
var $port = 21;
function connect($host, $user=false, $pass=false){
if($this->connection) @fclose($this->connection);
if(($this->connection = @fsockopen($host, (is_int($this->port)? $this->port : 21), $error, $errorstr, 5)) && function_exists('set_socket_blocking')){
@set_socket_blocking($this->connection, 0);
if((intval($this->reply()) == 220) && is_string($user) && ($replycode = intval($this->command('USER '.$user)))){
if($replycode == 331) $replycode = intval($this->command('PASS '.(is_string($pass)? $pass : 'anonymous')));
elseif(is_string($pass)) $replycode = intval($this->command('ACCT '.$pass));
$this->features(true);
return(($replycode == 230) || ($replycode == 202));
}
}
return false;
}
function reply($retry=true){
if($this->connection){
$reply = '';
while(true){
while(is_string($replypart = @fgets($this->connection, 512)) && (strlen($replypart) > 0)){
$reply .= $replypart;
if(is_string($replypart) && is_numeric(substr($replypart, 0, 3)) && (ord(substr($replypart, 3, 1)) == 32)) return $reply;
}
if(!$retry) break;
}
}
return false;
}
function umask($umask){
return(is_numeric($umask) && (intval($this->command('UMASK 0'.decoct(intval($umask))))));
}
function command($commandstring){
$this->reply(false);
if(is_string($commandstring) && $this->connection && fwrite($this->connection, $commandstring."\r\n")) return $this->reply();
return false;
}
function cdup(){
return(intval($this->command('CDUP')) == 200);
}
function cwdir($path){
return(is_string($path) && (intval($this->command('CWD '.$path)) == 250));
}
function rmdir($path){
return(is_string($path) && (intval($this->command('RMD '.$path)) == 250));
}
function mkdir($path, $umask=false){
return(is_string($path) && (!is_numeric($umask) || $this->umask($umask)) && (intval($this->command('MKD '.$path)) == 257));
}
function delete($path){
return(is_string($path) && (intval($this->command('DELE '.$path)) == 250));
}
function chmod($path, $chmod){
return(is_numeric($chmod) && (intval($this->command('SITE CHMOD 0'.decoct(intval($chmod)).' '.$path)) == 200));
}
function rename($path, $newpath){
return(is_string($path) && is_string($newpath) && (intval($this->command('RNFR '.$path)) == 350) && (intval($this->command('RNTO '.$newpath)) == 250));
}
function file_exists($path){
return(intval($this->command('RNFR '.$path)) == 350);
}
function pwdir(){
if((intval($reply = $this->command('PWD')) == 257) && is_int($spos = strpos($reply, '"')) && is_int($epos = strpos($reply, '"', ($spos + 1)))) return str_replace('""', '"', substr($reply, ($spos + 1), ($epos - $spos - 1)));
}
function mtime($path){
if(is_string($path) && (intval($reply = $this->command('MDTM '.$path)) == 213) && is_numeric($time = substr($reply, 4, 14)) && is_int($time = mktime(substr($time, 8, 2), substr($time, 10, 2), substr($time, 12, 2), substr($time, 4, 2), substr($time, 6, 2), substr($time, 0, 4)))) return $time;
return false;
}
function features($reget=false){
static $feats = Array();
if(!$reget) return $feats;
if(intval($reply = $this->command('FEAT')) == 211){
$feats = Array(); $feat = preg_split("#\s*([\r\n]+)\s*#", trim($reply)); $i = 0;
while(($i += 1) < (count($feat) - 1)) $feats[(is_int($pos = strpos($feat[$i], chr(32)))? substr($feat[$i], 0, $pos) : $feat[$i])] = (is_int($pos)? substr($feat[$i], ($pos + 1)) : '');
return $feats;
}
return false;
}
function abort(){
if(isset($this->dataconnection) && in_array(($reply = intval($this->command('ABOR'))), Array(225, 226))){
if(($reply == 226) && @fclose($this->dataconnection)) unset($this->dataconnection);
return false;
}
}
function systype(){
if(intval($reply = $this->command('SYST')) == 215) return(trim(substr($reply, 4)));
return false;
}
function storeunique($fileordata, $path=false){
if(is_string($path) && !$this->cwdir($path)) return false;
if(is_string($fileordata)){
$replycode = 425;
if(isset($this->dataconnection) && $this->type('I') && (intval($this->command('ALLO '.(is_file($fileordata)? filesize($fileordata) : strlen($fileordata))))) && in_array(($replycode = intval($this->command('STOU'))), Array(125, 150))){
if(!is_file($fileordata)) fwrite($this->dataconnection, $fileordata);
elseif($fp = @fopen($fileordata, 'rb')) while(is_string($filepart = @fgets($fp))) fwrite($this->dataconnection, $filepart);
elseif($this->abort()) return false;
if(@fclose($this->dataconnection)) unset($this->dataconnection);
return(intval($this->reply()) == 226);
}
elseif(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->storeunique($fileordata, $path);
$this->abort();
}
return false;
}
function retrieve($path, $file=false, $restore=false){
$replycode = 425;
if(!is_numeric($restore) || ($this->file_exists($path) && (!is_numeric($restore) || (intval($this->command('REST '.intval($restore))) == 350)))){
if(isset($this->dataconnection) && $this->type('I') && is_string($path) && in_array(($replycode = intval($this->command('RETR '.$path))), Array(150, 125))){
if(is_string($file)) $fp = fopen($file, 'wb');
$reply = '';
while(true){
if(is_string($replypart = fgets($this->dataconnection, 256))){
if(isset($fp) && ($reply = true)) fwrite($fp, $replypart);
else $reply .= $replypart;
}
if(!is_string($replypart)){
@fclose($this->dataconnection);
unset($this->dataconnection);
if(intval($this->reply()) == 226){
if(is_string($reply)) return $reply;
else return((!isset($fp) || @fclose($fp)) && !!$reply);
}
}
}
}
elseif(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->retrieve($path, $file);
$this->abort();
}
return false;
}
function store($remotefile, $fileordata, $restore=false){
$replycode = 425;
if(is_string($remotefile) && is_string($fileordata)){
if(isset($this->dataconnection) && $this->type('I') && (!is_numeric($restore) || (intval($this->command('REST '.($restore = intval($restore)))) == 350)) && in_array(intval($this->command('ALLO '.(is_file($fileordata)? (filesize($fileordata) - (is_int($restore)? $restore : 0)) : strlen($fileordata)))), Array(200, 202)) && in_array(($replycode = intval($this->command('STOR '.$remotefile))), Array(150, 125))){
if(!is_file($fileordata)) fwrite($this->dataconnection, $fileordata);
elseif(is_file($fileordata) && ($fp = fopen($fileordata, 'rb')) && (!is_int($restore) || is_int(@fseek($fp, $restore)))) while(is_string($filepart = @fgets($fp))) fwrite($this->dataconnection, $filepart);
elseif($this->abort()) return false;
if(@fclose($this->dataconnection)) unset($this->dataconnection);
return(intval($this->reply()) == 226);
}
elseif(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->store($remotefile, $fileordata, $restore);
$this->abort();
}
return false;
}
function append($file, $fileordata){
$replycode = 425;
if(is_string($file) && is_string($fileordata)){
$replycode = 425;
if(isset($this->dataconnection) && $this->type('I') && (intval($this->command('ALLO '.(is_file($fileordata)? filesize($fileordata) : strlen($fileordata))))) && in_array(($replycode = intval($this->command('APPE '.$file))), Array(125, 150))){
if(!is_file($fileordata)) fwrite($this->dataconnection, $fileordata);
elseif($fp = @fopen($fileordata, 'rb')) while(is_string($filepart = @fgets($fp))) fwrite($this->dataconnection, $filepart);
elseif($this->abort()) return false;
if(@fclose($this->dataconnection)) unset($this->dataconnection);
return(intval($this->reply()) == 226);
}
elseif(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->append($file, $fileordata);
$this->abort();
}
return false;
}
function passive(){
if(isset($this->dataconnection)) $this->abort();
if((intval($reply = $this->command('PASV')) == 227) && is_int($spos = strpos($reply, '(')) && is_int($epos = strpos($reply, ')', $spos))){
$ipparts = explode(',', substr($reply, ($spos + 1), ($epos - $spos - 1)));
if(count($ipparts) == 6){
$host = ''; $count = -1;
while(($count += 1) < 4){
if(is_numeric($ipparts[$count])) $host .= (($host == '')? '' : '.').intval($ipparts[$count]);
else return false;
}
$port = ((intval($ipparts[4]) << 8) | intval($ipparts[5]));
if($this->dataconnection = @fsockopen($host, $port, $errno, $errstr, 30)) return true;
}
}
return false;
}
function type($type=false){
return(is_string($type) && (intval($this->command('TYPE '.$type)) == 200));
}
function fileList($path=false, $raw=false){
$replycode = 425;
if(!is_string($path)) $raw = $path;
if(isset($this->dataconnection) && $this->type('A') && (($mlsd = in_array(intval($this->command('MLSD '.(is_string($path)? ' '.$path : ''))), Array(150, 125))) || in_array(intval($this->command('LIST -a'.(is_string($path)? ' '.$path : ''))), Array(150, 125)))){
$reply = '';
while(true){
if(is_string($replypart = fgets($this->dataconnection, 128))) $reply .= $replypart;
if(!is_string($replypart)){
@fclose($this->dataconnection);
unset($this->dataconnection);
if(intval($this->reply()) == 226){
if($raw) return($reply);
$files = Array();
$records = split("[\r\n]+", trim($reply));
if($mlsd){
# TODO: implement MLSD
}
else{
$systype = $this->systype();
if(preg_match('/(LI|U)NIX/i', $systype)){
while(is_string($record = current($records))){
next($records);
$fields = preg_split("/\s+/", $record);
if(count($fields) > 8){
$files[($index = count($files))] = Array('type' => ((($type = substr($fields[0], 0, 1)) == '-')? 'F' : strtoupper($type)), 'inode' => intval($fields[1]), 'perms' => 0, 'owner' => $fields[2], 'group' => $fields[3], 'size' => intval($fields[4]), 'name' => $fields[8]);
$perms = substr($fields[0], 1);
if(isset($fields[10])) $files[$index]['link'] = $fields[10];
$count = -1;
while(strlen($perms) > ($count += 1)) $files[$index]['perms'] = ((($files[$index]['perms'] << 1) | ((int) (substr($perms, $count, 1) != '-'))));
}
}
}
else if(preg_match('/WIN/i', $systype)){
while(is_string($record = current($records))){
next($records);
if(strlen($record) > 40){
$date = trim(substr($record, 0, 9));
$time = trim(substr($record, 10, 7));
$mtime = mktime((intval(substr($time, 0, 2)) + ((strtoupper(substr($time, 5, 2)) == 'PM')? 12 : 0)), intval(substr($time, 3, 2)), 0, intval(is_int($slashpos = strpos($date, '/'))? substr($date, 3, 2) : substr($date, 0, 2)), intval(is_int($slashpos)? substr($date, 0, 2) : substr($date, 3, 2)), ((($year = (2000 + intval(substr($date, 6, 2)))) > intval(date('Y')))? ($year - 100) : $year));
$size = trim(substr($record, 18, 20));
$files[($index = count($files))] = Array('type' => (!is_numeric($size)? 'D' : 'F'), 'size' => (is_numeric($size)? intval($size) : 0), 'name' => substr($record, 39), 'modify' => $mtime);
}
}
}
}
return $files;
}
break;
}
}
}
elseif(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->fileList($path, $raw);
$this->abort();
return false;
}
function namelist($path=false){
$replycode = 425;
if(isset($this->dataconnection) && $this->type('A') && in_array(($replycode = intval($this->command('NLST'.(is_string($path)? ' '.$path : '')))), Array(150, 125))){
$reply = '';
while(true){
if(is_string($replypart = fgets($this->dataconnection, 128))) $reply .= $replypart;
if(!is_string($replypart)){
@fclose($this->dataconnection);
unset($this->dataconnection);
if(intval($this->reply()) == 226) return(split("[\r\n\s]+", trim($reply)));
}
}
}
else if(in_array($replycode, Array(425, 426)) && $this->passive()) return $this->namelist($path);
$this->abort();
return false;
}
function quit(){
if(isset($this->dataconnection) && @fclose($this->dataconnection)) $this->reply(false);
return($this->command('REIN') && (intval($this->command('QUIT')) == 221) && @fclose($this->connection));
}
}
?>
Refactorings
No refactoring yet !
techietim
October 20, 2007, October 20, 2007 14:10, permalink
That's pretty good. You should post that up on:
http://php.net/ftp
Any thoughts on this?
Missing some functionallities?
Could some parts written shorter?
Thanxs in advance :D
BTW: Before questions, this class is written because of I'm currently using a server without FTP-support for PHP :(