PHP BitTorrent Announce Client

Sa, 07.03.2009 - 09:32 -- admin

Bittorrent macht mittlerweile einen Großteil des weltweiten Datenverkehrs aus. Ohne eine Torrentdatei und einen dazugehörigen Tracker läuft hier nichts. Ich habe eine Klasse in PHP erstellt, welche die Daten aus einer Torrentdatei auslesen kann und eine Anfrage an den Tracker schickt, welcher schließlich die IP-Adressen ausspuckt.
Es läßt sich mit dieser Klassen ein kompletter Client gegenüber dem Tracker simulieren, vom Starten des Downloads bis zum fertigstellen.
Dateien werden bei diesem Vorgang nicht übertragen (wird wohl in PHP nicht machbar sein). Es soll hier nur um das Verständnis der Kommunikation zwischen Client und Tracker über das http-Protokoll und des Bencoding gehen.

Torrent-Information

Als Beispiel Torrent-Datei hab ich mir ein Debian Image genommen

Zeigt den Torrentname (in der Torrent Dateien, nicht den Dateinamen) und die aufgeführten Dateien in der Torrentdatei

$client= new TorrentClient("Torrentflux","debian-500-i386-DVD-1.iso.torrent");
echo $client->getTorrentName()."\r\n";
print_r($client->getTorrentFiles());

debian-500-i386-DVD-1.iso
Array
(
    [debian-500-i386-DVD-1.iso] => Array
        (
            [size] => 4698322944
        )
 
)

Weitere Torrent-Informationen stehen zur Verfügung z.B Hashwert, größe des gesamten Downloads und Trackerurl

$client= new TorrentClient("Torrentflux","debian-500-i386-DVD-1.iso.torrent");
echo "AnnounceUrl:".$client->getTorrentAnnounceUrl()."\r\n";
echo "Hash:".$client->getTorrentHash()."\r\n";
echo "Size:".$client->getTorrentSize()."\r\n";

AnnounceUrl:http://bttracker.debian.org:6969/announce
Hash:cf92138268871bc3bb964ed69db1ec36c2ce9759
Size:4698322944

Tracker response

Möchte man eine Trackeranfrage senden so geht das über announcing. getAnnoucedPeers stellt schließlich als Array die Peers über IP,Port und PeerID (soweit vorhanden) zur Verfügung. Da viele Tracker aus Performence gründen nur max. 50 Peers zur Verfügung stellen, gibt meist in der Antwort noch einen extra Wert für die gesamt Anzahl der Peers. Diesen Wert bekommt man über getAnnoucedPeersCount.
Bei dieser Anfrage wird dem Tracker ein Start-Event geschickt, wie es jeder Client machen würde.

$client= new TorrentClient("Torrentflux","debian-500-i386-DVD-1.iso.torrent");
$client->announcing();
 
echo "Peers on server:\r\n";
echo $client->getAnnoucedPeersCount()."\r\n";
 
echo "Peers to connect:\r\n";
print_r($client->getAnnoucedPeers());

Peers on server:
49
Peers to connect:
Array
(
    [0] => Array
        (
            [ip] => 68.231.xxx.xxx
            [port] => 9798
        )
 
    [1] => Array
        (
            [ip] => 89.248.xxx.xxx
            [port] => 12730
        )
...
)

Neben dem Start Event gibt es weiterhin noch update, stopped und complete. Hier müssen allerdings weitere Angaben übernommen werden. Daten wie PeerID,PeerKey,Client (Beispiel $ar) müssen der Anfrage aus dem Start-Event entsprechen. Eine Übergabe der Torrentdatei ist nicht erforderlich sichert man sich vorher die entsprechenden Daten.

$a= new TorrentClient($ar["Client"]);
$a->peer_id=$ar['PeerID'];
$a->peer_key=$ar['PeerKey'];
$a->an_event="update";
$a->an_download=$ar['DownloadedBytes'];
$a->an_upload=$ar['UploadedBytes'];
$a->an_left=($ar['LeftBytes'] - $ar['DownloadedBytes']);
$an=$a->announcing($ar["Hash"],$ar["AnnouceURL"]);

VCS File: 

/trunk/bittorrent_announce/bittorrent_announce_class.php

<?php
// $Id: bittorrent_announce_class.php 10 2010-08-15 14:25:26Z espendiller $
 
/*
Version 0.1 public
more on http://www.espend.de - PHP BitTorrent Announce Client
*/
 
class TorrentClient {
	private $peer_header;
	private $peer_url;
		var $peer_id;
		var $peer_key;
 
	private $torrent_bdecode;
	private $torrent_raw;
 
	private $request;
	    var $request_proxy="";
	    var $request_php_proxy="";
 
	var $an_port=3366;
	var $an_event="started";
	var $an_upload=0;
	var $an_download=0;
	var $an_left=0;
	var $an_numpeers=50;
 
function TorrentClient($client="Info",$torrentfile="") {
	switch ($client) {
	case 'Transmission':
		$this->peer_url="%host%info_hash=%hash%&peer_id=-TR0006-%peer%&port=%port%&uploaded=%upload%&downloaded=%download%&left=%left%&compact=1&numwant=%numpeers%&key=%key%%event%";
		$this->peer_header=array("User-Agent: Transmission/0.6","Content-length: 0","Connection: close");
		$this->peer_id=$this->getpass(12,0);
		$this->peer_key=$this->getpass(20,0);
    	break;
	case 'Torrentflux':
		$this->peer_url="%host%info_hash=%hash%&peer_id=T03I-----%peer%&port=%port%&uploaded=%upload%&downloaded=%download%&left=%left%&no_peer_id=1&compact=1%event%&key=%key%";
		$this->peer_header=array("User-Agent: BitTornado/T-0.3.18","Accept-Encoding: gzip");
		$this->peer_id=$this->getpass(11,1);
		$this->peer_key=$this->getpass(6,1);
    	break;
	case 'Info':
    	break;
	default:
    	echo "TorrentClient error: Client ".$client." unknown"; exit;
	}
 
 
	if ($torrentfile!="") {
		if (is_file($torrentfile)) $torrentfile=$this->d_read($torrentfile);
		$this->torrent_bdecode=$this->BDecode($torrentfile);
		$this->torrent_raw=$torrentfile;
		$this->an_left=$this->getTorrentSize();
	}
 
	}
 
private function BDecode($wholefile) {
	$decoder = new BDecode;
	$return = $decoder->decodeEntry($wholefile);
	return $return[0];
}
 
function announcing($hash="",$announce="",$returnOnlyUrl=false) {
	if (is_array($this->torrent_bdecode)==false) {
		if ($hash=="" AND $announce=="") { echo "TorrentClient error: not enough announce information"; exit; return ""; }
	}
 
	$url = $this->peer_url;
	if ($hash=="") $hash=$this->getTorrentHash();
	if ($announce=="") $announce=$this->getTorrentAnnounceUrl();
	if (strpos($announce,"?") > 0 ) { $announce.="&"; } else { $announce.="?"; }
 
	$url = str_replace("%hash%",urlencode(pack("H*", $hash)),$url);
	$url = str_replace("%host%",$announce,$url);
 
	if ($this->an_event=="update") { $url = str_replace("%event%","",$url); } else {
		$url = str_replace("%event%","&event=".$this->an_event,$url); }
	$url = str_replace("%download%",$this->an_download,$url);
	$url = str_replace("%upload%",$this->an_upload,$url);
	$url = str_replace("%left%",$this->an_left,$url);
	$url = str_replace("%port%",$this->an_port,$url);
	$url = str_replace("%peer%",$this->peer_id,$url);
	$url = str_replace("%key%",$this->peer_key,$url);
	$url = str_replace("%numpeers%",$this->an_numpeers,$url);
 
	d_upd("debug.txt",date("H:i:s")." - ".$url."\r\n");
 
	if ($returnOnlyUrl==true) return $url;
	return $this->performRequest($url);
}
 
private function unesc($x) {
    if (get_magic_quotes_gpc()==false)
        return stripslashes($x);
    return $x;
}
 
 
private function fetchURL($url) {
   $url_parsed = parse_url($url);
   $host = $url_parsed["host"];
   $port = $url_parsed["port"];
   if ($port==0)
       $port = 80;
   $path = $url_parsed["path"];
   if ($url_parsed["query"] != "")
       $path .= "?".$url_parsed["query"];
 
  if($this->request_proxy!="") {
  	$pro=split(":",$this->request_proxy);
  	$fp = fsockopen($pro[0], $pro[1], $errno, $errstr, 30);
    $out  .= 'GET http://'.$host.":".$port.$path.' HTTP/1.0'."\r\n";
  } else {
    $fp = fsockopen($host, $port, $errno, $errstr, 30);
    $out  .= 'GET '.$path.' HTTP/1.0'."\r\n";
  }
 
  	$out .= 'Host: '.$host."\r\n";
	$out.=implode("\r\n", $this->peer_header);
	$out.="\r\n\r\n";
 
   fwrite($fp, $out);
   $body = false;
   while (!feof($fp)) {
      $s = fgets($fp, 1024);
      if($body==true) { $in .= $s;
      } else { $b.=$s;   }
      if ( $s == "\r\n" ) $body = true;
   }
 
   fclose($fp);
 
   	if (strpos($b,"deflate") > 0) $in = gzuncompress($in);
	if (strpos($b,"gzip") > 0) $in = gzinflate(substr($in,10));
 
 
   return $in;
}
 
private function performRequest($url) {
	$url1=$url;
	if ($this->request_php_proxy!="") $url1=$this->request_php_proxy."?p=".bin2hex($url);
	$get = $this->fetchURL($url1);
	if (strpos($get,"failure")>0) return "error:".$get;
	if (!strlen($get)>0 OR strpos($get,"404 - ")>0 ) return "error:keine/falsche antwort";
#	$get=$this->unesc($get);
	$this->request=$this->bdecode($get);
	return $get;
	#exit;
	$browser = new browser();
	$browser->set_extra_headers($this->peer_header);
	if ($this->request_proxy!="") $browser->proxy=$this->request_proxy;
 
	$site=$browser->site($url);
	if ($this->request_php_proxy!="") $site=$browser->site($this->request_php_proxy."?p=".bin2hex($url));
	$site->get();
	$get=$this->unesc($site->get_content());
	$get_h=$site->get_headers();
 
	if (strpos(implode("\r\n", $get_h),"deflate") > 0) $get = gzuncompress($get);
	if (strpos(implode("\r\n", $get_h),"gzip") > 0) $get = gzinflate(substr($get,10));
 
	if (strpos($get,"failure")>0) return "error:".$get;
	if (!strlen($get)>0 OR strpos($get,"404 - ")>0 ) return "error:keine/falsche antwort";
	$this->request=$this->bdecode($get);
	return $get;
}
 
function performRequestOverride($datei) {
	$str=$this->d_read($datei);
	$this->request=$this->bdecode($str);
}
 
function getTorrentFiles() {
	return $this->torFiles($this->torrent_bdecode);
}
 
function getTorrentSize() {
	return $this->torSize($this->torrent_bdecode);
}
 
function getPeerKey() {
	return $this->peer_key;
}
 
function getPeerID() {
	return $this->peer_id;
}
 
function getTorrentHash() {
	return sha1($this->BEncode($this->torrent_bdecode["info"]));
}
 
function replaceAnnounce($newURL) {
	$adresse= $this->torrent_bdecode['announce'];
	$this->torrent_raw=str_replace($this->BEncode($adresse),$this->BEncode($newURL),$this->torrent_raw);
	$this->torrent_bdecode=$this->BDecode($this->torrent_raw);
	return $this->torrent_raw;
}
 
function getTorrentName() {
	return $this->torrent_bdecode['info']['name'];
}
 
function getTorrentAnnounceUrl() {
	#not multitrackersuppert at the moment
	#[announce-list] => Array (  [0] => Array  (  [0] => http://tpb.tracker.thepiratebay.org/announce
	return $this->torrent_bdecode["announce"];
}
 
function getAnnoucedPeersCount() {
	$peers=array();
	if (is_array($this->request)) {
		if (!$peers>0) $peers=$this->request['value']['complete']['value']+$this->request['value']['incomplete']['value'];
		if (!$peers>0) $peers=$this->request['complete']+$this->request['incomplete'];
		if (!$peers>0) $peers = count($this->getAnnoucedPeers());
	}
	return $peers;
}
 
function getAnnoucedPeers() {
	#$an=$this->bdecode($this->request);
	$an=$this->request;
	if (count($an)>0) {
	$ips=$this->bdec_bin($an['peers']);
	return $ips;
	} else { return""; }
}
 
function getAnnoucedInterval() {
	return $this->request['interval'];
}
 
private function BEncode($array)
{
	$string = "";
	$encoder = new BEncode;
	$encoder->decideEncode($array, $string);
	return $string;
}
 
private function torFiles($torrentBec) {
  $b = array();
 
  // torrent with single file
	if (isset($torrentBec['info']['length'])) {
		$b[$torrentBec['info']['name']] = array(
      'path' => $torrentBec['info']['name'],      
      'size' => $torrentBec['info']['length'],
    );
 
		return $b;
	}
 
  // torent with more than one file
	foreach ($torrentBec['info']['files'] as $row) {
 
		$datei=implode("\\", $row['path']);
		$b[$datei] = array(
      'path' => implode("\\", $row['path']),
      'size' => $row['length'],
    );
  }
 
	return $b;
}
 
private function torSize($torrentBec) {
	if (!isset($torrentBec['info']['files'])) return $torrentBec['info']['length'];
	$files = $torrentBec['info']['files'];
	$size=0;
	foreach ($files as $row) {
		$size=$size+$row['length'];
	}
	return $size;
}
 
private function getpass($laenge,$str) {
    $newpass = "";
    $string[0]="abcdefghijklmnopqrstuvwxyz0123456789";
    $string[1]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 
    mt_srand((double)microtime()*1000000);
 
    for ($i=1; $i <= $laenge; $i++) {
        $newpass .= substr($string[$str], mt_rand(0,strlen($string[$str])-1), 1);
    }
 
    return $newpass;
}
 
private function d_read($datei) {
	if (!file_exists($datei)) return "";
	$header="";
	$dateizeiger=fopen($datei,"r");
	while(!feof($dateizeiger))
		{ $header .= fgets($dateizeiger); }
	fclose($dateizeiger);
	return $header;
}
 
 
private function bdec_bin($peerlist) {
	$back[]['info']="nichts";
    if (!is_array($peerlist)>0) {
	         $back=array();
	         $peers=str_split($peerlist,6);
	         foreach ($peers as $row) {
	         	$back[]=$this->bin_met($row);
	         }
	}
 
	if (is_array($peerlist)) return $peerlist;
 
    return $back;
}
 
private function bin_met($packed) {
	$back=array();
	if (strlen($packed)>0) {
		$back = unpack('Nip/nport', $packed);
		$back['ip']=long2ip($back['ip']);
	}
	return $back;
}
 
 
}
 
 
class BDecode {
 
function numberdecode($wholefile, $start)
{
	$ret[0] = 0;
	$offset = $start;
 
	// Funky handling of negative numbers and zero
	$negative = false;
	if ($wholefile[$offset] == '-')
	{
		$negative = true;
		$offset++;
	}
	if ($wholefile[$offset] == '0')
	{
		$offset++;
		if ($negative)
			return array(false);
		if ($wholefile[$offset] == ':' || $wholefile[$offset] == 'e')
		{
			$offset++;
			$ret[0] = 0;
			$ret[1] = $offset;
			return $ret;
		}
		return array(false);
	}
	while (true)
	{
 
		if ($wholefile[$offset] >= '0' && $wholefile[$offset] <= '9')
		{
 
			$ret[0] *= 10;
//Added 2005.02.21 - VisiGod
        //Changing the type of variable from integer to double to prevent a numeric overflow
                        settype($ret[0],"double");
//Added 2005.02.21 - VisiGod
			$ret[0] += ord($wholefile[$offset]) - ord("0");
			$offset++;
		}
		// Tolerate : or e because this is a multiuse function
		else if ($wholefile[$offset] == 'e' || $wholefile[$offset] == ':')
		{
			$ret[1] = $offset+1;
			if ($negative)
			{
				if ($ret[0] == 0)
					return array(false);
				$ret[0] = - $ret[0];
			}
			return $ret;
		}
		else
			return array(false);
	}
 
}
 
function decodeEntry($wholefile, $offset=0)
{
	if ($wholefile[$offset] == 'd')
		return $this->decodeDict($wholefile, $offset);
	if ($wholefile[$offset] == 'l')
		return $this->decodelist($wholefile, $offset);
	if ($wholefile[$offset] == "i")
	{
		$offset++;
		return $this->numberdecode($wholefile, $offset);
	}
	// String value: decode number, then grab substring
	$info = $this->numberdecode($wholefile, $offset);
	if ($info[0] === false)
		return array(false);
	$ret[0] = substr($wholefile, $info[1], $info[0]);
	$ret[1] = $info[1]+strlen($ret[0]);
	return $ret;
}
 
function decodeList($wholefile, $start)
{
	$offset = $start+1;
	$i = 0;
	if ($wholefile[$start] != 'l')
		return array(false);
	$ret = array();
	while (true)
	{
		if ($wholefile[$offset] == 'e')
			break;
		$value = $this->decodeEntry($wholefile, $offset);
		if ($value[0] === false)
			return array(false);
		$ret[$i] = $value[0];
		$offset = $value[1];
		$i ++;
	}
 
	// The empy list is an empty array. Seems fine.
	$final[0] = $ret;
	$final[1] = $offset+1;
	return $final;
 
 
 
}
 
// Tries to construct an array
function decodeDict($wholefile, $start=0)
{
	$offset = $start;
	if ($wholefile[$offset] == 'l')
		return $this->decodeList($wholefile, $start);
	if ($wholefile[$offset] != 'd')
		return false;
	$ret = array();
	$offset++;
	while (true)
	{
		if ($wholefile[$offset] == 'e')
		{
			$offset++;
			break;
		}
		$left = $this->decodeEntry($wholefile, $offset);
		if (!$left[0])
			return false;
		$offset = $left[1];
		if ($wholefile[$offset] == 'd')
		{
			// Recurse
			$value = $this->decodedict($wholefile, $offset);
			if (!$value[0])
				return false;
			$ret[addslashes($left[0])] = $value[0];
			$offset= $value[1];
			continue;
		}
		else if ($wholefile[$offset] == 'l')
		{
			$value = $this->decodeList($wholefile, $offset);
			if (!$value[0] && is_bool($value[0]))
				return false;
			$ret[addslashes($left[0])] = $value[0];
			$offset = $value[1];
		}
		else
		{
 			$value = $this->decodeEntry($wholefile, $offset);
			if ($value[0] === false)
				return false;
			$ret[addslashes($left[0])] = $value[0];
			$offset = $value[1];
		}
	}
	if (empty($ret))
		$final[0] = true;
	else
		$final[0] = $ret;
	$final[1] = $offset;
   	return $final;
 
 
}
 
 
} // End of class declaration.
 
class BEncode
{
 
// Dictionary keys must be sorted. foreach tends to iterate over the order
// the array was made, so we make a new one in sorted order. :)
function makeSorted($array)
{
	$i = 0;
 
	// Shouldn't happen!
	if (empty($array))
		return $array;
 
	foreach($array as $key => $value)
		$keys[$i++] = stripslashes($key);
	sort($keys);
	for ($i=0 ; isset($keys[$i]); $i++)
		$return[addslashes($keys[$i])] = $array[addslashes($keys[$i])];
	return $return;
}
 
// Encodes strings, integers and empty dictionaries.
// $unstrip is set to true when decoding dictionary keys
function encodeEntry($entry, &$fd, $unstrip = false)
{
	if (is_bool($entry))
	{
		$fd .= "de";
		return;
	}
	if (is_int($entry) || is_float($entry))
	{
		$fd .= "i".$entry."e";
		return;
	}
	if ($unstrip)
		$myentry = stripslashes($entry);
	else
		$myentry = $entry;
	$length = strlen($myentry);
	$fd .= $length.":".$myentry;
	return;
}
 
// Encodes lists
function encodeList($array, &$fd)
{
	$fd .= "l";
 
	// The empty list is defined as array();
	if (empty($array))
	{
		$fd .= "e";
		return;
	}
	for ($i = 0; isset($array[$i]); $i++)
		$this->decideEncode($array[$i], $fd);
	$fd .= "e";
}
 
// Passes lists and dictionaries accordingly, and has encodeEntry handle
// the strings and integers.
function decideEncode($unknown, &$fd)
{
	if (is_array($unknown))
	{
		if (isset($unknown[0]) || empty($unknown))
			return $this->encodeList($unknown, $fd);
		else
			return $this->encodeDict($unknown, $fd);
	}
	$this->encodeEntry($unknown, $fd);
}
 
// Encodes dictionaries
function encodeDict($array, &$fd)
{
	$fd .= "d";
	if (is_bool($array))
	{
		$fd .= "e";
		return;
	}
	// NEED TO SORT!
	$newarray = $this->makeSorted($array);
	foreach($newarray as $left => $right)
	{
		$this->encodeEntry($left, $fd, true);
		$this->decideEncode($right, $fd);
	}
	$fd .= "e";
	return;
}
 
 
 
} // End of class declaration.
 
 
class browser
{
 
	var $user_agent;
	var $accept;
	var $language;
	var $charset;
	var $referer;
	var $cookies;
	var $extra_headers;
	var $proxy;
 
	var $debug;
 
	function browser()
	{
		// defaults, can override
		$this->user_agent		= false;
		$this->accept				= false;
		$this->language			= false;
		$this->charset				= false;
		$this->referer				= false;
		$this->cookies				= false;
		$this->extra_headers	= false;
		$this->proxy				= false;
		$this->debug				= false;
	}
 
	// set user-agent
	// $user_agent as string
	function set_user_agent($user_agent = false)
	{
		$this->user_agent = $user_agent;
	}
 
	// set content-type
	// $accept  as string
	function set_accept($accept = false)
	{
		$this->accept = $accept;
	}
 
	// set language
	// $language as string
	function set_language($language = false)
	{
		$this->language = $language;
	}
 
	// set charset
	// $charset  as string
	function set_charset($charset = false)
	{
		$this->charset  = $charset ;
	}
 
	// set referer
	// $referer as string
	function set_referer($referer = false)
	{
		$this->referer  = $referer ;
	}
 
	// set cookies
	// $cookies as array
	// format: array("key1"=>"value1","key2"=>"value2")
	function set_cookies($cookies = false)
	{
		$this->cookies  = $cookies ;
}
 
	// set extra_header
	// $headers as array
	// format: array("key1: value","key2: value","key3: value")
	function set_extra_headers ($extra_headers = false)
	{
		$this->extra_headers  = $extra_headers;
	}
 
	// set proxy, if set proxy will use
	// $proxy as string
	// format: http://user:pass@server:port
	function set_proxy($proxy = false)
	{
		$this->proxy  = $proxy;
	}
 
	// set it to true and the request will only print out, not send, fsocket will not open
	// $debug as boolean
	function set_debug($debug = false)
	{
		$this->debug  = $debug;
	}
 
	// reset all to defaults
	function reset()
	{
		$this->browser();
	}
 
	function site($url)
	{
		return new site($url,$this->user_agent,$this->accept,$this->language,$this->charset,$this->referer,$this->cookies,$this->extra_headers,$this->debug,$this->proxy);
	}
}
 
class site
{
	var $url;
	var $user_agent;
	var $accept;
	var $language;
	var $charset;
	var $referer;
	var $cookies;
	var $extra_headers;
	var $proxy;
 
	var $debug;
 
	var $scheme;
	var $host;
	var $port;
	var $path;
 
	var $user;
	var $pass;
 
	var $proxy_host;
	var $proxy_port;
 
	var $proxy_user;
	var $proxy_pass;
 
	// private
	var $socket;
	var $data;
 
	// contructor
	function site($url,$user_agent,$accept,$language,$charset,$referer,$cookies,$extra_headers,$debug,$proxy)
	{
 
		$this->url 					= $url;
		$this->user_agent		= $user_agent;
		$this->accept				= $accept;
		$this->language			= $language;
		$this->charset				= $charset;
		$this->referer				= $referer;
		$this->cookies				= $cookies;
		$this->extra_headers	= $extra_headers;
		$this->proxy				= $proxy;
 
		$this->debug				= $debug;
 
		$this->scheme			= false;
		$this->host					= false;
		$this->port					= false;
		$this->user					= false;
		$this->pass					= false;
 
		$this->proxy_host		= false;
		$this->proxy_port		= false;
		$this->proxy_user		= false;
		$this->proxy_pass		= false;
 
		// parse URL
		$url_parts = parse_url($this->url);
		$this->host = $url_parts['host'];
 
		// setting scheme
		if ( $url_parts['scheme'] ) {
			$this->scheme = $url_parts['scheme'];
		} else {
			$this->scheme = "http";
		}
 
		// setting port
		if ( $url_parts['port'] ) {
			$this->port = $url_parts['port'];
		} else {
			$this->port = 80;
		}
 
		// setting path
		if ( $url_parts['path'] ) {
			$this->path =  $url_parts['path'];
		} else {
			$this->path = '/';
		}
 
		// adding query to path
		if ( $url_parts['query'] )
		{
			$this->path = $this->path.'?'.$url_parts['query'];
		}
 
		// authentification
		if ( $url_parts['user'] )
		{
			if ( $url_parts['pass']) {
				$this->pass = $url_parts['pass'];
			} else {
				$this->pass = "";
			}
			$this->user = $url_parts['user'];
		}
 
		// parse proxy url
		if ($this->proxy) {
 
			$url_parts = parse_url($this->proxy);
			$this->proxy_host = $url_parts['host'];
 
			// setting port
			if ( $url_parts['port'] ) {
				$this->proxy_port = $url_parts['port'];
			} else {
				$this->proxy_port = 8080;
			}
 
			// authentification für proxy
			if ( $url_parts['user'] )
			{
				if ( $url_parts['pass']) {
					$this->proxy_pass = $url_parts['pass'];
				} else {
					$this->proxy_pass = "";
				}
				$this->proxy_user = $url_parts['user'];
			}
		}
	}
 
	// GET a site, return boolean
	function get()
	{
 
		if ($this->proxy) {
			$connect_host = $this->proxy_host;
			$connect_port = $this->proxy_port;
		} else {
			$connect_host = $this->host;
			$connect_port = $this->port;
		}
 
		if ( $this->_fsockopen($connect_host, $connect_port) ) {
 
			if ($this->proxy) {
				if ( $this->port != 80 ) {
					$header  = 'GET '.$this->scheme.'://'.$this->host.$this->path.' HTTP/1.1'."\r\n";
				} else {
					$header  = 'GET '.$this->scheme.'://'.$this->host.':'.$this->port.$this->path.' HTTP/1.1'."\r\n";
				}
				$header .= 'Host: '.$this->proxy_host."\r\n";
			} else {
				$header  = 'GET '.$this->path.' HTTP/1.0'."\r\n";
				$header .= 'Host: '.$this->host."\r\n";
			}
 
			if ( $this->user ) {
				$header  .= 'Authorization: Basic '.base64_encode($this->user.':'.$this->pass)."\r\n";
			}
 
			if ( $this->language !== false ) {
				$header .= 'Accept-Language: '.$this->language."\r\n";
			}
 
			if ( $this->charset !== false ) {
				$header .= 'Accept-Charset: '.$this->charset."\r\n";
			}
 
			if ( $this->user_agent !== false ) {
				$header .= 'User-Agent: '.$this->user_agent."\r\n";
			}
 
			if ( $this->accept !== false ) {
				$header .= 'Accept: '.$this->accept."\r\n";
			}
 
			if ( $this->referer !== false ) {
				$header .= 'Referer: '.$this->referer."\r\n";
			}
 
			if ((is_array($this->extra_headers)) && (count($this->extra_headers))) {
				reset($this->extra_headers);
				foreach ($this->extra_headers as $extra_header) {
					if ( $extra_header ) {
						$header .= $extra_header."\r\n";
					}
				}
			}
 
			if ((is_array($this->cookies)) && (count($this->cookies))) {
				$cookie = false;
				reset($this->cookies);
				foreach ($this->cookies as $var => $value) {
					if ( ($var) && ($value != "") ) {
						if ( ! $cookie) {
							$cookie = $var.'='.$value;
						} else {
							$cookie .= '; '.$var.'='.$value;
						}
					}
				}
				if ($cookie) {
					$header .= 'Cookie: '.$cookie."\r\n";
				}
			}
 
			$header .=   "\r\n\r\n";
			$this->_fputs($header);
 			$this->data = addslashes($this->_fgets());
  			$this->_fclose(); #echo $this->data;
			return $header;
		} else {
			return false;
		}
	}
 
	// HEAD a site, return boolean
	function head()
	{
		if ($this->proxy) {
			$connect_host = $this->proxy_host;
			$connect_port = $this->proxy_port;
		} else {
			$connect_host = $this->host;
			$connect_port = $this->port;
		}
 
		if ( $this->_fsockopen($connect_host, $connect_port) ) {
 
			if ($this->proxy) {
				if ( $this->port != 80 ) {
					$header  = 'HEAD '.$this->scheme.'://'.$this->host.$this->path.' HTTP/1.1'."\r\n";
				} else {
					$header  = 'HEAD '.$this->scheme.'://'.$this->host.':'.$this->port.$this->path.' HTTP/1.1'."\r\n";
				}
				$header .= 'Host: '.$this->proxy_host."\r\n";
			} else {
				$header  = 'HEAD '.$this->path.' '.' HTTP/1.1'."\r\n";
				$header .= 'Host: '.$this->host."\r\n";
			}
 
			if ( $this->user ) {
				$header  .= 'Authorization: Basic '.base64_encode($this->user.':'.$this->pass)."\r\n";
			}
 
			if ( $this->language !== false ) {
				$header .= 'Accept-Language: '.$this->language."\r\n";
			}
 
			if ( $this->charset !== false ) {
				$header .= 'Accept-Charset: '.$this->charset."\r\n";
			}
 
			if ( $this->user_agent !== false ) {
				$header .= 'User-Agent: '.$this->user_agent."\r\n";
			}
 
			if ( $this->accept !== false ) {
				$header .= 'Accept: '.$this->accept."\r\n";
			}
 
			if ( $this->referer !== false ) {
				$header .= 'Referer: '.$this->referer."\r\n";
			}
 
			if ((is_array($this->extra_headers)) && (count($this->extra_headers))) {
				reset($this->extra_headers);
				foreach ($this->extra_headers as $extra_header) {
					if ( $extra_header ) {
						$header .= $extra_header."\r\n";
					}
				}
			}
 
			if ((is_array($this->cookies)) && (count($this->cookies))) {
				$cookie = false;
				reset($this->cookies);
				foreach ($this->cookies as $var => $value) {
					if ( ($var) && ($value != "") ) {
						if ( ! $cookie) {
							$cookie = $var.'='.$value;
						} else {
							$cookie .= '; '.$var.'='.$value;
						}
					}
				}
				if ($cookie) {
					$header .= 'Cookie: '.$cookie."\r\n";
				}
			}
 
			$header .=   "Connection: close\r\n\r\n";
 
			$this->_fputs($header);
			$this->data = $this->_fgets();
			$this->_fclose();
			return true;
		} else {
			return false;
		}
	}
 
	// POST a site, return boolean
	// $data data to send as array()
	// format: array("key1"=>"value1","key2"=>"value2")
	// $files files to send as array()
	// format: array(array("name"=>"file1","file"=>"/filename","type"=>"text/html","rename"="(optional) newfilename"),array(...))
	// before post check that files exist and readable !
	function post($data_to_send = false,$files_to_send = false)
	{
		if ($this->proxy) {
			$connect_host = $this->proxy_host;
			$connect_port = $this->proxy_port;
		} else {
			$connect_host = $this->host;
			$connect_port = $this->port;
		}
 
		if ( $this->_fsockopen($connect_host, $connect_port) ) {
 
			if ( ! is_array($data_to_send) ) {
				$data_to_send = array();
			}
 
			if ( ! is_array($files_to_send)  ) {
				$files_to_send = array();
			}
 
			if ($this->proxy) {
				if ( $this->port != 80 ) {
					$header  = 'POST '.$this->scheme.'://'.$this->host.$this->path.' HTTP/1.0'."\r\n";
				} else {
					$header  = 'POST '.$this->scheme.'://'.$this->host.':'.$this->port.$this->path.' HTTP/1.0'."\r\n";
				}
				$header .= 'Host: '.$this->proxy_host."\r\n";
			} else {
				$header  = 'POST '.$this->path.' '.' HTTP/1.0'."\r\n";
				$header .= 'Host: '.$this->host."\r\n";
			}
 
			if ( $this->user ) {
				$header  .= 'Authorization: Basic '.base64_encode($this->user.':'.$this->pass)."\r\n";
			}
 
			if ( $this->language !== false ) {
				$header .= 'Accept-Language: '.$this->language."\r\n";
			}
 
			if ( $this->charset !== false ) {
				$header .= 'Accept-Charset: '.$this->charset."\r\n";
			}
 
			if ( $this->user_agent !== false ) {
				$header .= 'User-Agent: '.$this->user_agent."\r\n";
			}
 
			if ( $this->accept !== false ) {
				$header .= 'Accept: '.$this->accept."\r\n";
			}
 
			if ( $this->referer !== false ) {
				$header .= 'Referer: '.$this->referer."\r\n";
			}
 
			if ((is_array($this->extra_headers)) && (count($this->extra_headers))) {
				reset($this->extra_headers);
				foreach ($this->extra_headers as $extra_header) {
					if ( $extra_header ) {
						$header .= $extra_header."\r\n";
					}
				}
			}
 
			if ((is_array($this->cookies)) && (count($this->cookies))) {
				$cookie = false;
				reset($this->cookies);
				foreach ($this->cookies as $var => $value) {
					if ( ($var) && ($value != "") ) {
						if ( ! $cookie) {
							$cookie = $var.'='.$value;
						} else {
							$cookie .= '; '.$var.'='.$value;
						}
					}
				}
				if ($cookie) {
					$header .= 'Cookie: '.$cookie."\r\n";
				}
			}
 
			$header .=   "Connection: close\r\n";
 
			$this->_fputs($header);
 
			if ( (count($data_to_send)) || (count($files_to_send)) ) {
 
				srand((double)microtime()*1000000);
				$boundary = "---------------------------".substr(md5(rand(0,32000)),0,10);
 
				$this->_fputs('Content-Type: multipart/form-data; boundary='.$boundary."\r\n");
 
				$length = 0;
				// calculate Content-Length
				reset($data_to_send);
				foreach($data_to_send as $key=>$val) {
					$length += 2+strlen($boundary)+strlen($key)+strlen($val)+strlen('Content-Disposition: form-data; name=""')+8;
				}
 
				reset($files_to_send);
				foreach($files_to_send as $key=>$file) {
					if ( ! $file['rename']) {
							$file['rename'] = basename($file['file']);
					}
					$length += 2+strlen($boundary)+strlen('Content-Disposition: form-data; name="'.$file['name'].'"; filename="'.$file['rename'].'"')+strlen('Content-Type: '.$file['type'])+strlen('Content-Transfer-Encoding: binary')+10+filesize($file['file']);
				}
 
				$this->_fputs('Content-Length: '.strval($length)."\r\n\r\n");
 
				if (count ($data_to_send)) {
					reset($data_to_send);
					foreach($data_to_send as $key=>$val) {
						$this->_fputs('--'.$boundary."\r\n");
						$this->_fputs('Content-Disposition: form-data; name="'.$key.'"'."\r\n\r\n".$val."\r\n");
					}
				}
 
				if (count ($files_to_send)) {
					reset($files_to_send);
					foreach($files_to_send as $file) {
 
						if ( (is_array($file)) && (count($file))) {
 
							if ( ! $file['rename']) {
								$file['rename'] = basename($file['file']);
							}
 
							$fh = fopen ($file['file'], "r");
 
							$this->_fputs('--'.$boundary."\r\n");
							$this->_fputs('Content-Disposition: form-data; name="'.$file['name'].'"; filename="'.$file['rename'].'"'."\r\n");
							$this->_fputs('Content-Type: '.$file['type']."\r\n");
							$this->_fputs('Content-Transfer-Encoding: binary'."\r\n\r\n");
							$this->_fputs(fread ($fh, filesize ($file['file']))."\r\n");
 
							fclose ($fh);
						}
					}
				}
				$this->_fputs('--'.$boundary.'--');
			}
			$this->data = $this->_fgets();
			$this->_fclose();
			return true;
		} else {
			return false;
		}
	}
 
	// return http-status as string
	function get_status()
	{
		$head = $this->get_headers();
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/HTTP\/(.*) ([0-9][0-9][0-9])(.*)/i',$headline)) {
				preg_match ("/HTTP\/(.*) ([0-9][0-9][0-9])(.*)/i",$headline,$tmp);
				if ((isset($tmp[2])) && (is_numeric($tmp[2]))) {
					return $tmp[2];
				}
			}
		}
		return false;
	}
 
	// return content-type as string
	// if $full == true return full content-type incl. q, otherwise only type (*/*)
	function get_type($full = false)
	{
		$head = $this->get_headers();
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/^Content-Type: /i',$headline)) {
				$type = preg_replace('/^Content-Type: /i', '', $headline);
				if ($full) {
					return $type;
				} else {
					$return = split(";",$type);
					return $return[0];
				}
			}
		}
		return false;
	}
 
	// return content-lenght as integer
	function get_length()
	{
		$head = $this->get_headers();
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/^Content-Length: /i',$headline)) {
				$length = preg_replace('/^Content-Length: /i', '', $headline);
				return $length;
			}
		}
		return false;
	}
 
	// return new location if set
	function get_location()
	{
		$head = $this->get_headers();
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/^Location: /i',$headline)) {
				$location = preg_replace('/^Location: /i', '', $headline);
				return $location;
			}
		}
		return false;
	}
 
	// return cookies as muti-array or false
	// format: array(array("name"=>"foo","value"=>"bar","path"=>"string","time"=>timestamp,"domain"=>"string","secure"=>boolean),array(...))
	function get_cookies()
	{
		$head = $this->get_headers();
		$cookies = false;
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/^Set-Cookie: /i',$headline)) {
 
					if (! is_array($cookies) ) {
						$cookies = array();
					}
 
					$headline = trim($headline);
					$headline = preg_replace("/^Set-Cookie: /i", "", $headline);
					$cookiesplit = split(";",$headline);
 
					$cookieinfo = array();
 
					// avr und value
					list($cookieinfo['name'],$cookieinfo['value']) = split("=",$cookiesplit[0],2);
 
					// zeit als timestamp
					if ( $cookiesplit[1]) {
						$cookieinfo['time'] = strtotime(preg_replace("/^expires=/i", "", trim($cookiesplit[1])));
					}
 
					// path
					if ( $cookiesplit[2]) {
						$cookieinfo['path'] = preg_replace("/^path=/i", "", trim($cookiesplit[2]));
					}
 
					//domain
					if ( $cookiesplit[3]) {
						$cookieinfo['domain'] = preg_replace("/^domain=/i", "", trim($cookiesplit[3]));
					}
 
					// secure
					if ( strtolower(trim($cookiesplit[4]))=="secure") {
						$cookieinfo['secure'] = true;
					}
 
					$cookies[] = $cookieinfo;
			}
		}
		return $cookies;
	}
 
	// return all headers as array
	// format: array("HTTP/1.1 200 OK","Date: Thu, 27 May 2004 20:33:10 GMT")
	function get_headers()
	{
		return split ("\r\n",$this->get_header());
	}
 
	// return date
	// return string like "Date: Thu, 27 May 2004 20:33:10 GMT"
	function get_date()
	{
		$head = $this->get_headers();
		reset($head);
		foreach($head as $headline) {
			if (preg_match('/^Date: /i',$headline)) {
				$date = preg_replace('/^Date: /i', '', $headline);
				return $date;
			}
		}
		return false;
	}
 
	// return complete header as string
	function get_header()
	{
		$tmp = split ("\r\n\r\n", $this->data,2);
		if (isset($tmp[0])) {
			return $tmp[0];
		} else {
			return "";
		}
	}
 
	// return content as string
	function get_content()
	{
		$tmp = split ("\r\n\r\n", $this->data,2);
		if (isset($tmp[1])) {
			return $tmp[1];
		} else {
			return "";
		}
	}
 
	// intern, for debugging
	function _fsockopen($host, $port)
	{
		if ( ! $this->debug) {
			if ( $this->socket = fsockopen($host, $port)) {
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}
 
	// intern, for debugging
	function _fputs($data)
	{
		if ( ! $this->debug) {
			fputs($this->socket, $data);
		}  else {
			echo $data;
		}
	}
 
	// intern, for debugging
	function _fgets()
	{
		if ( ! $this->debug) {
			while(!feof($this->socket)) {
				$return .= fgets($this->socket, 512);
			}
			return $return;
		} else {
			return false;
		}
	}
 
	// intern, for debugging
	function _fclose()
	{
		if ( ! $this->debug) {
			fclose($this->socket);
		}
	}
}
 
 
 
 
?>