<?php
/*
+---------------------------------------------------------------------------
|   PHP-IRC v2.2.1 Service Release
|   ========================================================
|   by Manick
|   (c) 2001-2005 by http://www.phpbots.org/
|   Contact: manick@manekian.com
|   irc: #manekian@irc.rizon.net
|   ========================================
+---------------------------------------------------------------------------
|   > dcc chat module
|   > Module written by Manick
|   > Module Version Number: 2.2.0
+---------------------------------------------------------------------------
|   > This program is free software; you can redistribute it and/or
|   > modify it under the terms of the GNU General Public License
|   > as published by the Free Software Foundation; either version 2
|   > of the License, or (at your option) any later version.
|   >
|   > This program is distributed in the hope that it will be useful,
|   > but WITHOUT ANY WARRANTY; without even the implied warranty of
|   > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|   > GNU General Public License for more details.
|   >
|   > You should have received a copy of the GNU General Public License
|   > along with this program; if not, write to the Free Software
|   > Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+---------------------------------------------------------------------------
|   Changes
|   =======-------
|   > If you wish to suggest or submit an update/change to the source
|   > code, email me at manick@manekian.com with the change, and I
|   > will look to adding it in as soon as I can.
+---------------------------------------------------------------------------
*/

class file {

  /* Chat specific Data */
  	public $id;
	public $status;
	public $sockInt;
	public $timeConnected;
	public $readQueue;
	public $port;
	public $dccString;
	public $type;
	public $transferType;
	public $nick;
	public $timeOutLevel;
	public $removed;
	public $connection;

	public $reverse; // reverse dcc?
	private $handShakeSent;
	private $handShakeTime;

	public $filename;
	public $filenameNoDir;
	public $filePointer;
	public $filesize;
	public $bytesTransfered;
	public $resumedSize;
	public $completed;
	public $reportedRecieved;

	public $connectHost;

	//private $resumed;
	private $started;

	private $sendQueue;
	private $sendQueueCount;

	//keep track of speed;
	private $speed_sec_add;
	public $speed_lastavg;
	private $speed_starttime;
	
  /* Classes */
  	private $dccClass;
	private $ircClass;
	private $socketClass;
	private $procQueue;
	private $timerClass;

	/* Constructor */
	public function __construct($id, $nick, $sockInt, $host, $port, $type, $reverse)
	{
		$this->id = $id;
		$this->nick = $nick;
		$this->sockInt = $sockInt;
		$this->connectHost = $host;
		$this->port = $port;
		$this->transferType = $type;
		$this->filesize = 0;
		$this->bytesTransfered = 0;
		$this->resumedSize = 0;
		$this->started = false;
		$this->status = DCC_WAITING;
		$this->reverse = $reverse;
		$this->handShakeSent = false;

		$this->speed_sec_add = 0;
		$this->speed_lastavg = 0;
		$this->speed_starttime = 0;

		if ($type == UPLOAD)
		{
			$this->dccString = "DCC UPLOAD[".$this->id."]: ";
		}
		else
		{
			$this->dccString = "DCC DOWNLOAD[".$this->id."]: ";
		}

	}

	public function setProcQueue($class)
	{
		$this->procQueue = $class;
	}

	public function setSocketClass($class)
	{
		$this->socketClass = $class;
	}

	public function setIrcClass($class)
	{
		$this->ircClass = $class;
	}

	public function setTimerClass($class)
	{
		$this->timerClass = $class;
	}

	public function setDccClass($class)
	{
		$this->dccClass = $class;
	}

	public function dccSend($data)
	{
		return $this->dccClass->dccSend($this, $data);
	}

	public function disconnect($msg = "")
	{

		$msg = str_replace("\r", "", $msg);
		$msg = str_replace("\n", "", $msg);

		if ($this->started == true)
		{
			fclose($this->filePointer);
		}

		if ($msg != "")
		{
			$this->dccClass->dccInform($this->dccString . "DCC session ended with " . $this->nick . " (" . $msg . ")", $this);
			$this->ircClass->notice($this->nick, "DCC session ended: " . $msg, 1);
		}
		else
		{
			$this->dccClass->dccInform($this->dccString . "DCC session ended with " . $this->nick, $this);
		}

		$this->status = false;

	  	$this->dccClass->disconnect($this);

	  	$this->connection = null;

		return true;
	}

	function xferUpload()
	{

		while ($this->readQueue != "")
		{
			$unsignedLong = substr($this->readQueue, 0, 4);

			if (strlen($unsignedLong) < 4)
			{
				break;
			}

			$sizeArray = unpack("N", $unsignedLong);

			$this->reportedRecieved = $sizeArray[1];

			$this->readQueue = substr($this->readQueue, 4);
		}

		if ($this->completed == 1)
		{
			if ($this->reportedRecieved >= $this->filesize)
			{
				$avgspeed = "";
				if ($this->speed_lastavg != 0)
				{
					$size = irc::intToSizeString($this->speed_lastavg);
					$avgspeed = " (" . $size . "/s)";
				}

				$totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec");
				$size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize);
				$this->disconnect("Transfer Completed, Sent " . $size . " in " . $totalTime . $avgspeed);
			}
			return;
		}

		if ($this->status != DCC_CONNECTED)
		{
			return;
		}

		if ($this->socketClass->hasWriteQueue($this->sockInt))
		{
			return;
		}

		if ($this->bytesTransfered >= $this->filesize)
		{
			$this->completed = 1;
			return;
		}

		if (time() >= $this->speed_starttime + 3)
		{
			$this->speed_lastavg = $this->speed_sec_add / 3.0;
			$this->speed_sec_add = 0;
			$this->speed_starttime = time();
		}

		if (!is_resource($this->filePointer))
		{
			$this->disconnect("File pointer is not a resource");
			return;
		}

		for ($i = 0; $i < 30; $i++)
		{
			if (($data = fread($this->filePointer, 4096)) === false)
			{
				$this->disconnect("Read error: Could not access file");
				return;
			}

			$this->dccSend($data);
			
			$dataSize = strlen($data);

			$this->bytesTransfered += $dataSize;
			$this->dccClass->addBytesUp($dataSize);
			$this->speed_sec_add += $dataSize;

			if ($this->socketClass->hasWriteQueue($this->sockInt))
			{
				break;
			}
		}

	}

	function xferDownload()
	{

		if ($this->status != DCC_CONNECTED)
		{
			return;
		}

		$readQueueSize = strlen($this->readQueue);

		if ($readQueueSize <= 0)
		{
			return;
		}

		if (fwrite($this->filePointer, $this->readQueue, $readQueueSize) === false)
		{
			$this->disconnect("Write error: Could not access file");
		}

		$this->speed_sec_add += $readQueueSize;
		$this->dccClass->addBytesDown($readQueueSize);
		$this->bytesTransfered += $readQueueSize;
		$this->readQueue = "";

		$this->dccSend(pack("N", $this->bytesTransfered));

		if ($this->bytesTransfered >= $this->filesize)
		{
			$avgspeed = "";
			if ($this->speed_lastavg != 0)
			{
				$size = irc::intToSizeString($this->speed_lastavg);
				$avgspeed = " (" . $size . "/s)";
			}

			$totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec");
			$size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize);
			$this->disconnect("Transfer Completed, Recieved " . $size . " in " . $totalTime . $avgspeed);
		}

		if (time() >= $this->speed_starttime + 3)
		{
			$this->speed_lastavg = $this->speed_sec_add / 3.0;
			$this->speed_sec_add = 0;
			$this->speed_starttime = time();
		}
	}


	private function doHandShake()
	{
		$this->dccSend("120 ".$this->ircClass->getNick()." ".$this->filesize." ".$this->filenameNoDir."\n");
		$this->handShakeSent = true;
		$this->timerClass->addTimer(irc::randomHash(), $this, "handShakeTimeout", "", 8);
	}

	private function processHandShake()
	{
		if ($this->readQueue == "")
		{
			return;
		}

		$response = $this->readQueue;
		$this->readQueue = "";
		$responseArray = explode(chr(32), $response);
		if ($responseArray[0] == "121")
		{
			$this->resumedSize = intval($responseArray[2]);
			$this->reverse = false;
			$this->onConnect($conn);
			return;
		}

		$this->disconnect("DCC Client Server reported error on attempt to send file");
	}

	public function handShakeTimeout()
	{
		if ($this->status != false)
		{
			if ($this->reverse == true)
			{
				$this->disconnect("DCC Reverse handshake timed out");
			}
		}
		return false;
	}
	
	

	/* Main events */
	public function onDead($conn)
	{
		if ($this->completed == 1)
		{
			$avgspeed = "";
			if ($this->speed_lastavg != 0)
			{
				$size = irc::intToSizeString($this->speed_lastavg);
				$avgspeed = " (" . $size . "/s)";
			}

			$totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec");
			$size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize);
			$this->disconnect("Transfer Completed, Sent " . $size . " in " . $totalTime . $avgspeed);
		}
		else
		{
			$this->disconnect($this->connection->getErrorMsg());
		}
	}

	public function onRead($conn)
	{
	
		$this->readQueue .= $this->socketClass->getQueue($this->sockInt);

		if ($this->status == DCC_CONNECTED)
		{

			if ($this->transferType == UPLOAD)
			{
				if ($this->reverse != false)
				{
					if ($this->handShakeSent != false)
					{
						$this->processHandShake();
					}
				}
			}
			else
			{
				$this->xferDownload();
			}
		}
		return false;
	}

	public function onWrite($conn)
	{
		if ($this->status == DCC_CONNECTED && $this->reverse == false)
		{
			$this->xferUpload();
		}
	}

	public function onAccept($oldConn, $newConn)
	{
		$this->dccClass->accepted($oldConn, $newConn);
		$this->connection = $newConn;
		$oldConn->disconnect();
		$this->sockInt = $newConn->getSockInt();
		$this->onConnect($newConn);
	}

	public function onTransferTimeout($conn)
	{
		$this->disconnect("Transfer timed out");
	}

	public function onConnectTimeout($conn)
	{
		$this->disconnect("Connection attempt timed out");
	}

	public function onConnect($conn)
	{
		$this->status = DCC_CONNECTED;

		$this->dccClass->dccInform($this->dccString . $this->nick . " connection established");

		if ($this->reverse != false)
		{
			$this->doHandShake();
			return;
		}

		if ($this->transferType == UPLOAD)
		{
			$this->dccClass->alterSocket($this->sockInt, SOL_SOCKET, SO_SNDBUF, 32768);

			$this->filePointer = fopen($this->filename, "rb");
			
			if ($this->filePointer === false)
			{
				$this->disconnect("Error opening local file for reading");
				return;
			}

			if ($this->resumedSize > 0)
			{
				if (fseek($this->filePointer, $this->resumedSize, SEEK_SET) == -1)
				{
					$this->disconnect("Error seeking to resumed file position in file");
					return;
				}
			}
			
			$this->xferUpload();

		}
		else
		{
			$this->dccClass->alterSocket($this->sockInt, SOL_SOCKET, SO_RCVBUF, 32768);

			$this->filePointer = fopen($this->filename, "ab");

			$this->ircClass->notice($this->nick, "DCC: Upload connection established", 0);

			if ($this->filePointer === false)
			{
				$this->disconnect("Error opening local file for writing");
				return;
			}

		}

		$this->started = true;
		$this->speed_starttime = time();

	}


	public function initialize($filename, $size = 0)
	{
		$this->reportedRecieved = 0;
		$this->completed = 0;
		$this->filesize = $size;
		$this->timeConnected = time();
		$this->timeOutLevel = 0;
		$this->readQueue = "";
		$this->type = FILE;

		if ($this->transferType == UPLOAD)
		{
			$this->filename = $filename;

			if (strpos($filename, "/") !== false)
			{
				$filenameArray = explode("/", $filename);
				$this->filenameNoDir = $filenameArray[count($filenameArray) - 1];
			}
			else if (strpos($filename, "\\") !== false)
			{
				$filenameArray = explode("\\", $filename);
				$this->filenameNoDir = $filenameArray[count($filenameArray) - 1];
			}
			else
			{
				$this->filenameNoDir = $filename;
			}

			$this->filenameNoDir = $this->cleanFilename($this->filenameNoDir);

			$this->dccClass->dccInform($this->dccString . "Initiating file transfer of (".$this->filenameNoDir.") to " . $this->nick);

			if (!$this->file_exists($this->filename))
			{
				$this->disconnect("File does not exist");
				return;
			}

			$fileSize = $this->filesize($this->filename);
			if ($fileSize === false)
			{
				$this->disconnect("File does not exist");
				return;
			}

			$this->filesize = $fileSize;


			$kbSize = irc::intToSizeString($fileSize);

			if ($this->reverse == false)
			{
				$this->ircClass->privMsg($this->nick, "\1DCC SEND " . $this->filenameNoDir . " " . $this->ircClass->getClientIP(1) . " " . $this->port . " " . $fileSize . "\1", 0);
			}

			$this->ircClass->notice($this->nick, "DCC: Sending you (\"" . $this->filenameNoDir . "\") which is " . $kbSize . " (resume supported)", 0);

		}
		else
		{
			$uldir = $this->ircClass->getClientConf('uploaddir');
			
			$lastChar = substr($uldir, strlen($uldir) - 1, 1);

			if ($lastChar != "\\" || $lastChar != "/")
			{
				$uldir .= "/";
			}
			
			$filename = $this->cleanFilename($filename);

			$this->filename = $uldir . $filename;
			$this->dccClass->dccInform($this->dccString . "Initiating file transfer of (".$filename.") from " . $this->nick);

			if ($this->file_exists($this->filename))
			{
				$bytesFinished = $this->filesize($this->filename);
				if ($bytesFinished >= $this->filesize)
				{
					$this->disconnect("Connection aborted. I already have that file.");
					return;
				}
				else
				{
					$this->status = DCC_WAITING;
					$this->bytesTransfered = $bytesFinished;
					$this->resumedSize = $bytesFinished;
					$this->ircClass->privMsg($this->nick, "\1DCC RESUME file.ext " . $this->port . " " . $bytesFinished . "\1", 0);
				}
				return;
			}

			$this->ircClass->notice($this->nick, "DCC: Upload accepted, connecting to you (" . $this->connectHost . ") on port " . $this->port . ".",0);


			$this->status = DCC_CONNECTING;
			$this->connection->connect();

		}

	}
	
	public function accepted()
	{
		$this->status = DCC_CONNECTING;
		$this->connection->connect();
	}

	public function resume($size)
	{
		$this->resumedSize = $size;
		$this->bytesTransfered = $size;
		
		$resumePlace = round($size / 1000, 0);
		$this->dccClass->dccInform($this->dccString . "Resumed at " . $resumePlace . "K");
		$this->ircClass->privMsg($this->nick, "\1DCC ACCEPT file.ext " . $this->port . " " . $size . "\1", 0);
	}

	public static function cleanFilename($filename)
	{
		$filename = str_replace("..", "__", $filename);
		$filename = str_replace(chr(47), "_", $filename);
		$filename = str_replace(chr(92), "_", $filename);
		$filename = str_replace(chr(58), "_", $filename);
		$filename = str_replace(chr(63), "_", $filename);
		$filename = str_replace(chr(34), "_", $filename);
		$filename = str_replace(chr(62), "_", $filename);
		$filename = str_replace(chr(60), "_", $filename);
		$filename = str_replace(chr(124), "_", $filename);
		$filename = str_replace(chr(32), "_", $filename);

		return $filename;
	}

	private function file_exists($filename)
	{
		$fp = @fopen($filename, "rb");
		if ($fp === false)
		{
			return false;
		}
		else
		{
			fclose($fp);
			return true;
		}
	}

	private function filesize($filename)
	{

		$fp = @fopen($filename, "rb");
		if ($fp === false)
		{
			return false;
		}
		else
		{
			$fstat = fstat($fp);
			fclose($fp);
			return $fstat['size'];
		}

	}

}

?>