<?php
##
## this file name is 'class.download.php'
##
## download object
##
## [author]
##  - Chilbong Kim, <san2(at)linuxchannel.net>
##  - http://linuxchannel.net/
##
## [changes]
##  - 2014.12.09 : timeout tunning
##  - 2009.08.04 : bug fixed _gethttp()
##  - 2008.12.03 : some stderr(), add more info(connect time, response time ...)
##  - 2008.12.01 : bug fixed _parse_url()
##  - 2008.06.18 : bug fixed none-blocking mode
##  - 2005.03.28 : bug fixed and tunning
##  - 2005.03.24 : new build
##
## [download & online source view]
##  - http://ftp.linuxchannel.net/devel/php_download/
##
## [references]
##
## [usage]
##

class download
{
  
/***
  var $sock = ''; // socket
  var $blocking = FALSE; // blocking mode
  var $bsize = 1048576; // socket send/receive buffer size(Bytes)
  var $debug = FALSE; // debug mode or gregress view mode
  var $timeout = array('sec'=>0,'usec'=>100000);
  ***/

  
function &gethttp($_url$blocking=FALSE$debug=FALSE)
  {
    static 
$bsize 1048576// socket send/receive buffer size(Bytes)
    
static $timeout = array('sec'=>0,'usec'=>500000);

    list(
$host,$port,$uri) = download::_parse_url($_url);

    if(!
$host) return;

    if(
preg_match('/[a-z]/',$host)) $ip gethostbyname($host); // to ip
    
else $ip $host;

    
$sock = @socket_create(AF_INETSOCK_STREAMSOL_TCP);

    @
socket_set_option($sock,SOL_SOCKET,SO_SNDBUF,$bsize);
    @
socket_set_option($sock,SOL_SOCKET,SO_RCVBUF,$bsize);
    @
socket_set_option($sock,SOL_SOCKET,SO_RCVTIMEO,$timeout); // good idea for blocking mode
    
@socket_set_option($sock,SOL_SOCKET,SO_SNDTIMEO,$timeout);

    if(
$blocking$bmode 'blocking-mode';
    else
    {
        @
socket_set_nonblock($sock); // set to non-blocking mode
        
$bmode 'non-blocking-mode';
    }

    
$req 'GET '.$uri.' HTTP/1.1'."\r\n".
        
'Host: '.$host."\r\n".
        
'User-Agent: PHP/gethttp'."\r\n".
        
'Connection: close'."\r\n"// good idea for blocking mode
        
"\r\n"// enf of request header mark

    
$conn_start microtime();
    
$code download::_connect($sock,$ip,$port,5,$blocking);
    
$conn_stop microtime();
    if(
$code != 1)
    {
        @
socket_close($sock);
        if(
$debugdownload::stderr('can not connect to '.$host.':'.$port);
        return;
    }

    if(!@
socket_write($sock,$req))
    {
        @
socket_close($sock);
        if(
$debugdownload::stderr('can not send request '.$uri);
        return;
    }
    
$req_stop microtime();

    
$header download::_getheader($sock,1024,$blocking); // read header
    
$header_stop microtime();
    
$header download::_parse_header($header);

    if(
$header['error'])
    {
        @
socket_close($sock);
        if(
$debugdownload::stderr($header['status'].' '.$url);
        return;
    }

    
$total $header['content-length']; // total size
    
$pg $debug $total 0// debug mode with progress

    
$transfer_start microtime();
    
$size download::_readsize($sock,$bsize,$blocking,$pg); // check bandwidth mode
    //download::_readbyall($sock,$buf='',$bsize,$blocking,$pg);
    //$size = strlen($buf);
    
@socket_close($sock);
    
$transfer_stop microtime();

    
//$secs = download::_getmicrotime(defined('_RESPONSE_END_')?_RESPONSE_END_:$transfer_start,$transfer_stop);
    
$secs download::_getmicrotime($transfer_start,$transfer_stop);
    
$rr = @sprintf("%s %.1f%s\t%.1f%s\t%d Bytes\t%.2f secs",
        
$bmode,$size*100/$total,'%',$size/1024/$secs,'KB/sec',$size,$secs);

    echo 
$rr;

    if(
$debug)
    {
        
$conn_time download::_getmicrotime($conn_start,$conn_stop) * 1000;
        
$request_time download::_getmicrotime($conn_start,$req_stop) * 1000;
        
$response_time download::_getmicrotime($conn_start,$header_stop) * 1000;
        
$transfer_time $secs 1000;
        
$total_time = ($response_time $transfer_time) / 1000;
        
printf
        
(
            
"\nconnect.time %.1f ms, request.time %.1f ms, response.time %.1f ms, ".
            
"transfer %.1f ms, total %.4f secs",
            
$conn_time$request_time$response_time$transfer_time$total_time
        
);
    }
  }

  function &
_gethttp($_url$debug=FALSE)
  {
    list(
$host,$port,$uri) = download::_parse_url($_url);

    if(!
$host) return;

    
$bmode 'blocking-mode';
    
$req 'GET '.$uri.' HTTP/1.1'."\r\n".
        
'Host: '.$host."\r\n".
        
'User-Agent: PHP/gethttp'."\r\n".
        
'Connection: close'."\r\n"// good idea for blocking mode
        
"\r\n"// enf of request header mark

    
if(!$fp = @fsockopen($host,$port,$errno,$errstr,3))
    {
        
download::stderr($errstr);
        return;
    }

    @
stream_set_timeout($fp,0,500000); // 0.5 seconds, good idea for blocking mode

    
if(!@fwrite($fp,$req)) return;

    
## read header
    ##
    
$header '';
    while(
$buf fgets($fp,1024))
    {
        if(!
trim($buf))
        {
            
fgets($fp,1024); // seek
            
break;
        }
        
$header .= $buf;
    }

    
$header download::_parse_header($header);
    if(
$header['error'])
    {
        @
fclose($fp);
        if(
$debugdownload::stderr($header['status'].' '.$_url);
        return;
    }

    
$total $header['content-length']; // total size
    
$pg $debug $total 0// debug mode with progress

    
$size 0;
    
$start microtime();
    while(
$buf fread($fp,4096))
    {
        
$size += strlen($buf);
        if(
$pgdownload::__progress($size,$total,&$op); // debug mode
    
}
    if(
$pg) echo (int)($size*100/$total)."%\n"// debug mode
    
@fclose($fp);

    
$secs download::_getmicrotime($start,microtime());
    
$rr = @sprintf("%s %.1f%s\t%.1f%s\t%d Bytes\t%.2f secs",
        
$bmode,$size*100/$total,'%',$size/1024/$secs,'KB/sec',$size,$secs);

    echo 
$rr;
  }

  
## connect to host:port
  ##
  ## [return]
  ##  1 or TRUE  : connected
  ##  0 or FALSE : can't connect
  ##  -1         : connetion time out
  ##
  
function &_connect(&$sock$host$port$timeout=3$blocking=FALSE)
  {
    if(
$blocking) return @socket_connect($sock,$host,$port);

    
$_time time();
    while(
1)
    {
        @
socket_connect($sock,$host,$port);
        if(@
socket_last_error()==56 || @socket_last_error()==106) { return TRUE; break; }
        else if((
time()-$_time) >= $timeout) { return -1; break; }
    }

    return 
FALSE;
  }

  
## read header by socket_read()
  ## PHP_NORMAL_READ : 1
  ## PHP_BINARY_READ : 2
  ##
  
function &__readheader(&$sock$bsize)
  {
    while(
$buf = @socket_read($sock,$bsize,PHP_NORMAL_READ))
    {
        
//if(!defined('_RESPONSE_END_')) define('_RESPONSE_END_',microtime());
        
if(!trim($obuf.$buf))
        {
            @
socket_read($sock,$bsize,PHP_NORMAL_READ); // seek 1 bytes
            
break; // end of header
        
}
        
$rbuf .= $buf;     $obuf $buf;
    }

    return 
$rbuf;
  }

  
## read HTTP all headers
  ##
  
function &_getheader(&$sock$bsize=1024$blocking=FALSE)
  {
    if(
$blocking) return @download::__readheader($sock,$bsize);

    
$stream = array($sock);
    while(@
socket_select($stream,$write=NULL,$except=NULL,10,0) !== FALSE)
    {
        
//if(!in_array($sock,$stream)) break; // is bug ???
        
$rbuf download::__readheader($sock,$bsize);
        break; 
// require
    
}

    return 
$rbuf;
  }

  function &
_parse_url($_url)
  {
    
$url parse_url(preg_replace(';^(?:http://)*;i','http://',trim($_url)));

    
$host $url['host'];
    
$port $url['port'] ? $url['port'] : 80;
    
$uri $url['path'] ? $url['path'] : '/';

    if(
$url['query']) $uri .= '?'.$url['query']; // add san2@2008.12.01

    
return array($host,$port,$uri);
  }

  function &
_parse_header($buf)
  {
    
$lines preg_split('/[\r\n]+/',trim($buf));
    
$size sizeof($lines);
    
$r['status'] = trim($lines[0]);

    if(!
preg_match(';200 OK;',$lines[0]))
    {
        
$r['error'] = TRUE;
        return 
$r;
    }

    for(
$i=1$i<$size$i++)
    {
        
$line trim($lines[$i]);
        list(
$k) = preg_split('/:\s+/',$line);
        
$r[strtolower($k)] = preg_replace(';^'.$k.':\s+;','',$line);
    }

    return 
$r;
  }

  function &
__progress($size$total, &$op)
  {
    
$np = (int)($size*100/$total);
    if(
$np != $op)
    {
        echo 
'=';
        
//if(!($np%25)) echo $np;
        //if($size == $total) break; // good idea in while()
    
}
    
$op $np;
    
flush();
  }

  
## read by size
  ##
  
function &_readbysize(&$sock$bsize=4096$blocking=FALSE)
  {
    if(
$blocking) return @socket_read($sock,$bsize);

    
$stream = array($sock);
    while(@
socket_select($stream,$write=NULL,$except=NULL,10,0) !== FALSE)
    {
        if(!
in_array($sock,$stream)) break;
        return @
socket_read($sock,$bsize);
        break; 
// require
    
}

    return;
  }

  
## read by all
  ##
  
function &_readbyall(&$sock, &$rbuf$bsize=1048576$blocking=FALSE$total=0)
  {
    if(
$blocking)
    {
        while((
$buf = @socket_read($sock,$bsize)))
        {
            
$rbuf .= $buf;
            if(
$totaldownload::__progress($rsize+=strlen($buf),$total,&$op);
        }
        if(
$total) echo (int)($rsize*100/$total)."%\n"// debug mode
        
return TRUE;
    }

    
$stream = array($sock);
    while(@
socket_select($stream,$write=NULL,$except=NULL,10,0) !== FALSE)
    {
        if(!
in_array($sock,$stream)) break;
        if((
$buf = @socket_read($sock,$bsize)))
        {
            
$rbuf .= $buf;
            if(
$totaldownload::__progress($rsize+=strlen($buf),$total,&$op);
        } else break; 
// good idea, EOF
    
}
    if(
$total) echo (int)($rsize*100/$total)."%\n"// debug mode

    
return TRUE;
  }

  
## only read size, this is a bandwidth test version
  ##
  
function &_readsize(&$sock$bsize=1048576$blocking=FALSE$total=0)
  {
    if(
$blocking)
    {
        while((
$tsize strlen(@socket_read($sock,$bsize))))
        {
            
$rsize += $tsize;
            if(
$totaldownload::__progress($rsize,$total,&$op); // debug mode
        
}
        if(
$total) echo (int)($rsize*100/$total)."%\n"// debug mode

        
return $rsize;
    }

    
$stream = array($sock);
    while(@
socket_select($stream,$write=NULL,$except=NULL,10,0) !== FALSE)
    {
        if(!
in_array($sock,$stream)) break;
        if(
$tsize strlen(@socket_read($sock,$bsize)))
        {
            
$rsize += $tsize;
            if(
$totaldownload::__progress($rsize,$total,&$op); // debug mode
        
} else break; // good idea, EOF
    
}
    if(
$total) echo (int)($rsize*100/$total)."%\n"// debug mode

    
return $rsize;
  }

  function &
_getmicrotime($_start$_end)
  {
    
$end explode(' '$_end);
    
$start explode(' '$_start);

    return 
sprintf('%.4f', ($end[1]+$end[0]) - ($start[1]+$start[0]));
  }

  function &
socket_info($socket)
  {
    
$opt = array
    (
        
'SO_DEBUG'=>SO_DEBUG,
        
'SO_ACCEPTCONN'=>SO_ACCEPTCONN,
        
'SO_BROADCAST'=>SO_BROADCAST,
        
'SO_REUSEADDR'=>SO_REUSEADDR,
        
'SO_KEEPALIVE'=>SO_KEEPALIVE,
        
'SO_LINGER'=>SO_LINGER,
        
'SO_OOBINLINE'=>SO_OOBINLINE,
        
'SO_SNDBUF'=>SO_SNDBUF,
        
'SO_RCVBUF'=>SO_RCVBUF,
        
'SO_ERROR'=>SO_ERROR,
        
'SO_TYPE'=>SO_TYPE,
        
'SO_DONTROUTE'=>SO_DONTROUTE,
        
'SO_RCVLOWAT'=>SO_RCVLOWAT,
        
'SO_RCVTIMEO'=>SO_RCVTIMEO,
        
'SO_SNDLOWAT'=>SO_SNDLOWAT,
        
'SO_SNDTIMEO'=>SO_SNDTIMEO,
    );

    foreach(
$opt AS $k=>$v)
    { 
$r[$k] = @socket_get_option($socket,SOL_SOCKET,$v); }

    return 
$r// array
  
}

  
## print to stderr such as error_log()
  ##
  ## arguments :
  ##      $error    string, error message
  ##
  ## return :
  ##      $return mixed, the number of bytes written, or FALSE on error
  ##
  
function _stderr($errstr)
  {
    global 
$_SERVER;

    if(
$fp = @fopen('php://stderr','w'))
    {
        
## add error_log format style
        ##
        
if($_SERVER['HTTP_USER_AGENT'])
        {
            
$head '['.date('D M d H:i:s Y').'] [PHP-stderr] '.
            
'[client '.$_SERVER['REMOTE_ADDR'].'] ';
        }

        
$return = @fwrite($fp,$head.$errstr."\n");
        
fclose($fp);
    }

    return 
$return;
  }

  function 
stderr($errstr)
  {
    return 
error_log($errstr,0);
  }
// end of class

/*** example ***
set_time_limit(0);
ob_implicit_flush();
//ini_set('memory_limit','150M');
$host = 'www.foobar.com';
download::gethttp("http://$host/test/100M.data",0,1);
***/
?>